CI: produce JUnit XML from Unity tests for GitHub test reporting

Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/0bc0c851-36e7-4d13-a393-517477e66c73

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-22 21:41:53 +00:00
committed by GitHub
parent d810782392
commit 73bda79fe6
4 changed files with 98 additions and 25 deletions

View File

@@ -8,6 +8,8 @@ on:
permissions: permissions:
contents: read contents: read
checks: write
pull-requests: write
jobs: jobs:
build: build:
@@ -24,3 +26,10 @@ jobs:
- name: test - name: test
working-directory: tests working-directory: tests
run: make test run: make test
- name: publish test results
uses: dorny/test-reporter@v1
if: always()
with:
name: Unity Tests
path: tests/test-results/*.xml
reporter: java-junit

1
.gitignore vendored
View File

@@ -100,3 +100,4 @@ tests/test_utils
tests/test_random tests/test_random
tests/test_interpreter tests/test_interpreter
tests/test_class tests/test_class
tests/test-results/

View File

@@ -66,38 +66,28 @@ test_class: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \
$(COMPILE) -o $@ $^ $(LIBS) $(COMPILE) -o $@ $^ $(LIBS)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Run all tests # Run all tests and produce JUnit XML files in test-results/
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
test: $(TESTS) test: $(TESTS)
@echo "==========================================" @echo "=========================================="
@echo "Running tbaMUD unit tests" @echo "Running tbaMUD unit tests"
@echo "==========================================" @echo "=========================================="
@mkdir -p test-results
@status=0; \ @status=0; \
if ./test_utils; then \ for t in $(TESTS); do \
echo "[PASS] test_utils"; \ ./$$t > test-results/$$t.out 2>&1; \
rc=$$?; \
cat test-results/$$t.out; \
python3 unity_to_junit.py $$t test-results/$$t.xml < test-results/$$t.out; \
if [ $$rc -eq 0 ]; then \
echo "[PASS] $$t"; \
else \ else \
echo "[FAIL] test_utils"; \ echo "[FAIL] $$t"; \
status=1; \
fi; \
if ./test_random; then \
echo "[PASS] test_random"; \
else \
echo "[FAIL] test_random"; \
status=1; \
fi; \
if ./test_interpreter; then \
echo "[PASS] test_interpreter"; \
else \
echo "[FAIL] test_interpreter"; \
status=1; \
fi; \
if ./test_class; then \
echo "[PASS] test_class"; \
else \
echo "[FAIL] test_class"; \
status=1; \ status=1; \
fi; \ fi; \
done; \
exit $$status exit $$status
clean: clean:
rm -f $(TESTS) rm -f $(TESTS)
rm -rf test-results

73
tests/unity_to_junit.py Normal file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Convert Unity test-runner output to JUnit XML.
Usage:
./test_binary | python3 unity_to_junit.py <suite_name> <output.xml>
Unity emits one result line per test:
path/to/file.c:LINE:TEST_NAME:PASS
path/to/file.c:LINE:TEST_NAME:FAIL:message
path/to/file.c:LINE:TEST_NAME:IGNORE:message
followed by a summary line:
N Tests N Failures N Ignored
"""
import re
import sys
import xml.etree.ElementTree as ET
def parse_unity(lines):
tests = []
total = failures = ignored = 0
for line in lines:
line = line.rstrip("\n")
m = re.match(
r"^.+:\d+:([^:]+):(PASS|FAIL|IGNORE)(?::(.*))?$", line
)
if m:
name, result, message = m.group(1), m.group(2), m.group(3) or ""
tests.append((name, result, message))
continue
m2 = re.match(r"^(\d+) Tests (\d+) Failures (\d+) Ignored", line)
if m2:
total, failures, ignored = int(m2.group(1)), int(m2.group(2)), int(m2.group(3))
if not total:
total = len(tests)
failures = sum(1 for _, r, _ in tests if r == "FAIL")
ignored = sum(1 for _, r, _ in tests if r == "IGNORE")
return tests, total, failures, ignored
def build_xml(suite_name, tests, total, failures, ignored):
suite = ET.Element(
"testsuite",
name=suite_name,
tests=str(total),
failures=str(failures),
errors="0",
skipped=str(ignored),
)
for name, result, message in tests:
case = ET.SubElement(suite, "testcase", name=name, classname=suite_name)
if result == "FAIL":
f = ET.SubElement(case, "failure", message=message)
f.text = message
elif result == "IGNORE":
ET.SubElement(case, "skipped", message=message)
return ET.ElementTree(suite)
def main():
if len(sys.argv) != 3:
print(f"usage: {sys.argv[0]} <suite_name> <output.xml>", file=sys.stderr)
sys.exit(1)
suite_name, output_file = sys.argv[1], sys.argv[2]
tests, total, failures, ignored = parse_unity(sys.stdin.readlines())
tree = build_xml(suite_name, tests, total, failures, ignored)
ET.indent(tree, space=" ")
tree.write(output_file, encoding="unicode", xml_declaration=True)
if __name__ == "__main__":
main()