Artifact fb10ec333da8e1e0ac385fe3f63319fdb5f2d9346fcf32b0dd6c6724a452f005:
- File
internal/ci/ci_test.go
— part of check-in
[0502a93834]
at
2026-08-17 13:02:21
on branch trunk
— ci: the trigger half of v3 — dagnats is the engine
0001 §6 splits this narrowly: dagnats runs pipelines, quarry notices a check-in and shows the result. quarry defines nothing, schedules nothing, retries nothing.
A pipeline is TRACKED CONTENT — ci/*.json in the repository, dagnats' native format — read at the check-in under test, so the pipeline for a historical check-in is the pipeline that ran and there is no forge-side store to drift. Only files directly under ci/ count; a nested path is somebody's fixture.
Two behaviours worth naming. First sight of a repository takes the tip as a watermark and launches NOTHING: running a pipeline per historical check-in would be a denial of service against the engine and would say nothing about code nobody just changed — on an imported corpus that is tens of thousands of runs. And an unrecognised run status counts as still running, because claiming a run finished when it has not is how a verdict gets written against a build still going.
Verified against the live dagnats for the wire shapes; the trigger itself is covered by tests. (user: dmestas, size: 5631) [annotate] [blame] [check-ins using]
package ci import ( "encoding/json" "net/http" "net/http/httptest" "sync" "testing" ) func TestIsPipeline(t *testing.T) { for name, want := range map[string]bool{ "ci/build.json": true, "ci/test.json": true, "ci/nested/thing.json": false, // a fixture, not a pipeline "ci/README.md": false, "ci/": false, "cirrus/build.json": false, "build.json": false, "docs/ci/build.json": false, } { if got := IsPipeline(name); got != want { t.Errorf("IsPipeline(%q) = %v, want %v", name, got, want) } } } // An unrecognised status must read as "still running". Reporting a run // finished when it has not is the worse error: a verdict would be // written against a build that is still going. func TestUnknownStatusIsNotTerminal(t *testing.T) { for _, s := range []string{"running", "queued", "", "paused", "something-new"} { if (Run{Status: s}).Terminal() { t.Errorf("status %q reported terminal", s) } } for _, s := range []string{"completed", "failed", "cancelled", "error"} { if !(Run{Status: s}).Terminal() { t.Errorf("status %q not reported terminal", s) } } if !(Run{Status: "completed"}).Passed() || (Run{Status: "failed"}).Passed() { t.Fatal("Passed disagrees with the status") } } // fakeRepo is a repository with one pipeline. type fakeRepo struct { files []string blobs map[string][]byte news []Checkin } func (f *fakeRepo) NewCheckins(after string, limit int) ([]Checkin, error) { if after == "" && len(f.news) > 0 { return f.news[len(f.news)-1:], nil } return f.news, nil } func (f *fakeRepo) Files(string) ([]string, error) { return f.files, nil } func (f *fakeRepo) Read(p, _ string) ([]byte, error) { return f.blobs[p], nil } type fakeStore struct { mu sync.Mutex mark map[string]string runs []string } func (s *fakeStore) CIWatermark(repo string) (string, bool) { s.mu.Lock() defer s.mu.Unlock() v, ok := s.mark[repo] return v, ok } func (s *fakeStore) SetCIWatermark(repo, c string) error { s.mu.Lock() defer s.mu.Unlock() s.mark[repo] = c return nil } func (s *fakeStore) AddRun(runID, repo, checkin, workflow, pipeline, status string) error { s.mu.Lock() defer s.mu.Unlock() s.runs = append(s.runs, runID+" "+checkin+" "+workflow) return nil } // A repository seen for the first time must NOT launch its whole // history — on an imported corpus that is tens of thousands of runs. func TestFirstSightOnlySetsTheWatermark(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Errorf("engine called on first sight: %s", r.URL.Path) })) defer srv.Close() st := &fakeStore{mark: map[string]string{}} repo := &fakeRepo{news: []Checkin{{Hash: "old"}, {Hash: "tip"}}} if err := Trigger(New(srv.URL), st, "alpha", repo, "http://x/r/alpha"); err != nil { t.Fatal(err) } if st.mark["alpha"] != "tip" { t.Fatalf("watermark = %q, want tip", st.mark["alpha"]) } if len(st.runs) != 0 { t.Fatalf("launched %v on first sight", st.runs) } } func TestLaunchesPipelineWithCheckinContext(t *testing.T) { var gotInput map[string]any srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/workflows": w.WriteHeader(http.StatusCreated) w.Write([]byte(`{"status":"registered"}`)) case "/runs": var body struct { Workflow string `json:"workflow"` Input map[string]any `json:"input"` } json.NewDecoder(r.Body).Decode(&body) gotInput = body.Input w.WriteHeader(http.StatusCreated) w.Write([]byte(`{"run_id":"r-1"}`)) } })) defer srv.Close() st := &fakeStore{mark: map[string]string{"alpha": "old"}} repo := &fakeRepo{ files: []string{"README.md", "ci/build.json", "ci/notes.txt"}, blobs: map[string][]byte{"ci/build.json": []byte(`{"name":"build","version":"1.0","steps":[]}`)}, news: []Checkin{{Hash: "new1", Branch: "trunk", User: "dan"}}, } if err := Trigger(New(srv.URL), st, "alpha", repo, "http://x/r/alpha"); err != nil { t.Fatal(err) } if len(st.runs) != 1 || st.runs[0] != "r-1 new1 build" { t.Fatalf("runs = %v", st.runs) } // The pipeline must be told what it is building and how to get it. for _, k := range []string{"repo", "checkin", "branch", "cloneUrl", "pipeline"} { if gotInput[k] == nil || gotInput[k] == "" { t.Errorf("input missing %q: %+v", k, gotInput) } } if st.mark["alpha"] != "new1" { t.Fatalf("watermark not advanced: %q", st.mark["alpha"]) } } // A repository with no ci/ directory is the common case and must be // silent — no runs, no error, and the watermark still advances or every // sync round would reconsider the same check-ins forever. func TestNoPipelineIsQuietAndStillAdvances(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Errorf("engine called for a repo with no pipelines: %s", r.URL.Path) })) defer srv.Close() st := &fakeStore{mark: map[string]string{"alpha": "old"}} repo := &fakeRepo{files: []string{"README.md"}, news: []Checkin{{Hash: "new1"}}} if err := Trigger(New(srv.URL), st, "alpha", repo, "u"); err != nil { t.Fatal(err) } if st.mark["alpha"] != "new1" { t.Fatalf("watermark stuck at %q", st.mark["alpha"]) } } // A disabled engine (no --dagnats) does nothing at all. func TestDisabledEngineDoesNothing(t *testing.T) { st := &fakeStore{mark: map[string]string{}} if err := Trigger(New(""), st, "alpha", &fakeRepo{}, "u"); err != nil { t.Fatal(err) } if len(st.mark) != 0 { t.Fatalf("disabled engine touched the store: %v", st.mark) } }