diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 78a11de..dbde1cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,8 @@ on: permissions: contents: read + checks: write + pull-requests: write jobs: build: @@ -24,3 +26,10 @@ jobs: - name: test working-directory: tests 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 diff --git a/.gitignore b/.gitignore index 3b611fd..f6139fd 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,4 @@ tests/test_utils tests/test_random tests/test_interpreter tests/test_class +tests/test-results/ diff --git a/tests/Makefile.in b/tests/Makefile.in index c9daf3f..f2c6286 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -66,38 +66,28 @@ test_class: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \ $(COMPILE) -o $@ $^ $(LIBS) # --------------------------------------------------------------------------- -# Run all tests +# Run all tests and produce JUnit XML files in test-results/ # --------------------------------------------------------------------------- test: $(TESTS) @echo "==========================================" @echo "Running tbaMUD unit tests" @echo "==========================================" + @mkdir -p test-results @status=0; \ - if ./test_utils; then \ - echo "[PASS] test_utils"; \ - else \ - echo "[FAIL] test_utils"; \ - 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; \ - fi; \ + for t in $(TESTS); do \ + ./$$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 \ + echo "[FAIL] $$t"; \ + status=1; \ + fi; \ + done; \ exit $$status clean: rm -f $(TESTS) + rm -rf test-results diff --git a/tests/unity_to_junit.py b/tests/unity_to_junit.py new file mode 100644 index 0000000..be5ee01 --- /dev/null +++ b/tests/unity_to_junit.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Convert Unity test-runner output to JUnit XML. + +Usage: + ./test_binary | python3 unity_to_junit.py + +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]} ", 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()