Skip to content
quarry Artifact [49c254895a]
Login

Artifact [49c254895a]

Artifact 49c254895a4bd7e9224f68e96ce300edd6c8432212d54d717567bb5231d231ac:


package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/ci"
	"craftdesign.group/quarry/internal/events"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ingest"
	"craftdesign.group/quarry/internal/lease"
	"craftdesign.group/quarry/internal/zitcli"
)

// subjectEngine is a CI engine that records the subject of every run
// it is asked to start.
func subjectEngine(t *testing.T) (*ci.Engine, func() []string) {
	t.Helper()
	var mu sync.Mutex
	var started []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.URL.Path == "/workflows":
			w.WriteHeader(http.StatusCreated)
			w.Write([]byte(`{"status":"registered"}`))
		case r.URL.Path == "/runs" && r.Method == http.MethodPost:
			var body struct {
				Input map[string]any `json:"input"`
			}
			json.NewDecoder(r.Body).Decode(&body)
			mu.Lock()
			started = append(started, fmt.Sprint(body.Input["checkin"]))
			id := fmt.Sprintf("r-%d", len(started))
			mu.Unlock()
			w.WriteHeader(http.StatusCreated)
			fmt.Fprintf(w, `{"run_id":%q}`, id)
		case r.URL.Path == "/runs":
			w.Write([]byte(`[]`))
		default:
			http.NotFound(w, r)
		}
	}))
	t.Cleanup(srv.Close)
	return ci.New(srv.URL), func() []string {
		mu.Lock()
		defer mu.Unlock()
		out := started
		started = nil
		return out
	}
}

// A LANDING IS TESTED AS ITS MERGE, AS A PULL REQUEST IS.
//
// quarry#290: the sweep after a fold starts a pair run for a landing
// whose branch tip has none, and nothing again for that tip. A target
// that moves re-runs nothing, as on a pull request; a push to the
// branch tests its merge with the target as it is then.
func TestTheSweepTestsALandingAsItsMerge(t *testing.T) {
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir := t.TempDir()
	write := func(name, body string) {
		t.Helper()
		if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
			t.Fatal(err)
		}
	}
	zitIn(t, dir, "init", ".")
	write("ci/test.json", `{"name":"test","version":"1.0","steps":[]}`)
	write("f.txt", "root\n")
	zitIn(t, dir, "add", "ci/test.json", "f.txt")
	zitIn(t, dir, "commit", "-m", "root", "--user", "u")
	write("g.txt", "feat\n")
	zitIn(t, dir, "add", "g.txt")
	zitIn(t, dir, "commit", "-m", "feat", "--branch", "feat", "--user", "u")
	cli := zitcli.New(dir)
	if _, err := cli.LandingOpen(lease.Unguarded("test"), "dan", "feat", "trunk", "land feat", ""); err != nil {
		t.Fatal(err)
	}
	ev, err := events.Start(filepath.Join(t.TempDir(), "nats"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(ev.Close)
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	pipe := ingest.NewPipeline(cli, ev, ix, "r", "n1")
	if _, err := pipe.Sync(); err != nil {
		t.Fatal(err)
	}

	rg := newTestRegistrar(t, 25)
	engine, started := subjectEngine(t)
	rg.engine = engine
	rg.deps = &api.Deps{Repos: api.NewRepos(map[string]*api.Repo{"r": {Index: ix, Zit: cli}})}
	tip := func(b string) string {
		t.Helper()
		h, err := ix.BranchTip(b)
		if err != nil || h == "" {
			t.Fatalf("no tip for %s: %v", b, err)
		}
		return h
	}
	sweep := func() []string {
		t.Helper()
		open, err := ix.Landings("open", 500)
		if err != nil {
			t.Fatal(err)
		}
		rg.pairSweep("r", ix, open, nil)
		return started()
	}

	feat, root := tip("feat"), tip("trunk")
	if got := sweep(); len(got) != 1 || got[0] != ci.Pair(feat, root) {
		t.Fatalf("FAIL: an opened merge-mode landing started %v, want its pair with trunk", got)
	}
	if got := sweep(); len(got) != 0 {
		t.Fatalf("FAIL: a pair already tested against trunk as it is started again: %v", got)
	}

	zitIn(t, dir, "update", "trunk")
	write("f.txt", "moved\n")
	zitIn(t, dir, "commit", "-m", "trunk moves", "--user", "u")
	if _, err := pipe.Sync(); err != nil {
		t.Fatal(err)
	}
	moved := tip("trunk")
	if got := sweep(); len(got) != 0 {
		t.Fatalf("FAIL: trunk moving re-ran %v; a pull request's checks do not re-run for its base", got)
	}

	zitIn(t, dir, "update", "feat")
	write("g.txt", "feat, again\n")
	zitIn(t, dir, "commit", "-m", "feat again", "--user", "u")
	if _, err := pipe.Sync(); err != nil {
		t.Fatal(err)
	}
	pushed := tip("feat")
	if got := sweep(); len(got) != 1 || got[0] != ci.Pair(pushed, moved) {
		t.Fatalf("FAIL: a push to the branch started %v, want its merge with trunk at %.12s", got, moved)
	}
	t.Log("ok: a merge run when the landing opens and when its branch moves, none when trunk moves")
}