Skip to content

Commit 655b487

Browse files
committed
Merge remote-tracking branch 'origin/main' into mini-fp
2 parents 3492c06 + f42af14 commit 655b487

File tree

20,612 files changed

+1474266
-741515
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

20,612 files changed

+1474266
-741515
lines changed

.ci/all_requirements.txt

Lines changed: 192 additions & 6 deletions
Large diffs are not rendered by default.

.ci/generate_test_report_github.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,9 @@
44
"""Script to generate a build report for Github."""
55

66
import argparse
7-
import platform
87

98
import generate_test_report_lib
109

11-
def compute_platform_title() -> str:
12-
logo = ":window:" if platform.system() == "Windows" else ":penguin:"
13-
# On Linux the machine value is x86_64 on Windows it is AMD64.
14-
if platform.machine() == "x86_64" or platform.machine() == "AMD64":
15-
arch = "x64"
16-
else:
17-
arch = platform.machine()
18-
return f"{logo} {platform.system()} {arch} Test Results"
19-
2010

2111
if __name__ == "__main__":
2212
parser = argparse.ArgumentParser()
@@ -27,7 +17,9 @@ def compute_platform_title() -> str:
2717
args = parser.parse_args()
2818

2919
report = generate_test_report_lib.generate_report_from_files(
30-
compute_platform_title(), args.return_code, args.build_test_logs
20+
generate_test_report_lib.compute_platform_title(),
21+
args.return_code,
22+
args.build_test_logs,
3123
)
3224

3325
print(report)

.ci/generate_test_report_lib.py

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,22 @@
33
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44
"""Library to parse JUnit XML files and return a markdown report."""
55

6+
from typing import TypedDict, Optional
7+
import platform
8+
69
from junitparser import JUnitXml, Failure
710

11+
12+
# This data structure should match the definition in llvm-zorg in
13+
# premerge/advisor/advisor_lib.py
14+
# TODO(boomanaiden154): Drop the Optional here and switch to str | None when
15+
# we require Python 3.10.
16+
class FailureExplanation(TypedDict):
17+
name: str
18+
explained: bool
19+
reason: Optional[str]
20+
21+
822
SEE_BUILD_FILE_STR = "Download the build's log file to see the details."
923
UNRELATED_FAILURES_STR = (
1024
"If these failures are unrelated to your changes (for example "
@@ -41,11 +55,25 @@ def _parse_ninja_log(ninja_log: list[str]) -> list[tuple[str, str]]:
4155
# touch test/4.stamp
4256
#
4357
# index will point to the line that starts with Failed:. The progress
44-
# indicator is the line before this ([4/5] test/4.stamp) and contains a pretty
45-
# printed version of the target being built (test/4.stamp). We use this line
46-
# and remove the progress information to get a succinct name for the target.
47-
failing_action = ninja_log[index - 1].split("] ")[1]
58+
# indicator is sometimes the line before this ([4/5] test/4.stamp) and
59+
# will contain a pretty printed version of the target being built
60+
# (test/4.stamp) when accurate. We instead parse the failed line rather
61+
# than the progress indicator as the progress indicator may not be
62+
# aligned with the failure.
63+
failing_action = ninja_log[index].split("FAILED: ")[1]
4864
failure_log = []
65+
66+
# Parse the lines above the FAILED: string if the line does not come
67+
# immediately after a progress indicator to ensure that we capture the
68+
# entire failure message.
69+
if not ninja_log[index - 1].startswith("["):
70+
before_index = index - 1
71+
while before_index > 0 and not ninja_log[before_index].startswith("["):
72+
failure_log.append(ninja_log[before_index])
73+
before_index = before_index - 1
74+
failure_log.reverse()
75+
76+
# Parse the failure information, which comes after the FAILED: tag.
4977
while (
5078
index < len(ninja_log)
5179
and not ninja_log[index].startswith("[")
@@ -80,16 +108,29 @@ def find_failure_in_ninja_logs(ninja_logs: list[list[str]]) -> list[tuple[str, s
80108
return failures
81109

82110

83-
def _format_ninja_failures(ninja_failures: list[tuple[str, str]]) -> list[str]:
84-
"""Formats ninja failures into summary views for the report."""
111+
def _format_failures(
112+
failures: list[tuple[str, str]], failure_explanations: dict[str, FailureExplanation]
113+
) -> list[str]:
114+
"""Formats failures into summary views for the report."""
85115
output = []
86-
for build_failure in ninja_failures:
116+
for build_failure in failures:
87117
failed_action, failure_message = build_failure
118+
failure_explanation = None
119+
if failed_action in failure_explanations:
120+
failure_explanation = failure_explanations[failed_action]
121+
output.append("<details>")
122+
if failure_explanation:
123+
output.extend(
124+
[
125+
f"<summary>{failed_action} (Likely Already Failing)</summary>" "",
126+
failure_explanation["reason"],
127+
"",
128+
]
129+
)
130+
else:
131+
output.extend([f"<summary>{failed_action}</summary>", ""])
88132
output.extend(
89133
[
90-
"<details>",
91-
f"<summary>{failed_action}</summary>",
92-
"",
93134
"```",
94135
failure_message,
95136
"```",
@@ -98,6 +139,7 @@ def _format_ninja_failures(ninja_failures: list[tuple[str, str]]) -> list[str]:
98139
)
99140
return output
100141

142+
101143
def get_failures(junit_objects) -> dict[str, list[tuple[str, str]]]:
102144
failures = {}
103145
for results in junit_objects:
@@ -116,6 +158,17 @@ def get_failures(junit_objects) -> dict[str, list[tuple[str, str]]]:
116158
return failures
117159

118160

161+
def are_all_failures_explained(
162+
failures: list[tuple[str, str]], failure_explanations: dict[str, FailureExplanation]
163+
) -> bool:
164+
for failed_action, _ in failures:
165+
if failed_action not in failure_explanations:
166+
return False
167+
else:
168+
assert failure_explanations[failed_action]["explained"]
169+
return True
170+
171+
119172
# Set size_limit to limit the byte size of the report. The default is 1MB as this
120173
# is the most that can be put into an annotation. If the generated report exceeds
121174
# this limit and failures are listed, it will be generated again without failures
@@ -129,12 +182,25 @@ def generate_report(
129182
ninja_logs: list[list[str]],
130183
size_limit=1024 * 1024,
131184
list_failures=True,
132-
):
185+
failure_explanations_list: list[FailureExplanation] = [],
186+
) -> tuple[str, bool]:
133187
failures = get_failures(junit_objects)
134188
tests_run = 0
135189
tests_skipped = 0
136190
tests_failed = 0
137191

192+
failure_explanations: dict[str, FailureExplanation] = {}
193+
for failure_explanation in failure_explanations_list:
194+
if not failure_explanation["explained"]:
195+
continue
196+
failure_explanations[failure_explanation["name"]] = failure_explanation
197+
all_failures_explained = True
198+
if failures:
199+
for _, failures_list in failures.items():
200+
all_failures_explained &= are_all_failures_explained(
201+
failures_list, failure_explanations
202+
)
203+
138204
for results in junit_objects:
139205
for testsuite in results:
140206
tests_run += testsuite.tests
@@ -147,13 +213,17 @@ def generate_report(
147213
if return_code == 0:
148214
report.extend(
149215
[
150-
"The build succeeded and no tests ran. This is expected in some "
151-
"build configurations."
216+
":white_check_mark: The build succeeded and no tests ran. "
217+
"This is expected in some build configurations."
152218
]
153219
)
154220
else:
155221
ninja_failures = find_failure_in_ninja_logs(ninja_logs)
222+
all_failures_explained &= are_all_failures_explained(
223+
ninja_failures, failure_explanations
224+
)
156225
if not ninja_failures:
226+
all_failures_explained = False
157227
report.extend(
158228
[
159229
"The build failed before running any tests. Detailed "
@@ -173,14 +243,14 @@ def generate_report(
173243
"",
174244
]
175245
)
176-
report.extend(_format_ninja_failures(ninja_failures))
246+
report.extend(_format_failures(ninja_failures, failure_explanations))
177247
report.extend(
178248
[
179249
"",
180250
UNRELATED_FAILURES_STR,
181251
]
182252
)
183-
return "\n".join(report)
253+
return ("\n".join(report), all_failures_explained)
184254

185255
tests_passed = tests_run - tests_skipped - tests_failed
186256

@@ -209,43 +279,40 @@ def plural(num_tests):
209279

210280
for testsuite_name, failures in failures.items():
211281
report.extend(["", f"### {testsuite_name}"])
212-
for name, output in failures:
213-
report.extend(
214-
[
215-
"<details>",
216-
f"<summary>{name}</summary>",
217-
"",
218-
"```",
219-
output,
220-
"```",
221-
"</details>",
222-
]
223-
)
282+
report.extend(_format_failures(failures, failure_explanations))
224283
elif return_code != 0:
225284
# No tests failed but the build was in a failed state. Bring this to the user's
226285
# attention.
227286
ninja_failures = find_failure_in_ninja_logs(ninja_logs)
228287
if not ninja_failures:
288+
all_failures_explained = False
229289
report.extend(
230290
[
231291
"",
232-
"All tests passed but another part of the build **failed**. "
292+
"All executed tests passed, but another part of the build **failed**. "
233293
"Information about the build failure could not be automatically "
234294
"obtained.",
235295
"",
236296
SEE_BUILD_FILE_STR,
237297
]
238298
)
239299
else:
300+
all_failures_explained &= are_all_failures_explained(
301+
ninja_failures, failure_explanations
302+
)
240303
report.extend(
241304
[
242305
"",
243-
"All tests passed but another part of the build **failed**. Click on "
306+
"All executed tests passed, but another part of the build **failed**. Click on "
244307
"a failure below to see the details.",
245308
"",
246309
]
247310
)
248-
report.extend(_format_ninja_failures(ninja_failures))
311+
report.extend(_format_failures(ninja_failures, failure_explanations))
312+
else:
313+
report.extend(
314+
["", ":white_check_mark: The build succeeded and all tests passed."]
315+
)
249316

250317
if failures or return_code != 0:
251318
report.extend(["", UNRELATED_FAILURES_STR])
@@ -256,11 +323,12 @@ def plural(num_tests):
256323
title,
257324
return_code,
258325
junit_objects,
326+
ninja_logs,
259327
size_limit,
260328
list_failures=False,
261329
)
262330

263-
return report
331+
return (report, all_failures_explained)
264332

265333

266334
def load_info_from_files(build_log_files):
@@ -282,3 +350,13 @@ def load_info_from_files(build_log_files):
282350
def generate_report_from_files(title, return_code, build_log_files):
283351
junit_objects, ninja_logs = load_info_from_files(build_log_files)
284352
return generate_report(title, return_code, junit_objects, ninja_logs)
353+
354+
355+
def compute_platform_title() -> str:
356+
logo = ":window:" if platform.system() == "Windows" else ":penguin:"
357+
# On Linux the machine value is x86_64 on Windows it is AMD64.
358+
if platform.machine() == "x86_64" or platform.machine() == "AMD64":
359+
arch = "x64"
360+
else:
361+
arch = platform.machine()
362+
return f"{logo} {platform.system()} {arch} Test Results"

0 commit comments

Comments
 (0)