mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-15 22:13:24 +09:00
[CI] (Test): annotate failed ctest entries with their failure lines, since the run logs need a signed-in reader
This commit is contained in:
@@ -413,6 +413,12 @@ jobs:
|
||||
--discovery "${RUNNER_TEMP}/primary-inproc-lane.json" \
|
||||
--junit "${RUNNER_TEMP}/primary-inproc.xml" --output "${RUNNER_TEMP}/primary-inproc-proof.json"
|
||||
|
||||
# The run log needs a signed-in reader; annotations do not. Name every failed ctest entry,
|
||||
# with the lines of its output that say why, where anyone reading the run can see them.
|
||||
- name: Annotate failed entries
|
||||
if: failure()
|
||||
run: python3 scripts/ci/junit_annotate.py "${RUNNER_TEMP}"
|
||||
|
||||
- name: Upload integration lane evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -1169,6 +1175,12 @@ jobs:
|
||||
# arm proof read; verify-split-logs/ is whatever the control steps left behind, which for
|
||||
# twelve entries is a deliberately red re-run under the same name. Uploading only the
|
||||
# second - which is what this step did before the copy above existed - loses the first.
|
||||
# The run log needs a signed-in reader; annotations do not. Name every failed ctest entry,
|
||||
# with the lines of its output that say why, where anyone reading the run can see them.
|
||||
- name: Annotate failed entries
|
||||
if: failure()
|
||||
run: python3 scripts/ci/junit_annotate.py "${RUNNER_TEMP}"
|
||||
|
||||
- name: Upload verify-split lane logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -1481,7 +1493,7 @@ jobs:
|
||||
working-directory: build-split
|
||||
env:
|
||||
MOBILEGL_ITEST_REQUIRE_GPU: "1"
|
||||
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)"
|
||||
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)" --output-junit "${RUNNER_TEMP}/unit-split.xml"
|
||||
|
||||
# P7 wave 2-F, PH-7 (1). THE TCP SUPERVISOR LANE NEEDS flatc, AND CI HAD NONE.
|
||||
#
|
||||
@@ -1648,6 +1660,12 @@ jobs:
|
||||
CONTROL_TMPDIR: ${{ runner.temp }}
|
||||
run: bash "${GITHUB_WORKSPACE}/scripts/ci/split_negative_controls.sh"
|
||||
|
||||
# The run log needs a signed-in reader; annotations do not. Name every failed ctest entry,
|
||||
# with the lines of its output that say why, where anyone reading the run can see them.
|
||||
- name: Annotate failed entries
|
||||
if: failure()
|
||||
run: python3 scripts/ci/junit_annotate.py "${RUNNER_TEMP}"
|
||||
|
||||
- name: Upload split lane logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -1737,7 +1755,7 @@ jobs:
|
||||
env:
|
||||
MOBILEGL_ITEST_REQUIRE_GPU: "1"
|
||||
MOBILEGL_IPC_STRICT_ERRORS: "1"
|
||||
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)"
|
||||
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)" --output-junit "${RUNNER_TEMP}/unit-strict.xml"
|
||||
|
||||
# ---- P5e (ra), MG_Remote/CONTRACT-P5E.md §7: ONE STEP, TWO ARMS, AND THE TREE PICKS ----
|
||||
#
|
||||
@@ -2246,6 +2264,12 @@ jobs:
|
||||
slp.require_green(lane, sys.argv[3], names)
|
||||
PY
|
||||
|
||||
# The run log needs a signed-in reader; annotations do not. Name every failed ctest entry,
|
||||
# with the lines of its output that say why, where anyone reading the run can see them.
|
||||
- name: Annotate failed entries
|
||||
if: failure()
|
||||
run: python3 scripts/ci/junit_annotate.py "${RUNNER_TEMP}"
|
||||
|
||||
- name: Upload strict lane logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -2751,7 +2775,11 @@ jobs:
|
||||
&& [ '${{ matrix.case }}' = 'improved-transparency-minecraft-26.3' ]; then
|
||||
export MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE=1
|
||||
fi
|
||||
ctest -V --no-tests=error -R '^MobileGLTraceReplay\.${{ matrix.case }}\.${{ matrix.backend }}$'
|
||||
ctest -V --no-tests=error -R '^MobileGLTraceReplay\.${{ matrix.case }}\.${{ matrix.backend }}$' --output-junit "${RUNNER_TEMP}/retrace.xml"
|
||||
|
||||
- name: Annotate failed entries
|
||||
if: failure()
|
||||
run: python3 scripts/ci/junit_annotate.py --excerpts 1 "${RUNNER_TEMP}"
|
||||
|
||||
- name: Upload core dumps
|
||||
if: failure()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Turn the failed entries of ctest --output-junit files into GitHub error annotations.
|
||||
|
||||
A red lane's log needs a signed-in reader, but its annotations do not, so the names of the
|
||||
failed entries - and the lines of their output that say why - are what a reader of the run
|
||||
page (or of the check-runs API) can see without downloading anything. GitHub keeps at most
|
||||
ten error annotations per step, so this prints one summary naming every failed entry and then
|
||||
one excerpt for each of the first few.
|
||||
|
||||
Usage: junit_annotate.py [--excerpts N] FILE_OR_DIR... (a directory means every *.xml in it)
|
||||
Exit status is always 0: this reports a failure, it is never one.
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
|
||||
INTERESTING = re.compile(
|
||||
r"Failure|FAILED|Failed|error|Error|Fatal|Refuse|Expected|Which is|Value of|Actual|"
|
||||
r"Abort|abort|Segmentation|SIGSEGV|signal|timed out|Timeout|assert", re.ASCII)
|
||||
|
||||
|
||||
def escape(text):
|
||||
return text.replace("%", "%25").replace("\r", "").replace("\n", "%0A")
|
||||
|
||||
|
||||
def failed_cases(path):
|
||||
try:
|
||||
root = ET.parse(path).getroot()
|
||||
except (ET.ParseError, OSError) as error:
|
||||
print(f"junit_annotate: {path}: {error}", file=sys.stderr)
|
||||
return []
|
||||
out = []
|
||||
for case in root.iter("testcase"):
|
||||
status = case.get("status", "")
|
||||
failure = case.find("failure")
|
||||
error = case.find("error")
|
||||
if status not in ("fail", "failed") and failure is None and error is None:
|
||||
continue
|
||||
text = case.findtext("system-out") or ""
|
||||
if failure is not None:
|
||||
text = (failure.get("message") or "") + "\n" + (failure.text or "") + "\n" + text
|
||||
out.append((case.get("name", "?"), text))
|
||||
return out
|
||||
|
||||
|
||||
def excerpt(text, limit=40):
|
||||
lines = [line.rstrip() for line in text.splitlines() if line.strip()]
|
||||
keep = set()
|
||||
for index, line in enumerate(lines):
|
||||
if INTERESTING.search(line):
|
||||
keep.update(range(index, min(index + 4, len(lines))))
|
||||
picked = [lines[index] for index in sorted(keep)] or lines[-limit:]
|
||||
return "\n".join(line[:400] for line in picked[:limit])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--excerpts", type=int, default=8)
|
||||
parser.add_argument("paths", nargs="+")
|
||||
args = parser.parse_args()
|
||||
files = []
|
||||
for name in args.paths:
|
||||
path = Path(name)
|
||||
files.extend(sorted(path.glob("*.xml")) if path.is_dir() else [path])
|
||||
failed = []
|
||||
for path in files:
|
||||
if path.is_file():
|
||||
failed.extend((path.name, name, text) for name, text in failed_cases(path))
|
||||
if not failed:
|
||||
print("junit_annotate: no failed entries in " + ", ".join(str(f) for f in files))
|
||||
return 0
|
||||
names = "\n".join(f"{name} ({source})" for source, name, _ in failed)
|
||||
print(f"::error title={len(failed)} failed ctest entr(y/ies)::{escape(names[:60000])}")
|
||||
for source, name, text in failed[:max(args.excerpts, 0)]:
|
||||
print(f"::error title={name[:200]}::{escape(excerpt(text))}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user