Skip to content
quarry Artifact [90a67a65c5]
Login

Artifact [90a67a65c5]

Artifact 90a67a65c515c11954d8bdd13192abcc74b10c3b14d6f6afbb458983a7ab2305:


package zitcli

import (
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"slices"
	"testing"
)

// A live fold takes a tree check-in's changed paths from ChangedFiles
// and a rebuild from crosslink, so the two must name the same paths for
// every kind of change: an add, an edit, a rename, a mode change, a
// whole directory removed, a binary file and an empty one, a file
// replaced by a directory, a merge (both diff against the primary
// parent) and the root (its whole file list). The store is a Git import
// with sub-manifests, so the check-ins are tree check-ins.
func TestChangedFilesAgreesWithCrosslink(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	if _, err := exec.LookPath("git"); err != nil {
		t.Skip("git not on PATH")
	}
	tmp := t.TempDir()
	g := filepath.Join(tmp, "git")
	env := append(os.Environ(), "HOME="+tmp,
		"GIT_AUTHOR_NAME=Ana", "[email protected]",
		"GIT_COMMITTER_NAME=Ana", "[email protected]")
	in := func(dir, name string, args ...string) {
		t.Helper()
		c := exec.Command(name, args...)
		c.Dir, c.Env = dir, env
		if out, err := c.CombinedOutput(); err != nil {
			t.Fatalf("%s %v: %v\n%s", name, args, err, out)
		}
	}
	write := func(path, body string) {
		t.Helper()
		full := filepath.Join(g, path)
		if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(full, []byte(body), 0o644); err != nil {
			t.Fatal(err)
		}
	}
	if err := os.MkdirAll(g, 0o755); err != nil {
		t.Fatal(err)
	}
	in(g, "git", "init", "-q", "-b", "main")
	for _, p := range []string{"a/1.txt", "a/2.txt", "b/3.txt", "b/4.txt", "c/5.txt", "c/6.txt", "c/7.txt", "d/8.txt"} {
		write(p, p+"\n")
	}
	in(g, "git", "add", "-A")
	in(g, "git", "commit", "-qm", "base")
	in(g, "git", "mv", "a/1.txt", "d/moved.txt")
	in(g, "git", "commit", "-qm", "rename")
	in(g, "git", "rm", "-rq", "c")
	in(g, "git", "commit", "-qm", "remove a directory")
	if err := os.Chmod(filepath.Join(g, "b/3.txt"), 0o755); err != nil {
		t.Fatal(err)
	}
	in(g, "git", "commit", "-qam", "mode")
	write("b/bin.dat", "\x00\x01\x02")
	write("b/empty.txt", "")
	write("a/2.txt", "edited\n")
	in(g, "git", "add", "-A")
	in(g, "git", "commit", "-qm", "binary, empty, edit")
	// A file replaced by a directory of the same name.
	in(g, "git", "rm", "-q", "d/8.txt")
	write("d/8.txt/inner.txt", "inner\n")
	in(g, "git", "add", "-A")
	in(g, "git", "commit", "-qm", "file to directory")
	// A merge: a side branch and trunk each change a file.
	in(g, "git", "checkout", "-qb", "side")
	write("b/side.txt", "side\n")
	in(g, "git", "add", "-A")
	in(g, "git", "commit", "-qm", "side work")
	in(g, "git", "checkout", "-q", "main")
	write("a/2.txt", "edited on main\n")
	in(g, "git", "commit", "-qam", "main work")
	in(g, "git", "merge", "-q", "--no-ff", "-m", "merge side", "side")
	store := filepath.Join(tmp, "store")
	in(tmp, "zit", "import-git", g, store, "--submanifest-k", "1", "--quiet")

	z := New(store)
	ctx := context.Background()
	checked, roots, merges := 0, 0, 0
	if err := z.Crosslink(ctx, func(r CrosslinkRecord) error {
		if !r.FilesKnown {
			t.Errorf("FAIL: crosslink knows no files for %.12s (%q)", r.Hash, r.Comment)
			return nil
		}
		var got []string
		var err error
		if len(r.Parents) == 0 {
			// A root's changes are its files: the live fold lists them.
			roots++
			var entries []LsEntry
			if entries, err = z.LsNames(ctx, r.Hash); err == nil {
				for _, e := range entries {
					got = append(got, e.Name)
				}
				slices.Sort(got)
			}
		} else {
			if len(r.Parents) > 1 {
				merges++
			}
			got, err = z.ChangedFiles(ctx, r.Hash)
		}
		if err != nil {
			t.Errorf("FAIL: changed files of %.12s: %v", r.Hash, err)
			return nil
		}
		want := slices.Sorted(slices.Values(r.Changes))
		if !slices.Equal(got, want) {
			t.Errorf("FAIL: %.12s (%q) changed %q, crosslink says %q", r.Hash, r.Comment, got, want)
		}
		checked++
		return nil
	}); err != nil {
		t.Fatal(err)
	}
	if checked != 9 || roots != 1 || merges != 1 {
		t.Errorf("FAIL: compared %d check-ins (%d roots, %d merges), want 9 (1, 1)", checked, roots, merges)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}