Artifact f2540858127c5411a5d4df4fcea3e5c2001e8befe0d074154ed33810fcd396cb:
- File
internal/harness/harness_test.go
— part of check-in
[4a479e6ce2]
at
2026-08-18 01:15:43
on branch trunk
— api: make the repo set a concurrent registry; serve rejects positionals
Deps.Repos was a bare map, read by every op on the request path and written only at startup. Adding repositories at runtime — the point of the registry ticket — makes that a data race with no symptom until it corrupts one. api.Repos owns the lock; a *Repo is published complete or not at all, and Delete hands back the entry so the caller can close the index and stop the pipeline.
serve now refuses leftover arguments. Go's flag package stops at the first non-flag and returns the rest, so one unquoted shell variable silently dropped twelve --repo flags and quarry served the remaining eleven without a word. (user: dmestas, size: 8967) [annotate] [blame] [check-ins using]
// Package harness runs the disposability loop (0001 §11): ingest real // stores, snapshot every rendered surface, rm -rf the derived data // directory, re-ingest from the stores, snapshot again, and semantically // diff. Any diff is hostage state and fails the build. The fleet version // runs two stores through one process — shared NATS, per-repo indexes — // and asserts they cannot bleed into each other. package harness import ( "encoding/json" "io" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/events" "craftdesign.group/quarry/internal/index" "craftdesign.group/quarry/internal/ingest" "craftdesign.group/quarry/internal/ops" "craftdesign.group/quarry/internal/semdiff" "craftdesign.group/quarry/internal/web" "craftdesign.group/quarry/internal/zitcli" ) type surface struct { path string kind semdiff.Kind } // surfaces enumerates what gets snapshotted for one repo, plus the // fleet-level pages. func surfaces(repos []string) []surface { out := []surface{ {"/", semdiff.HTML}, {"/api/v1/repos", semdiff.JSON}, {"/api/v1/version", semdiff.JSON}, {"/api/v1/openapi.json", semdiff.JSON}, } for _, r := range repos { p := "/r/" + r a := "/api/v1/r/" + r out = append(out, surface{p + "/timeline", semdiff.HTML}, surface{p + "/wiki", semdiff.HTML}, surface{p + "/wiki/Guide", semdiff.HTML}, surface{p + "/wiki/Guide/history", semdiff.HTML}, surface{a + "/wiki/Guide", semdiff.JSON}, surface{a + "/wiki/Guide/history", semdiff.JSON}, surface{p + "/ticket", semdiff.HTML}, surface{p + "/search?q=README", semdiff.HTML}, surface{a + "/timeline", semdiff.JSON}, surface{a + "/wiki", semdiff.JSON}, surface{a + "/ticket", semdiff.JSON}, surface{a + "/search?q=README", semdiff.JSON}, ) } return out } func zrun(t *testing.T, dir string, args ...string) { t.Helper() cmd := exec.Command("zit", args...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("zit %v: %v\n%s", args, err, out) } } // buildStore creates one store under test: a zit repository with a few // commits carrying a distinguishing seed word. func buildStore(t *testing.T, seed string) string { t.Helper() if _, err := exec.LookPath("zit"); err != nil { t.Skip("zit not on PATH") } repo := t.TempDir() zrun(t, repo, "init") for _, f := range []struct{ name, body, msg string }{ {"README.md", "# store " + seed + "\n", "add README " + seed}, {"src/main.go", "package " + seed + "\n", "add main"}, {"README.md", "# store " + seed + "\nmore\n", "extend README"}, } { if err := os.MkdirAll(filepath.Dir(filepath.Join(repo, f.name)), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(repo, f.name), []byte(f.body), 0o644); err != nil { t.Fatal(err) } zrun(t, repo, "add", f.name) zrun(t, repo, "commit", "-m", f.msg) } // Non-checkin artifacts too (zit#71 verbs): the disposability rule // covers every rendered surface, wiki and tickets included. wiki := exec.Command("zit", "wiki", "commit", "Guide", "--user", seed, "--mimetype", "text/x-markdown") wiki.Dir = repo wiki.Stdin = strings.NewReader("# Guide\n\n" + seed + " wiki body\n") if out, err := wiki.CombinedOutput(); err != nil { t.Fatalf("zit wiki commit: %v\n%s", err, out) } zrun(t, repo, "ticket", "new", "--user", seed, "title="+seed+" ticket", "status=Open") return repo } // ingestAndServe is the from-nothing build: one embedded NATS and one // index per repo, all under dataDir (so the wipe destroys stream, // cursors, and every table), each store flowing watcher → events → fold // under its own project code, both registry consumers on top. func ingestAndServe(t *testing.T, repos map[string]string, dataDir string) *httptest.Server { t.Helper() return ingestAndServeAuth(t, repos, dataDir, nil) } func ingestAndServeAuth(t *testing.T, repos map[string]string, dataDir string, verify api.Verify) *httptest.Server { t.Helper() ev, err := events.Start(filepath.Join(dataDir, "nats")) if err != nil { t.Fatal(err) } t.Cleanup(ev.Close) deps := api.Deps{Version: "0.0.0-harness", Repos: api.NewRepos(nil)} for name, path := range repos { ix, err := index.Open(filepath.Join(dataDir, name+".db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ix.Close() }) cli := zitcli.New(path) pipe := ingest.NewPipeline(cli, ev, ix, name, "harness") deps.Repos.Put(name, &api.Repo{Index: ix, Zit: cli, Sync: pipe.Sync}) if _, err := pipe.Sync(); err != nil { t.Fatal(err) } } r := ops.NewRegistry() if err := api.RegisterAll(r, deps); err != nil { t.Fatal(err) } apiRouter, err := api.Router(r, "0.0.0-harness", verify) if err != nil { t.Fatal(err) } mux := http.NewServeMux() mux.Handle("/api/", apiRouter) mux.Handle("/", web.Handler(r, nil, nil, web.Verify(verify))) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) return srv } func snapshot(t *testing.T, srv *httptest.Server, path string) []byte { t.Helper() resp, err := srv.Client().Get(srv.URL + path) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("%s: status %d", path, resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } return body } func TestDisposability(t *testing.T) { stores := map[string]string{ "alpha": buildStore(t, "alphaseed"), "beta": buildStore(t, "betaseed"), } sf := surfaces([]string{"alpha", "beta"}) dataA := filepath.Join(t.TempDir(), "data") if err := os.MkdirAll(dataA, 0o755); err != nil { t.Fatal(err) } first := ingestAndServe(t, stores, dataA) // Per-repo check-in and file surfaces are discovered live; their // paths are content-addressed hashes, identical across rebuilds. for _, repo := range []string{"alpha", "beta"} { var tl struct { Entries []struct { Kind string `json:"kind"` Hash string `json:"hash"` } `json:"entries"` } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/"+repo+"/timeline?y=ci"), &tl); err != nil { t.Fatal(err) } if len(tl.Entries) == 0 { t.Fatalf("%s: no check-ins", repo) } for _, e := range tl.Entries { if e.Kind != "ci" { continue } sf = append(sf, surface{"/r/" + repo + "/checkin/" + e.Hash, semdiff.HTML}, surface{"/r/" + repo + "/checkin/" + e.Hash + "/diff", semdiff.HTML}, surface{"/api/v1/r/" + repo + "/checkin/" + e.Hash, semdiff.JSON}) } var tip struct { Files []struct { Name string `json:"name"` } `json:"files"` } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/"+repo+"/checkin/"+tl.Entries[0].Hash), &tip); err != nil { t.Fatal(err) } for _, f := range tip.Files { sf = append(sf, surface{"/r/" + repo + "/file/" + tl.Entries[0].Hash + "/" + f.Name, semdiff.HTML}, surface{"/api/v1/r/" + repo + "/file/" + tl.Entries[0].Hash + "/" + f.Name, semdiff.JSON}) } } // Isolation: repo A's content is invisible from repo B's surfaces. var cross struct { Count int `json:"count"` } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/beta/search?q=alphaseed"), &cross); err != nil { t.Fatal(err) } if cross.Count != 0 { t.Fatalf("repo isolation broken: alpha content searchable from beta (%d hits)", cross.Count) } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/alpha/search?q=alphaseed"), &cross); err != nil { t.Fatal(err) } if cross.Count == 0 { t.Fatal("alpha content not searchable from alpha") } before := map[string][]byte{} for _, s := range sf { before[s.path] = snapshot(t, first, s.path) } first.Close() // The wipe (0001 §11 step 2): every derived byte goes — both // indexes, the event stream, the cursors. if err := os.RemoveAll(dataA); err != nil { t.Fatal(err) } dataB := filepath.Join(t.TempDir(), "data") if err := os.MkdirAll(dataB, 0o755); err != nil { t.Fatal(err) } second := ingestAndServe(t, stores, dataB) for _, s := range sf { after := snapshot(t, second, s.path) d, err := semdiff.Diff(s.kind, before[s.path], after) if err != nil { t.Fatalf("%s: %v", s.path, err) } if d != "" { t.Errorf("%s: semantic diff after wipe-and-reingest — hostage state:\n%s", s.path, d) } } } // TestSemanticNotByteEquality pins why the harness diffs semantically: // a rendering-order change must pass, missing data must fail (0001 §11). func TestSemanticNotByteEquality(t *testing.T) { d, err := semdiff.Diff(semdiff.JSON, []byte(`{"b":1,"a":"2026-08-14T10:00:00+02:00"}`), []byte(`{"a":"2026-08-14 08:00:00Z","b":1}`)) if err != nil || d != "" { t.Fatalf("harmless drift flagged: %q %v", d, err) } d, err = semdiff.Diff(semdiff.JSON, []byte(`{"a":1,"b":2}`), []byte(`{"a":1}`)) if err != nil || d == "" { t.Fatal("dropped data not flagged") } if !strings.Contains(d, "b") { t.Fatalf("diff should name the loss: %s", d) } }