22"""Render the PR body for an automated flaky-test fix.
33
44Inputs:
5- --selected selected.json from find-flaky-test.py (or build-override.py)
6- --copilot-log copilot-output.jsonl produced by `copilot ... --output-format json`
7- (optional)
5+ --selected selected.json from find-flaky-test.py
86 --diagnosis build/flaky-fix/diagnosis.md written by Copilot (optional)
97 --output path to write the PR body to
108
119The body always includes Develocity scan links, a per-day flake breakdown,
1210and the captured failure stack. If a Copilot diagnosis file is present, it
13- is included verbatim. Otherwise the script falls back to extracting the
14- final assistant message from the Copilot JSONL log.
11+ is included verbatim.
1512"""
1613
1714from __future__ import annotations
2320from pathlib import Path
2421
2522
26- def _last_assistant_message (jsonl_path : Path ) -> str :
27- """Return the last non-empty content of an ``assistant.message`` event."""
28- last = ""
29- with jsonl_path .open ("r" , encoding = "utf-8" , errors = "replace" ) as f :
30- for line in f :
31- line = line .strip ()
32- if not line :
33- continue
34- try :
35- d = json .loads (line )
36- except json .JSONDecodeError :
37- continue
38- if d .get ("type" ) != "assistant.message" :
39- continue
40- content = (d .get ("data" ) or {}).get ("content" ) or ""
41- if isinstance (content , str ) and content .strip ():
42- last = content
43- return last .strip ()
44-
45-
4623def _format_per_day (rows : list ) -> list [str ]:
4724 if not rows :
4825 return []
@@ -51,11 +28,10 @@ def _format_per_day(rows: list) -> list[str]:
5128 "| Day | flaky | failed | passed |" ,
5229 "| --- | ---: | ---: | ---: |" ]
5330 for r in rows :
54- day = dt .datetime .fromtimestamp ((r .get ("start_ms" ) or 0 ) / 1000 ,
55- tz = dt .timezone .utc )
31+ day = dt .datetime .fromtimestamp (r ["start_ms" ] / 1000 , tz = dt .timezone .utc )
5632 out .append (
5733 f"| { day .strftime ('%Y-%m-%d' )} | "
58- f"{ r . get ( 'flaky' , 0 ) } | { r . get ( 'failed' , 0 ) } | { r . get ( 'passed' , 0 ) } |"
34+ f"{ r [ 'flaky' ] } | { r [ 'failed' ] } | { r [ 'passed' ] } |"
5935 )
6036 out .append ("" )
6137 return out
@@ -66,12 +42,9 @@ def _format_recent_scans(scans: list) -> list[str]:
6642 return []
6743 out = ["### Recent failed/flaky scans" , "" ]
6844 for s in scans [:5 ]:
69- url = s .get ("scan_url" , "" )
70- outcome = s .get ("outcome" , "" )
71- wu = s .get ("work_unit" , "" )
72- bullet = f"- [{ s .get ('build_id' , '' )[:13 ]} ]({ url } ) ({ outcome } "
73- if wu :
74- bullet += f", `{ wu } `"
45+ bullet = f"- [{ s ['build_id' ][:13 ]} ]({ s ['scan_url' ]} ) ({ s ['outcome' ]} "
46+ if s ["work_unit" ]:
47+ bullet += f", `{ s ['work_unit' ]} `"
7548 bullet += ")"
7649 out .append (bullet )
7750 out .append ("" )
@@ -81,56 +54,45 @@ def _format_recent_scans(scans: list) -> list[str]:
8154def main () -> int :
8255 ap = argparse .ArgumentParser ()
8356 ap .add_argument ("--selected" , type = Path , required = True )
84- ap .add_argument ("--copilot-log" , type = Path , default = None )
8557 ap .add_argument ("--diagnosis" , type = Path , default = None )
8658 ap .add_argument ("--output" , type = Path , required = True )
8759 args = ap .parse_args ()
8860
8961 selected = json .loads (args .selected .read_text (encoding = "utf-8" ))
90- cls = selected .get ("class" , "" )
91- method = selected .get ("method" , "" )
92- fq = selected .get ("fully_qualified" ) or f"{ cls } .{ method } "
93- source_file = selected .get ("source_file" , "" )
94- flaky_count = selected .get ("flaky_count" , 0 )
95- container_flaky = selected .get ("container_flaky_count" , 0 )
96- window_days = selected .get ("window_days" , 0 )
97- sample_url = selected .get ("sample_scan_url" ) or ""
98- sample_failure = (selected .get ("sample_failure" ) or "" ).rstrip ()
62+ fq = selected ["fully_qualified" ]
63+ source_file = selected ["source_file" ]
64+ window_days = selected ["window_days" ]
65+ sample_url = selected ["sample_scan_url" ]
66+ sample_failure = selected ["sample_failure" ].rstrip ()
9967
10068 lines : list [str ] = [
10169 f"Automated attempt at fixing flakiness in `{ fq } `." ,
10270 "" ,
10371 f"- Source: [`{ source_file } `]({ source_file } )" ,
104- f"- Flaky executions in last { window_days } d (this test): **{ flaky_count } **" ,
72+ f"- Flaky executions in last { window_days } d (this test): "
73+ f"**{ selected ['flaky_count' ]} **" ,
10574 f"- Flaky executions in last { window_days } d (test container): "
106- f"**{ container_flaky } **" ,
75+ f"**{ selected [ 'container_flaky_count' ] } **" ,
10776 ]
10877 if sample_url :
10978 lines .append (f"- Primary failed scan: { sample_url } " )
11079 lines .append ("" )
11180
112- lines += _format_recent_scans (selected . get ( "recent_flaky_scans" ) or [ ])
113- lines += _format_per_day (selected . get ( "per_day_breakdown" ) or [ ])
81+ lines += _format_recent_scans (selected [ "recent_flaky_scans" ])
82+ lines += _format_per_day (selected [ "per_day_breakdown" ])
11483
11584 lines += ["### Sample failure (from Develocity)" , "" , "```" ]
116- lines .append (sample_failure if sample_failure else "(no failure message captured)" )
85+ lines .append (sample_failure or "(no failure message captured)" )
11786 lines += ["```" , "" ]
11887
119- diagnosis_text = ""
12088 if args .diagnosis and args .diagnosis .exists ():
121- diagnosis_text = args .diagnosis .read_text (
122- encoding = "utf-8" , errors = "replace"
123- ).strip ()
124- if not diagnosis_text and args .copilot_log and args .copilot_log .exists ():
125- diagnosis_text = _last_assistant_message (args .copilot_log )
126-
127- if diagnosis_text :
128- lines += ["## Copilot diagnosis" , "" , diagnosis_text , "" ]
89+ diagnosis_text = args .diagnosis .read_text (encoding = "utf-8" ).strip ()
90+ if diagnosis_text :
91+ lines += ["## Copilot diagnosis" , "" , diagnosis_text , "" ]
12992
13093 lines += [
13194 "---" ,
13295 "" ,
133- "Generated locally by `.github/scripts/flaky-test-fix/run-local.sh`. "
13496 "Review the diagnosis and the diff carefully before merging - "
13597 "automated fixes can mask flakiness instead of addressing the root cause." ,
13698 ]
0 commit comments