gotest.tools/gotestsum@v1.11.0/testjson/execution_test.go (about) 1 package testjson 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 "gotest.tools/v3/assert" 13 "gotest.tools/v3/golden" 14 ) 15 16 func TestExecution_Add_PackageCoverage(t *testing.T) { 17 exec := newExecution() 18 exec.add(TestEvent{ 19 Package: "mytestpkg", 20 Action: ActionOutput, 21 Output: "coverage: 33.1% of statements\n", 22 }) 23 24 pkg := exec.Package("mytestpkg") 25 expected := &Package{ 26 coverage: "coverage: 33.1% of statements", 27 output: map[int][]string{ 28 0: {"coverage: 33.1% of statements\n"}, 29 }, 30 running: map[string]TestCase{}, 31 } 32 assert.DeepEqual(t, pkg, expected, cmpPackage) 33 } 34 35 var cmpPackage = cmp.Options{ 36 cmp.AllowUnexported(Package{}), 37 cmpopts.EquateEmpty(), 38 } 39 40 func TestScanTestOutput_MinimalConfig(t *testing.T) { 41 in := bytes.NewReader(golden.Get(t, "input/go-test-json.out")) 42 exec, err := ScanTestOutput(ScanConfig{Stdout: in}) 43 assert.NilError(t, err) 44 // a weak check to show that all the stdout was scanned 45 assert.Equal(t, exec.Total(), 59) 46 } 47 48 func TestScanTestOutput_CallsStopOnError(t *testing.T) { 49 var called bool 50 stop := func() { 51 called = true 52 } 53 cfg := ScanConfig{ 54 Stdout: bytes.NewReader(golden.Get(t, "input/go-test-json.out")), 55 Handler: &handlerFails{}, 56 Stop: stop, 57 } 58 _, err := ScanTestOutput(cfg) 59 assert.Error(t, err, "something failed") 60 assert.Assert(t, called) 61 } 62 63 type handlerFails struct { 64 count int 65 } 66 67 func (s *handlerFails) Event(_ TestEvent, _ *Execution) error { 68 if s.count > 1 { 69 return fmt.Errorf("something failed") 70 } 71 s.count++ 72 return nil 73 } 74 75 func (s *handlerFails) Err(_ string) error { 76 return nil 77 } 78 79 func TestParseEvent(t *testing.T) { 80 // nolint: lll 81 raw := `{"Time":"2018-03-22T22:33:35.168308334Z","Action":"output","Package":"example.com/good","Test": "TestOk","Output":"PASS\n"}` 82 event, err := parseEvent([]byte(raw)) 83 assert.NilError(t, err) 84 expected := TestEvent{ 85 Time: time.Date(2018, 3, 22, 22, 33, 35, 168308334, time.UTC), 86 Action: "output", 87 Package: "example.com/good", 88 Test: "TestOk", 89 Output: "PASS\n", 90 raw: []byte(raw), 91 } 92 cmpTestEvent := cmp.AllowUnexported(TestEvent{}) 93 assert.DeepEqual(t, event, expected, cmpTestEvent) 94 } 95 96 func TestPackage_AddEvent(t *testing.T) { 97 type testCase struct { 98 name string 99 event string 100 expected Package 101 } 102 103 run := func(t *testing.T, tc testCase) { 104 te, err := parseEvent([]byte(tc.event)) 105 assert.NilError(t, err) 106 107 p := newPackage() 108 p.addEvent(te) 109 assert.DeepEqual(t, p, &tc.expected, cmpPackage) 110 } 111 112 var testCases = []testCase{ 113 { 114 name: "coverage with -cover", 115 event: `{"Action":"output","Package":"gotest.tools/testing","Output":"coverage: 4.2% of statements\n"}`, 116 expected: Package{ 117 coverage: "coverage: 4.2% of statements", 118 output: pkgOutput(0, "coverage: 4.2% of statements\n"), 119 }, 120 }, 121 { 122 name: "coverage with -coverpkg", 123 event: `{"Action":"output","Package":"gotest.tools/testing","Output":"coverage: 7.5% of statements in ./testing\n"}`, 124 expected: Package{ 125 coverage: "coverage: 7.5% of statements in ./testing", 126 output: pkgOutput(0, "coverage: 7.5% of statements in ./testing\n"), 127 }, 128 }, 129 { 130 name: "package failed", 131 event: `{"Action":"fail","Package":"gotest.tools/testing","Elapsed":0.012}`, 132 expected: Package{action: ActionFail, elapsed: 12 * time.Millisecond}, 133 }, 134 { 135 name: "package is cached", 136 event: `{"Action":"output","Package":"gotest.tools/testing","Output":"ok \tgotest.tools/testing\t(cached)\n"}`, 137 expected: Package{ 138 cached: true, 139 output: pkgOutput(0, "ok \tgotest.tools/testing\t(cached)\n"), 140 }, 141 }, 142 { 143 name: "package pass", 144 event: `{"Action":"pass","Package":"gotest.tools/testing","Elapsed":0.012}`, 145 expected: Package{action: ActionPass, elapsed: 12 * time.Millisecond}, 146 }, 147 } 148 149 for _, tc := range testCases { 150 t.Run(tc.name, func(t *testing.T) { 151 run(t, tc) 152 }) 153 } 154 } 155 156 func pkgOutput(id int, line string) map[int][]string { 157 return map[int][]string{id: {line}} 158 } 159 160 func TestScanTestOutput_WithMissingEvents(t *testing.T) { 161 source := golden.Get(t, "go-test-json-missing-test-events.out") 162 handler := &captureHandler{} 163 cfg := ScanConfig{ 164 Stdout: bytes.NewReader(source), 165 Handler: handler, 166 } 167 _, err := ScanTestOutput(cfg) 168 assert.NilError(t, err) 169 170 var cmpTestEventShallow = cmp.Options{ 171 cmp.Comparer(func(x, y TestEvent) bool { 172 return x.Test == y.Test && x.Action == y.Action && x.Elapsed == y.Elapsed 173 }), 174 cmpopts.SortSlices(func(x, y TestEvent) bool { 175 return x.Test < y.Test 176 }), 177 } 178 179 // the package end event should come immediately before the artificial events 180 expected := []TestEvent{ 181 {Action: ActionPass}, 182 {Action: ActionFail, Test: "TestMissing", Elapsed: -1}, 183 {Action: ActionFail, Test: "TestMissing/a", Elapsed: -1}, 184 {Action: ActionFail, Test: "TestMissingEvent", Elapsed: -1}, 185 {Action: ActionFail, Test: "TestFailed/a", Elapsed: -1}, 186 {Action: ActionFail, Test: "TestFailed/a/sub", Elapsed: -1}, 187 } 188 assert.Assert(t, len(handler.events) > len(expected)) 189 start := len(handler.events) - len(expected) 190 assert.DeepEqual(t, expected, handler.events[start:], cmpTestEventShallow) 191 } 192 193 func TestScanTestOutput_WithNonJSONLines(t *testing.T) { 194 source := golden.Get(t, "go-test-json-with-nonjson-stdout.out") 195 nonJSONLine := "|||This line is not valid test2json output.|||" 196 197 // Test that when we ignore non-JSON lines, scanning completes, and test 198 // that when we don't ignore non-JSON lines, scanning fails. 199 for _, ignore := range []bool{true, false} { 200 t.Run(fmt.Sprintf("ignore-non-json=%v", ignore), func(t *testing.T) { 201 handler := &captureHandler{} 202 cfg := ScanConfig{ 203 Stdout: bytes.NewReader(source), 204 Handler: handler, 205 IgnoreNonJSONOutputLines: ignore, 206 } 207 _, err := ScanTestOutput(cfg) 208 if ignore { 209 assert.DeepEqual(t, handler.errs, []string{nonJSONLine}) 210 assert.NilError(t, err) 211 return 212 } 213 assert.DeepEqual(t, handler.errs, []string{}, cmpopts.EquateEmpty()) 214 expected := "failed to parse test output: " + 215 nonJSONLine + ": invalid character '|' looking for beginning of value" 216 assert.Error(t, err, expected) 217 }) 218 } 219 } 220 221 func TestScanTestOutput_WithGODEBUG(t *testing.T) { 222 goDebugSource := `HASH[moduleIndex] 223 HASH[moduleIndex]: "go1.20.4" 224 HASH /usr/lib/go/src/runtime/debuglog_off.go: d6f147198 225 testcache: package: input list not found: ...` 226 227 handler := &captureHandler{} 228 cfg := ScanConfig{ 229 Stdout: bytes.NewReader(nil), 230 Stderr: strings.NewReader(goDebugSource), 231 Handler: handler, 232 } 233 exec, err := ScanTestOutput(cfg) 234 assert.NilError(t, err) 235 assert.DeepEqual(t, handler.errs, strings.Split(goDebugSource, "\n")) 236 assert.DeepEqual(t, exec.Errors(), []string(nil)) 237 } 238 239 type captureHandler struct { 240 events []TestEvent 241 errs []string 242 } 243 244 func (s *captureHandler) Event(event TestEvent, _ *Execution) error { 245 s.events = append(s.events, event) 246 return nil 247 } 248 249 func (s *captureHandler) Err(text string) error { 250 s.errs = append(s.errs, text) 251 return nil 252 } 253 254 func TestFilterFailedUnique_MultipleNested(t *testing.T) { 255 source := []byte(`{"Package": "pkg", "Action": "run"} 256 {"Package": "pkg", "Test": "TestParent", "Action": "run"} 257 {"Package": "pkg", "Test": "TestParent/TestNested", "Action": "run"} 258 {"Package": "pkg", "Test": "TestParent/TestNested/TestOne", "Action": "run"} 259 {"Package": "pkg", "Test": "TestParent/TestNested/TestOne", "Action": "fail"} 260 {"Package": "pkg", "Test": "TestParent/TestNested/TestOnePrefix", "Action": "run"} 261 {"Package": "pkg", "Test": "TestParent/TestNested/TestOnePrefix", "Action": "fail"} 262 {"Package": "pkg", "Test": "TestParent/TestNested", "Action": "fail"} 263 {"Package": "pkg", "Test": "TestParent", "Action": "fail"} 264 {"Package": "pkg", "Test": "TestTop", "Action": "run"} 265 {"Package": "pkg", "Test": "TestTop", "Action": "fail"} 266 {"Package": "pkg", "Test": "TestTopPrefix", "Action": "run"} 267 {"Package": "pkg", "Test": "TestTopPrefix", "Action": "fail"} 268 {"Package": "pkg", "Action": "fail"} 269 {"Package": "pkg2", "Action": "run"} 270 {"Package": "pkg2", "Test": "TestParent", "Action": "run"} 271 {"Package": "pkg2", "Test": "TestParent/TestNested", "Action": "run"} 272 {"Package": "pkg2", "Test": "TestParent/TestNested", "Action": "fail"} 273 {"Package": "pkg2", "Test": "TestParent/TestNestedPrefix", "Action": "run"} 274 {"Package": "pkg2", "Test": "TestParent/TestNestedPrefix", "Action": "fail"} 275 {"Package": "pkg2", "Test": "TestParent", "Action": "fail"} 276 {"Package": "pkg2", "Test": "TestParentPrefix", "Action": "run"} 277 {"Package": "pkg2", "Test": "TestParentPrefix", "Action": "fail"} 278 {"Package": "pkg2", "Action": "fail"}`) 279 280 handler := &captureHandler{} 281 cfg := ScanConfig{ 282 Stdout: bytes.NewReader(source), 283 Handler: handler, 284 } 285 exec, err := ScanTestOutput(cfg) 286 assert.NilError(t, err) 287 actual := FilterFailedUnique(exec.Failed()) 288 289 expected := []TestCase{ 290 {ID: 3, Package: "pkg", Test: TestName("TestParent/TestNested/TestOne")}, 291 {ID: 4, Package: "pkg", Test: TestName("TestParent/TestNested/TestOnePrefix")}, 292 {ID: 5, Package: "pkg", Test: TestName("TestTop")}, 293 {ID: 6, Package: "pkg", Test: TestName("TestTopPrefix")}, 294 {ID: 2, Package: "pkg2", Test: TestName("TestParent/TestNested")}, 295 {ID: 3, Package: "pkg2", Test: TestName("TestParent/TestNestedPrefix")}, 296 {ID: 4, Package: "pkg2", Test: TestName("TestParentPrefix")}, 297 } 298 cmpTestCase := cmp.AllowUnexported(TestCase{}) 299 assert.DeepEqual(t, expected, actual, cmpTestCase) 300 }