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.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 1h ago 4b8c9e1d
history blame raw 4.9 KiB
1package 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
8import (
9 "net/http"
10 "strings"
11 "testing"
12 "time"
13
14 "rodent.software/vole/ci/runs"
15 "rodent.software/vole/forge/config"
16)
17
18func 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
27func TestAdminRunnersTabShowsAHeldJobAndTheQueueDepth(t *testing.T) {
28 t.Parallel()
29 f := newFixture(t)
30 f.publish(f.repo)
31 now := time.Now().UTC()
32
33 waiting := queuedRun("01WAITINGRUN00000000000000", f.repo, "waiting", now)
34 if _, err := f.srv.Runs.Enqueue(waiting, nil); err != nil {
35 t.Fatal(err)
36 }
37 building := queuedRun("01BUILDINGRUN0000000000000", f.repo, "building", now)
38 if _, err := f.srv.Runs.Enqueue(building, nil); err != nil {
39 t.Fatal(err)
40 }
41 // A runner connected as the owner, holding the second job — the state the page
42 // exists to show.
43 if err := f.srv.Runs.TouchSession("session-1", f.ownerID, nil, now); err != nil {
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
50 code, body := f.adminGet(t, "/-/admin?tab=runners")
51 if code != 200 {
52 t.Fatalf("GET /admin?tab=runners = %d\n%s", code, body)
53 }
54 for _, want := range []string{
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
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 }
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.
76func TestAdminRunnersTabSaysWhenARepositoryIsAtItsCap(t *testing.T) {
77 t.Parallel()
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)
84 if _, err := f.srv.Runs.Enqueue(running, nil); err != nil {
85 t.Fatal(err)
86 }
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 {
89 t.Fatal(err)
90 }
91 waiting := queuedRun("01WAITINGRUN00000000000000", f.repo, "waiting", now)
92 if _, err := f.srv.Runs.Enqueue(waiting, nil); err != nil {
93 t.Fatal(err)
94 }
95
96 code, body := f.adminGet(t, "/-/admin?tab=runners")
97 if code != 200 {
98 t.Fatalf("GET /admin?tab=runners = %d\n%s", code, body)
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 }
103}
104
105func TestRunnersRedirectsToAdmin(t *testing.T) {
106 t.Parallel()
107 f := newFixture(t)
108 resp, _ := f.browserGet(t, "/-/runners", "")
109 if resp.StatusCode != http.StatusMovedPermanently {
110 t.Fatalf("GET /runners = %d, want a redirect", resp.StatusCode)
111 }
112 if got := resp.Header.Get("Location"); got != "/-/admin?tab=runners" {
113 t.Fatalf("GET /runners redirected to %q", got)
114 }
115}
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
119// labels are the missing half, so the page shows them next to the agent.
120func TestAdminRunnersTabShowsWhatEachRunnerTakes(t *testing.T) {
121 t.Parallel()
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
133 code, body := f.adminGet(t, "/-/admin?tab=runners")
134 if code != 200 {
135 t.Fatalf("GET /admin?tab=runners = %d\n%s", code, body)
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}
144