e2e/writerate_test.go
78 lines · 3122 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10// Mutating commands are bounded per account in the dispatcher, so the
11// budget is the same one whichever surface spends it. Reads are not
12// charged (#148).
13func TestWriteRateLimit(t *testing.T) {
14 inst := startInstanceWith(t, "[limits]\nwrite_rate = 4\n")
15 aliceKey := inst.newKey(t, "alice")
16 bobKey := inst.newKey(t, "bob")
17 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
18 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
19
20 // repo create plus three issues is the whole budget.
21 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
22 t.Fatalf("repo create: %s", errOut)
23 }
24 for i := 0; i < 3; i++ {
25 if _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app", "--title", "t"); code != 0 {
26 t.Fatalf("issue %d within the budget was refused: %s", i+1, errOut)
27 }
28 }
29 _, errOut, code := inst.ssh(t, aliceKey, "", "issue", "create", "alice/app", "--title", "over")
30 if code != 4 {
31 t.Fatalf("write past the budget exited %d, want 4:\n%s", code, errOut)
32 }
33 if !strings.Contains(errOut, "too many writes") || !strings.Contains(errOut, "try again in") {
34 t.Errorf("refusal does not say what happened or when to retry: %s", errOut)
35 }
36
37 // Reads are not charged, so a throttled account can still look.
38 if out, _, code := inst.ssh(t, aliceKey, "", "issue", "list", "alice/app"); code != 0 {
39 t.Fatalf("a read was refused while throttled: %s", out)
40 }
41
42 // The budget is per account, not per instance.
43 if _, errOut, code := inst.ssh(t, bobKey, "", "repo", "create", "bob/app"); code != 0 {
44 t.Fatalf("bob was charged for alice's writes: %s", errOut)
45 }
46}
47
48// The runner protocol is exempt: a build streams its log in many small
49// writes, and throttling those would throttle CI itself.
50func TestWriteRateLimitSparesTheRunner(t *testing.T) {
51 inst := startInstanceWith(t, "[limits]\nwrite_rate = 2\n")
52 inst.runner = buildRunner(t)
53 aliceKey := inst.newKey(t, "alice")
54 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
55 runnerKey := inst.newKey(t, "ci")
56 inst.admin(t, "admin", "user", "create", "ci", "--key", runnerKey+".pub", "--admin")
57
58 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
59 t.Fatalf("repo create: %s", errOut)
60 }
61 env := inst.gitEnv(aliceKey)
62 work := t.TempDir()
63 mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
64 dir := filepath.Join(work, "w")
65 os.MkdirAll(filepath.Join(dir, ".gitbay"), 0o755)
66 os.WriteFile(filepath.Join(dir, ".gitbay", "ci.yml"),
67 []byte("jobs:\n smoke:\n steps:\n - echo ok\n"), 0o644)
68 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
69 mustGit(t, dir, env, "add", ".")
70 mustGit(t, dir, env, "commit", "-q", "-m", "base")
71 mustGit(t, dir, env, "push", "-q", "origin", "main")
72
73 inst.runnerOnce(t, runnerKey)
74 out, _, _ := inst.ssh(t, aliceKey, "", "build", "list", "alice/app")
75 if !strings.Contains(out, "success") {
76 t.Fatalf("the build did not run under a tight write budget:\n%s", out)
77 }
78}