1+ # This file is used by Makefile in testing to check whether test
2+ # is successful
3+ import glob
4+ import json
5+ import sys
6+ import argparse
7+
8+ # Parse optional debug flag from CLI
9+ # When enabled, prints detailed report inspection info
10+ parser = argparse .ArgumentParser ()
11+ parser .add_argument ("--debug" , action = "store_true" )
12+ args = parser .parse_args ()
13+
14+ DEBUG = args .debug
15+
16+
17+ def dprint (* args , ** kwargs ):
18+ """
19+ Debug print helper.
20+ Only prints when DEBUG flag is enabled to avoid polluting CI output.
21+ """
22+ if DEBUG :
23+ print (* args , ** kwargs )
24+
25+
26+ # Collect all JSON reports under reports/
27+ paths = sorted (glob .glob ("reports/*.json" ))
28+ dprint ("DEBUG: report paths =" , paths )
29+
30+
31+ def count_failures (node ):
32+ """
33+ Recursively count failures in a JSON report.
34+
35+ Rules:
36+ - If a node has 'number_of_failures', treat it as authoritative
37+ but still compare with nested counts (take max for safety)
38+ - Otherwise, aggregate failures from children
39+ - Handles mixed nested structures (dict/list)
40+
41+ This makes the checker robust to different report schemas.
42+ """
43+ if isinstance (node , list ):
44+ return sum (count_failures (x ) for x in node )
45+
46+ if not isinstance (node , dict ):
47+ return 0
48+
49+ # Direct failure count (if present)
50+ direct = node .get ("number_of_failures" )
51+ try :
52+ direct_val = int (direct ) if direct is not None else None
53+ except (TypeError , ValueError ):
54+ direct_val = None
55+
56+ # Recursively count nested failures
57+ nested = sum (count_failures (v ) for v in node .values ())
58+
59+ # Prefer explicit count but guard against under-reporting
60+ return nested if direct_val is None else max (direct_val , nested )
61+
62+
63+ # If no reports exist, treat as failure (fail closed)
64+ total = 1 if not paths else 0
65+
66+ for path in paths :
67+ dprint (f"\n ===== DEBUG REPORT: { path } =====" )
68+
69+ with open (path , encoding = "utf-8" ) as f :
70+ data = json .load (f )
71+
72+ # Print top-level structure for debugging schema issues
73+ if isinstance (data , dict ):
74+ dprint ("top-level keys =" , list (data .keys ()))
75+ dprint ("top-level number_of_failures =" , data .get ("number_of_failures" ))
76+ dprint ("top-level error =" , data .get ("error" ))
77+ dprint (
78+ "top-level results type =" ,
79+ type (data .get ("results" )).__name__ if "results" in data else "MISSING" ,
80+ )
81+ else :
82+ dprint ("root type =" , type (data ).__name__ )
83+
84+ failures = count_failures (data )
85+ dprint ("computed failures =" , failures )
86+
87+ total += failures
88+
89+
90+ # Final aggregated result
91+ dprint (f"\n DEBUG: total_failures={ total } " )
92+
93+ # Exit code:
94+ # 0 -> success (no failures)
95+ # 1 -> failure detected
96+ sys .exit (1 if total else 0 )
0 commit comments