package harness
import (
"encoding/json"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// Leaf status is repo-state: the repo-state RFC defines it and zit
// derives and caches it. quarry must not re-derive it.
//
// Its own predicate answered "no check-in lists this one as a parent" —
// which §7.2 names explicitly as NOT the canonical test. The canonical
// one is "no child ON THE SAME BRANCH", and open leaves additionally
// exclude an active closed tag. On the fossil corpus that was 490
// marked against a true 1432, and a different set: a forge stating
// false history about a store that is entirely correct.
//
// The fixture branches deliberately. On a linear history the two
// definitions agree and this test would pass while the bug stood.
func TestLeafMarksAgreeWithZit(t *testing.T) {
repo := buildStore(t, "leafparity")
if err := os.WriteFile(filepath.Join(repo, "side.txt"), []byte("side\n"), 0o644); err != nil {
t.Fatal(err)
}
zrun(t, repo, "add", "side.txt")
// --branch starts a new branch AT this check-in, so trunk's previous
// tip keeps a child that is not on trunk: exactly the shape where
// "no child at all" and "no same-branch child" disagree.
zrun(t, repo, "commit", "-m", "work on the side branch", "--branch", "side")
srv := ingestAndServe(t, map[string]string{"alpha": repo}, t.TempDir())
// zit's answer, which is the definition.
out, err := exec.Command("zit", "leaves", "-R", repo).Output()
if err != nil {
t.Fatalf("zit leaves: %v", err)
}
want := map[string]bool{}
for _, line := range strings.Split(string(out), "\n") {
f := strings.Fields(strings.TrimPrefix(strings.TrimSpace(line), "* "))
if len(f) > 0 && len(f[0]) >= 40 {
want[f[0]] = true
}
}
if len(want) == 0 {
t.Fatal("zit reported no leaves for the fixture")
}
resp, err := srv.Client().Get(srv.URL + "/api/v1/r/alpha/timeline?limit=200")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var tl struct {
Entries []struct {
Kind string `json:"kind"`
Hash string `json:"hash"`
IsLeaf bool `json:"isLeaf"`
} `json:"entries"`
}
if err := json.Unmarshal(body, &tl); err != nil {
t.Fatal(err)
}
checkins, marked, disagree := 0, 0, 0
for _, e := range tl.Entries {
if e.Kind != "ci" {
continue
}
checkins++
if e.IsLeaf {
marked++
}
if e.IsLeaf != want[e.Hash] {
disagree++
t.Errorf("check-in %s: quarry says leaf=%v, zit says leaf=%v",
e.Hash[:12], e.IsLeaf, want[e.Hash])
}
}
if checkins == 0 {
t.Fatal("no check-ins on the timeline")
}
if disagree == 0 {
t.Logf("%d check-ins, %d marked leaf, agreeing with zit's %d", checkins, marked, len(want))
}
}