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