cmd/gitbay-runner/report_test.go
86 lines · 2731 bytes
6 symbols in this file
1package main
2
3import (
4 "errors"
5 "os/exec"
6 "strings"
7 "testing"
8 "time"
9
10 "gitbay.org/gitbay/internal/protocol"
11)
12
13// exitErr fabricates the error ssh returns for a given exit status.
14func exitErr(t *testing.T, code int) error {
15 t.Helper()
16 err := exec.Command("sh", "-c", "exit "+itoa(code)).Run()
17 var ee *exec.ExitError
18 if !errors.As(err, &ee) || ee.ExitCode() != code {
19 t.Fatalf("could not fabricate exit %d: %v", code, err)
20 }
21 return err
22}
23
24func itoa(n int) string {
25 return string(rune('0'+n/100)) + string(rune('0'+n/10%10)) + string(rune('0'+n%10))
26}
27
28// A connection failure (ssh exit 255) is retried until it succeeds; the
29// result is not lost to a server that was restarting (#179).
30func TestReportRetriesConnectionFailure(t *testing.T) {
31 calls := 0
32 err := reportWithRetry(func() (string, error) {
33 calls++
34 if calls < 3 {
35 return "ssh: connect to host 127.0.0.1 port 22: Connection refused", exitErr(t, 255)
36 }
37 return "", nil
38 }, 7, []time.Duration{0, 0, 0, 0})
39 if err != nil || calls != 3 {
40 t.Fatalf("err=%v calls=%d, want success on the third attempt", err, calls)
41 }
42}
43
44// The server's own refusal is an answer, not a transient: no retry.
45func TestReportDoesNotRetryServerAnswer(t *testing.T) {
46 calls := 0
47 err := reportWithRetry(func() (string, error) {
48 calls++
49 return `{"error":"build 7 is not running"}`, exitErr(t, 3)
50 }, 7, []time.Duration{0, 0})
51 if err == nil || calls != 1 {
52 t.Fatalf("err=%v calls=%d, want one attempt and an error", err, calls)
53 }
54}
55
56// Retries are bounded: after the delays are spent the error surfaces.
57func TestReportGivesUp(t *testing.T) {
58 calls := 0
59 err := reportWithRetry(func() (string, error) {
60 calls++
61 return "", exitErr(t, 255)
62 }, 7, []time.Duration{0, 0})
63 if err == nil || calls != 3 {
64 t.Fatalf("err=%v calls=%d, want three attempts then an error", err, calls)
65 }
66}
67
68// The reason survives the trip: ssh joins arguments with spaces and the
69// server splits the line again with POSIX rules (#266).
70func TestDoneArgsNameTheFailedStep(t *testing.T) {
71 got := doneArgs(7, "failure", &failure{Step: 3, Reason: "can't: exit 1"})
72 argv, err := protocol.Tokenize(strings.Join(got, " "))
73 if err != nil {
74 t.Fatal(err)
75 }
76 want := []string{"runner", "done", "7", "failure", "--step", "3", "--reason", "can't: exit 1"}
77 if strings.Join(argv, "|") != strings.Join(want, "|") {
78 t.Fatalf("server reads %q, want %q", argv, want)
79 }
80 if got := doneArgs(7, "success", nil); strings.Join(got, " ") != "runner done 7 success" {
81 t.Fatalf("success: %q", got)
82 }
83 if got := doneArgs(7, "failure", &failure{Reason: "git clone: exit 128"}); strings.Contains(strings.Join(got, " "), "--step") {
84 t.Fatalf("a failure before any step sent a step: %q", got)
85 }
86}