22"""
33Upload pytest results to MySQL.
44Tables:
5- - ci_runs : one row per run
6- - ci_tests : one row per unique test
7- - ci_results : one row per test per run
5+ - ci_runs_dup : one row per run
6+ - ci_tests_dup : one row per unique test
7+ - ci_results_dup : one row per test per run
88"""
99
1010from __future__ import annotations
@@ -72,37 +72,41 @@ def nodeid_parts(nodeid: str) -> Tuple[str, str, str]:
7272 return f , c , t
7373
7474
75- def list_test_jsons (logs_dir : Path ) -> List [Path ]:
76- """Return per-test JSON files under logs_dir; skip aux files."""
77- return sorted (
75+ def find_single_report_json (logs_dir : Path ) -> Path :
76+ jsons = [
7877 p
7978 for p in logs_dir .iterdir ()
8079 if p .is_file ()
8180 and p .suffix .lower () == ".json"
82- and p .name not in SKIP_FILES
8381 and not p .name .endswith ("last_running.json" )
84- )
85-
86-
87- def load_from_per_test_jsons (files : Iterable [Path ]) -> Tuple [datetime , List [dict ]]:
88- """Load tests and set run created_at to the latest per-file 'created'.
89-
90- Each per-test JSON file must have a 'created' timestamp. We use the maximum
91- of these values as the run's created_at (i.e., when the run finished).
92- """
93- tests : List [dict ] = []
94- created_values : List [float ] = []
95-
96- for path in files :
97- with path .open ("r" , encoding = "utf-8" ) as fh :
98- data = json .load (fh )
99- if "created" not in data :
100- raise ValueError (f"Missing 'created' in { path .name } " )
101- created_values .append (float (data ["created" ]))
102- tests .extend (data .get ("tests" , []))
103-
104- created_at = datetime .fromtimestamp (max (created_values ))
105- return created_at , tests
82+ ]
83+ if not jsons :
84+ print (f"No JSON report found in { logs_dir } " )
85+ raise SystemExit (2 )
86+ if len (jsons ) != 1 :
87+ print (
88+ f"Expected exactly ONE JSON report, found { len (jsons )} : "
89+ + ", " .join (p .name for p in jsons )
90+ )
91+ raise SystemExit (2 )
92+ return jsons [0 ]
93+
94+
95+ def load_from_single_json (path : Path ) -> Tuple [datetime , List [dict ]]:
96+ with path .open ("r" , encoding = "utf-8" ) as fh :
97+ data = json .load (fh )
98+ if isinstance (data , dict ):
99+ tests = data .get ("tests" , [])
100+ created = data .get ("created" )
101+ if created is not None :
102+ created_at = datetime .fromtimestamp (float (created ))
103+ else :
104+ created_at = datetime .fromtimestamp (path .stat ().st_mtime )
105+ return created_at , tests
106+ if isinstance (data , list ):
107+ created_at = datetime .fromtimestamp (path .stat ().st_mtime )
108+ return created_at , data
109+ raise ValueError (f"Unexpected JSON structure in { path .name } " )
106110
107111
108112def extract_result_fields (
@@ -263,10 +267,10 @@ def connect():
263267
264268
265269def insert_run (cur , created_at : datetime , meta : dict ) -> int :
266- """Insert one row into ci_runs and return run_id. Idempotence is not enforced here."""
270+ """Insert one row into ci_runs_dup and return run_id. Idempotence is not enforced here."""
267271 cur .execute (
268272 """
269- INSERT INTO ci_runs (
273+ INSERT INTO ci_runs_dup (
270274 created_at, commit_sha, runner_label, ubuntu_version,
271275 rocm_version, build_num, github_run_id, run_tag, logs_path
272276 ) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
@@ -287,11 +291,11 @@ def insert_run(cur, created_at: datetime, meta: dict) -> int:
287291
288292
289293def sync_tests_and_get_ids (cur , tests : List [dict ]) -> Dict [Tuple [str , str , str ], int ]:
290- """Ensure all tests exist in ci_tests and return an ID mapping.
294+ """Ensure all tests exist in ci_tests_dup and return an ID mapping.
291295
292296 Uses a TEMPORARY TABLE for efficiency with large runs:
293297 1) Bulk insert unique (filename, classname, test_name) into a temp table.
294- 2) INSERT any missing rows into ci_tests in one set operation.
298+ 2) INSERT any missing rows into ci_tests_dup in one set operation.
295299 3) SELECT back (file, class, test) -> id mapping in one query.
296300 """
297301 uniq = {nodeid_parts (t ["nodeid" ]) for t in tests }
@@ -315,10 +319,10 @@ def sync_tests_and_get_ids(cur, tests: List[dict]) -> Dict[Tuple[str, str, str],
315319
316320 cur .execute (
317321 """
318- INSERT INTO ci_tests (filename, classname, test_name)
322+ INSERT INTO ci_tests_dup (filename, classname, test_name)
319323 SELECT s.filename, s.classname, s.test_name
320324 FROM tmp_tests s
321- LEFT JOIN ci_tests t
325+ LEFT JOIN ci_tests_dup t
322326 ON t.filename = s.filename
323327 AND t.classname = s.classname
324328 AND t.test_name = s.test_name
@@ -330,7 +334,7 @@ def sync_tests_and_get_ids(cur, tests: List[dict]) -> Dict[Tuple[str, str, str],
330334 """
331335 SELECT t.id, s.filename, s.classname, s.test_name
332336 FROM tmp_tests s
333- JOIN ci_tests t
337+ JOIN ci_tests_dup t
334338 ON t.filename = s.filename
335339 AND t.classname = s.classname
336340 AND t.test_name = s.test_name
@@ -356,7 +360,7 @@ def batch_insert_results(
356360 return
357361
358362 sql = """
359- INSERT INTO ci_results
363+ INSERT INTO ci_results_dup
360364 (run_id, test_id, outcome, duration, longrepr, message, skip_label)
361365 VALUES (%s,%s,%s,%s,%s,%s,%s)
362366 ON DUPLICATE KEY UPDATE
@@ -388,16 +392,13 @@ def upload_pytest_results( # pylint: disable=too-many-arguments, too-many-local
388392
389393 Flow:
390394 1) Parse JSONs and gather tests.
391- 2) Insert a ci_runs row and get run_id.
395+ 2) Insert a ci_runs_dup row and get run_id.
392396 3) Ensure all tests exist; get (file,class,test) -> test_id map.
393- 4) Bulk insert/update ci_results for this run.
397+ 4) Bulk insert/update ci_results_dup for this run.
394398 """
395- files = list_test_jsons (logs_dir )
396- if not files :
397- print (f"No JSON files under { logs_dir } " )
398- raise SystemExit (2 ) # input error
399-
400- created_at , tests = load_from_per_test_jsons (files )
399+ report = find_single_report_json (logs_dir )
400+ created_at , tests = load_from_single_json (report )
401+
401402 if not tests :
402403 print ("No tests found in JSONs" )
403404 raise SystemExit (2 ) # input error
0 commit comments