🤔 Background
--coverage-report-cobertura writes XML, and every attribute it emits carries a path. They go in raw, so a file whose name holds &, < or > produces a document that does not parse:
$ bashunit --coverage --coverage-report-cobertura cob.xml tests/
$ grep -o 'filename="[^"]*"' cob.xmlfilename="src/a&b.sh"
$ python3 -c 'import sys,xml.dom.minidom as m; m.parse("cob.xml")'ExpatError: not well-formed (invalid token): line 9, column 27Cobertura is what the CI platforms with built-in coverage UIs consume — GitLab MR visualisation, Azure PublishCodeCoverageResults, the Jenkins Coverage plugin — so an unparseable report means the coverage view silently shows nothing, or the publish step errors.
Four sites interpolate a path: filename=, class name=, package name= and the <source> body.
💡 Cause
The HTML coverage report was given an escaper in #1254 (bashunit::coverage::html_escape). src/coverage/report_cobertura.sh never got one — it has only a rate formatter.
The escaper has to go through sed, not ${text//&/&}: Bash 5.2 reads a bare & in the REPLACEMENT as "the matched text", so that spelling yields <lt; there while producing < on 3.2, and escaping it for 5.2 emits a literal backslash on 3.2 (#1096). A case guard keeps it fork-free for the paths that need nothing, which is nearly all of them, and the result goes into a return slot rather than a $(...) capture — that would fork once per file regardless.
💡 Found by
The report-writer sweep from #1305/#1307/#1309, continued into the coverage writers.
🤔 Background
--coverage-report-coberturawrites XML, and every attribute it emits carries a path. They go in raw, so a file whose name holds&,<or>produces a document that does not parse:Cobertura is what the CI platforms with built-in coverage UIs consume — GitLab MR visualisation, Azure
PublishCodeCoverageResults, the Jenkins Coverage plugin — so an unparseable report means the coverage view silently shows nothing, or the publish step errors.Four sites interpolate a path:
filename=,class name=,package name=and the<source>body.💡 Cause
The HTML coverage report was given an escaper in #1254 (
bashunit::coverage::html_escape).src/coverage/report_cobertura.shnever got one — it has only a rate formatter.The escaper has to go through
sed, not${text//&/&}: Bash 5.2 reads a bare&in the REPLACEMENT as "the matched text", so that spelling yields<lt;there while producing<on 3.2, and escaping it for 5.2 emits a literal backslash on 3.2 (#1096). Acaseguard keeps it fork-free for the paths that need nothing, which is nearly all of them, and the result goes into a return slot rather than a$(...)capture — that would fork once per file regardless.💡 Found by
The report-writer sweep from #1305/#1307/#1309, continued into the coverage writers.