@@ -30,10 +30,10 @@ type TestNode struct {
3030 Children map [string ]* TestNode // Child tests
3131 Parent * TestNode // Parent test
3232 IsSubtest bool // Is this a subtest
33- // Track whether this node is directly matched by patterns
34- DirectlyIgnored bool
3533 // Store output messages for direct failure detection
3634 OutputMessages []string
35+ // Flag to track if this node has any failing subtests
36+ HasFailingSubtests bool
3737}
3838
3939// TransformJSON transforms go test -json output according to the options
@@ -62,11 +62,11 @@ func TransformJSON(input io.Reader, output io.Writer, opts *Options) (int, error
6262 // Track output messages for direct failure detection
6363 captureOutputMessages (events , testTree )
6464
65- // Apply ignore rules
66- applyIgnoreRules (testTree , opts )
65+ // Mark parents with failing subtests
66+ markParentsWithFailingSubtests (testTree )
6767
68- // Propagate ignore status - but only in specific ways
69- propagateIgnoreStatus (testTree , opts )
68+ // Identify which tests should be passed
69+ identifyTestsToIgnore (testTree , opts )
7070
7171 // Transform events
7272 transformedEvents , anyRemainingFailures := transformEvents (events , testTree )
@@ -228,121 +228,58 @@ func captureOutputMessages(events []TestEvent, tree map[string]*TestNode) {
228228 }
229229}
230230
231- // applyIgnoreRules applies ignore rules to the test tree
232- func applyIgnoreRules (tree map [string ]* TestNode , opts * Options ) {
231+ // markParentsWithFailingSubtests marks parent nodes that have any failing subtests
232+ func markParentsWithFailingSubtests (tree map [string ]* TestNode ) {
233233 // Process each package
234234 for _ , pkgNode := range tree {
235- // Process all tests in this package recursively
236- var processNode func (* TestNode )
237- processNode = func (node * TestNode ) {
238- // Only need to check if it's a failing test
239- if node .Failed {
240- // Apply ignore rules
241- if node .IsSubtest && opts .IgnoreAllSubtestFailures {
242- node .Ignored = true
243- node .DirectlyIgnored = true
244- }
245- }
235+ // Bottom-up traversal to mark parents with failing subtests
236+ var markParents func (* TestNode ) bool
237+ markParents = func (node * TestNode ) bool {
238+ // Check if any child is failing or has failing subtests
239+ hasFailingSubtests := false
246240
247- // Recursively process children
248241 for _ , child := range node .Children {
249- processNode (child )
242+ // Process child first (depth-first)
243+ childHasFailingSubtests := markParents (child )
244+
245+ // Check if child is failing or has failing subtests
246+ if child .Failed || childHasFailingSubtests {
247+ hasFailingSubtests = true
248+ }
250249 }
250+
251+ // Update the node's status
252+ node .HasFailingSubtests = hasFailingSubtests
253+
254+ // Return whether this node has failing subtests
255+ return hasFailingSubtests
251256 }
252257
253- processNode (pkgNode )
258+ markParents (pkgNode )
254259 }
255260}
256261
257- // propagateIgnoreStatus propagates ignore status up the tree
258- func propagateIgnoreStatus (tree map [string ]* TestNode , opts * Options ) {
262+ // identifyTestsToIgnore identifies which tests should be converted to pass
263+ func identifyTestsToIgnore (tree map [string ]* TestNode , opts * Options ) {
259264 // Process each package
260265 for _ , pkgNode := range tree {
261- // We need to be careful how we propagate status
262- // Only propagate from children to parent when ALL failing children are ignored
263-
264- // Bottom-up traversal
265- var markIgnored func (* TestNode ) bool
266- markIgnored = func (node * TestNode ) bool {
267- // First process all children
268- allFailingChildrenIgnored := true
269- anyFailingChildren := false
270-
266+ // Process all nodes in the tree and set ignore status
267+ var processNode func (* TestNode )
268+ processNode = func (node * TestNode ) {
269+ // Process children first
271270 for _ , child := range node .Children {
272- if child .Failed {
273- anyFailingChildren = true
274- if ! markIgnored (child ) {
275- allFailingChildrenIgnored = false
276- }
277- } else {
278- // Make sure to process non-failing children too
279- markIgnored (child )
280- }
271+ processNode (child )
281272 }
282273
283- // Now decide for this node
284- if node .Failed {
285- // If explicitly ignored, return that status
286- if node .DirectlyIgnored || node .Ignored {
287- return true
288- }
289-
290- // If this is a parent and ALL failing children are ignored,
291- // Decide if we should ignore the parent
292- if anyFailingChildren && allFailingChildrenIgnored {
293- // For TestNestedSubtests, we should always propagate
294- if containsNestedFail (node ) || containsParallel (node ) {
295- node .Ignored = true
296- return true
297- }
298-
299- // For IgnoreAllSubtestFailures, we should always propagate
300- if opts .IgnoreAllSubtestFailures {
301- node .Ignored = true
302- return true
303- }
304- }
305-
306- // Default case: not ignored
307- return false
274+ // If this node has failing subtests and it's failing itself,
275+ // mark it as ignored (so it will be converted to PASS)
276+ if node .HasFailingSubtests && node .Failed {
277+ node .Ignored = true
308278 }
309-
310- // Node isn't failed, so it's "ignored" for propagation purposes
311- return true
312- }
313-
314- markIgnored (pkgNode )
315- }
316- }
317-
318- // containsNestedFail checks if this node or any of its children contain NestedFail
319- func containsNestedFail (node * TestNode ) bool {
320- if strings .Contains (node .Name , "NestedFail" ) {
321- return true
322- }
323-
324- for _ , child := range node .Children {
325- if containsNestedFail (child ) {
326- return true
327279 }
328- }
329-
330- return false
331- }
332280
333- // containsParallel checks if this node or any of its children contain Parallel
334- func containsParallel (node * TestNode ) bool {
335- if strings .Contains (node .Name , "Parallel" ) {
336- return true
337- }
338-
339- for _ , child := range node .Children {
340- if containsParallel (child ) {
341- return true
342- }
281+ processNode (pkgNode )
343282 }
344-
345- return false
346283}
347284
348285// transformEvents applies transformations to events based on the test tree
@@ -386,18 +323,6 @@ func transformEvents(events []TestEvent, tree map[string]*TestNode) ([]TestEvent
386323 return nil
387324 }
388325
389- // Helper function to check if a node has direct failure messages
390- hasDirectFailure := func (node * TestNode ) bool {
391- for _ , msg := range node .OutputMessages {
392- // Check if the message indicates a direct failure (not just reporting a child failure)
393- // This is a simple heuristic - direct failures usually don't mention child tests
394- if ! strings .Contains (msg , "===" ) && ! strings .Contains (msg , "---" ) {
395- return true
396- }
397- }
398- return false
399- }
400-
401326 for i , event := range events {
402327 // Make a copy of the event
403328 transformedEvents [i ] = event
@@ -406,16 +331,8 @@ func transformEvents(events []TestEvent, tree map[string]*TestNode) ([]TestEvent
406331 if event .Action == "fail" {
407332 node := findNode (event .Package , event .Test )
408333 if node != nil && node .Ignored {
409- // For leaf subtests, keep them as failed
410- // For parent tests with direct failures, keep them as failed
411- // For parent tests that only fail because of child failures, change to pass
412- if (node .IsSubtest && len (node .Children ) == 0 ) || hasDirectFailure (node ) {
413- // This is a leaf subtest or has a direct failure - keep it as a failure
414- anyRemainingFailures = true
415- } else {
416- // This is a parent test without direct failures - change to pass
417- transformedEvents [i ].Action = "pass"
418- }
334+ // Convert this fail to a pass
335+ transformedEvents [i ].Action = "pass"
419336 } else {
420337 // We're keeping this as a failure
421338 anyRemainingFailures = true
@@ -426,10 +343,8 @@ func transformEvents(events []TestEvent, tree map[string]*TestNode) ([]TestEvent
426343 } else if event .Action == "output" {
427344 node := findNode (event .Package , event .Test )
428345 if node != nil && node .Failed && node .Ignored {
429- // Only transform output text for non-subtests or parent tests without direct failures
430- if ! node .IsSubtest && ! hasDirectFailure (node ) {
431- transformedEvents [i ].Output = transformOutputText (event .Output )
432- }
346+ // Transform output text for passed tests
347+ transformedEvents [i ].Output = transformOutputText (event .Output )
433348 }
434349 }
435350 }
0 commit comments