cmd/gitbay-runner/report_test.go

9df917e73a67d15adecc3f45976690f6fcd4e47a
gitbay/cmd/gitbay-runner/report_test.go history · blame · raw

86 lines · 2731 bytes

 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}