Artifact d0139d3fdc2c1567e3f078df94ccf348b48c5cfe12684b4da07b1de48a181148:
- File
internal/zitcli/zitcli_test.go
— part of check-in
[85b9909d1c]
at
2026-08-17 20:51:22
on branch trunk
— web: report where a request's time went, not just how long
The footer said 'rendered in 344ms'; it now says 'rendered in 344ms (zit 270ms)'. One number says a page is slow, not which layer to look at, and quarry's layers differ by three orders of magnitude — an index read is microseconds, zit ls --long on tk is 2.6 seconds.
The accumulator rides the request context, which is what makes it correct under load: package-level counters would blend concurrent requests together, and concurrency is what this exists to measure.
zit read calls take a context now and use exec.CommandContext, so an abandoned request kills its subprocess instead of leaving it to finish a 2.6-second listing nobody will read.
SQL is not instrumented yet; the index does not take a context. (user: dmestas, size: 6435) [annotate] [blame] [check-ins using]
package zitcli import ( "errors" "os" "os/exec" "path/filepath" "strings" "testing" ) // testRepo builds a throwaway zit repository with n one-file commits and // returns its path. Requires the zit binary; skips when absent. func testRepo(t *testing.T, n int) string { t.Helper() if _, err := exec.LookPath("zit"); err != nil { t.Skip("zit not on PATH") } dir := t.TempDir() run(t, dir, "init") for i := 0; i < n; i++ { name := string(rune('a'+i)) + ".txt" if err := os.WriteFile(filepath.Join(dir, name), []byte(name+" body\n"), 0o644); err != nil { t.Fatal(err) } run(t, dir, "add", name) run(t, dir, "commit", "-m", "add "+name) } return dir } func run(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) } } func TestFramesBootstrapAndIncremental(t *testing.T) { repo := testRepo(t, 2) z := New(repo) // Bootstrap: full census. batch, err := z.Frames(Cursor{}) if err != nil { t.Fatal(err) } if len(batch.Artifacts) == 0 { t.Fatal("bootstrap emitted nothing") } for _, a := range batch.Artifacts { if len(a.Hash) != 64 { t.Fatalf("bad hash %q", a.Hash) } if len(a.Bytes) == 0 { t.Fatalf("%s: empty artifact", a.Hash) } } // Current cursor: no new artifacts, cursor unchanged. again, err := z.Frames(batch.Cursor) if err != nil { t.Fatal(err) } if len(again.Artifacts) != 0 { t.Fatalf("current cursor re-emitted %d artifacts", len(again.Artifacts)) } if again.Cursor != batch.Cursor { t.Fatalf("cursor moved without appends: %v -> %v", batch.Cursor, again.Cursor) } // Append: only the new artifacts stream. if err := os.WriteFile(filepath.Join(repo, "new.txt"), []byte("new\n"), 0o644); err != nil { t.Fatal(err) } run(t, repo, "add", "new.txt") run(t, repo, "commit", "-m", "add new") delta, err := z.Frames(batch.Cursor) if err != nil { t.Fatal(err) } if len(delta.Artifacts) == 0 || len(delta.Artifacts) >= len(batch.Artifacts) { t.Fatalf("delta emitted %d of %d total", len(delta.Artifacts), len(batch.Artifacts)) } if delta.Cursor == batch.Cursor { t.Fatal("cursor did not advance past appends") } } func TestFramesCursorInvalidAfterStoreRewrite(t *testing.T) { repo := testRepo(t, 3) z := New(repo) batch, err := z.Frames(Cursor{}) if err != nil { t.Fatal(err) } // Shun a blob out of the store — the reclaim removes bytes from the // consumed prefix, so the cursor must die. (A no-op `pack` on an // already-optimal store rightly leaves cursors valid, which is why // this test rewrites via shun instead.) head, err := exec.Command("zit", "ls", "-R", repo, "tip").Output() if err != nil { t.Fatal(err) } blob := strings.Fields(string(head))[0] run(t, repo, "shun", blob, "--force") _, err = z.Frames(batch.Cursor) if !errors.Is(err, ErrCursorInvalid) { t.Fatalf("want ErrCursorInvalid after pack, got %v", err) } // Recovery: bootstrap from zero succeeds. if _, err := z.Frames(Cursor{}); err != nil { t.Fatalf("bootstrap after invalidation failed: %v", err) } } func TestFramesBytesMatchArtifactRaw(t *testing.T) { repo := testRepo(t, 1) z := New(repo) batch, err := z.Frames(Cursor{}) if err != nil { t.Fatal(err) } a := batch.Artifacts[0] cmd := exec.Command("zit", "artifact", a.Hash, "--raw") cmd.Dir = repo raw, err := cmd.Output() if err != nil { t.Fatal(err) } if string(raw) != string(a.Bytes) { t.Fatalf("frames bytes differ from artifact --raw for %s", a.Hash) } } func TestFramesMissingRepo(t *testing.T) { if _, err := exec.LookPath("zit"); err != nil { t.Skip("zit not on PATH") } z := New(t.TempDir()) if _, err := z.Frames(Cursor{}); err == nil { t.Fatal("want error for non-repository, got nil") } } func TestLs(t *testing.T) { repo := testRepo(t, 2) z := New(repo) files, err := z.Ls(t.Context(), "tip") if err != nil { t.Fatal(err) } if len(files) != 2 || files[0].Name != "a.txt" || files[1].Name != "b.txt" { t.Fatalf("files = %+v", files) } if len(files[0].Blob) != 64 { t.Fatalf("blob id = %q", files[0].Blob) } if _, err := z.Ls(t.Context(), "nope"); err == nil { t.Fatal("want error for bad version") } } func TestCat(t *testing.T) { repo := testRepo(t, 2) z := New(repo) got, err := z.Cat(t.Context(), "a.txt", "tip") if err != nil { t.Fatal(err) } if string(got) != "a.txt body\n" { t.Fatalf("content = %q", got) } if _, err := z.Cat(t.Context(), "missing.txt", "tip"); err == nil { t.Fatal("want error for missing file") } } func TestDiff(t *testing.T) { repo := testRepo(t, 1) commitFileZ(t, repo, "a.txt", "a.txt body\nplus a line\n", "grow a") commitFileZ(t, repo, "new.txt", "fresh\n", "add new") head, err := exec.Command("zit", "timeline", "-R", repo).Output() if err != nil { t.Fatal(err) } lines := strings.Split(strings.TrimSpace(string(head)), "\n") first := strings.Fields(lines[len(lines)-1])[0] unified, brief, err := New(repo).Diff(t.Context(), first, "tip") if err != nil { t.Fatal(err) } if !strings.Contains(unified, "+plus a line") || !strings.Contains(unified, "+fresh") { t.Fatalf("unified missing changes:\n%s", unified) } if len(brief) != 2 || brief[0].Status != "M" || brief[1].Status != "A" { t.Fatalf("brief = %+v", brief) } } func commitFileZ(t *testing.T, repo, name, body, msg string) { t.Helper() if err := os.WriteFile(filepath.Join(repo, name), []byte(body), 0o644); err != nil { t.Fatal(err) } run(t, repo, "add", name) run(t, repo, "commit", "-m", msg) } // A listing may only be memoised under a version that cannot move. This // is the guard on a cache that would otherwise serve a stale file tree // forever after the next commit. func TestOnlyHashVersionsAreCacheable(t *testing.T) { for _, v := range []string{ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", } { if !immutableVersion(v) { t.Errorf("hash %q rejected as cacheable", v) } } for _, v := range []string{ "trunk", "tip", "", "release-1.2", "2026-08-17", "aaaaaaaa", // a prefix still resolves by search, not identity "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", // uppercase is not the stored form "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", } { if immutableVersion(v) { t.Errorf("%q accepted as cacheable", v) } } }