Skip to content

Fail loudly when junit.xml is missing, empty, or unusable - #339

Open
ekamran wants to merge 1 commit into
WordPress:masterfrom
ekamran:fix/311-junit-fail-loud
Open

Fail loudly when junit.xml is missing, empty, or unusable#339
ekamran wants to merge 1 commit into
WordPress:masterfrom
ekamran:fix/311-junit-fail-loud

Conversation

@ekamran

@ekamran ekamran commented Aug 31, 2026

Copy link
Copy Markdown

Fixes #311.

process_junit_xml() had several weak failure paths: empty input returned an empty string, which the reporting API only rejected much later with a confusing JSON validation error and a wasted upload; valid but unusable XML, such as an empty testsuites element or a root level testsuite, produced a payload with empty counts that uploaded cleanly and rendered with no diagnostics; a payload carrying a test count without failure and error counts casts to zero downstream and displays as Passed; and invalid XML was not checked before later property and xpath access, producing a fatal error with no explanation of the cause. That last case is how reporting broke for every host in April 2026 (#310), when email test data containing raw invalid UTF-8 bytes made junit.xml unparseable. In every case the host saw either a clean exit or an unexplained crash, so nothing pointed at the real problem.

With this change, report.php stops with a clear error when junit.xml does not exist or is not readable, and process_junit_xml() stops when the XML is empty, cannot be parsed, or parses without usable result counts. The unparseable case includes the first libxml parser error and line number, since that is the hardest one to diagnose from a host. The libxml error handler state is restored either way.

The parsed counts guard requires the tests, failures, and errors attributes together, because a payload with a test count but missing failure and error counts casts to zero downstream and would display as Passed. A root level testsuite element, a legitimate JUnit shape from producers other than PHPUnit, now parses to correct counts and failure details where it previously produced empty strings. A junit file that genuinely reports zero tests still passes through unchanged, and the reporter already displays that as Errored rather than Passed.

Verified with a local battery rather than CI, because this repository's workflow runs report.php with continue-on-error, so CI only exercises the happy path. The battery: valid PHPUnit-shaped junit producing byte-identical output to master, empty file, an attribute poisoned with a raw 0x80 byte matching the April incident, XML with no testsuite, missing failures or errors attributes, tests="0", a root testsuite file, and full report.php runs for the missing, unreadable, poisoned, and valid cases with exit codes checked. Each failure fixture was also run through master's code to confirm the described before behavior.

One related gap stays out of scope: in the wordpress-develop reporting workflow, later small PHPUnit invocations overwrite the same junit.xml target, so the uploaded result can reflect only the final group. That is core workflow territory and worth its own issue.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code and Codex
Used for: Investigation, implementation review, edge-case testing, and PR wording. I reviewed the reasoning and test results, and I take responsibility for the contribution.

process_junit_xml() had several weak failure paths: empty input
returned an empty string, which the reporting API only rejected much
later with a confusing JSON validation error and a wasted upload;
valid but unusable XML, such as an empty testsuites element or a root
level testsuite, produced a payload with empty counts that uploaded
cleanly and rendered with no diagnostics; a payload carrying a test
count without failure and error counts casts to zero downstream and
displays as Passed; and invalid XML was not checked before later
property and xpath access, producing a fatal error with no explanation
of the cause. That last case is how reporting broke for every host in
April 2026 (WordPress#310), when email test data containing raw invalid UTF-8
bytes made junit.xml unparseable. In every case the host saw either a
clean exit or an unexplained crash, so nothing pointed at the real
problem. This is issue WordPress#311.

report.php now stops with a clear error when junit.xml is missing or
unreadable, and process_junit_xml() stops when the XML is empty, cannot
be parsed, or parses without the tests, failures, and errors counts
together. The unparseable case includes the first libxml parser error
and line number, since that is the hardest one to diagnose from a host.
The libxml error handler state is restored either way.

A root level testsuite element, a legitimate JUnit shape from producers
other than PHPUnit, now parses to correct counts and failure details
where it previously produced empty strings. A junit file that genuinely
reports zero tests still passes through unchanged, and the reporter
displays that as Errored rather than Passed.

Also removes an unreachable duplicated return statement.

Fixes WordPress#311.
@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: ekamran <ekamran@git.wordpress.org>
Co-authored-by: jazzsequence <jazzs3quence@git.wordpress.org>
Co-authored-by: kittenkamala <amykamala@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@jazzsequence

Copy link
Copy Markdown
Contributor

@ekamran Did you use AI to assist this PR? If so, our new policy is that anything that has had AI assistance be disclosed.

On the CI point -- I removed continue-on-error from the report step in Pantheon's fork and left it only on the test step. A failed upload means nothing gets reported on the test results page, but this can fail silently if we just continue -- hosts would be running the tests but no results show up and they wouldn't even realize it. That's not a great outcome. It's not really the point of this PR to make that change, but it's worth calling out.

@ekamran

ekamran commented Aug 31, 2026

Copy link
Copy Markdown
Author

Yes, I used AI assistance and I have added the disclosure to the PR description.

On continue-on-error, agreed. A failed report upload should not silently pass in real host runs, because hosts may think tests are reporting when nothing reaches the results page. I kept that out of this PR because this change is focused on making bad or unusable junit.xml fail clearly before upload, but I agree it is worth tracking separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test results default to success at 0 bytes

2 participants