🤔 Background
bashunit::reports::__xml_escape handles & < > " ' and is applied to the test name and the failure message. The path-derived attributes are interpolated raw:
<testsuite name="..."><testcase classname="..." file="...">
A test file whose name contains " therefore closes the attribute early:
<testsuitename="tests/say "hi"_test.sh"tests="1" ...>
ExpatError: not well-formed (invalid token): line 8, column 30
& and < in a path break it the same way. Affects both --report-junit and --output junit.
Filenames may legally contain ", & and < on both macOS and Linux, and the path also feeds classname, which is what CI test reporters group by.
💡 Proposal
Apply the existing escaper at the three path-derived sites. Nothing else changes — the name and message paths already go through it.
💡 Found by
Extending the report-writer sweep to hostile paths rather than hostile messages. --list --list-format json and --report-json both handled the same paths correctly (tests/say \"hi\"_test.sh, tests/back\\slash_test.sh come out properly escaped and parse); JUnit was the one that did not.
This is the third instance of one pattern: #1307 (GHA properties), #1311 (Cobertura attributes) and now JUnit — an escaper exists and is correct, but does not reach every interpolation site.
🤔 Background
bashunit::reports::__xml_escapehandles& < > " 'and is applied to the test name and the failure message. The path-derived attributes are interpolated raw:<testsuite name="..."><testcase classname="..." file="...">A test file whose name contains
"therefore closes the attribute early:&and<in a path break it the same way. Affects both--report-junitand--output junit.Filenames may legally contain
",&and<on both macOS and Linux, and the path also feedsclassname, which is what CI test reporters group by.💡 Proposal
Apply the existing escaper at the three path-derived sites. Nothing else changes — the name and message paths already go through it.
💡 Found by
Extending the report-writer sweep to hostile paths rather than hostile messages.
--list --list-format jsonand--report-jsonboth handled the same paths correctly (tests/say \"hi\"_test.sh,tests/back\\slash_test.shcome out properly escaped and parse); JUnit was the one that did not.This is the third instance of one pattern: #1307 (GHA properties), #1311 (Cobertura attributes) and now JUnit — an escaper exists and is correct, but does not reach every interpolation site.