Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

Blame · server/runners_page_test.go

Line attribution on main · 143 line(s)

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 }