133728e barerepo 1mo
1
package e2e
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"os/exec"
133728e barerepo 1mo
6
"strings"
133728e barerepo 1mo
7
"testing"
133728e barerepo 1mo
9
"github.com/barerepo/server/internal/store"
133728e barerepo 1mo
10
"github.com/barerepo/server/internal/token"
133728e barerepo 1mo
13
// Chapter 15A end to end: a repository whose CI is a GitHub workflow and nothing else.
133728e barerepo 1mo
14
func TestWorkflowBuildsWithoutABarerepoConfig(t *testing.T) {
133728e barerepo 1mo
15
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
16
t.Skip("git is not installed")
133728e barerepo 1mo
18
in := newInstance(t)
133728e barerepo 1mo
19
tok := in.account("john")
133728e barerepo 1mo
20
ctx := context.Background()
133728e barerepo 1mo
22
work := t.TempDir()
133728e barerepo 1mo
23
run(t, work, "git", "init", "-q", "-b", "master")
133728e barerepo 1mo
24
write(t, work, "config.go", "package main\n")
133728e barerepo 1mo
25
// No [build] command anywhere, which is the whole point: the workflow is the only build.
133728e barerepo 1mo
26
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n")
133728e barerepo 1mo
27
write(t, work, ".github/workflows/ci.yml", `
133728e barerepo 1mo
28
name: ci
133728e barerepo 1mo
29
on: [push]
133728e barerepo 1mo
30
jobs:
133728e barerepo 1mo
31
test:
133728e barerepo 1mo
32
runs-on: ubuntu-latest
133728e barerepo 1mo
33
steps:
133728e barerepo 1mo
34
- uses: actions/checkout@v4
133728e barerepo 1mo
35
- name: unit
133728e barerepo 1mo
36
run: go test ./...
133728e barerepo 1mo
37
- name: deploy
133728e barerepo 1mo
38
uses: some/deploy-action@v1
133728e barerepo 1mo
40
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
41
run(t, work, "git", "commit", "-qm", "first")
133728e barerepo 1mo
43
// With no machine attached, the push declines and says where to attach one.
133728e barerepo 1mo
44
out := run(t, work, "git", "push", in.url(tok, "/john/bot"), "master")
133728e barerepo 1mo
45
for _, want := range []string{
133728e barerepo 1mo
46
"skipped deploy",
133728e barerepo 1mo
47
"some/deploy-action@v1",
133728e barerepo 1mo
48
"wants a ubuntu-latest machine and none is attached",
133728e barerepo 1mo
49
"/john/bot/runners/new",
133728e barerepo 1mo
50
} {
133728e barerepo 1mo
51
if !strings.Contains(out, want) {
133728e barerepo 1mo
52
t.Errorf("the push does not say %q\n%s", want, out)
133728e barerepo 1mo
55
if n := queued(t, in, "john/bot"); n != 0 {
133728e barerepo 1mo
56
t.Fatalf("%d jobs were queued with no machine to run them", n)
133728e barerepo 1mo
59
// A mac cannot take it either, and the job must not be handed to the wrong machine.
133728e barerepo 1mo
60
attach(t, in, "john/bot", "macbook", "darwin", nil)
133728e barerepo 1mo
61
run(t, work, "git", "commit", "-qm", "second", "--allow-empty")
133728e barerepo 1mo
62
out = run(t, work, "git", "push", in.url(tok, "/john/bot"), "master")
133728e barerepo 1mo
63
if !strings.Contains(out, "none is attached") {
133728e barerepo 1mo
64
t.Errorf("a mac was treated as an ubuntu machine\n%s", out)
133728e barerepo 1mo
67
// Attach linux and the same push queues the job.
133728e barerepo 1mo
68
attach(t, in, "john/bot", "builder", "linux", nil)
133728e barerepo 1mo
69
run(t, work, "git", "commit", "-qm", "third", "--allow-empty")
133728e barerepo 1mo
70
out = run(t, work, "git", "push", in.url(tok, "/john/bot"), "master")
133728e barerepo 1mo
71
if !strings.Contains(out, "queued test from .github/workflows/ci.yml") {
133728e barerepo 1mo
72
t.Errorf("the push did not queue the workflow job\n%s", out)
133728e barerepo 1mo
75
jobs, err := in.db.QueuedJobs(ctx, "john/bot")
133728e barerepo 1mo
76
if err != nil {
133728e barerepo 1mo
77
t.Fatal(err)
133728e barerepo 1mo
79
if len(jobs) != 1 {
133728e barerepo 1mo
80
t.Fatalf("got %d queued jobs, want 1", len(jobs))
133728e barerepo 1mo
82
j := jobs[0]
133728e barerepo 1mo
83
if !strings.Contains(j.Command, "go test ./...") {
133728e barerepo 1mo
84
t.Errorf("the run step did not reach the job: %q", j.Command)
133728e barerepo 1mo
86
if len(j.Labels) != 1 || j.Labels[0] != "ubuntu-latest" {
133728e barerepo 1mo
87
t.Errorf("the job did not keep what it asked for: %v", j.Labels)
133728e barerepo 1mo
90
// The mac must not be able to take it, and the linux machine must.
133728e barerepo 1mo
91
mac := runnerNamed(t, in, "john/bot", "macbook")
133728e barerepo 1mo
92
if got, err := in.db.TakeJob(ctx, "john/bot", mac); err != nil || got != nil {
133728e barerepo 1mo
93
t.Errorf("a mac took an ubuntu job: %+v %v", got, err)
133728e barerepo 1mo
95
builder := runnerNamed(t, in, "john/bot", "builder")
133728e barerepo 1mo
96
got, err := in.db.TakeJob(ctx, "john/bot", builder)
133728e barerepo 1mo
97
if err != nil || got == nil {
133728e barerepo 1mo
98
t.Fatalf("the linux machine could not take its own job: %+v %v", got, err)
133728e barerepo 1mo
102
// A repository with both keeps barerepo's own answer, because [build] command is one command.
133728e barerepo 1mo
103
func TestABarerepoConfigWinsOverAWorkflow(t *testing.T) {
133728e barerepo 1mo
104
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
105
t.Skip("git is not installed")
133728e barerepo 1mo
106
}
133728e barerepo 1mo
107
in := newInstance(t)
133728e barerepo 1mo
108
tok := in.account("john")
133728e barerepo 1mo
110
work := t.TempDir()
133728e barerepo 1mo
111
run(t, work, "git", "init", "-q", "-b", "master")
133728e barerepo 1mo
112
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n[build]\ncommand = \"make ci\"\n")
133728e barerepo 1mo
113
write(t, work, ".github/workflows/ci.yml",
133728e barerepo 1mo
114
"jobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: go test ./...\n")
133728e barerepo 1mo
115
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
116
run(t, work, "git", "commit", "-qm", "first")
133728e barerepo 1mo
117
out := run(t, work, "git", "push", in.url(tok, "/john/bot"), "master")
133728e barerepo 1mo
119
if !strings.Contains(out, "queued a build for") {
133728e barerepo 1mo
120
t.Errorf("the repository's own build did not run\n%s", out)
133728e barerepo 1mo
121
}
133728e barerepo 1mo
122
if strings.Contains(out, "workflows/ci.yml") {
133728e barerepo 1mo
123
t.Errorf("the workflow ran as well, so the build happened twice\n%s", out)
133728e barerepo 1mo
124
}
133728e barerepo 1mo
125
jobs, err := in.db.QueuedJobs(context.Background(), "john/bot")
133728e barerepo 1mo
126
if err != nil {
133728e barerepo 1mo
127
t.Fatal(err)
133728e barerepo 1mo
128
}
133728e barerepo 1mo
129
if len(jobs) != 1 || jobs[0].Command != "make ci" {
133728e barerepo 1mo
130
t.Errorf("queued %d jobs, first %q", len(jobs), jobs[0].Command)
133728e barerepo 1mo
131
}
133728e barerepo 1mo
134
func queued(t *testing.T, in *instance, repo string) int {
133728e barerepo 1mo
135
t.Helper()
133728e barerepo 1mo
136
jobs, err := in.db.QueuedJobs(context.Background(), repo)
133728e barerepo 1mo
137
if err != nil {
133728e barerepo 1mo
138
t.Fatal(err)
133728e barerepo 1mo
139
}
133728e barerepo 1mo
140
return len(jobs)
133728e barerepo 1mo
143
// attach registers a machine the way a real runner does, so the matching is the real matching.
133728e barerepo 1mo
144
func attach(t *testing.T, in *instance, repo, host, os string, labels []string) {
133728e barerepo 1mo
145
t.Helper()
133728e barerepo 1mo
146
ctx := context.Background()
133728e barerepo 1mo
147
owner, _, _ := strings.Cut(repo, "/")
133728e barerepo 1mo
148
tok, _, err := in.db.CreateToken(ctx, token.Runner, owner, repo, "runner for "+host)
133728e barerepo 1mo
149
if err != nil {
133728e barerepo 1mo
150
t.Fatal(err)
133728e barerepo 1mo
151
}
133728e barerepo 1mo
152
rec, err := in.db.AccountForToken(ctx, token.Runner, tok)
133728e barerepo 1mo
153
if err != nil {
133728e barerepo 1mo
154
t.Fatal(err)
133728e barerepo 1mo
155
}
133728e barerepo 1mo
156
if _, err := in.db.AttachRunner(ctx, rec.ID, repo, host, os, "amd64", labels); err != nil {
133728e barerepo 1mo
157
t.Fatal(err)
133728e barerepo 1mo
158
}
133728e barerepo 1mo
161
func runnerNamed(t *testing.T, in *instance, repo, host string) store.Runner {
133728e barerepo 1mo
162
t.Helper()
133728e barerepo 1mo
163
runners, err := in.db.RunnersOf(context.Background(), repo)
133728e barerepo 1mo
164
if err != nil {
133728e barerepo 1mo
165
t.Fatal(err)
133728e barerepo 1mo
166
}
133728e barerepo 1mo
167
for _, r := range runners {
133728e barerepo 1mo
168
if r.Hostname == host {
133728e barerepo 1mo
169
return r
133728e barerepo 1mo
170
}
133728e barerepo 1mo
171
}
133728e barerepo 1mo
172
t.Fatalf("no runner named %s", host)
133728e barerepo 1mo
173
return store.Runner{}
133728e barerepo 1mo
176
// A matrix builds one commit several times, and each build has to be told apart. Chapter 15A.
133728e barerepo 1mo
177
func TestMatrixQueuesOneNamedJobPerCombination(t *testing.T) {
133728e barerepo 1mo
178
if _, err := exec.LookPath("git"); err != nil {
133728e barerepo 1mo
179
t.Skip("git is not installed")
133728e barerepo 1mo
180
}
133728e barerepo 1mo
181
in := newInstance(t)
133728e barerepo 1mo
182
tok := in.account("john")
133728e barerepo 1mo
183
ctx := context.Background()
133728e barerepo 1mo
185
work := t.TempDir()
133728e barerepo 1mo
186
run(t, work, "git", "init", "-q", "-b", "master")
133728e barerepo 1mo
187
write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n")
133728e barerepo 1mo
188
write(t, work, ".github/workflows/ci.yml", `
133728e barerepo 1mo
189
name: ci
133728e barerepo 1mo
190
jobs:
133728e barerepo 1mo
191
test:
133728e barerepo 1mo
192
runs-on: ${{ matrix.os }}
133728e barerepo 1mo
193
strategy:
133728e barerepo 1mo
194
matrix:
133728e barerepo 1mo
195
os: [ubuntu-latest, macos-latest]
133728e barerepo 1mo
196
steps:
133728e barerepo 1mo
197
- run: go test ./...
133728e barerepo 1mo
198
`)
133728e barerepo 1mo
199
run(t, work, "git", "add", "-A")
133728e barerepo 1mo
200
run(t, work, "git", "commit", "-qm", "first")
133728e barerepo 1mo
202
// Only linux is attached, so the mac half is declined and the linux half is queued.
133728e barerepo 1mo
203
attach(t, in, "john/bot", "builder", "linux", nil)
133728e barerepo 1mo
204
out := run(t, work, "git", "push", in.url(tok, "/john/bot"), "master")
133728e barerepo 1mo
205
if !strings.Contains(out, "wants a macos-latest machine and none is attached") {
133728e barerepo 1mo
206
t.Errorf("the mac combination was not declined\n%s", out)
133728e barerepo 1mo
207
}
133728e barerepo 1mo
208
if !strings.Contains(out, "queued test (os ubuntu-latest)") {
133728e barerepo 1mo
209
t.Errorf("the linux combination was not queued\n%s", out)
133728e barerepo 1mo
210
}
133728e barerepo 1mo
212
jobs, err := in.db.QueuedJobs(ctx, "john/bot")
133728e barerepo 1mo
213
if err != nil {
133728e barerepo 1mo
214
t.Fatal(err)
133728e barerepo 1mo
215
}