package api
import (
"context"
"fmt"
"os/exec"
"strings"
"testing"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/zitcli"
)
// A file's history reads to its first change a page at a time: each
// page names where the next begins, and no change is listed twice.
func TestAFilesHistoryPagesToItsFirstChange(t *testing.T) {
if _, err := exec.LookPath("zit"); err != nil {
t.Skip("zit not on PATH")
}
dir := t.TempDir()
zrun(t, dir, "init")
for i := 1; i <= 5; i++ {
zwrite(t, dir, "f", fmt.Sprintf("%d\n", i))
if i == 1 {
zrun(t, dir, "add", "f")
}
zrun(t, dir, "commit", "-m", fmt.Sprintf("c%d", i), "--user", "u")
zwrite(t, dir, "other", fmt.Sprintf("%d\n", i)) // check-ins that leave f alone
if i == 1 {
zrun(t, dir, "add", "other")
}
zrun(t, dir, "commit", "-m", fmt.Sprintf("o%d", i), "--user", "u")
}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Zit: zitcli.New(dir)}})}); err != nil {
t.Fatal(err)
}
ctx := context.Background()
var seen []string
version, pages := "trunk", 0
for version != "" && pages < 10 {
out, err := r.Invoke(ctx, "file.history", fileHistoryIn{Repo: "test", Version: version, Path: "f", Limit: 2})
if err != nil {
t.Fatal(err)
}
page := out.(fileHistoryOut)
for _, c := range page.Changes {
seen = append(seen, c.Hash)
}
version, pages = page.Next, pages+1
}
distinct := map[string]bool{}
for _, h := range seen {
distinct[h] = true
}
if len(seen) != 5 || len(distinct) != 5 || pages != 3 {
t.Errorf("FAIL: %d pages listed %d changes (%d distinct), want 3 pages of the 5", pages, len(seen), len(distinct))
} else {
t.Log("ok: three pages, five changes, none twice")
}
}
// Across a merge the pages are the one newest-first listing cut in
// pieces. A side branch forked before a trunk change but committed after
// it lists between the two; asking for the next page from the side
// check-in's own ancestry dropped the trunk change.
func TestAFilesHistoryPagesAcrossAMergeWithoutLosingRows(t *testing.T) {
if _, err := exec.LookPath("zit"); err != nil {
t.Skip("zit not on PATH")
}
dir := t.TempDir()
zrun(t, dir, "init")
zwrite(t, dir, "f", "a\nb\nc\nd\ne\n")
zrun(t, dir, "add", "f")
zrun(t, dir, "commit", "-m", "c0", "--user", "u")
base := firstHash(zrun(t, dir, "info", "tip"))
zwrite(t, dir, "f", "A\nb\nc\nd\ne\n")
zrun(t, dir, "commit", "-m", "t1", "--user", "u")
zrun(t, dir, "update", base)
zwrite(t, dir, "f", "a\nb\nc\nd\nE\n")
zrun(t, dir, "commit", "--branch", "side", "-m", "s1", "--user", "u")
zrun(t, dir, "update", "trunk")
zrun(t, dir, "merge", "side")
zrun(t, dir, "commit", "-m", "m", "--user", "u")
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Zit: zitcli.New(dir)}})}); err != nil {
t.Fatal(err)
}
whole, err := r.Invoke(context.Background(), "file.history", fileHistoryIn{Repo: "test", Version: "trunk", Path: "f", Limit: 50})
if err != nil {
t.Fatal(err)
}
want := len(whole.(fileHistoryOut).Changes)
var seen []string
version, pages := "trunk", 0
for version != "" && pages < 10 {
out, err := r.Invoke(context.Background(), "file.history", fileHistoryIn{Repo: "test", Version: version, Path: "f", Limit: 1})
if err != nil {
t.Fatal(err)
}
page := out.(fileHistoryOut)
for _, c := range page.Changes {
seen = append(seen, c.Hash)
}
version, pages = page.Next, pages+1
}
distinct := map[string]bool{}
for _, h := range seen {
distinct[h] = true
}
if want < 3 || len(seen) != want || len(distinct) != want {
t.Errorf("FAIL: pages of one listed %d (%d distinct); the whole listing has %d", len(seen), len(distinct), want)
} else {
t.Logf("ok: %d rows across %d pages, none lost or repeated", want, pages)
}
}
// firstHash is the first 64-hex token in zit's output.
func firstHash(s string) string {
for _, f := range strings.Fields(s) {
if len(f) == 64 {
return f
}
}
return ""
}