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.
|
ac433531
Keenan Nemetz
16d ago
|
1 | package server_test |
| 2 | ||
| 3 | // The runners page is built from two derived facts in the queue: the presence a | |
| 4 | // session writes while it is connected, and the work the queue is holding. It answers | |
| 5 | // "is my build waiting because nobody is listening", which is otherwise a question | |
| 6 | // about a process on somebody else's machine. | |
| 7 | ||
| 8 | import ( | |
|
4506a4d3
Keenan Nemetz
14d ago
|
9 | "net/http" |
|
ac433531
Keenan Nemetz
16d ago
|
10 | "strings" |
| 11 | "testing" | |
| 12 | "time" | |
| 13 | ||
|
d37d301f
Keenan Nemetz
17h ago
|
14 | "rodent.software/vole/ci/runs" |
| 15 | "rodent.software/vole/forge/config" | |
|
ac433531
Keenan Nemetz
16d ago
|
16 | ) |
| 17 | ||
| 18 | func queuedRun(id, repo, job string, created time.Time) runs.Run { | |
| 19 | return runs.Run{ | |
| 20 | ID: id, Repo: repo, Ref: "refs/heads/main", Branch: "main", | |
| 21 | SHA: strings.Repeat("ab", 20), | |
| 22 | Workflow: ".vole/workflows/ci.yaml", WorkflowSHA: strings.Repeat("cd", 20), | |
| 23 | Job: job, Steps: []runs.Step{{Name: "test", Run: "true"}}, Created: created, | |
| 24 | } | |
| 25 | } | |
| 26 | ||
|
43e681d8
Keenan Nemetz
4d ago
|
27 | func TestAdminRunnersTabShowsAHeldJobAndTheQueueDepth(t *testing.T) { |
|
44530e74
Keenan Nemetz
16d ago
|
28 | t.Parallel() |
|
ac433531
Keenan Nemetz
16d ago
|
29 | f := newFixture(t) |
| 30 | f.publish(f.repo) | |
| 31 | now := time.Now().UTC() | |
| 32 | ||
| 33 | waiting := queuedRun("01WAITINGRUN00000000000000", f.repo, "waiting", now) | |
|
6c03e461
Keenan Nemetz
16d ago
|
34 | if _, err := f.srv.Runs.Enqueue(waiting, nil); err != nil { |
|
ac433531
Keenan Nemetz
16d ago
|
35 | t.Fatal(err) |
| 36 | } | |
| 37 | building := queuedRun("01BUILDINGRUN0000000000000", f.repo, "building", now) | |
|
6c03e461
Keenan Nemetz
16d ago
|
38 | if _, err := f.srv.Runs.Enqueue(building, nil); err != nil { |
|
ac433531
Keenan Nemetz
16d ago
|
39 | t.Fatal(err) |
| 40 | } | |
| 41 | // A runner connected as the owner, holding the second job — the state the page | |
| 42 | // exists to show. | |
|
6c03e461
Keenan Nemetz
16d ago
|
43 | if err := f.srv.Runs.TouchSession("session-1", f.ownerID, nil, now); err != nil { |
|
ac433531
Keenan Nemetz
16d ago
|
44 | t.Fatal(err) |
| 45 | } | |
| 46 | if err := f.srv.Runs.SessionWorking("session-1", f.repo, building.ID, now); err != nil { | |
| 47 | t.Fatal(err) | |
| 48 | } | |
| 49 | ||
|
ed03b3d1
Keenan Nemetz
4h ago
|
50 | code, body := f.adminGet(t, "/-/admin?tab=runners") |
|
ac433531
Keenan Nemetz
16d ago
|
51 | if code != 200 { |
|
43e681d8
Keenan Nemetz
4d ago
|
52 | t.Fatalf("GET /admin?tab=runners = %d\n%s", code, body) |
|
ac433531
Keenan Nemetz
16d ago
|
53 | } |
| 54 | for _, want := range []string{ | |
|
ed03b3d1
Keenan Nemetz
4h ago
|
55 | "keenan", // the account the key resolved to |
| 56 | "/demo/runs/" + building.ID, // the job it is building, linked | |
| 57 | "building", // its job name | |
| 58 | "Waiting", "Running", "demo", // the queue, per repository | |
|
ac433531
Keenan Nemetz
16d ago
|
59 | } { |
| 60 | if !strings.Contains(body, want) { | |
| 61 | t.Errorf("the runners page does not mention %q:\n%s", want, body) | |
| 62 | } | |
| 63 | } | |
| 64 | if strings.Contains(body, "No runner is connected") { | |
| 65 | t.Error("the page says no runner is connected while one is holding a job") | |
| 66 | } | |
|
8d336083
Keenan Nemetz
16d ago
|
67 | // No cap is configured, so the depth is a plain count with nothing to bound it. |
| 68 | if strings.Contains(body, " of 1") { | |
| 69 | t.Errorf("the queue shows a cap that is not configured:\n%s", body) | |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | // A repository at its per-repository cap is the case where a waiting job has a | |
| 74 | // reason, and the page is where that reason has to be readable: "7 waiting, 4 running" | |
| 75 | // is a mystery, "4 of 4 (at the cap)" is not. | |
|
43e681d8
Keenan Nemetz
4d ago
|
76 | func TestAdminRunnersTabSaysWhenARepositoryIsAtItsCap(t *testing.T) { |
|
44530e74
Keenan Nemetz
16d ago
|
77 | t.Parallel() |
|
8d336083
Keenan Nemetz
16d ago
|
78 | f := newFixtureWith(t, func(c *config.Config) { c.MaxJobsPerRepo = 1 }) |
| 79 | f.publish(f.repo) | |
| 80 | now := time.Now().UTC() | |
| 81 | ||
| 82 | // One running (leased) and one waiting, with a cap of one. | |
| 83 | running := queuedRun("01RUNNINGRUN00000000000000", f.repo, "running", now) | |
|
6c03e461
Keenan Nemetz
16d ago
|
84 | if _, err := f.srv.Runs.Enqueue(running, nil); err != nil { |
|
8d336083
Keenan Nemetz
16d ago
|
85 | t.Fatal(err) |
| 86 | } | |
|
b238ac24
Keenan Nemetz
16d ago
|
87 | if _, err := f.srv.Runs.Lease(runs.LeaseRequest{Owner: f.ownerID, |
| 88 | Repos: []string{f.repo}, TTL: time.Minute, MaxPerRepo: 1}, now); err != nil { | |
|
8d336083
Keenan Nemetz
16d ago
|
89 | t.Fatal(err) |
| 90 | } | |
| 91 | waiting := queuedRun("01WAITINGRUN00000000000000", f.repo, "waiting", now) | |
|
6c03e461
Keenan Nemetz
16d ago
|
92 | if _, err := f.srv.Runs.Enqueue(waiting, nil); err != nil { |
|
8d336083
Keenan Nemetz
16d ago
|
93 | t.Fatal(err) |
| 94 | } | |
| 95 | ||
|
ed03b3d1
Keenan Nemetz
4h ago
|
96 | code, body := f.adminGet(t, "/-/admin?tab=runners") |
|
8d336083
Keenan Nemetz
16d ago
|
97 | if code != 200 { |
|
43e681d8
Keenan Nemetz
4d ago
|
98 | t.Fatalf("GET /admin?tab=runners = %d\n%s", code, body) |
|
8d336083
Keenan Nemetz
16d ago
|
99 | } |
| 100 | if !strings.Contains(body, "1 of 1") || !strings.Contains(body, "at the cap") { | |
| 101 | t.Fatalf("the page does not say the repository is at its cap of 1:\n%s", body) | |
| 102 | } | |
|
ac433531
Keenan Nemetz
16d ago
|
103 | } |
| 104 | ||
|
43e681d8
Keenan Nemetz
4d ago
|
105 | func TestRunnersRedirectsToAdmin(t *testing.T) { |
|
44530e74
Keenan Nemetz
16d ago
|
106 | t.Parallel() |
|
ac433531
Keenan Nemetz
16d ago
|
107 | f := newFixture(t) |
|
ed03b3d1
Keenan Nemetz
4h ago
|
108 | resp, _ := f.browserGet(t, "/-/runners", "") |
|
4506a4d3
Keenan Nemetz
14d ago
|
109 | if resp.StatusCode != http.StatusMovedPermanently { |
| 110 | t.Fatalf("GET /runners = %d, want a redirect", resp.StatusCode) | |
|
ac433531
Keenan Nemetz
16d ago
|
111 | } |
|
ed03b3d1
Keenan Nemetz
4h ago
|
112 | if got := resp.Header.Get("Location"); got != "/-/admin?tab=runners" { |
|
4506a4d3
Keenan Nemetz
14d ago
|
113 | t.Fatalf("GET /runners redirected to %q", got) |
|
ac433531
Keenan Nemetz
16d ago
|
114 | } |
| 115 | } | |
|
6c03e461
Keenan Nemetz
16d ago
|
116 | |
| 117 | // A job waiting for a machine that is not connected is invisible from the queue side: | |
| 118 | // the depth says one is waiting, and nothing says why nobody took it. The runner's own | |
|
c5902451
Keenan Nemetz
16d ago
|
119 | // labels are the missing half, so the page shows them next to the agent. |
|
43e681d8
Keenan Nemetz
4d ago
|
120 | func TestAdminRunnersTabShowsWhatEachRunnerTakes(t *testing.T) { |
|
44530e74
Keenan Nemetz
16d ago
|
121 | t.Parallel() |
|
6c03e461
Keenan Nemetz
16d ago
|
122 | f := newFixture(t) |
| 123 | f.publish(f.repo) | |
| 124 | now := time.Now().UTC() | |
| 125 | ||
| 126 | if err := f.srv.Runs.TouchSession("session-linux", f.ownerID, []string{"linux", "arm64"}, now); err != nil { | |
| 127 | t.Fatal(err) | |
| 128 | } | |
| 129 | if err := f.srv.Runs.TouchSession("session-plain", f.ownerID, nil, now); err != nil { | |
| 130 | t.Fatal(err) | |
| 131 | } | |
| 132 | ||
|
ed03b3d1
Keenan Nemetz
4h ago
|
133 | code, body := f.adminGet(t, "/-/admin?tab=runners") |
|
6c03e461
Keenan Nemetz
16d ago
|
134 | if code != 200 { |
|
43e681d8
Keenan Nemetz
4d ago
|
135 | t.Fatalf("GET /admin?tab=runners = %d\n%s", code, body) |
|
6c03e461
Keenan Nemetz
16d ago
|
136 | } |
| 137 | if !strings.Contains(body, "linux, arm64") { | |
| 138 | t.Fatalf("the page does not show the labels a runner carries:\n%s", body) | |
| 139 | } | |
| 140 | if !strings.Contains(body, "any job") { | |
| 141 | t.Fatalf("the page does not say an unlabelled runner takes any job:\n%s", body) | |
| 142 | } | |
| 143 | } |