Skip to content
quarry Artifact [72bf71ffe9]
Login

Artifact [72bf71ffe9]

Artifact 72bf71ffe971db52e00a092d7be716e0403900bd2fc791a36543ce6f8ca8e3db:


package ci

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"slices"
	"sync"
	"testing"
)

// startedEngine accepts every registration and start, and records which
// pipeline each started run was for, at which check-in.
func startedEngine(t *testing.T) (*httptest.Server, 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.MethodGet:
			fmt.Fprint(w, `[]`)
		case r.URL.Path == "/runs":
			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"], " ", body.Input["pipeline"]))
			n := len(started)
			mu.Unlock()
			w.WriteHeader(http.StatusCreated)
			fmt.Fprintf(w, `{"run_id":"r-%d"}`, n)
		}
	}))
	t.Cleanup(srv.Close)
	return srv, func() []string {
		mu.Lock()
		defer mu.Unlock()
		out := append([]string(nil), started...)
		started = started[:0]
		slices.Sort(out)
		return out
	}
}

// A PIPELINE MARKED "when": "landing" RUNS ON TRUNK AND ON PROPOSED
// BRANCHES, NEVER ON A BARE BRANCH PUSH.
//
// The race detector is the slow step, and a branch nobody has proposed
// does not need it; trunk and every proposed tip do. A re-run somebody
// asks for runs everything.
func TestALandingPipelineRunsOnTrunkAndProposedBranchesOnly(t *testing.T) {
	srv, started := startedEngine(t)
	e := New(srv.URL)
	st := &fakeStore{mark: map[string]string{"alpha": "old"}}
	repo := landingRepo()
	round := func(news ...Checkin) []string {
		repo.news = news
		if err := Trigger(e, st, "alpha", repo, "http://x/r/alpha"); err != nil {
			t.Fatal(err)
		}
		return started()
	}
	if got := round(Checkin{Hash: "b1", Branch: "feat"}); !slices.Equal(got, []string{"b1 ci/quarry.json"}) {
		t.Errorf("FAIL: a bare branch push started %v; want only ci/quarry.json", got)
	}
	repo.landings["feat"] = true
	if got := round(Checkin{Hash: "b2", Branch: "feat"}); !slices.Equal(got, []string{"b2 ci/quarry.json", "b2 ci/race.json"}) {
		t.Errorf("FAIL: a push to a proposed branch started %v; want both pipelines", got)
	}
	if got := round(Checkin{Hash: "t1", Branch: "trunk"}); !slices.Equal(got, []string{"t1 ci/quarry.json", "t1 ci/race.json"}) {
		t.Errorf("FAIL: a trunk check-in started %v; want both pipelines", got)
	}
	repo.news = nil
	if err := RunAt(e, st, "alpha", repo, "http://x/r/alpha", "b9"); err != nil {
		t.Fatal(err)
	}
	if got := started(); !slices.Equal(got, []string{"b9 ci/quarry.json", "b9 ci/race.json"}) {
		t.Errorf("FAIL: an asked-for re-run started %v; want every pipeline", got)
	}
	t.Log("ok: the landing pipeline runs on trunk and proposed branches, and on asked-for re-runs")
}

// OPENING A LANDING STARTS ONLY WHAT ITS TIP LACKS.
//
// A landing opened on a tip whose push ran ci/quarry.json gets exactly
// one more run, ci/race.json, which the bare push skipped; asking again
// starts nothing, and a pipeline whose only run was cancelled has no
// verdict and runs again.
func TestRunMissingAtStartsOnlyThePipelinesATipLacks(t *testing.T) {
	srv, started := startedEngine(t)
	e := New(srv.URL)
	st := &fakeStore{mark: map[string]string{"alpha": "old"}}
	repo := landingRepo()
	repo.news = []Checkin{{Hash: "b1", Branch: "feat"}}
	if err := Trigger(e, st, "alpha", repo, "http://x/r/alpha"); err != nil {
		t.Fatal(err)
	}
	started()
	repo.news = nil
	repo.landings["feat"] = true
	tip := Checkin{Hash: "b1", Branch: "feat"}
	if err := RunMissingAt(e, st, "alpha", repo, "http://x/r/alpha", tip); err != nil {
		t.Fatal(err)
	}
	if got := started(); !slices.Equal(got, []string{"b1 ci/race.json"}) {
		t.Errorf("FAIL: opening a landing on b1 started %v; want exactly ci/race.json", got)
	}
	if err := RunMissingAt(e, st, "alpha", repo, "http://x/r/alpha", tip); err != nil {
		t.Fatal(err)
	}
	if got := started(); len(got) != 0 {
		t.Errorf("FAIL: asking again started %v", got)
	}
	st.mu.Lock()
	st.ran["b1"] = []string{"ci/race.json"} // the ci/quarry.json run was cancelled: no verdict
	st.mu.Unlock()
	if err := RunMissingAt(e, st, "alpha", repo, "http://x/r/alpha", tip); err != nil {
		t.Fatal(err)
	}
	if got := started(); !slices.Equal(got, []string{"b1 ci/quarry.json"}) {
		t.Errorf("FAIL: a tip whose only quarry run was cancelled started %v; want ci/quarry.json", got)
	}
	t.Log("ok: an opened landing gets exactly the runs its tip lacks")
}

func landingRepo() *fakeRepo {
	return &fakeRepo{
		files: []string{"ci/quarry.json", "ci/race.json"},
		blobs: map[string][]byte{
			"ci/quarry.json": []byte(`{"name":"quarry","version":"1.0","steps":[]}`),
			"ci/race.json":   []byte(`{"name":"race","version":"1.0","when":"landing","steps":[]}`),
		},
		landings: map[string]bool{},
	}
}