internal/store/runners_test.go
172 lines · 5337 bytes
5 symbols in this file
1package store
2
3import (
4 "errors"
5 "testing"
6)
7
8// runnerFixture is one user with a runner key and two repositories.
9func runnerFixture(t *testing.T) (s *Store, uid, keyID, repoA, repoB int64) {
10 t.Helper()
11 s = open(t)
12 if err := s.MigrateUp(); err != nil {
13 t.Fatal(err)
14 }
15 uid, err := s.CreateUser("alice", false)
16 if err != nil {
17 t.Fatal(err)
18 }
19 if err := s.AddSSHKey(uid, "SHA256:runnerkey", "ssh-ed25519", []byte("blob"), "runner", ""); err != nil {
20 t.Fatal(err)
21 }
22 k, err := s.SSHKeyByFingerprint("SHA256:runnerkey")
23 if err != nil {
24 t.Fatal(err)
25 }
26 repoA, err = s.CreateRepo("user", uid, "a", "public")
27 if err != nil {
28 t.Fatal(err)
29 }
30 repoB, err = s.CreateRepo("user", uid, "b", "public")
31 if err != nil {
32 t.Fatal(err)
33 }
34 return s, uid, k.ID, repoA, repoB
35}
36
37// Attaching twice is one row; detaching what is not attached is not found.
38func TestAttachRunnerIdempotentAndDetach(t *testing.T) {
39 s, _, keyID, repoA, repoB := runnerFixture(t)
40 for range 2 {
41 if err := s.AttachRunner(keyID, repoA); err != nil {
42 t.Fatal(err)
43 }
44 }
45 ids, err := s.RunnerRepoIDs(keyID)
46 if err != nil || len(ids) != 1 || ids[0] != repoA {
47 t.Fatalf("attached repos %v err=%v, want [%d]", ids, err, repoA)
48 }
49 if ok, _ := s.RunnerAttached(keyID, repoB); ok {
50 t.Fatal("attached to a repo it was never attached to")
51 }
52 if err := s.DetachRunner(repoB, "SHA256:runnerkey"); !errors.Is(err, ErrNotFound) {
53 t.Fatalf("detach of an unattached repo: %v, want ErrNotFound", err)
54 }
55 if err := s.DetachRunner(repoA, "SHA256:runnerkey"); err != nil {
56 t.Fatal(err)
57 }
58 if ok, _ := s.RunnerAttached(keyID, repoA); ok {
59 t.Fatal("still attached after detach")
60 }
61}
62
63// Removing the key or the repository removes the attachment with it.
64func TestRunnerAttachmentCascades(t *testing.T) {
65 s, uid, keyID, repoA, repoB := runnerFixture(t)
66 if err := s.AttachRunner(keyID, repoA); err != nil {
67 t.Fatal(err)
68 }
69 if err := s.AttachRunner(keyID, repoB); err != nil {
70 t.Fatal(err)
71 }
72 if _, err := s.DB.Exec("DELETE FROM repos WHERE id = ?", repoB); err != nil {
73 t.Fatal(err)
74 }
75 if ids, _ := s.RunnerRepoIDs(keyID); len(ids) != 1 {
76 t.Fatalf("after repo delete: %v, want one attachment", ids)
77 }
78 if err := s.RemoveSSHKey(uid, "SHA256:runnerkey"); err != nil {
79 t.Fatal(err)
80 }
81 var n int
82 if err := s.DB.QueryRow("SELECT count(*) FROM runner_repos").Scan(&n); err != nil || n != 0 {
83 t.Fatalf("after key delete: %d rows err=%v, want 0", n, err)
84 }
85}
86
87// The heartbeat is per key: two keys on one account are two rows, and a
88// repository's runner list shows each key's last poll and the build it holds.
89func TestRunnerSeenPerKeyAndRepoList(t *testing.T) {
90 s, uid, keyID, repoA, _ := runnerFixture(t)
91 if err := s.AddSSHKey(uid, "SHA256:second", "ssh-ed25519", []byte("blob2"), "runner", ""); err != nil {
92 t.Fatal(err)
93 }
94 k2, _ := s.SSHKeyByFingerprint("SHA256:second")
95 for _, id := range []int64{keyID, k2.ID} {
96 if err := s.AttachRunner(id, repoA); err != nil {
97 t.Fatal(err)
98 }
99 }
100 if _, err := s.CreateBuild(repoA, "unit", "abc123", "main", `["true"]`, "", "", true); err != nil {
101 t.Fatal(err)
102 }
103 b, ok, err := s.ClaimBuild(nil, false)
104 if err != nil || !ok {
105 t.Fatalf("claim: %v ok=%v", err, ok)
106 }
107 if err := s.TouchRunner(keyID, uid, "", b.ID); err != nil {
108 t.Fatal(err)
109 }
110 if err := s.TouchRunner(k2.ID, uid, "", 0); err != nil {
111 t.Fatal(err)
112 }
113 runners, err := s.ListRunners()
114 if err != nil || len(runners) != 2 {
115 t.Fatalf("ListRunners: %v err=%v, want two rows", runners, err)
116 }
117 list, err := s.ListRepoRunners(repoA)
118 if err != nil || len(list) != 2 {
119 t.Fatalf("ListRepoRunners: %v err=%v, want two rows", list, err)
120 }
121 var held, idle int
122 for _, r := range list {
123 if r.Username != "alice" || r.LastSeen == "" || r.AddedAt == "" {
124 t.Fatalf("row %+v lacks username, last_seen or added_at", r)
125 }
126 if r.BuildNumber == b.Number && r.BuildJob == "unit" && r.BuildRepo == "alice/a" {
127 held++
128 } else if r.BuildNumber == 0 {
129 idle++
130 }
131 }
132 if held != 1 || idle != 1 {
133 t.Fatalf("held=%d idle=%d, want 1 and 1: %+v", held, idle, list)
134 }
135 if err := s.RunnerDone(keyID); err != nil {
136 t.Fatal(err)
137 }
138 list, _ = s.ListRepoRunners(repoA)
139 for _, r := range list {
140 if r.BuildNumber != 0 {
141 t.Fatalf("build still held after RunnerDone: %+v", r)
142 }
143 }
144 paths, err := s.RunnerRepoPaths(keyID)
145 if err != nil || len(paths) != 1 || paths[0] != "alice/a" {
146 t.Fatalf("RunnerRepoPaths: %v err=%v", paths, err)
147 }
148}
149
150// A heartbeat row outlives its usefulness when a key polled once by
151// mistake; forgetting it by fingerprint removes the row and nothing else.
152func TestForgetRunner(t *testing.T) {
153 s, uid, keyID, _, _ := runnerFixture(t)
154 if err := s.TouchRunner(keyID, uid, "", 0); err != nil {
155 t.Fatal(err)
156 }
157 if err := s.ForgetRunner("SHA256:nobody"); !errors.Is(err, ErrNotFound) {
158 t.Fatalf("unknown fingerprint: %v, want ErrNotFound", err)
159 }
160 if err := s.ForgetRunner("SHA256:runnerkey"); err != nil {
161 t.Fatal(err)
162 }
163 if rows, _ := s.ListRunners(); len(rows) != 0 {
164 t.Fatalf("row survived forget: %+v", rows)
165 }
166 if _, err := s.SSHKeyByFingerprint("SHA256:runnerkey"); err != nil {
167 t.Fatalf("forget removed the key itself: %v", err)
168 }
169 if err := s.ForgetRunner("SHA256:runnerkey"); !errors.Is(err, ErrNotFound) {
170 t.Fatalf("second forget: %v, want ErrNotFound", err)
171 }
172}