adoroszlai commented on code in PR #10857: URL: https://github.com/apache/ozone/pull/10857#discussion_r3672073177
########## dev-support/ci/junit_summary.py: ########## @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Parse Maven Surefire/Failsafe JUnit XML reports and print a Markdown test summary. + +Adapted from Apache Kafka's .github/scripts/junit.py (summary format), reworked for Maven Surefire XML. + +Intended for GitHub Actions: junit_summary.py [--path DIR] [--quarantine] >> "$GITHUB_STEP_SUMMARY" +Optional env: JUNIT_REPORT_URL (link to the archived test report artifact). +Prints nothing and exits 0 when no reports are found. Always exits 0. +""" + +import argparse +import dataclasses +import html +import os +import sys +import xml.etree.ElementTree as ET +from glob import glob + +PASSED = "PASSED ✅" +FAILED = "FAILED ❌" +FLAKY = "FLAKY ⚠️" +SKIPPED = "SKIPPED 🙈" +QUARANTINED = "QUARANTINED 😷" + +FAIL_TAGS = frozenset(("failure", "error")) +FLAKY_TAGS = frozenset(("flakyFailure", "flakyError")) +MESSAGE_LIMIT = 300 + + [email protected] +class TestCase: + module: str + class_name: str + test_name: str + time: float + status: str # passed | failed | flaky | skipped + message: str = "" + + +def module_name(xml_path): + # Works for both layouts: <module>/target/surefire-reports/TEST-*.xml (normal run) + # and target/<check>/<module-path>/TEST-*.xml (failed tests moved by _mvn_unit_report.sh). + parts = os.path.normpath(xml_path).split(os.sep)[:-1] + while parts and parts[-1] in ("surefire-reports", "failsafe-reports", "target"): + parts.pop() + return parts[-1] if parts else "-" + + +def clean_message(elem): + message = elem.get("message") or (elem.text or "") + message = " ".join(message.split()) + if len(message) > MESSAGE_LIMIT: + message = message[:MESSAGE_LIMIT] + "..." + return message + + +def parse_report(xml_path): + module = module_name(xml_path) + cases = [] + for testcase in ET.parse(xml_path).getroot().iter("testcase"): + failure = next((c for c in testcase if c.tag in FAIL_TAGS), None) + flaky = next((c for c in testcase if c.tag in FLAKY_TAGS), None) + skipped = next((c for c in testcase if c.tag == "skipped"), None) + status, message = "passed", "" + if failure is not None: + status, message = "failed", clean_message(failure) + elif flaky is not None: + status, message = "flaky", clean_message(flaky) + elif skipped is not None: + status = "skipped" + cases.append(TestCase(module, testcase.get("classname") or "", testcase.get("name") or "", + float(testcase.get("time") or 0), status, message)) + return cases + + +def format_time(seconds): + minutes, secs = divmod(int(seconds), 60) + hours, minutes = divmod(minutes, 60) + if hours: + return "%dh%dm%ds" % (hours, minutes, secs) + if minutes: + return "%dm%ds" % (minutes, secs) + return "%ds" % secs + + +def cell(text): + return html.escape(" ".join(str(text).split())).replace("|", "\\|") + + +def render_table(title, header, rows): + lines = ["<details><summary><b>%s (%d)</b></summary>" % (title, len(rows)), ""] + lines.append("|" + "|".join(header) + "|") + lines.append("|" + "|".join("---" for _ in header) + "|") + lines.extend("|" + "|".join(cell(value) for value in row) + "|" for row in rows) + lines.extend(["", "</details>", ""]) + return lines + + +def render_summary(cases, quarantine=False): + def select(status): + return [c for c in cases if c.status == status] + + def full_name(case): + return "%s.%s" % (case.class_name, case.test_name) + + passed, failed, flaky, skipped = select("passed"), select("failed"), select("flaky"), select("skipped") + lines = ["## Test Summary", ""] + # sum of per-test times, not wall clock (parallel forks make wall clock much shorter) + lines.append("%d tests run in %s (total test time): %d %s, %d %s, %d %s, %d %s." % ( Review Comment: I would change the test summary from: > 3646 tests run in 32m0s (total test time): 3597 PASSED ✅, 0 FAILED ❌, 0 FLAKY ⚠️, 49 SKIPPED 🙈. to: > 3646 tests run in 32m0s (total test time): > - ✅ 3597 PASSED > - ❌ 0 FAILED > - ⚠️ 0 FLAKY > - 🙈 49 SKIPPED Also consider omitting items with count = 0. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
