diff --git a/testjson/execution.go b/testjson/execution.go index 51cac7d9..531aa4a2 100644 --- a/testjson/execution.go +++ b/testjson/execution.go @@ -223,6 +223,14 @@ func (n TestName) Name() string { return string(n) } +func (n TestName) Parent() string { + idx := strings.LastIndex(string(n), "/") + if idx < 0 { + return "" + } + return string(n)[:idx] +} + func (p *Package) removeOutput(id int) { delete(p.output, id) @@ -538,15 +546,41 @@ func (e *Execution) Failed() []TestCase { return failed } -// FilterFailedUnique filters a slice of failed TestCases by removing root test -// case that have failed subtests. +// FilterFailedUnique filters a slice of failed TestCases to remove any parent +// tests that have failed subtests. The parent test will always be run when +// running any of its subtests. func FilterFailedUnique(tcs []TestCase) []TestCase { - var result []TestCase + sort.Slice(tcs, func(i, j int) bool { + a, b := tcs[i], tcs[j] + if a.Package != b.Package { + return a.Package < b.Package + } + return len(a.Test.Name()) > len(b.Test.Name()) + }) + + var result []TestCase //nolint:prealloc + var parents = make(map[string]map[string]bool) for _, tc := range tcs { - if !tc.hasSubTestFailed { - result = append(result, tc) + if _, exists := parents[tc.Package]; !exists { + parents[tc.Package] = make(map[string]bool) } + if parent := tc.Test.Parent(); parent != "" { + parents[tc.Package][parent] = true + } + if _, exists := parents[tc.Package][tc.Test.Name()]; exists { + continue // tc is a parent of a failing subtest + } + result = append(result, tc) } + + // Restore the original order of test cases + sort.Slice(result, func(i, j int) bool { + a, b := result[i], result[j] + if a.Package != b.Package { + return a.Package < b.Package + } + return a.ID < b.ID + }) return result } diff --git a/testjson/execution_test.go b/testjson/execution_test.go index 8c7566ee..dbf88237 100644 --- a/testjson/execution_test.go +++ b/testjson/execution_test.go @@ -250,3 +250,51 @@ func (s *captureHandler) Err(text string) error { s.errs = append(s.errs, text) return nil } + +func TestFilterFailedUnique_MultipleNested(t *testing.T) { + source := []byte(`{"Package": "pkg", "Action": "run"} + {"Package": "pkg", "Test": "TestParent", "Action": "run"} + {"Package": "pkg", "Test": "TestParent/TestNested", "Action": "run"} + {"Package": "pkg", "Test": "TestParent/TestNested/TestOne", "Action": "run"} + {"Package": "pkg", "Test": "TestParent/TestNested/TestOne", "Action": "fail"} + {"Package": "pkg", "Test": "TestParent/TestNested/TestOnePrefix", "Action": "run"} + {"Package": "pkg", "Test": "TestParent/TestNested/TestOnePrefix", "Action": "fail"} + {"Package": "pkg", "Test": "TestParent/TestNested", "Action": "fail"} + {"Package": "pkg", "Test": "TestParent", "Action": "fail"} + {"Package": "pkg", "Test": "TestTop", "Action": "run"} + {"Package": "pkg", "Test": "TestTop", "Action": "fail"} + {"Package": "pkg", "Test": "TestTopPrefix", "Action": "run"} + {"Package": "pkg", "Test": "TestTopPrefix", "Action": "fail"} + {"Package": "pkg", "Action": "fail"} + {"Package": "pkg2", "Action": "run"} + {"Package": "pkg2", "Test": "TestParent", "Action": "run"} + {"Package": "pkg2", "Test": "TestParent/TestNested", "Action": "run"} + {"Package": "pkg2", "Test": "TestParent/TestNested", "Action": "fail"} + {"Package": "pkg2", "Test": "TestParent/TestNestedPrefix", "Action": "run"} + {"Package": "pkg2", "Test": "TestParent/TestNestedPrefix", "Action": "fail"} + {"Package": "pkg2", "Test": "TestParent", "Action": "fail"} + {"Package": "pkg2", "Test": "TestParentPrefix", "Action": "run"} + {"Package": "pkg2", "Test": "TestParentPrefix", "Action": "fail"} + {"Package": "pkg2", "Action": "fail"}`) + + handler := &captureHandler{} + cfg := ScanConfig{ + Stdout: bytes.NewReader(source), + Handler: handler, + } + exec, err := ScanTestOutput(cfg) + assert.NilError(t, err) + actual := FilterFailedUnique(exec.Failed()) + + expected := []TestCase{ + {ID: 3, Package: "pkg", Test: TestName("TestParent/TestNested/TestOne")}, + {ID: 4, Package: "pkg", Test: TestName("TestParent/TestNested/TestOnePrefix")}, + {ID: 5, Package: "pkg", Test: TestName("TestTop")}, + {ID: 6, Package: "pkg", Test: TestName("TestTopPrefix")}, + {ID: 2, Package: "pkg2", Test: TestName("TestParent/TestNested")}, + {ID: 3, Package: "pkg2", Test: TestName("TestParent/TestNestedPrefix")}, + {ID: 4, Package: "pkg2", Test: TestName("TestParentPrefix")}, + } + cmpTestCase := cmp.AllowUnexported(TestCase{}) + assert.DeepEqual(t, expected, actual, cmpTestCase) +}