cmd/gitbay-runner/main_test.go
74 lines · 2102 bytes
6 symbols in this file
1package main
2
3import (
4 "errors"
5 "os/exec"
6 "strings"
7 "testing"
8)
9
10type failingWriter struct{ n int }
11
12func (f *failingWriter) Write(p []byte) (int, error) {
13 f.n++
14 return 0, errors.New("broken pipe")
15}
16
17// A build's log stream is a recording device. When it breaks, os/exec
18// surfaces the write error through cmd.Wait(), which used to mark a step
19// that had exited 0 as failed — a green suite reported red, with the line
20// explaining it written to the same dead pipe.
21func TestBrokenLogSinkDoesNotFailTheStep(t *testing.T) {
22 sink := &logSink{w: &failingWriter{}}
23
24 cmd := exec.Command("sh", "-c", "echo out; echo err >&2; exit 0")
25 cmd.Stdout, cmd.Stderr = sink, sink
26 if err := cmd.Run(); err != nil {
27 t.Fatalf("step reported failed because its log sink broke: %v", err)
28 }
29 if !sink.broken() {
30 t.Error("sink did not record that the stream was lost")
31 }
32}
33
34// The converse: a step that genuinely fails still fails.
35func TestGenuineStepFailureStillFails(t *testing.T) {
36 sink := &logSink{w: &failingWriter{}}
37 cmd := exec.Command("sh", "-c", "exit 3")
38 cmd.Stdout, cmd.Stderr = sink, sink
39 if err := cmd.Run(); err == nil {
40 t.Fatal("a step exiting 3 was reported as success")
41 }
42}
43
44// Once the stream is gone the sink stops touching it, rather than calling a
45// broken pipe once per write for the rest of a long build.
46func TestLogSinkStopsWritingAfterFailure(t *testing.T) {
47 w := &failingWriter{}
48 sink := &logSink{w: w}
49 for i := 0; i < 5; i++ {
50 if _, err := sink.Write([]byte("x")); err != nil {
51 t.Fatalf("sink returned an error: %v", err)
52 }
53 }
54 if w.n != 1 {
55 t.Errorf("underlying writer called %d times, want 1", w.n)
56 }
57}
58
59// A healthy sink still forwards everything.
60func TestHealthyLogSinkForwards(t *testing.T) {
61 var b strings.Builder
62 sink := &logSink{w: &b}
63 cmd := exec.Command("sh", "-c", "echo hello")
64 cmd.Stdout, cmd.Stderr = sink, sink
65 if err := cmd.Run(); err != nil {
66 t.Fatal(err)
67 }
68 if got := b.String(); !strings.Contains(got, "hello") {
69 t.Errorf("sink dropped output: %q", got)
70 }
71 if sink.broken() {
72 t.Error("healthy sink reported broken")
73 }
74}