gotest.tools/gotestsum@v1.11.0/cmd/rerunfails_test.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "strings" 9 "testing" 10 11 "gotest.tools/gotestsum/testjson" 12 "gotest.tools/v3/assert" 13 "gotest.tools/v3/fs" 14 "gotest.tools/v3/golden" 15 ) 16 17 func TestWriteRerunFailsReport(t *testing.T) { 18 reportFile := fs.NewFile(t, t.Name()) 19 defer reportFile.Remove() 20 21 opts := &options{ 22 rerunFailsReportFile: reportFile.Path(), 23 rerunFailsMaxAttempts: 4, 24 } 25 26 exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ 27 Stdout: bytes.NewReader(golden.Get(t, "go-test-json-flaky-rerun.out")), 28 }) 29 assert.NilError(t, err) 30 31 err = writeRerunFailsReport(opts, exec) 32 assert.NilError(t, err) 33 34 raw, err := ioutil.ReadFile(reportFile.Path()) 35 assert.NilError(t, err) 36 golden.Assert(t, string(raw), t.Name()+"-expected") 37 } 38 39 func TestWriteRerunFailsReport_HandlesMissingActionRunEvents(t *testing.T) { 40 reportFile := fs.NewFile(t, t.Name()) 41 defer reportFile.Remove() 42 43 opts := &options{ 44 rerunFailsReportFile: reportFile.Path(), 45 rerunFailsMaxAttempts: 4, 46 } 47 48 exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ 49 Stdout: bytes.NewReader(golden.Get(t, "go-test-missing-run-events.out")), 50 }) 51 assert.NilError(t, err) 52 53 err = writeRerunFailsReport(opts, exec) 54 assert.NilError(t, err) 55 56 raw, err := ioutil.ReadFile(reportFile.Path()) 57 assert.NilError(t, err) 58 golden.Assert(t, string(raw), t.Name()+"-expected") 59 } 60 61 func TestGoTestRunFlagFromTestCases(t *testing.T) { 62 type testCase struct { 63 input string 64 expected string 65 } 66 fn := func(t *testing.T, tc testCase) { 67 actual := goTestRunFlagForTestCase(testjson.TestName(tc.input)) 68 assert.Equal(t, actual, tc.expected) 69 } 70 71 var testCases = map[string]testCase{ 72 "root test case": { 73 input: "TestOne", 74 expected: "-test.run=^TestOne$", 75 }, 76 "sub test case": { 77 input: "TestOne/SubtestA", 78 expected: "-test.run=^TestOne$/^SubtestA$", 79 }, 80 "sub test case with special characters": { 81 input: "TestOne/Subtest(A)[100]", 82 expected: `-test.run=^TestOne$/^Subtest\(A\)\[100\]$`, 83 }, 84 "nested sub test case": { 85 input: "TestOne/Nested/SubtestA", 86 expected: `-test.run=^TestOne$/^Nested$/^SubtestA$`, 87 }, 88 } 89 90 for name := range testCases { 91 t.Run(name, func(t *testing.T) { 92 fn(t, testCases[name]) 93 }) 94 } 95 } 96 97 func TestRerunFailed_ReturnsAnErrorWhenTheLastTestIsSuccessful(t *testing.T) { 98 type result struct { 99 out string 100 err error 101 } 102 jsonFailed := `{"Package": "pkg", "Action": "run"} 103 {"Package": "pkg", "Test": "TestOne", "Action": "run"} 104 {"Package": "pkg", "Test": "TestOne", "Action": "fail"} 105 {"Package": "pkg", "Action": "fail"} 106 ` 107 events := []result{ 108 {out: jsonFailed, err: newExitCode("run-failed-1", 1)}, 109 {out: jsonFailed, err: newExitCode("run-failed-2", 1)}, 110 {out: jsonFailed, err: newExitCode("run-failed-3", 1)}, 111 { 112 out: `{"Package": "pkg", "Action": "run"} 113 {"Package": "pkg", "Test": "TestOne", "Action": "run"} 114 {"Package": "pkg", "Test": "TestOne", "Action": "pass"} 115 {"Package": "pkg", "Action": "pass"} 116 `, 117 }, 118 } 119 120 fn := func(args []string) *proc { 121 next := events[0] 122 events = events[1:] 123 return &proc{ 124 cmd: fakeWaiter{result: next.err}, 125 stdout: strings.NewReader(next.out), 126 stderr: bytes.NewReader(nil), 127 } 128 } 129 reset := patchStartGoTestFn(fn) 130 defer reset() 131 132 stdout := new(bytes.Buffer) 133 ctx := context.Background() 134 opts := &options{ 135 rerunFailsMaxInitialFailures: 10, 136 rerunFailsMaxAttempts: 2, 137 stdout: stdout, 138 } 139 cfg := testjson.ScanConfig{ 140 Execution: newExecutionWithTwoFailures(t), 141 Handler: noopHandler{}, 142 } 143 err := rerunFailed(ctx, opts, cfg) 144 assert.Error(t, err, "run-failed-3") 145 } 146 147 func patchStartGoTestFn(f func(args []string) *proc) func() { 148 orig := startGoTestFn 149 startGoTestFn = func(ctx context.Context, dir string, args []string) (*proc, error) { 150 return f(args), nil 151 } 152 return func() { 153 startGoTestFn = orig 154 } 155 } 156 157 func newExecutionWithTwoFailures(t *testing.T) *testjson.Execution { 158 t.Helper() 159 160 out := `{"Package": "pkg", "Action": "run"} 161 {"Package": "pkg", "Test": "TestOne", "Action": "run"} 162 {"Package": "pkg", "Test": "TestOne", "Action": "fail"} 163 {"Package": "pkg", "Test": "TestTwo", "Action": "run"} 164 {"Package": "pkg", "Test": "TestTwo", "Action": "fail"} 165 {"Package": "pkg", "Action": "fail"} 166 ` 167 exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ 168 Stdout: strings.NewReader(out), 169 Stderr: strings.NewReader(""), 170 }) 171 assert.NilError(t, err) 172 return exec 173 } 174 175 type fakeWaiter struct { 176 result error 177 } 178 179 func (f fakeWaiter) Wait() error { 180 return f.result 181 } 182 183 type exitCodeError struct { 184 error 185 code int 186 } 187 188 func (e exitCodeError) ExitCode() int { 189 return e.code 190 } 191 192 func newExitCode(msg string, code int) error { 193 return exitCodeError{error: fmt.Errorf(msg), code: code} 194 } 195 196 type noopHandler struct{} 197 198 func (s noopHandler) Event(testjson.TestEvent, *testjson.Execution) error { 199 return nil 200 } 201 202 func (s noopHandler) Err(string) error { 203 return nil 204 }