Commit e14c4caeec
Verified · cmc ci/build: success ci/test: success
Layout: unified · split
.gitbay/wiki/Architecture/07-CI-and-Supply-Chain.org +1 −1
| @@ -23,7 +23,7 @@ commit instead of failing silently. | ||
| 23 | 23 | 1. *Queue.* The post-receive hook calls =queueJobs= |
| 24 | 24 | (=internal/control/build.go=). Each job gets a =ci/<job>= status: |
| 25 | 25 | =pending= when queued, =skipped= when path filters exclude it, or |
| 26 | =success= copied from an earlier trusted build of the same tree on the same image (#177, #258). | |
| 26 | =success= copied from an earlier trusted build of the same tree on the same ref and image (#177, #258). | |
| 27 | 27 | Merge requests from forks are queued against the target repository |
| 28 | 28 | with =trusted = false=. |
| 29 | 29 | 2. *Claim.* A runner calls =runner next= over SSH |
.gitbay/wiki/CI.org +15 −8
| @@ -2,10 +2,13 @@ | ||
| 2 | 2 | |
| 3 | 3 | Three mechanisms decide what a push does to CI, and they interact: |
| 4 | 4 | |
| 5 | - *Dedupe.* A job's result is a property of the commit's tree. A commit | |
| 6 | that already has a passed, queued or running build for a job is not | |
| 7 | queued again; a commit whose tree already passed a job gets that | |
| 8 | result as its status, naming the build it came from (#177). Only a | |
| 5 | - *Dedupe.* A job's result is a property of the commit's tree and the | |
| 6 | ref it ran for, since a job sees =GITBAY_REF=. A commit that already | |
| 7 | has a passed, queued or running build for a job on this ref is not | |
| 8 | queued again; a commit whose tree already passed a job on this ref | |
| 9 | gets that result as its status, naming the build it came from (#177). | |
| 10 | A merge onto the default branch is built there even when the branch | |
| 11 | passed. Only a | |
| 9 | 12 | trusted build counts, and for tree reuse only one on the image the |
| 10 | 13 | job names: a fork's green build does not stand for the repository's |
| 11 | 14 | own, so its commit is built again when it lands on a branch (#258). A |
| @@ -168,7 +171,8 @@ push produced for that job; the commit's =ci/<job>= status follows: | ||
| 168 | 171 | | a new branch, no old sha | queued | queued | skipped | — | — | — | |
| 169 | 172 | | rebase onto a moved default branch, old not an ancestor | queued | queued | skipped | — | — | — | |
| 170 | 173 | | rewritten commit, same tree as a passed build | reused | reused | skipped | — | — | — | |
| 171 | | fast-forward of a commit built on another branch | — | — | — | registered | — | — | | |
| 174 | | fast-forward of a commit built on another branch | queued | queued | — | registered | — | — | | |
| 175 | | squash merge onto the default branch, same tree as a branch build | queued | queued | skipped | registered | — | — | | |
| 172 | 176 | | a commit whose earlier build failed, on a new branch | queued | queued | — | — | — | — | |
| 173 | 177 | | merge request head from a fork | queued, untrusted | queued, untrusted | queued, untrusted | — | — | — | |
| 174 | 178 | | tag push | — | — | — | — | queued | — | |
| @@ -190,9 +194,12 @@ Rows worth a second look: | ||
| 190 | 194 | because the tree check comes before the filter. =notapp= never had a |
| 191 | 195 | passed build to reuse, and the push changed nothing, so the filter |
| 192 | 196 | skips it again. |
| 193 | - A fast-forward of a commit built elsewhere queues nothing: the | |
| 194 | builds belong to the commit, not the branch. The default-branch push | |
| 195 | still registers the schedule. | |
| 197 | - A build stands for its commit, or its tree, only on the ref it ran | |
| 198 | for: a job sees =GITBAY_REF= and may act on it, as one that publishes | |
| 199 | only from the default branch does. A fast-forward or a squash merge | |
| 200 | onto the default branch is built there although the branch passed. | |
| 201 | =notapp= on the fast-forward keeps the skipped status the branch push | |
| 202 | gave the same commit. | |
| 196 | 203 | - A failed build does not stand for its commit. The same commit pushed |
| 197 | 204 | to another branch runs again; =notapp= keeps its skipped status. |
| 198 | 205 | - A merge request head from a fork has no diff base and, unlike a |
e2e/ci_dedupe_test.go +24 −22
| @@ -7,9 +7,11 @@ import ( | ||
| 7 | 7 | "testing" |
| 8 | 8 | ) |
| 9 | 9 | |
| 10 | // A fast-forward lands the exact commit that was already built on its | |
| 11 | // branch; that commit is not built again. A commit whose build failed is. | |
| 12 | func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) { | |
| 10 | // A fast-forward lands a commit already built on its branch, and it is | |
| 11 | // built again for the target: a job sees GITBAY_REF, so the branch's | |
| 12 | // result does not say what the job does on main (#335). That holds for | |
| 13 | // a branch build that passed, one still queued, and one that failed. | |
| 14 | func TestFastForwardMergeBuildsOnTarget(t *testing.T) { | |
| 13 | 15 | t.Parallel() |
| 14 | 16 | inst := startInstance(t) |
| 15 | 17 | inst.runner = buildRunner(t) |
| @@ -20,6 +22,10 @@ func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) { | ||
| 20 | 22 | if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 { |
| 21 | 23 | t.Fatalf("repo create: %s", errOut) |
| 22 | 24 | } |
| 25 | mainBuilds := func() int { | |
| 26 | out, _, _ := inst.ssh(t, aliceKey, "", "build", "list", "alice/app", "--ref", "main") | |
| 27 | return strings.Count(out, "\n") | |
| 28 | } | |
| 23 | 29 | work := t.TempDir() |
| 24 | 30 | env := inst.gitEnv(aliceKey) |
| 25 | 31 | mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w") |
| @@ -39,27 +45,22 @@ func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) { | ||
| 39 | 45 | mustGit(t, dir, env, "commit", "-q", "-m", "good change") |
| 40 | 46 | mustGit(t, dir, env, "push", "-q", "origin", "good") |
| 41 | 47 | inst.runnerOnce(t, runnerKey) |
| 42 | list := inst.buildList(t, aliceKey) | |
| 43 | if !strings.Contains(list, "good\tunit\tsuccess") && !strings.Contains(list, "unit\tsuccess") { | |
| 48 | if list := inst.buildList(t, aliceKey); !strings.Contains(list, "unit\tsuccess") { | |
| 44 | 49 | t.Fatalf("branch build did not pass:\n%s", list) |
| 45 | 50 | } |
| 46 | 51 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/app", "--source", "good", "--target", "main", "--title", "good"); code != 0 { |
| 47 | 52 | t.Fatalf("mr create: %s", errOut) |
| 48 | 53 | } |
| 49 | before := strings.Count(inst.buildList(t, aliceKey), "\n") | |
| 54 | before := mainBuilds() | |
| 50 | 55 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "1", "--strategy", "ff"); code != 0 { |
| 51 | 56 | t.Fatalf("mr merge: %s", errOut) |
| 52 | 57 | } |
| 53 | if after := strings.Count(inst.buildList(t, aliceKey), "\n"); after != before { | |
| 54 | t.Fatalf("fast-forward queued a build for an already-built commit:\n%s", inst.buildList(t, aliceKey)) | |
| 55 | } | |
| 56 | sha := strings.Fields(strings.TrimSpace(mustGit(t, dir, env, "ls-remote", "origin", "refs/heads/main")))[0] | |
| 57 | if status, _, _ := inst.ssh(t, aliceKey, "", "status", "list", "alice/app", sha); !strings.Contains(status, "success") || strings.Contains(status, "pending") { | |
| 58 | t.Fatalf("statuses on the merged commit changed:\n%s", status) | |
| 58 | if after := mainBuilds(); after != before+1 { | |
| 59 | t.Fatalf("fast-forward of a passed commit queued %d builds on main, want 1:\n%s", after-before, inst.buildList(t, aliceKey)) | |
| 59 | 60 | } |
| 61 | inst.runnerOnce(t, runnerKey) | |
| 60 | 62 | |
| 61 | // A branch merged before its build has run: the queued build will | |
| 62 | // say, so the merge queues nothing either. | |
| 63 | // A branch merged before its build has run: main gets its own. | |
| 63 | 64 | mustGit(t, dir, env, "checkout", "-q", "-b", "early") |
| 64 | 65 | os.WriteFile(filepath.Join(dir, "g.txt"), []byte("y\n"), 0o644) |
| 65 | 66 | mustGit(t, dir, env, "add", ".") |
| @@ -68,16 +69,17 @@ func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) { | ||
| 68 | 69 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/app", "--source", "early", "--target", "main", "--title", "early"); code != 0 { |
| 69 | 70 | t.Fatalf("mr create: %s", errOut) |
| 70 | 71 | } |
| 71 | before = strings.Count(inst.buildList(t, aliceKey), "\n") | |
| 72 | before = mainBuilds() | |
| 72 | 73 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "2", "--strategy", "ff"); code != 0 { |
| 73 | 74 | t.Fatalf("mr merge early: %s", errOut) |
| 74 | 75 | } |
| 75 | if after := strings.Count(inst.buildList(t, aliceKey), "\n"); after != before { | |
| 76 | t.Fatalf("a merge before the branch build ran queued a second build:\n%s", inst.buildList(t, aliceKey)) | |
| 76 | if after := mainBuilds(); after != before+1 { | |
| 77 | t.Fatalf("a merge before the branch build ran queued %d builds on main, want 1:\n%s", after-before, inst.buildList(t, aliceKey)) | |
| 77 | 78 | } |
| 78 | inst.runnerOnce(t, runnerKey) // the branch's queued build, now the only one | |
| 79 | inst.runnerOnce(t, runnerKey) // the branch's | |
| 80 | inst.runnerOnce(t, runnerKey) // main's | |
| 79 | 81 | |
| 80 | // A branch whose build fails is built again when it lands. | |
| 82 | // A branch whose build fails. | |
| 81 | 83 | mustGit(t, dir, env, "checkout", "-q", "-b", "bad") |
| 82 | 84 | os.WriteFile(filepath.Join(dir, "fail.txt"), []byte("x\n"), 0o644) |
| 83 | 85 | os.Remove(filepath.Join(dir, "f.txt")) |
| @@ -91,11 +93,11 @@ func TestFastForwardMergeSkipsBuiltCommit(t *testing.T) { | ||
| 91 | 93 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/app", "--source", "bad", "--target", "main", "--title", "bad"); code != 0 { |
| 92 | 94 | t.Fatalf("mr create: %s", errOut) |
| 93 | 95 | } |
| 94 | before = strings.Count(inst.buildList(t, aliceKey), "\n") | |
| 96 | before = mainBuilds() | |
| 95 | 97 | if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "3", "--strategy", "ff"); code != 0 { |
| 96 | 98 | t.Fatalf("mr merge bad: %s", errOut) |
| 97 | 99 | } |
| 98 | if after := strings.Count(inst.buildList(t, aliceKey), "\n"); after != before+1 { | |
| 99 | t.Fatalf("a failed commit was not rebuilt on landing:\n%s", inst.buildList(t, aliceKey)) | |
| 100 | if after := mainBuilds(); after != before+1 { | |
| 101 | t.Fatalf("a failed commit queued %d builds on main, want 1:\n%s", after-before, inst.buildList(t, aliceKey)) | |
| 100 | 102 | } |
| 101 | 103 | } |
internal/control/build.go +29 −15
| @@ -1020,20 +1020,28 @@ func queueJobs( | ||
| 1020 | 1020 | st.SetCommitStatus(repo.ID, sha, "ci/config", "failure", err.Error(), "", userID) |
| 1021 | 1021 | return |
| 1022 | 1022 | } |
| 1023 | // A build is a fact about a commit, not a ref: a job has no branch | |
| 1024 | // filter, so a commit that already passed a job on another branch has | |
| 1025 | // nothing left to prove when a fast-forward lands it here, and one | |
| 1026 | // still queued or running there will say soon enough. A failed, | |
| 1027 | // abandoned or cancelled build does not count; that commit runs again. | |
| 1028 | built, err := st.BuildsForCommit(repo.ID, sha) | |
| 1023 | // A build is a fact about a commit on a ref: a job sees GITBAY_REF and | |
| 1024 | // may act on it, as a job that publishes only from the default branch | |
| 1025 | // does, so a build for another ref does not stand for this one. A | |
| 1026 | // commit already built for this ref, or queued or running for it, has | |
| 1027 | // nothing left to prove. A failed, abandoned or cancelled build does | |
| 1028 | // not count; that commit runs again. | |
| 1029 | built, err := st.BuildsForCommitOnRef(repo.ID, sha, ref) | |
| 1029 | 1030 | if err != nil { |
| 1030 | 1031 | built = nil |
| 1031 | 1032 | } |
| 1032 | // A job's result is a property of the tree, not the commit: a rebase | |
| 1033 | // onto a base that touched nothing the branch did gives every commit | |
| 1034 | // a new sha and the same tree, and re-running the suite over it | |
| 1035 | // proves nothing it did not already prove (#177). A success recorded | |
| 1036 | // against the tree stands for the new commit. | |
| 1033 | // The ci/<job> status belongs to the commit, so a build of it for | |
| 1034 | // another ref reports there too, and a skip here must not overwrite it. | |
| 1035 | elsewhere, err := st.BuildsForCommit(repo.ID, sha) | |
| 1036 | if err != nil { | |
| 1037 | elsewhere = nil | |
| 1038 | } | |
| 1039 | // A job's result is a property of the tree and the ref, not the | |
| 1040 | // commit: a rebase onto a base that touched nothing the branch did | |
| 1041 | // gives every commit a new sha and the same tree, and re-running the | |
| 1042 | // suite over it proves nothing it did not already prove (#177). A | |
| 1043 | // success recorded against the tree on this ref stands for the new | |
| 1044 | // commit; a squash merge onto another branch is built there. | |
| 1037 | 1045 | tree, _ := gitutil.ResolveTree(dir, sha) |
| 1038 | 1046 | // The changed-file list a job's path filters run against, computed |
| 1039 | 1047 | // once and only if some job actually declares one. When the diff |
| @@ -1092,11 +1100,15 @@ func queueJobs( | ||
| 1092 | 1100 | // stands for it — unless this queue is trusted and that build was |
| 1093 | 1101 | // not: a fork's head that lands on a branch is built again as the |
| 1094 | 1102 | // repository's own (#258). |
| 1095 | if b, ok := built[j.Name]; ok && (b.Trusted || !trusted) && | |
| 1096 | (b.Status == "success" || b.Status == "pending" || b.Status == "running") { | |
| 1103 | stands := func(m map[string]store.Build) bool { | |
| 1104 | b, ok := m[j.Name] | |
| 1105 | return ok && (b.Trusted || !trusted) && | |
| 1106 | (b.Status == "success" || b.Status == "pending" || b.Status == "running") | |
| 1107 | } | |
| 1108 | if stands(built) { | |
| 1097 | 1109 | continue |
| 1098 | 1110 | } |
| 1099 | if prev, ok, _ := st.SuccessBuildForTree(repo.ID, tree, j.Name, j.Image); ok && prev.SHA != sha { | |
| 1111 | if prev, ok, _ := st.SuccessBuildForTree(repo.ID, tree, ref, j.Name, j.Image); ok && prev.SHA != sha { | |
| 1100 | 1112 | url := fmt.Sprintf("%s/%s/builds/%d", siteURL, repo.Path(), prev.Number) |
| 1101 | 1113 | st.SetCommitStatus(repo.ID, sha, "ci/"+j.Name, "success", |
| 1102 | 1114 | fmt.Sprintf("passed in build %d as %.10s, same tree", prev.Number, prev.SHA), url, userID) |
| @@ -1117,7 +1129,9 @@ func queueJobs( | ||
| 1117 | 1129 | // require_checks with a skipped status instead of leaving the |
| 1118 | 1130 | // commit with none at all, which the gate refuses outright (#172). |
| 1119 | 1131 | if filtered && !ci.Selected(j, changed) { |
| 1120 | st.SetCommitStatus(repo.ID, sha, "ci/"+j.Name, "skipped", skipReason(j, changed), "", userID) | |
| 1132 | if !stands(elsewhere) { | |
| 1133 | st.SetCommitStatus(repo.ID, sha, "ci/"+j.Name, "skipped", skipReason(j, changed), "", userID) | |
| 1134 | } | |
| 1121 | 1135 | continue |
| 1122 | 1136 | } |
| 1123 | 1137 | steps, _ := json.Marshal(j.Steps) |
internal/hookd/pushshapes_test.go +14 −1
| @@ -330,7 +330,20 @@ var pushShapes = []pushShape{ | ||
| 330 | 330 | f.git(f.src, "merge", "-q", "--ff-only", "feat") |
| 331 | 331 | f.mark(c1) |
| 332 | 332 | f.push("main", f.base, c1) |
| 333 | }, []string{"—", "—", "—", "registered", "—", "—"}}, | |
| 333 | }, []string{"queued", "queued", "—", "registered", "—", "—"}}, | |
| 334 | ||
| 335 | {"squash merge onto the default branch, same tree as a branch build", func(f *shapeFixture) { | |
| 336 | f.git(f.src, "checkout", "-q", "-b", "feat") | |
| 337 | c1 := f.appCommit("more") | |
| 338 | f.push("feat", zeroSHA40, c1) | |
| 339 | f.finish("success") | |
| 340 | f.git(f.src, "checkout", "-q", "main") | |
| 341 | f.git(f.src, "merge", "-q", "--squash", "feat") | |
| 342 | f.git(f.src, "commit", "-q", "-m", "squashed") | |
| 343 | c2 := f.sha("HEAD") | |
| 344 | f.mark(c2) | |
| 345 | f.push("main", f.base, c2) | |
| 346 | }, []string{"queued", "queued", "skipped", "registered", "—", "—"}}, | |
| 334 | 347 | |
| 335 | 348 | {"a commit whose earlier build failed, on a new branch", func(f *shapeFixture) { |
| 336 | 349 | f.git(f.src, "checkout", "-q", "-b", "feat") |
internal/store/builds.go +18 −5
| @@ -437,7 +437,18 @@ func (s *Store) LatestBuild(repoID int64, job string) (Build, error) { | ||
| 437 | 437 | // request's checks are ci/<job> statuses; this is where their timing comes |
| 438 | 438 | // from, in one query rather than one per check. |
| 439 | 439 | func (s *Store) BuildsForCommit(repoID int64, sha string) (map[string]Build, error) { |
| 440 | rows, err := s.DB.Query(buildSelect+" WHERE repo_id = ? AND sha = ? ORDER BY number ASC", repoID, sha) | |
| 440 | return s.newestPerJob(" WHERE repo_id = ? AND sha = ? ORDER BY number ASC", repoID, sha) | |
| 441 | } | |
| 442 | ||
| 443 | // BuildsForCommitOnRef returns the newest build per job for one commit | |
| 444 | // made for one ref. A job sees GITBAY_REF, so a build for another ref | |
| 445 | // says nothing about what the job does on this one. | |
| 446 | func (s *Store) BuildsForCommitOnRef(repoID int64, sha, ref string) (map[string]Build, error) { | |
| 447 | return s.newestPerJob(" WHERE repo_id = ? AND sha = ? AND ref = ? ORDER BY number ASC", repoID, sha, ref) | |
| 448 | } | |
| 449 | ||
| 450 | func (s *Store) newestPerJob(where string, args ...any) (map[string]Build, error) { | |
| 451 | rows, err := s.DB.Query(buildSelect+where, args...) | |
| 441 | 452 | if err != nil { |
| 442 | 453 | return nil, err |
| 443 | 454 | } |
| @@ -490,18 +501,20 @@ func (s *Store) CancelBuild(id int64) error { | ||
| 490 | 501 | |
| 491 | 502 | // SuccessBuildForTree finds a passed build of the job for a tree rather |
| 492 | 503 | // than a commit: a rebase that changes nothing in the tree has already |
| 493 | // been built (#177). Only a trusted build on the image the job names | |
| 504 | // been built (#177). Only a build for the same ref counts, since a job | |
| 505 | // sees GITBAY_REF and may do something else on another one. Only a | |
| 506 | // trusted build on the image the job names | |
| 494 | 507 | // counts: a fork's result, or one from an image the job has left, does |
| 495 | 508 | // not stand for the repository's own (#258). A job naming no image |
| 496 | 509 | // matches builds that named none, whichever default the runner used; |
| 497 | 510 | // the CI wiki page says so. An empty tree never matches. |
| 498 | func (s *Store) SuccessBuildForTree(repoID int64, tree, job, image string) (Build, bool, error) { | |
| 511 | func (s *Store) SuccessBuildForTree(repoID int64, tree, ref, job, image string) (Build, bool, error) { | |
| 499 | 512 | if tree == "" { |
| 500 | 513 | return Build{}, false, nil |
| 501 | 514 | } |
| 502 | 515 | b, err := scanBuild(s.DB.QueryRow(buildSelect+ |
| 503 | " WHERE repo_id = ? AND tree = ? AND job = ? AND image = ? AND trusted = 1 AND status = 'success'"+ | |
| 504 | " ORDER BY number DESC LIMIT 1", repoID, tree, job, image)) | |
| 516 | " WHERE repo_id = ? AND tree = ? AND ref = ? AND job = ? AND image = ? AND trusted = 1 AND status = 'success'"+ | |
| 517 | " ORDER BY number DESC LIMIT 1", repoID, tree, ref, job, image)) | |
| 505 | 518 | if errors.Is(err, sql.ErrNoRows) { |
| 506 | 519 | return Build{}, false, nil |
| 507 | 520 | } |
internal/store/builds_test.go +11 −7
| @@ -214,8 +214,9 @@ func TestBuildLogSaysWhenItTruncates(t *testing.T) { | ||
| 214 | 214 | } |
| 215 | 215 | } |
| 216 | 216 | |
| 217 | // A success is found by tree across commits; an empty tree never matches, | |
| 218 | // so builds queued without one (scheduled, tag) are never reused (#177). | |
| 217 | // A success is found by tree across commits on one ref; an empty tree | |
| 218 | // never matches, so builds queued without one (scheduled, tag) are never | |
| 219 | // reused (#177). | |
| 219 | 220 | func TestSuccessBuildForTree(t *testing.T) { |
| 220 | 221 | s := open(t) |
| 221 | 222 | if err := s.MigrateUp(); err != nil { |
| @@ -239,13 +240,16 @@ func TestSuccessBuildForTree(t *testing.T) { | ||
| 239 | 240 | if err := s.FinishBuild(b["unit"].ID, "success"); err != nil { |
| 240 | 241 | t.Fatal(err) |
| 241 | 242 | } |
| 242 | if prev, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "unit", ""); !ok || prev.SHA != "aaa" { | |
| 243 | if prev, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "main", "unit", ""); !ok || prev.SHA != "aaa" { | |
| 243 | 244 | t.Fatalf("success not found by tree: ok=%v prev=%+v", ok, prev) |
| 244 | 245 | } |
| 245 | if _, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "other", ""); ok { | |
| 246 | if _, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "main", "other", ""); ok { | |
| 246 | 247 | t.Error("matched a different job") |
| 247 | 248 | } |
| 248 | if _, ok, _ := s.SuccessBuildForTree(repoID, "", "unit", ""); ok { | |
| 249 | if _, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "feat", "unit", ""); ok { | |
| 250 | t.Error("matched a build for another ref") | |
| 251 | } | |
| 252 | if _, ok, _ := s.SuccessBuildForTree(repoID, "", "main", "unit", ""); ok { | |
| 249 | 253 | t.Error("an empty tree matched") |
| 250 | 254 | } |
| 251 | 255 | } |
| @@ -278,10 +282,10 @@ func TestSuccessReuseNeedsTrustAndImage(t *testing.T) { | ||
| 278 | 282 | t.Fatal(err) |
| 279 | 283 | } |
| 280 | 284 | } |
| 281 | if prev, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "unit", ""); ok { | |
| 285 | if prev, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "main", "unit", ""); ok { | |
| 282 | 286 | t.Fatalf("reused build %d: untrusted, or on another image", prev.Number) |
| 283 | 287 | } |
| 284 | if prev, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "unit", "localhost/old:1"); !ok || prev.SHA != "bbb" { | |
| 288 | if prev, ok, _ := s.SuccessBuildForTree(repoID, "tree1", "main", "unit", "localhost/old:1"); !ok || prev.SHA != "bbb" { | |
| 285 | 289 | t.Fatalf("trusted build on the same image not found: ok=%v prev=%+v", ok, prev) |
| 286 | 290 | } |
| 287 | 291 | if _, ok, _ := s.SuccessBuildFor(repoID, "aaa", "unit"); ok { |