Share a GoogleTest report

C++ test output has an unusual sharing problem: there is no HTML report to share. GoogleTest writes to the console and to XML, and that is it.

./my_tests --gtest_output=xml:results.xml

The XML is the JUnit schema, which is the good news — it means the whole JUnit-consuming ecosystem already reads GoogleTest output. But results.xml is not something you send a colleague, so what actually gets shared is a paste of the console tail:

[  FAILED  ] BufferTest.HandlesUnalignedRead
/src/buffer_test.cc:214: Failure
Expected equality of these values:
  buf.read_u32(1)
    Which is: 3735928559
  expected
    Which is: 3405691582

Which is fine for one failure and useless for nineteen, and which arrives with no way to see what else failed, what passed, or how long the run took.

Convert the XML

./my_tests --gtest_output=xml:results.xml

# either of these
npx xunit-viewer --results=results.xml --output=report.html
pip install junit2html && junit2html results.xml report.html

Both produce a single self-contained page: the suite tree, pass/fail/skip counts, per-test durations, and the assertion message expanded under each failure.

For a sharded run, merge first:

for i in 0 1 2 3; do
  GTEST_TOTAL_SHARDS=4 GTEST_SHARD_INDEX=$i \
    ./my_tests --gtest_output="xml:results-$i.xml" &
done
wait

npx xunit-viewer --results=. --output=report.html

Publish it

Drag report.html into the app, or POST it:

curl -fsS -X POST "https://commareports.com/api/v1/reports" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html report.html \
        --arg t "Unit tests — libcore @ $(git rev-parse --short HEAD)" \
        '{title: $t, html: $html}')"

Report HTML renders with scripts enabled inside a sandboxed iframe (allow-scripts, no allow-same-origin), so the filter controls and the expandable failure details in the generated report keep working.

From CI

ctest --output-on-failure || TESTS_FAILED=1
npx xunit-viewer --results=build/test-results --output=report.html

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html report.html \
        --arg t "Unit tests — libcore @ $GITHUB_SHA" '{title: $t, html: $html}')"

exit "${TESTS_FAILED:-0}"

Publish before failing the job, with if: always() (GitHub Actions), when: always (GitLab, CircleCI) or post { always { … } } (Jenkins). A green run's report is a formality; a red run's report is the whole point, and it is the one a build that exits early never produces.

PATCHing one report id keeps a single URL per suite and appends a revision per run — so "is this the flake from Tuesday?" is a diff instead of a search through expired artifacts.

Platform matrices

C++ suites usually run across a matrix — GCC and Clang, Linux and macOS, x86-64 and ARM — and the interesting failures are the ones that only appear on one cell. Use one report id per cell:

REPORT_ID="${REPORT_IDS[$MATRIX_KEY]}"

so each configuration has a stable URL and its own revision history. "Fails on ARM only" then has a link rather than a claim.

What review adds

  • Anchored threads on the failing assertion — see commenting on HTML.
  • Revisions, so a flake's history is visible rather than remembered.
  • Access per report — private, team, domain-gated or named reviewers. See the sharing model.

Limits

  • Entry HTML: 5 MB. A suite with tens of thousands of test cases can approach it — publish the failures-and-summary view rather than every passing case.
  • 60 requests/minute per token.

Try it

Comma is free — unlimited reports, unlimited commenters, unlimited revision history.

Publish a test report →

Related