Skip to content
quarry Artifact [490b11ca7b]
Login

Artifact [490b11ca7b]

Artifact 490b11ca7bde8356c1a76e65a3078b03d01d82c0ed46f320e5791d682154f40e:


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)
	}
	// zit ≤0.2 printed "* <hash>  <branch>"; 0.3 prints Fossil's
	// timeline line with a twelve-character bracketed hash. Collect
	// prefixes and compare prefix-wise — the definition is zit's
	// either way.
	want := map[string]bool{}
	for _, line := range strings.Split(string(out), "\n") {
		s := strings.TrimSpace(line)
		if f := strings.Fields(strings.TrimPrefix(s, "* ")); len(f) > 0 && len(f[0]) >= 40 {
			want[f[0][:12]] = true
			continue
		}
		if o, c := strings.IndexByte(s, '['), strings.IndexByte(s, ']'); o >= 0 && c > o+1 {
			want[s[o+1:c]] = 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[:12]] {
			disagree++
			t.Errorf("check-in %s: quarry says leaf=%v, zit says leaf=%v",
				e.Hash[:12], e.IsLeaf, want[e.Hash[:12]])
		}
	}
	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))
	}
}