package ci
import (
"slices"
"testing"
)
// askedRepo records which versions its pipeline files were read at.
type askedRepo struct {
*fakeRepo
asked []string
}
func (a *askedRepo) Files(v string) ([]string, error) {
a.asked = append(a.asked, v)
return a.fakeRepo.Files(v)
}
func (a *askedRepo) Read(p, v string) ([]byte, error) {
a.asked = append(a.asked, v)
return a.fakeRepo.Read(p, v)
}
// A PAIR RUN STARTS EVERY PIPELINE ONCE, FROM THE BRANCH TIP'S
// DEFINITIONS, AND STOPS ONLY OLDER PAIR RUNS.
//
// A pair is not a check-in, so its definitions are the branch tip's.
// The landing-only pipeline runs too: a pair exists only for a proposed
// branch. Asking again starts nothing. A pair against a newer target
// supersedes the older pair's runs, and never the branch tip's own.
func TestAPairRunStartsOnceFromTheBranchTip(t *testing.T) {
srv, started, cancelled := cancellingEngine(t)
e := New(srv.URL)
st := &fakeStore{mark: map[string]string{"alpha": "old"}}
repo := &askedRepo{fakeRepo: landingRepo()}
repo.landings["feat"] = true
repo.news = []Checkin{{Hash: "b1", Branch: "feat"}}
if err := Trigger(e, st, "alpha", repo, "http://x/r/alpha"); err != nil {
t.Fatal(err)
}
repo.news = nil
started()
repo.asked = nil
if _, err := RunPairAt(e, st, "alpha", repo, "http://x/r/alpha", "feat", "b1", "t1"); err != nil {
t.Fatal(err)
}
if got := started(); !slices.Equal(got, []string{"pair:b1:t1 ci/quarry.json", "pair:b1:t1 ci/race.json"}) {
t.Errorf("FAIL: the pair started %v; want both pipelines at pair:b1:t1", got)
}
for _, v := range repo.asked {
if v != "b1" {
t.Errorf("FAIL: a pipeline file was read at %q, want the branch tip b1", v)
}
}
if got := cancelled(); len(got) != 0 {
t.Errorf("FAIL: starting a pair cancelled %v; the branch tip's runs are still its verdict", got)
}
if _, err := RunPairAt(e, st, "alpha", repo, "http://x/r/alpha", "feat", "b1", "t1"); err != nil {
t.Fatal(err)
}
if got := started(); len(got) != 0 {
t.Errorf("FAIL: asking for the same pair again started %v", got)
}
if _, err := RunPairAt(e, st, "alpha", repo, "http://x/r/alpha", "feat", "b1", "t2"); err != nil {
t.Fatal(err)
}
started()
if got := cancelled(); !slices.Equal(got, []string{"pair:b1:t1 ci/quarry.json", "pair:b1:t1 ci/race.json"}) {
t.Errorf("FAIL: a pair against a newer target cancelled %v; want the older pair's runs only", got)
}
t.Log("ok: a pair runs once, from the branch tip's definitions, and supersedes only older pairs")
}
// A branch tip with no pipelines has nothing to run for its merge, and
// says so, so the sweep need not list its files again.
func TestAPairWithNoPipelinesSaysItRanNothing(t *testing.T) {
srv, started := startedEngine(t)
e := New(srv.URL)
repo := &fakeRepo{files: []string{"README.md"}, landings: map[string]bool{}}
ran, err := RunPairAt(e, &fakeStore{}, "alpha", repo, "http://x/r/alpha", "feat", "b1", "t1")
if err != nil || ran {
t.Fatalf("FAIL: a tip with no pipelines answered ran=%v err=%v", ran, err)
}
if got := started(); len(got) != 0 {
t.Fatalf("FAIL: started %v", got)
}
t.Log("ok: nothing to run, and it says so")
}
// A Re-run of a merge starts every pipeline again, where asking for the
// merge's missing runs starts nothing a second time.
func TestAReRunOfAMergeStartsEverythingAgain(t *testing.T) {
srv, started := startedEngine(t)
e := New(srv.URL)
st := &fakeStore{}
repo := landingRepo()
if _, err := RunPairAt(e, st, "alpha", repo, "http://x/r/alpha", "feat", "b1", "t1"); err != nil {
t.Fatal(err)
}
started()
if err := RerunPairAt(e, st, "alpha", repo, "http://x/r/alpha", "feat", "b1", "t1"); err != nil {
t.Fatal(err)
}
if got := started(); !slices.Equal(got, []string{"pair:b1:t1 ci/quarry.json", "pair:b1:t1 ci/race.json"}) {
t.Fatalf("FAIL: re-running the merge started %v, want both pipelines again", got)
}
t.Log("ok: a re-run of a merge starts everything again")
}