vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 1h ago
4b8c9e1d
| 1 | package landing |
| 2 | |
| 3 | // The gate reads one job's newest run for a commit and turns it into a gap or |
| 4 | // nothing. Before #246 every non-success was a refusal, so a runner that could not |
| 5 | // start the job red-flagged a commit whose tests passed. These cases pin which |
| 6 | // statuses are the author's problem and which are the machine's. |
| 7 | |
| 8 | import ( |
| 9 | "strings" |
| 10 | "testing" |
| 11 | |
| 12 | "rodent.software/vole/ci" |
| 13 | "rodent.software/vole/ci/runs" |
| 14 | ) |
| 15 | |
| 16 | func TestCheckGapReadsEveryRunStatus(t *testing.T) { |
| 17 | t.Parallel() |
| 18 | wf := &ci.Workflow{Path: ".vole/workflows/verify.yaml", Branches: []string{"*"}} |
| 19 | job := ci.Job{ID: "verify"} |
| 20 | const sha = "0123456789abcdef0123456789abcdef01234567" |
| 21 | |
| 22 | cases := []struct { |
| 23 | name string |
| 24 | run *runs.Run |
| 25 | gap bool |
| 26 | wait bool |
| 27 | wantHas []string |
| 28 | }{ |
| 29 | { |
| 30 | name: "no run is a wait", gap: true, wait: true, |
| 31 | wantHas: []string{"no run", "push the branch"}, |
| 32 | }, |
| 33 | { |
| 34 | name: "a failed job is a refusal", wait: false, gap: true, |
| 35 | run: &runs.Run{Status: runs.StatusFailed, ExitCode: 1}, |
| 36 | wantHas: []string{"failed", "exit 1"}, |
| 37 | }, |
| 38 | { |
| 39 | name: "a canceled job is a refusal", wait: false, gap: true, |
| 40 | run: &runs.Run{Status: runs.StatusCanceled}, |
| 41 | wantHas: []string{"canceled"}, |
| 42 | }, |
| 43 | { |
| 44 | // The case this rule exists for: the runner could not build the image, so |
| 45 | // nothing ran and the commit is not what stopped it. |
| 46 | name: "a job that never started is a wait", wait: true, gap: true, |
| 47 | run: &runs.Run{ |
| 48 | ID: "01M6YY97B5V966TVRED4QA2643", Status: runs.StatusError, |
| 49 | Failure: runs.FailureSetup, |
| 50 | FailureMessage: `building image-file "verify" exited 125`, |
| 51 | }, |
| 52 | wantHas: []string{ |
| 53 | "could not run", |
| 54 | `building image-file "verify" exited 125`, |
| 55 | "vole run rerun 01M6YY97B5V966TVRED4QA2643", |
| 56 | }, |
| 57 | }, |
| 58 | { |
| 59 | name: "a queued job is a wait", gap: true, wait: true, |
| 60 | run: &runs.Run{Status: runs.StatusQueued}, |
| 61 | wantHas: []string{"queued", "wait"}, |
| 62 | }, |
| 63 | { |
| 64 | name: "a passing job is no gap", |
| 65 | run: &runs.Run{Status: runs.StatusSuccess}, |
| 66 | }, |
| 67 | } |
| 68 | for _, c := range cases { |
| 69 | t.Run(c.name, func(t *testing.T) { |
| 70 | t.Parallel() |
| 71 | problem, wait, gap := checkGap(wf, job, sha, c.run) |
| 72 | if gap != c.gap || wait != c.wait { |
| 73 | t.Fatalf("checkGap = gap %v wait %v, want gap %v wait %v (%q)", |
| 74 | gap, wait, c.gap, c.wait, problem) |
| 75 | } |
| 76 | for _, want := range c.wantHas { |
| 77 | if !strings.Contains(problem, want) { |
| 78 | t.Fatalf("the gap does not say %q: %q", want, problem) |
| 79 | } |
| 80 | } |
| 81 | }) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // A run with no recorded reason still says something a reader can act on, rather than |
| 86 | // trailing off after the status. |
| 87 | func TestCheckGapNamesAMissingReason(t *testing.T) { |
| 88 | t.Parallel() |
| 89 | wf := &ci.Workflow{Path: "verify.yaml"} |
| 90 | run := &runs.Run{ID: "run1", Status: runs.StatusError, Failure: runs.FailureSetup} |
| 91 | problem, wait, gap := checkGap(wf, ci.Job{ID: "verify"}, "0123456", run) |
| 92 | if !gap || !wait { |
| 93 | t.Fatalf("checkGap = gap %v wait %v", gap, wait) |
| 94 | } |
| 95 | if !strings.Contains(problem, "the runner recorded no reason") { |
| 96 | t.Fatalf("the gap does not account for a missing reason: %q", problem) |
| 97 | } |
| 98 | } |
| 99 |