diff --git a/internal/server/pi_agent_datadir_test.go b/internal/server/pi_agent_datadir_test.go new file mode 100644 index 00000000..02cd9b58 --- /dev/null +++ b/internal/server/pi_agent_datadir_test.go @@ -0,0 +1,54 @@ +package server_test + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestResolvePiAgentSharedDataDir_PrefersRepoRootDevOverPackageLocal(t *testing.T) { + root := t.TempDir() + mustMkdirAll(t, filepath.Join(root, "data", "dev", "workspaces")) + mustWrite(t, filepath.Join(root, "go.mod"), "module example\n") + + pkgLocal := filepath.Join(root, "internal", "server", "data", "test", "workspaces") + mustMkdirAll(t, pkgLocal) + + cwd := filepath.Join(root, "internal", "server") + got := resolvePiAgentSharedDataDir(cwd, "") + want := filepath.Join(root, "data", "dev") + if got != want { + t.Fatalf("got %q want repo-root %q (must not pick package-local %q)", got, want, filepath.Join(root, "internal", "server", "data", "test")) + } + if strings.Contains(filepath.ToSlash(got), "/internal/") { + t.Fatalf("resolved path must not be under internal/: %q", got) + } +} + +func TestResolvePiAgentSharedDataDir_EnvOverrides(t *testing.T) { + root := t.TempDir() + mustMkdirAll(t, filepath.Join(root, "data", "dev", "workspaces")) + mustWrite(t, filepath.Join(root, "go.mod"), "module example\n") + override := filepath.Join(root, "custom-data") + mustMkdirAll(t, filepath.Join(override, "workspaces")) + + got := resolvePiAgentSharedDataDir(filepath.Join(root, "internal", "server"), override) + if got != override { + t.Fatalf("got %q want env override %q", got, override) + } +} + +func mustMkdirAll(t *testing.T, path string) { + t.Helper() + if err := os.MkdirAll(path, 0o755); err != nil { + t.Fatalf("mkdir %s: %v", path, err) + } +} + +func mustWrite(t *testing.T, path, body string) { + t.Helper() + if err := os.WriteFile(path, []byte(body), 0o644); err != nil { + t.Fatalf("write %s: %v", path, err) + } +} diff --git a/internal/server/test_helpers_test.go b/internal/server/test_helpers_test.go index 9259c862..8747388d 100644 --- a/internal/server/test_helpers_test.go +++ b/internal/server/test_helpers_test.go @@ -118,24 +118,42 @@ func envOr(key, fallback string) string { return fallback } -func piAgentSharedDataDir(t *testing.T) string { - t.Helper() - if d := strings.TrimSpace(os.Getenv("DATA_DIR")); d != "" { +// resolvePiAgentSharedDataDir picks the host DATA_DIR that the pi-agent runner +// bind-mounts (compose: ./data//workspaces → /app/data/workspaces). +// Prefer DATA_DIR env, then repo-root data/dev|test via go.mod walk — never the +// package-local internal/server/data/* tree, which the runner cannot see. +func resolvePiAgentSharedDataDir(cwd, envDataDir string) string { + if d := strings.TrimSpace(envDataDir); d != "" { return d } + dir := filepath.Clean(cwd) + for i := 0; i < 8; i++ { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + for _, envName := range []string{"dev", "test"} { + candidate := filepath.Join(dir, "data", envName) + if st, err := os.Stat(filepath.Join(candidate, "workspaces")); err == nil && st.IsDir() { + return candidate + } + } + return "" + } + parent := filepath.Dir(dir) + if parent == dir { + break + } + dir = parent + } + return "" +} + +func piAgentSharedDataDir(t *testing.T) string { + t.Helper() cwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } - for _, rel := range []string{".", "..", "../..", "../../.."} { - base := filepath.Clean(filepath.Join(cwd, rel)) - for _, envName := range []string{"dev", "test"} { - candidate := filepath.Join(base, "data", envName) - ws := filepath.Join(candidate, "workspaces") - if st, statErr := os.Stat(ws); statErr == nil && st.IsDir() { - return candidate - } - } + if d := resolvePiAgentSharedDataDir(cwd, os.Getenv("DATA_DIR")); d != "" { + return d } t.Skip("shared pi-agent DATA_DIR not found (mount data/test/workspaces or data/dev/workspaces)") return "" diff --git a/scripts/run-nightly-real-pi.sh b/scripts/run-nightly-real-pi.sh index d3ddc169..011ffe86 100755 --- a/scripts/run-nightly-real-pi.sh +++ b/scripts/run-nightly-real-pi.sh @@ -127,6 +127,10 @@ export RUNNER_AUTH_TOKEN="${TOKEN}" export PI_AGENT_MODEL="${MODEL_ID}" export RUN_PI_E2E=1 export KB_REQUIRE_ONLINE="${KB_REQUIRE_ONLINE:-1}" +# Host Go tests must write workspaces into the same tree the runner mounts +# (docker-compose.dev.yml: ./data/dev/workspaces → /app/data/workspaces). +export DATA_DIR="${DATA_DIR:-${ROOT}/data/dev}" +echo "==> DATA_DIR=${DATA_DIR}" # Shared KB preflight (host :5432). Fail closed unless KB_ALLOW_OFFLINE=1. preflight_shared_kb() {