package runner
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// Exercise the job's actual environment, not just taskTemp's return value.
func TestAJobCanBindAChromeSocketWithTheDefaultTempRoot(t *testing.T) {
work := filepath.Join(t.TempDir(), strings.Repeat("deployment-depth/", 8), "runner")
job, err := json.Marshal(Job{Steps: []JobStep{{
Run: fmt.Sprintf("%q -test.run=^TestChromeSocketJobHelper$", os.Args[0]),
Env: map[string]string{"QUARRY_TEST_CHROME_SOCKET": "1"},
}}})
if err != nil {
t.Fatal(err)
}
tk := task("t1", "c1")
tk.Metadata = map[string]string{JobKey: string(job)}
br := &fakeBridge{queue: map[string][]polled{"t-job": {tk}}, resolved: map[string]map[string]any{}, logs: map[string]string{}}
srv := httptest.NewServer(br.handler())
defer srv.Close()
r, err := New(Config{
Bridge: srv.URL, Token: "WT", Tasks: []string{"t-job"}, MaxTasks: 1,
WorkDir: work, Checkout: appCheckout{}, Heartbeat: 100 * time.Millisecond,
Logf: func(string, ...any) {},
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.RemoveAll(r.cfg.TempRoot) })
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
done := make(chan struct{})
go func() { r.Run(ctx); close(done) }()
defer func() { cancel(); <-done }()
for ctx.Err() == nil {
br.mu.Lock()
result, resolved := br.resolved["t1"]
logs := br.logs["t1"]
br.mu.Unlock()
if resolved {
if result["action"] != "complete" {
t.Fatalf("job failed: %v\n%s", result, logs)
}
var actualTemp string
for _, line := range strings.Split(logs, "\n") {
if value, ok := strings.CutPrefix(line, "CHROME_SOCKET_BOUND "); ok {
actualTemp = strings.TrimSpace(value)
}
}
if actualTemp == "" {
t.Fatalf("job did not report its socket's temp directory:\n%s", logs)
}
if filepath.Dir(actualTemp) != r.cfg.TempRoot {
t.Fatalf("job temp directory %q is not owned by runner root %q", actualTemp, r.cfg.TempRoot)
}
if entries, err := os.ReadDir(r.cfg.TempRoot); err != nil || len(entries) != 0 {
t.Fatalf("task temp directory survived completion: %v, %v", entries, err)
}
return
}
time.Sleep(20 * time.Millisecond)
}
t.Fatal("job did not resolve before the deadline")
}
func TestChromeSocketJobHelper(t *testing.T) {
if os.Getenv("QUARRY_TEST_CHROME_SOCKET") != "1" {
return
}
tmp := os.Getenv("TMPDIR")
if tmp == "" || !filepath.IsAbs(tmp) {
t.Fatalf("TMPDIR must be a nonempty absolute path, got %q", tmp)
}
for _, name := range []string{"GOTMPDIR", "TMP", "TEMP"} {
if os.Getenv(name) != tmp {
t.Fatalf("%s differs from TMPDIR", name)
}
}
chrome := filepath.Join(tmp, "com.google.Chrome.XXXXXX")
if err := os.Mkdir(chrome, 0o700); err != nil {
t.Fatal(err)
}
socket := filepath.Join(chrome, "SingletonSocket")
listener, err := net.Listen("unix", socket)
if err != nil {
t.Fatalf("cannot bind %d-byte Chrome socket: %v", len(socket), err)
}
defer listener.Close()
fmt.Println("CHROME_SOCKET_BOUND", tmp)
}