Skip to content
quarry Artifact [852fda83a8]
Login

Artifact [852fda83a8]

Artifact 852fda83a878c6918f880b438a8fc3055757d407cbf15b031b9e68740cf3ad53:


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
	longCursor := false
	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)
		}
		if len(page.Next) > 100 {
			longCursor = true
		}
		version, pages = page.Next, pages+1
	}
	distinct := map[string]bool{}
	for _, h := range seen {
		distinct[h] = true
	}
	// zit's cursor is two full hashes: a next longer than 100 characters
	// must be taken back as a version (it was refused, a 400 on every
	// second page).
	if !longCursor {
		t.Error("FAIL: no page carried zit's long cursor")
	}
	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 ""
}