chore: import upstream snapshot with attribution
This commit is contained in:
Executable
+119
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Post-processes a JUnit XML so that "expected"/environmental failures
|
||||
(e.g., permission prompts, empty MCP resources, or schema hiccups)
|
||||
are converted to <skipped/>. Leaves real failures intact.
|
||||
|
||||
Usage:
|
||||
python .github/scripts/mark_skipped.py reports/claude-nl-tests.xml
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
PATTERNS = [
|
||||
r"\bpermission\b",
|
||||
r"\bpermissions\b",
|
||||
r"\bautoApprove\b",
|
||||
r"\bapproval\b",
|
||||
r"\bdenied\b",
|
||||
r"requested\s+permissions",
|
||||
r"^MCP resources list is empty$",
|
||||
r"No MCP resources detected",
|
||||
r"aggregator.*returned\s*\[\s*\]",
|
||||
r"Unknown resource:\s*mcpforunity://",
|
||||
r"Input should be a valid dictionary.*ctx",
|
||||
r"validation error .* ctx",
|
||||
]
|
||||
|
||||
|
||||
def should_skip(msg: str) -> bool:
|
||||
if not msg:
|
||||
return False
|
||||
msg_l = msg.strip()
|
||||
for pat in PATTERNS:
|
||||
if re.search(pat, msg_l, flags=re.IGNORECASE | re.MULTILINE):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def summarize_counts(ts: ET.Element):
|
||||
tests = 0
|
||||
failures = 0
|
||||
errors = 0
|
||||
skipped = 0
|
||||
for case in ts.findall("testcase"):
|
||||
tests += 1
|
||||
if case.find("failure") is not None:
|
||||
failures += 1
|
||||
if case.find("error") is not None:
|
||||
errors += 1
|
||||
if case.find("skipped") is not None:
|
||||
skipped += 1
|
||||
return tests, failures, errors, skipped
|
||||
|
||||
|
||||
def main(path: str) -> int:
|
||||
if not os.path.exists(path):
|
||||
print(f"[mark_skipped] No JUnit at {path}; nothing to do.")
|
||||
return 0
|
||||
|
||||
try:
|
||||
tree = ET.parse(path)
|
||||
except ET.ParseError as e:
|
||||
print(f"[mark_skipped] Could not parse {path}: {e}")
|
||||
return 0
|
||||
|
||||
root = tree.getroot()
|
||||
suites = root.findall("testsuite") if root.tag == "testsuites" else [root]
|
||||
|
||||
changed = False
|
||||
for ts in suites:
|
||||
for case in list(ts.findall("testcase")):
|
||||
nodes = [n for n in list(case) if n.tag in ("failure", "error")]
|
||||
if not nodes:
|
||||
continue
|
||||
# If any node matches skip patterns, convert the whole case to skipped.
|
||||
first_match_text = None
|
||||
to_skip = False
|
||||
for n in nodes:
|
||||
msg = (n.get("message") or "") + "\n" + (n.text or "")
|
||||
if should_skip(msg):
|
||||
first_match_text = (
|
||||
n.text or "").strip() or first_match_text
|
||||
to_skip = True
|
||||
if to_skip:
|
||||
for n in nodes:
|
||||
case.remove(n)
|
||||
reason = "Marked skipped: environment/permission precondition not met"
|
||||
skip = ET.SubElement(case, "skipped")
|
||||
skip.set("message", reason)
|
||||
skip.text = first_match_text or reason
|
||||
changed = True
|
||||
# Recompute tallies per testsuite
|
||||
tests, failures, errors, skipped = summarize_counts(ts)
|
||||
ts.set("tests", str(tests))
|
||||
ts.set("failures", str(failures))
|
||||
ts.set("errors", str(errors))
|
||||
ts.set("skipped", str(skipped))
|
||||
|
||||
if changed:
|
||||
tree.write(path, encoding="utf-8", xml_declaration=True)
|
||||
print(
|
||||
f"[mark_skipped] Updated {path}: converted environmental failures to skipped.")
|
||||
else:
|
||||
print(f"[mark_skipped] No environmental failures detected in {path}.")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
target = (
|
||||
sys.argv[1]
|
||||
if len(sys.argv) > 1
|
||||
else os.environ.get("JUNIT_OUT", "reports/junit-nl-suite.xml")
|
||||
)
|
||||
raise SystemExit(main(target))
|
||||
Reference in New Issue
Block a user