Commit 7de4e7bd74
Verified · cmc
internal/ci/sched_test.go +5 −5
| @@ -70,7 +70,7 @@ func TestSchedulerRunDue(t *testing.T) { | ||
| 70 | 70 | RepoDir: func(owner, name string) string { return bare }} |
| 71 | 71 | s.RunDue(now) |
| 72 | 72 | |
| 73 | builds, err := st.ListBuilds(repoID, 10) | |
| 73 | builds, err := st.ListBuilds(repoID, store.BuildFilter{}, 10) | |
| 74 | 74 | if err != nil || len(builds) != 1 { |
| 75 | 75 | t.Fatalf("builds after run: %v %v", builds, err) |
| 76 | 76 | } |
| @@ -98,7 +98,7 @@ func TestSchedulerRunDue(t *testing.T) { | ||
| 98 | 98 | |
| 99 | 99 | // A second pass fires nothing: next_run is in the future. |
| 100 | 100 | s.RunDue(now) |
| 101 | if builds, _ = st.ListBuilds(repoID, 10); len(builds) != 1 { | |
| 101 | if builds, _ = st.ListBuilds(repoID, store.BuildFilter{}, 10); len(builds) != 1 { | |
| 102 | 102 | t.Fatalf("second pass queued extra builds: %+v", builds) |
| 103 | 103 | } |
| 104 | 104 | |
| @@ -111,7 +111,7 @@ func TestSchedulerRunDue(t *testing.T) { | ||
| 111 | 111 | t.Fatal(err) |
| 112 | 112 | } |
| 113 | 113 | s.RunDue(now) |
| 114 | if builds, _ = st.ListBuilds(repoID, 10); len(builds) != 1 { | |
| 114 | if builds, _ = st.ListBuilds(repoID, store.BuildFilter{}, 10); len(builds) != 1 { | |
| 115 | 115 | t.Fatalf("tick with the last build pending queued another: %+v", builds) |
| 116 | 116 | } |
| 117 | 117 | b, ok, err := st.ClaimBuild(nil, false) |
| @@ -122,7 +122,7 @@ func TestSchedulerRunDue(t *testing.T) { | ||
| 122 | 122 | t.Fatal(err) |
| 123 | 123 | } |
| 124 | 124 | s.RunDue(now) |
| 125 | if builds, _ = st.ListBuilds(repoID, 10); len(builds) != 1 { | |
| 125 | if builds, _ = st.ListBuilds(repoID, store.BuildFilter{}, 10); len(builds) != 1 { | |
| 126 | 126 | t.Fatalf("tick with the last build running queued another: %+v", builds) |
| 127 | 127 | } |
| 128 | 128 | if err := st.FinishBuild(b.ID, "success"); err != nil { |
| @@ -132,7 +132,7 @@ func TestSchedulerRunDue(t *testing.T) { | ||
| 132 | 132 | t.Fatal(err) |
| 133 | 133 | } |
| 134 | 134 | s.RunDue(now) |
| 135 | if builds, _ = st.ListBuilds(repoID, 10); len(builds) != 2 { | |
| 135 | if builds, _ = st.ListBuilds(repoID, store.BuildFilter{}, 10); len(builds) != 2 { | |
| 136 | 136 | t.Fatalf("tick after the last build finished did not queue: %+v", builds) |
| 137 | 137 | } |
| 138 | 138 | } |
internal/control/build.go +18 −4
| @@ -7,6 +7,7 @@ import ( | ||
| 7 | 7 | "io" |
| 8 | 8 | "log/slog" |
| 9 | 9 | "regexp" |
| 10 | "slices" | |
| 10 | 11 | "strconv" |
| 11 | 12 | "strings" |
| 12 | 13 | "time" |
| @@ -21,7 +22,7 @@ import ( | ||
| 21 | 22 | func init() { |
| 22 | 23 | register(Command{Path: []string{"build", "list"}, |
| 23 | 24 | Summary: "list recent builds", |
| 24 | Usage: "build list <owner/name>", ReadOnly: true, Run: runBuildList}) | |
| 25 | Usage: "build list <owner/name> [--ref <branch>] [--status <state>] [--job <name>]", ReadOnly: true, Run: runBuildList}) | |
| 25 | 26 | register(Command{Path: []string{"build", "show"}, |
| 26 | 27 | Summary: "show one build", |
| 27 | 28 | Usage: "build show <owner/name> <n>", ReadOnly: true, Run: runBuildShow}) |
| @@ -102,15 +103,28 @@ func buildRef(c *Ctx, args []string) (store.Repo, store.Build, int) { | ||
| 102 | 103 | return repo, b, -1 |
| 103 | 104 | } |
| 104 | 105 | |
| 106 | // buildStatuses is the vocabulary --status accepts, and what a bad value | |
| 107 | // is told to pick from. | |
| 108 | var buildStatuses = []string{"pending", "running", "success", "failure", "cancelled"} | |
| 109 | ||
| 105 | 110 | func runBuildList(c *Ctx, args []string) int { |
| 106 | if len(args) != 1 { | |
| 111 | f, err := parseFlags(args, flagSpec{Values: []string{"--ref", "--status", "--job"}, MaxPos: 1, Usage: c.Cmd.Usage}) | |
| 112 | if err != nil { | |
| 113 | return c.fail(protocol.ExitUsage, "%v", err) | |
| 114 | } | |
| 115 | path := f.pos(0) | |
| 116 | if path == "" { | |
| 107 | 117 | return c.usage() |
| 108 | 118 | } |
| 109 | repo, code := resolveRepo(c, args[0], policy.CanRead) | |
| 119 | status := f.Value("--status") | |
| 120 | if f.Has("--status") && !slices.Contains(buildStatuses, status) { | |
| 121 | return c.fail(protocol.ExitUsage, "--status must be one of %s", strings.Join(buildStatuses, ", ")) | |
| 122 | } | |
| 123 | repo, code := resolveRepo(c, path, policy.CanRead) | |
| 110 | 124 | if code >= 0 { |
| 111 | 125 | return code |
| 112 | 126 | } |
| 113 | builds, err := c.Store.ListBuilds(repo.ID, 50) | |
| 127 | builds, err := c.Store.ListBuilds(repo.ID, store.BuildFilter{Ref: f.Value("--ref"), Status: status, Job: f.Value("--job")}, 50) | |
| 114 | 128 | if err != nil { |
| 115 | 129 | return c.fail(protocol.ExitFailure, "%v", err) |
| 116 | 130 | } |
internal/control/build_test.go +71 −11
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | package control |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "bytes" | |
| 4 | 5 | "os" |
| 5 | 6 | "os/exec" |
| 6 | 7 | "path/filepath" |
| @@ -8,6 +9,7 @@ import ( | ||
| 8 | 9 | "testing" |
| 9 | 10 | "time" |
| 10 | 11 | |
| 12 | "gitbay.org/gitbay/internal/protocol" | |
| 11 | 13 | "gitbay.org/gitbay/internal/store" |
| 12 | 14 | ) |
| 13 | 15 | |
| @@ -102,7 +104,7 @@ func TestQueueBranchBuildsFailsOpenOnDiffFailure(t *testing.T) { | ||
| 102 | 104 | |
| 103 | 105 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "main", old, newSHA, time.Now()) |
| 104 | 106 | |
| 105 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 107 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 106 | 108 | if err != nil || len(builds) != 1 { |
| 107 | 109 | t.Fatalf("builds after queue: %v %v", builds, err) |
| 108 | 110 | } |
| @@ -142,7 +144,7 @@ func TestQueueBranchBuildsNewBranchIgnoredPathSkips(t *testing.T) { | ||
| 142 | 144 | |
| 143 | 145 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "feature", testZeroSHA, featureSHA, time.Now()) |
| 144 | 146 | |
| 145 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 147 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 146 | 148 | if err != nil { |
| 147 | 149 | t.Fatal(err) |
| 148 | 150 | } |
| @@ -180,7 +182,7 @@ func TestQueueBranchBuildsNewBranchMatchedPathQueues(t *testing.T) { | ||
| 180 | 182 | |
| 181 | 183 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "feature", testZeroSHA, featureSHA, time.Now()) |
| 182 | 184 | |
| 183 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 185 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 184 | 186 | if err != nil || len(builds) != 1 { |
| 185 | 187 | t.Fatalf("builds after queue: %v %v", builds, err) |
| 186 | 188 | } |
| @@ -225,7 +227,7 @@ func TestQueueBranchBuildsNewBranchFailsOpenWithoutMergeBase(t *testing.T) { | ||
| 225 | 227 | |
| 226 | 228 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "feature", testZeroSHA, otherSHA, time.Now()) |
| 227 | 229 | |
| 228 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 230 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 229 | 231 | if err != nil || len(builds) != 1 { |
| 230 | 232 | t.Fatalf("expected fail-open to queue the job: %v %v", builds, err) |
| 231 | 233 | } |
| @@ -257,7 +259,7 @@ func TestQueueBranchBuildsFreshDefaultBranchFailsOpen(t *testing.T) { | ||
| 257 | 259 | |
| 258 | 260 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "main", testZeroSHA, sha, time.Now()) |
| 259 | 261 | |
| 260 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 262 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 261 | 263 | if err != nil || len(builds) != 1 { |
| 262 | 264 | t.Fatalf("expected fail-open on the repository's first commit: %v %v", builds, err) |
| 263 | 265 | } |
| @@ -292,7 +294,7 @@ func TestQueueBranchBuildsOrdinaryPushStillFilters(t *testing.T) { | ||
| 292 | 294 | |
| 293 | 295 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "main", oldSHA, newSHA, time.Now()) |
| 294 | 296 | |
| 295 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 297 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 296 | 298 | if err != nil { |
| 297 | 299 | t.Fatal(err) |
| 298 | 300 | } |
| @@ -445,7 +447,7 @@ func TestQueueBranchBuildsAlreadyBuiltJobRecordsNoSkippedStatus(t *testing.T) { | ||
| 445 | 447 | if len(statuses) != 0 { |
| 446 | 448 | t.Fatalf("already-built job recorded a status: %+v", statuses) |
| 447 | 449 | } |
| 448 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 450 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 449 | 451 | if err != nil || len(builds) != 1 { |
| 450 | 452 | t.Fatalf("expected only the pre-existing build: %v %v", builds, err) |
| 451 | 453 | } |
| @@ -484,7 +486,7 @@ func TestQueueMRBuildsStillFailsOpen(t *testing.T) { | ||
| 484 | 486 | |
| 485 | 487 | QueueMRBuilds(st, root, "https://x.test", repo, uid, 1, prSHA) |
| 486 | 488 | |
| 487 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 489 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 488 | 490 | if err != nil || len(builds) != 1 { |
| 489 | 491 | t.Fatalf("expected the MR head to fail open and queue a build: %v %v", builds, err) |
| 490 | 492 | } |
| @@ -541,7 +543,7 @@ func TestQueueBranchBuildsRebaseFiltersAgainstMergeBase(t *testing.T) { | ||
| 541 | 543 | |
| 542 | 544 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "feat", oldTip, newTip, time.Now()) |
| 543 | 545 | |
| 544 | builds, err := st.ListBuilds(repo.ID, 10) | |
| 546 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 545 | 547 | if err != nil { |
| 546 | 548 | t.Fatal(err) |
| 547 | 549 | } |
| @@ -578,7 +580,7 @@ func TestQueueBranchBuildsSameTreeReusesSuccess(t *testing.T) { | ||
| 578 | 580 | git(root, "clone", "-q", "--bare", src, dir) |
| 579 | 581 | |
| 580 | 582 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "main", "", first, time.Now()) |
| 581 | builds, _ := st.ListBuilds(repo.ID, 10) | |
| 583 | builds, _ := st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 582 | 584 | if len(builds) != 1 { |
| 583 | 585 | t.Fatalf("first commit queued %d builds, want 1", len(builds)) |
| 584 | 586 | } |
| @@ -590,7 +592,7 @@ func TestQueueBranchBuildsSameTreeReusesSuccess(t *testing.T) { | ||
| 590 | 592 | } |
| 591 | 593 | |
| 592 | 594 | QueueBranchBuilds(st, root, "https://x.test", repo, uid, "main", first, second, time.Now()) |
| 593 | builds, _ = st.ListBuilds(repo.ID, 10) | |
| 595 | builds, _ = st.ListBuilds(repo.ID, store.BuildFilter{}, 10) | |
| 594 | 596 | if len(builds) != 1 { |
| 595 | 597 | t.Fatalf("same tree queued a second build: %+v", builds) |
| 596 | 598 | } |
| @@ -605,3 +607,61 @@ func TestQueueBranchBuildsSameTreeReusesSuccess(t *testing.T) { | ||
| 605 | 607 | t.Fatalf("second commit has no success status from the first: %+v", statuses) |
| 606 | 608 | } |
| 607 | 609 | } |
| 610 | ||
| 611 | // build list's --ref, --status and --job flags narrow the CLI listing the | |
| 612 | // same way the store filter does, and combine when more than one is given. | |
| 613 | // An invalid --status is refused rather than silently matching nothing (#224). | |
| 614 | func TestBuildListFlagsFilter(t *testing.T) { | |
| 615 | st, repo, uid := newQueueTestRepo(t) | |
| 616 | if _, err := st.CreateBuild(repo.ID, "unit", "aaa", "main", `["true"]`, "", "", true); err != nil { | |
| 617 | t.Fatal(err) | |
| 618 | } | |
| 619 | if _, err := st.CreateBuild(repo.ID, "lint", "bbb", "feature", `["true"]`, "", "", true); err != nil { | |
| 620 | t.Fatal(err) | |
| 621 | } | |
| 622 | if _, ok, err := st.ClaimBuild([]int64{repo.ID}, false); err != nil || !ok { | |
| 623 | t.Fatalf("claim: %v ok=%v", err, ok) | |
| 624 | } | |
| 625 | if err := st.FinishBuild(1, "failure"); err != nil { | |
| 626 | t.Fatal(err) | |
| 627 | } | |
| 628 | c, errOut := pruneCtx(st, t.TempDir(), store.User{ID: uid}) | |
| 629 | ||
| 630 | run := func(args ...string) string { | |
| 631 | t.Helper() | |
| 632 | out := c.Stdout.(*bytes.Buffer) | |
| 633 | out.Reset() | |
| 634 | errOut.Reset() | |
| 635 | argv := append([]string{"build", "list", repo.Path()}, args...) | |
| 636 | if code := Dispatch(c, argv); code != protocol.ExitOK { | |
| 637 | t.Fatalf("build list %v: exit %d: %s", args, code, errOut.String()) | |
| 638 | } | |
| 639 | return out.String() | |
| 640 | } | |
| 641 | ||
| 642 | if out := run(); !strings.Contains(out, "unit") || !strings.Contains(out, "lint") { | |
| 643 | t.Fatalf("unfiltered listing missing a build:\n%s", out) | |
| 644 | } | |
| 645 | if out := run("--ref", "main"); !strings.Contains(out, "unit") || strings.Contains(out, "lint") { | |
| 646 | t.Fatalf("--ref main:\n%s", out) | |
| 647 | } | |
| 648 | if out := run("--job", "lint"); strings.Contains(out, "unit") || !strings.Contains(out, "lint") { | |
| 649 | t.Fatalf("--job lint:\n%s", out) | |
| 650 | } | |
| 651 | if out := run("--status", "failure"); !strings.Contains(out, "unit") || strings.Contains(out, "lint") { | |
| 652 | t.Fatalf("--status failure:\n%s", out) | |
| 653 | } | |
| 654 | if out := run("--ref", "main", "--status", "pending"); strings.TrimSpace(out) != "" { | |
| 655 | t.Fatalf("non-matching combination returned rows:\n%s", out) | |
| 656 | } | |
| 657 | ||
| 658 | out := c.Stdout.(*bytes.Buffer) | |
| 659 | out.Reset() | |
| 660 | errOut.Reset() | |
| 661 | if code := Dispatch(c, []string{"build", "list", repo.Path(), "--status", "bogus"}); code != protocol.ExitUsage { | |
| 662 | t.Fatalf("bad --status: exit %d, want %d (usage)", code, protocol.ExitUsage) | |
| 663 | } | |
| 664 | if !strings.Contains(errOut.String(), "pending") || !strings.Contains(errOut.String(), "cancelled") { | |
| 665 | t.Fatalf("bad --status error does not name the valid states: %s", errOut.String()) | |
| 666 | } | |
| 667 | } | |
internal/control/runnernext_test.go +1 −1
| @@ -165,7 +165,7 @@ func TestRunnerNextOrphanedQueuePastCapReportsNoPendingBuilds(t *testing.T) { | ||
| 165 | 165 | t.Fatalf("expected no pending builds, got:\n%s", out.String()) |
| 166 | 166 | } |
| 167 | 167 | |
| 168 | builds, err := st.ListBuilds(repo.ID, total+1) | |
| 168 | builds, err := st.ListBuilds(repo.ID, store.BuildFilter{}, total+1) | |
| 169 | 169 | if err != nil { |
| 170 | 170 | t.Fatal(err) |
| 171 | 171 | } |
internal/hookd/pushshapes_test.go +2 −2
| @@ -214,7 +214,7 @@ func (f *shapeFixture) schedules() map[string]bool { | ||
| 214 | 214 | |
| 215 | 215 | // mark snapshots the state the observed action starts from. |
| 216 | 216 | func (f *shapeFixture) mark(sha string) { |
| 217 | builds, err := f.st.ListBuilds(f.repo.ID, 1000) | |
| 217 | builds, err := f.st.ListBuilds(f.repo.ID, store.BuildFilter{}, 1000) | |
| 218 | 218 | if err != nil { |
| 219 | 219 | f.t.Fatal(err) |
| 220 | 220 | } |
| @@ -226,7 +226,7 @@ func (f *shapeFixture) mark(sha string) { | ||
| 226 | 226 | // the ci/config status. The words are the table's vocabulary. |
| 227 | 227 | func (f *shapeFixture) observe() map[string]string { |
| 228 | 228 | out := map[string]string{} |
| 229 | builds, err := f.st.ListBuilds(f.repo.ID, 1000) | |
| 229 | builds, err := f.st.ListBuilds(f.repo.ID, store.BuildFilter{}, 1000) | |
| 230 | 230 | if err != nil { |
| 231 | 231 | f.t.Fatal(err) |
| 232 | 232 | } |
internal/httpd/facts.go +2 −1
| @@ -6,6 +6,7 @@ import ( | ||
| 6 | 6 | |
| 7 | 7 | "gitbay.org/gitbay/internal/control" |
| 8 | 8 | "gitbay.org/gitbay/internal/gitutil" |
| 9 | "gitbay.org/gitbay/internal/store" | |
| 9 | 10 | ) |
| 10 | 11 | |
| 11 | 12 | // repoFacts is the "what is this repository" summary on a repo home: the |
| @@ -86,7 +87,7 @@ func (s *Server) factsFor(p repoPage) repoFacts { | ||
| 86 | 87 | if rels, err := s.st.ListReleases(p.Repo.ID); err == nil && len(rels) > 0 { |
| 87 | 88 | f.Release = rels[0].Tag |
| 88 | 89 | } |
| 89 | if builds, err := s.st.ListBuilds(p.Repo.ID, 1); err == nil && len(builds) > 0 { | |
| 90 | if builds, err := s.st.ListBuilds(p.Repo.ID, store.BuildFilter{}, 1); err == nil && len(builds) > 0 { | |
| 90 | 91 | f.Build = builds[0].Status |
| 91 | 92 | } |
| 92 | 93 | f.Bookmarks = s.st.BookmarkCount(p.Repo.ID) |
internal/store/builds.go +25 −2
| @@ -274,8 +274,31 @@ func (s *Store) BuildByNumber(repoID, number int64) (Build, error) { | ||
| 274 | 274 | return b, err |
| 275 | 275 | } |
| 276 | 276 | |
| 277 | func (s *Store) ListBuilds(repoID int64, limit int) ([]Build, error) { | |
| 278 | rows, err := s.DB.Query(buildSelect+" WHERE repo_id = ? ORDER BY number DESC LIMIT ?", repoID, limit) | |
| 277 | // BuildFilter narrows ListBuilds to builds matching every non-empty field. | |
| 278 | type BuildFilter struct { | |
| 279 | Ref string | |
| 280 | Status string | |
| 281 | Job string | |
| 282 | } | |
| 283 | ||
| 284 | func (s *Store) ListBuilds(repoID int64, f BuildFilter, limit int) ([]Build, error) { | |
| 285 | q := buildSelect + " WHERE repo_id = ?" | |
| 286 | args := []any{repoID} | |
| 287 | if f.Ref != "" { | |
| 288 | q += " AND ref = ?" | |
| 289 | args = append(args, f.Ref) | |
| 290 | } | |
| 291 | if f.Status != "" { | |
| 292 | q += " AND status = ?" | |
| 293 | args = append(args, f.Status) | |
| 294 | } | |
| 295 | if f.Job != "" { | |
| 296 | q += " AND job = ?" | |
| 297 | args = append(args, f.Job) | |
| 298 | } | |
| 299 | q += " ORDER BY number DESC LIMIT ?" | |
| 300 | args = append(args, limit) | |
| 301 | rows, err := s.DB.Query(q, args...) | |
| 279 | 302 | if err != nil { |
| 280 | 303 | return nil, err |
| 281 | 304 | } |
internal/store/builds_test.go +57
| @@ -332,6 +332,63 @@ func TestQueueStatsFractionalAverage(t *testing.T) { | ||
| 332 | 332 | } |
| 333 | 333 | } |
| 334 | 334 | |
| 335 | // ListBuilds narrows on ref, status and job independently, and combines | |
| 336 | // when more than one is given (#224). | |
| 337 | func TestListBuildsFilters(t *testing.T) { | |
| 338 | s := open(t) | |
| 339 | if err := s.MigrateUp(); err != nil { | |
| 340 | t.Fatal(err) | |
| 341 | } | |
| 342 | uid, err := s.CreateUser("cmc", true) | |
| 343 | if err != nil { | |
| 344 | t.Fatal(err) | |
| 345 | } | |
| 346 | repoID, err := s.CreateRepo("user", uid, "app", "public") | |
| 347 | if err != nil { | |
| 348 | t.Fatal(err) | |
| 349 | } | |
| 350 | if _, err := s.CreateBuild(repoID, "unit", "aaa", "main", `["true"]`, "", "", true); err != nil { | |
| 351 | t.Fatal(err) | |
| 352 | } | |
| 353 | if _, err := s.CreateBuild(repoID, "lint", "bbb", "feature", `["true"]`, "", "", true); err != nil { | |
| 354 | t.Fatal(err) | |
| 355 | } | |
| 356 | if err := s.FinishBuild(mustClaim(t, s, repoID).ID, "failure"); err != nil { | |
| 357 | t.Fatal(err) | |
| 358 | } | |
| 359 | ||
| 360 | all, err := s.ListBuilds(repoID, BuildFilter{}, 10) | |
| 361 | if err != nil || len(all) != 2 { | |
| 362 | t.Fatalf("unfiltered: %+v %v", all, err) | |
| 363 | } | |
| 364 | if byRef, err := s.ListBuilds(repoID, BuildFilter{Ref: "main"}, 10); err != nil || len(byRef) != 1 || byRef[0].Ref != "main" { | |
| 365 | t.Fatalf("by ref: %+v %v", byRef, err) | |
| 366 | } | |
| 367 | if byJob, err := s.ListBuilds(repoID, BuildFilter{Job: "lint"}, 10); err != nil || len(byJob) != 1 || byJob[0].Job != "lint" { | |
| 368 | t.Fatalf("by job: %+v %v", byJob, err) | |
| 369 | } | |
| 370 | if byStatus, err := s.ListBuilds(repoID, BuildFilter{Status: "failure"}, 10); err != nil || len(byStatus) != 1 || byStatus[0].Status != "failure" { | |
| 371 | t.Fatalf("by status: %+v %v", byStatus, err) | |
| 372 | } | |
| 373 | if combined, err := s.ListBuilds(repoID, BuildFilter{Ref: "main", Status: "failure"}, 10); err != nil || len(combined) != 1 { | |
| 374 | t.Fatalf("combined filter: %+v %v", combined, err) | |
| 375 | } | |
| 376 | if none, err := s.ListBuilds(repoID, BuildFilter{Ref: "main", Status: "pending"}, 10); err != nil || len(none) != 0 { | |
| 377 | t.Fatalf("non-matching combination: %+v %v", none, err) | |
| 378 | } | |
| 379 | } | |
| 380 | ||
| 381 | // mustClaim claims the oldest pending build for repoID, failing the test | |
| 382 | // if none is available. | |
| 383 | func mustClaim(t *testing.T, s *Store, repoID int64) Build { | |
| 384 | t.Helper() | |
| 385 | b, ok, err := s.ClaimBuild([]int64{repoID}, false) | |
| 386 | if err != nil || !ok { | |
| 387 | t.Fatalf("claim: %v ok=%v", err, ok) | |
| 388 | } | |
| 389 | return b | |
| 390 | } | |
| 391 | ||
| 335 | 392 | // A merge request head from a fork is untrusted. A claim skips it unless |
| 336 | 393 | // the runner asked for untrusted builds, so a runner on someone's laptop |
| 337 | 394 | // never executes a stranger's branch by default. |