https://github.com/DavidSpickett updated https://github.com/llvm/llvm-project/pull/113703
>From b7c270ad11c7b19edc7eff71922be9e19e830362 Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Wed, 23 Oct 2024 11:39:15 +0100 Subject: [PATCH 1/3] [ci] New script to generate test reports as Buildkite Annotations The CI builds now send the results of every lit run to a unique file. This means we can read them all to make a combined report for all tests. This report will be shown as an "annotation" in the build results: https://buildkite.com/docs/agent/v3/cli-annotate#creating-an-annotation Here is an example: https://buildkite.com/llvm-project/github-pull-requests/builds/112546 (make sure it is showing "All" instead of "Failures") This is an alternative to using the existing Buildkite plugin: https://github.com/buildkite-plugins/junit-annotate-buildkite-plugin As the plugin is: * Specific to Buildkite, and we may move away from Buildkite. * Requires docker, unless we were to fork it ourselves. * Does not let you customise the report format unless again, we make our own fork. Annotations use GitHub's flavour of Markdown so the main code in the script generates that text. There is an extra "style" argument generated to make the formatting nicer in Buildkite. "context" is the name of the annotation that will be created. By using different context names for Linux and Windows results we get 2 separate annotations. The script also handles calling the buildkite-agent. This makes passing extra arguments to the agent easier, rather than piping the output of this script into the agent. In the future we can remove the agent part of it and simply use the report content. Either printed to stdout or as a comment on the GitHub PR. --- .ci/generate_test_report.py | 328 ++++++++++++++++++++++++++++++++++++ .ci/monolithic-linux.sh | 8 +- .ci/monolithic-windows.sh | 8 +- 3 files changed, 340 insertions(+), 4 deletions(-) create mode 100644 .ci/generate_test_report.py diff --git a/.ci/generate_test_report.py b/.ci/generate_test_report.py new file mode 100644 index 00000000000000..f2ae116ace99af --- /dev/null +++ b/.ci/generate_test_report.py @@ -0,0 +1,328 @@ +# Script to parse many JUnit XML result files and send a report to the buildkite +# agent as an annotation. +# +# To run the unittests: +# python3 -m unittest discover -p generate_test_report.py + +import argparse +import unittest +from io import StringIO +from junitparser import JUnitXml, Failure +from textwrap import dedent +from subprocess import check_call + + +def junit_from_xml(xml): + return JUnitXml.fromfile(StringIO(xml)) + + +class TestReports(unittest.TestCase): + def test_title_only(self): + self.assertEqual(_generate_report("Foo", []), ("", None)) + + def test_no_tests_in_testsuite(self): + self.assertEqual( + _generate_report( + "Foo", + [ + junit_from_xml( + dedent( + """\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites time="0.00"> + <testsuite name="Empty" tests="0" failures="0" skipped="0" time="0.00"> + </testsuite> + </testsuites>""" + ) + ) + ], + ), + ("", None), + ) + + def test_no_failures(self): + self.assertEqual( + _generate_report( + "Foo", + [ + junit_from_xml( + dedent( + """\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites time="0.00"> + <testsuite name="Passed" tests="1" failures="0" skipped="0" time="0.00"> + <testcase classname="Bar/test_1" name="test_1" time="0.00"/> + </testsuite> + </testsuites>""" + ) + ) + ], + ), + ( + dedent( + """\ + # Foo + + * 1 test passed""" + ), + "success", + ), + ) + + def test_report_single_file_single_testsuite(self): + self.assertEqual( + _generate_report( + "Foo", + [ + junit_from_xml( + dedent( + """\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites time="8.89"> + <testsuite name="Bar" tests="4" failures="2" skipped="1" time="410.63"> + <testcase classname="Bar/test_1" name="test_1" time="0.02"/> + <testcase classname="Bar/test_2" name="test_2" time="0.02"> + <skipped message="Reason"/> + </testcase> + <testcase classname="Bar/test_3" name="test_3" time="0.02"> + <failure><![CDATA[Output goes here]]></failure> + </testcase> + <testcase classname="Bar/test_4" name="test_4" time="0.02"> + <failure><![CDATA[Other output goes here]]></failure> + </testcase> + </testsuite> + </testsuites>""" + ) + ) + ], + ), + ( + dedent( + """\ + # Foo + + * 1 test passed + * 1 test skipped + * 2 tests failed + + ## Failed tests + (click to see output) + + ### Bar + <details> + <summary>Bar/test_3/test_3</summary> + + ``` + Output goes here + ``` + </details> + <details> + <summary>Bar/test_4/test_4</summary> + + ``` + Other output goes here + ``` + </details>""" + ), + "error", + ), + ) + + MULTI_SUITE_OUTPUT = ( + dedent( + """\ + # ABC and DEF + + * 1 test passed + * 1 test skipped + * 2 tests failed + + ## Failed tests + (click to see output) + + ### ABC + <details> + <summary>ABC/test_2/test_2</summary> + + ``` + ABC/test_2 output goes here + ``` + </details> + + ### DEF + <details> + <summary>DEF/test_2/test_2</summary> + + ``` + DEF/test_2 output goes here + ``` + </details>""" + ), + "error", + ) + + def test_report_single_file_multiple_testsuites(self): + self.assertEqual( + _generate_report( + "ABC and DEF", + [ + junit_from_xml( + dedent( + """\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites time="8.89"> + <testsuite name="ABC" tests="2" failures="1" skipped="0" time="410.63"> + <testcase classname="ABC/test_1" name="test_1" time="0.02"/> + <testcase classname="ABC/test_2" name="test_2" time="0.02"> + <failure><![CDATA[ABC/test_2 output goes here]]></failure> + </testcase> + </testsuite> + <testsuite name="DEF" tests="2" failures="1" skipped="1" time="410.63"> + <testcase classname="DEF/test_1" name="test_1" time="0.02"> + <skipped message="reason"/> + </testcase> + <testcase classname="DEF/test_2" name="test_2" time="0.02"> + <failure><![CDATA[DEF/test_2 output goes here]]></failure> + </testcase> + </testsuite> + </testsuites>""" + ) + ) + ], + ), + self.MULTI_SUITE_OUTPUT, + ) + + def test_report_multiple_files_multiple_testsuites(self): + self.assertEqual( + _generate_report( + "ABC and DEF", + [ + junit_from_xml( + dedent( + """\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites time="8.89"> + <testsuite name="ABC" tests="2" failures="1" skipped="0" time="410.63"> + <testcase classname="ABC/test_1" name="test_1" time="0.02"/> + <testcase classname="ABC/test_2" name="test_2" time="0.02"> + <failure><![CDATA[ABC/test_2 output goes here]]></failure> + </testcase> + </testsuite> + </testsuites>""" + ) + ), + junit_from_xml( + dedent( + """\ + <?xml version="1.0" encoding="UTF-8"?> + <testsuites time="8.89"> + <testsuite name="DEF" tests="2" failures="1" skipped="1" time="410.63"> + <testcase classname="DEF/test_1" name="test_1" time="0.02"> + <skipped message="reason"/> + </testcase> + <testcase classname="DEF/test_2" name="test_2" time="0.02"> + <failure><![CDATA[DEF/test_2 output goes here]]></failure> + </testcase> + </testsuite> + </testsuites>""" + ) + ), + ], + ), + self.MULTI_SUITE_OUTPUT, + ) + + +def _generate_report(title, junit_objects): + style = None + + if not junit_objects: + return ("", style) + + failures = {} + tests_run = 0 + tests_skipped = 0 + tests_failed = 0 + + for results in junit_objects: + for testsuite in results: + tests_run += testsuite.tests + tests_skipped += testsuite.skipped + tests_failed += testsuite.failures + + for test in testsuite: + if ( + not test.is_passed + and test.result + and isinstance(test.result[0], Failure) + ): + if failures.get(testsuite.name) is None: + failures[testsuite.name] = [] + failures[testsuite.name].append( + (test.classname + "/" + test.name, test.result[0].text) + ) + + if not tests_run: + return ("", style) + + style = "error" if tests_failed else "success" + report = [f"# {title}", ""] + + tests_passed = tests_run - tests_skipped - tests_failed + + def plural(num_tests): + return "test" if num_tests == 1 else "tests" + + if tests_passed: + report.append(f"* {tests_passed} {plural(tests_passed)} passed") + if tests_skipped: + report.append(f"* {tests_skipped} {plural(tests_skipped)} skipped") + if tests_failed: + report.append(f"* {tests_failed} {plural(tests_failed)} failed") + + if failures: + report.extend(["", "## Failed tests", "(click to see output)"]) + for testsuite_name, failures in failures.items(): + report.extend(["", f"### {testsuite_name}"]) + for name, output in failures: + report.extend( + [ + "<details>", + f"<summary>{name}</summary>", + "", + "```", + output, + "```", + "</details>", + ] + ) + + return "\n".join(report), style + + +def generate_report(title, junit_files): + return _generate_report(title, [JUnitXml.fromfile(p) for p in junit_files]) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "title", help="Title of the test report, without Markdown formatting." + ) + parser.add_argument("context", help="Annotation context to write to.") + parser.add_argument("junit_files", help="Paths to JUnit report files.", nargs="*") + args = parser.parse_args() + + report, style = generate_report(args.title, args.junit_files) + check_call( + [ + "buildkite-agent", + "annotate", + "--context", + args.context, + "--style", + style, + report, + ] + ) diff --git a/.ci/monolithic-linux.sh b/.ci/monolithic-linux.sh index b78dc59432b65c..ed4c032e023786 100755 --- a/.ci/monolithic-linux.sh +++ b/.ci/monolithic-linux.sh @@ -28,11 +28,14 @@ if [[ -n "${CLEAR_CACHE:-}" ]]; then ccache --clear fi -function show-stats { +function at-exit { + python3 "${MONOREPO_ROOT}"/.ci/generate_test_report.py ":linux: Linux x64 Test Results" \ + "linux-x64-test-results" "${BUILD_DIR}"/test-results*.xml + mkdir -p artifacts ccache --print-stats > artifacts/ccache_stats.txt } -trap show-stats EXIT +trap at-exit EXIT projects="${1}" targets="${2}" @@ -40,6 +43,7 @@ targets="${2}" echo "--- cmake" pip install -q -r "${MONOREPO_ROOT}"/mlir/python/requirements.txt pip install -q -r "${MONOREPO_ROOT}"/lldb/test/requirements.txt +pip install -q junitparser==3.2.0 cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \ -D LLVM_ENABLE_PROJECTS="${projects}" \ -G Ninja \ diff --git a/.ci/monolithic-windows.sh b/.ci/monolithic-windows.sh index 91e719c52d4363..daac89b4a632d3 100755 --- a/.ci/monolithic-windows.sh +++ b/.ci/monolithic-windows.sh @@ -27,16 +27,20 @@ if [[ -n "${CLEAR_CACHE:-}" ]]; then fi sccache --zero-stats -function show-stats { +function at-exit { + python "${MONOREPO_ROOT}"/.ci/generate_test_report.py ":windows: Windows x64 Test Results" \ + "windows-x64-test-results" "${BUILD_DIR}"/test-results*.xml + mkdir -p artifacts sccache --show-stats >> artifacts/sccache_stats.txt } -trap show-stats EXIT +trap at-exit EXIT projects="${1}" targets="${2}" echo "--- cmake" +pip install junitparser==3.2.0 pip install -q -r "${MONOREPO_ROOT}"/mlir/python/requirements.txt # The CMAKE_*_LINKER_FLAGS to disable the manifest come from research >From e242decad7131811cd1b36b91382fa1bf79510cb Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Wed, 23 Oct 2024 14:35:57 +0100 Subject: [PATCH 2/3] Example changes to trigger builds --- clang/README.md | 2 ++ clang/test/Driver/cl-pch.c | 2 +- llvm/README.txt | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/clang/README.md b/clang/README.md index b98182d8a3f684..94b1e1a7a07433 100644 --- a/clang/README.md +++ b/clang/README.md @@ -23,3 +23,5 @@ If you're interested in more (including how to build Clang) it is best to read t * If you find a bug in Clang, please file it in the LLVM bug tracker: https://github.com/llvm/llvm-project/issues + +test change \ No newline at end of file diff --git a/clang/test/Driver/cl-pch.c b/clang/test/Driver/cl-pch.c index 36d83a11242dc6..1a9527458ebf4f 100644 --- a/clang/test/Driver/cl-pch.c +++ b/clang/test/Driver/cl-pch.c @@ -25,7 +25,7 @@ // CHECK-YCTP-SAME: -o // CHECK-YCTP-SAME: pchfile.pch // CHECK-YCTP-SAME: -x -// CHECK-YCTP-SAME: c++-header +// CHECK-YCTP-SAME: c++-header -- this will fail tests on windows! // Except if a later /TC changes it back. // RUN: %clang_cl -Werror /Yc%S/Inputs/pchfile.h /FI%S/Inputs/pchfile.h /c /Fo%t/pchfile.obj /Fp%t/pchfile.pch -v -- %s 2>&1 \ diff --git a/llvm/README.txt b/llvm/README.txt index b9b71a3b6daff1..ba60b8ffdd072c 100644 --- a/llvm/README.txt +++ b/llvm/README.txt @@ -15,3 +15,5 @@ documentation setup. If you are writing a package for LLVM, see docs/Packaging.rst for our suggestions. + +test change \ No newline at end of file >From ffe6c67a2d71ef931dbf4d058f3908b829a5cd87 Mon Sep 17 00:00:00 2001 From: David Spickett <david.spick...@linaro.org> Date: Fri, 25 Oct 2024 15:09:51 +0000 Subject: [PATCH 3/3] WIP --- .ci/fifo_read.py | 14 ++++++++++++++ .ci/monolithic-linux.sh | 9 +++++++++ .ci/monolithic-windows.sh | 9 +++++++++ 3 files changed, 32 insertions(+) create mode 100755 .ci/fifo_read.py diff --git a/.ci/fifo_read.py b/.ci/fifo_read.py new file mode 100755 index 00000000000000..5ed31bf70d72cf --- /dev/null +++ b/.ci/fifo_read.py @@ -0,0 +1,14 @@ +import os +import sys +import tempfile + +if __name__ == "__main__": + while True: + fifo_path = sys.argv[1] + with open(sys.argv[1], 'rb') as fifo: + filename, ext = os.path.splitext(os.path.basename(fifo_path)) + fd, _ = tempfile.mkstemp( + suffix=ext, prefix=f"{filename}.", dir=os.path.dirname(fifo_path)) + with os.fdopen(fd, 'wb') as out: + out.write(fifo.read()) + diff --git a/.ci/monolithic-linux.sh b/.ci/monolithic-linux.sh index ed4c032e023786..d1910f04081c01 100755 --- a/.ci/monolithic-linux.sh +++ b/.ci/monolithic-linux.sh @@ -20,6 +20,7 @@ MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}" BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build}" INSTALL_DIR="${BUILD_DIR}/install" rm -rf "${BUILD_DIR}" +mkdir -p ${BUILD_DIR} ccache --zero-stats @@ -28,7 +29,15 @@ if [[ -n "${CLEAR_CACHE:-}" ]]; then ccache --clear fi +# This will capture each time lit results out results and put each in its own file. +test_result_path="${BUILD_DIR}"/test-results.xml +mkfifo $test_result_path +python3 "${MONOREPO_ROOT}"/.ci/fifo_read.py $test_result_path & +fifo_pid=$! + function at-exit { + kill $fifo_pid + rm $test_result_path python3 "${MONOREPO_ROOT}"/.ci/generate_test_report.py ":linux: Linux x64 Test Results" \ "linux-x64-test-results" "${BUILD_DIR}"/test-results*.xml diff --git a/.ci/monolithic-windows.sh b/.ci/monolithic-windows.sh index daac89b4a632d3..bd5330d579644b 100755 --- a/.ci/monolithic-windows.sh +++ b/.ci/monolithic-windows.sh @@ -20,14 +20,23 @@ MONOREPO_ROOT="${MONOREPO_ROOT:="$(git rev-parse --show-toplevel)"}" BUILD_DIR="${BUILD_DIR:=${MONOREPO_ROOT}/build}" rm -rf "${BUILD_DIR}" +mkdir -p ${BUILD_DIR} if [[ -n "${CLEAR_CACHE:-}" ]]; then echo "clearing sccache" rm -rf "$SCCACHE_DIR" fi +# This will capture each time lit results out results and put each in its own file. +test_result_path="${BUILD_DIR}"/test-results.xml +mkfifo $test_result_path +python "${MONOREPO_ROOT}"/.ci/fifo_read.py $test_result_path & +fifo_pid=$! + sccache --zero-stats function at-exit { + kill $fifo_pid + rm $test_result_path python "${MONOREPO_ROOT}"/.ci/generate_test_report.py ":windows: Windows x64 Test Results" \ "windows-x64-test-results" "${BUILD_DIR}"/test-results*.xml _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits