# Share a CTest Report — Without Standing Up a CDash

Canonical: https://commareports.com/share-ctest-report
Published: 2026-09-08

> CTest writes XML for CDash, and most teams never deploy one. Convert the results to HTML and publish to Comma for a URL with the failing tests and a comment thread.

# Share a CTest report

CTest's designed sharing story is CDash: run the tests with `-T Test` and
`-T Submit`, and results appear on a dashboard server. That works, and it means
standing up and maintaining a CDash instance — a reasonable trade for LLVM or
VTK, and disproportionate for a team of six who want to send a colleague the
list of what broke.

The alternative most teams end up with is pasting the tail of `ctest` output
into a chat window:

```
The following tests FAILED:
	 14 - buffer_unaligned_read (Failed)
	 22 - net_timeout_retry (Timeout)
Errors while running CTest
```

Which names the failures and tells you nothing about them.

## Emit JUnit XML

CMake 3.21 and later can write JUnit directly:

```bash
ctest --output-on-failure --output-junit results.xml
```

`--output-on-failure` is the important half — without it the XML records that a
test failed and not what it printed, and a C++ test's stdout is usually the
entire diagnosis.

On older CMake, use the CDash-format output and convert:

```bash
ctest -T Test --output-on-failure     # → Testing/<tag>/Test.xml
```

## Convert and publish

```bash
npx xunit-viewer --results=results.xml --output=report.html
# or
pip install junit2html && junit2html results.xml report.html
```

Drag `report.html` into [the app](https://commareports.com/), or POST it:

```bash
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 "CTest — libcore, $(git rev-parse --short HEAD)" \
        '{title: $t, html: $html}')"
```

The generated page gives you the suite tree, durations per test, and the
captured output expanded under each failure — which is the thing the console
tail leaves out.

## Timeouts deserve their own attention

CTest reports timeouts as failures, and they are the category most likely to be
a flake and most likely to be misdiagnosed from a log tail. Set the timeout
explicitly so the report distinguishes them:

```cmake
set_tests_properties(net_timeout_retry PROPERTIES TIMEOUT 30)
```

Then a timeout in the published report is a test that exceeded a limit somebody
chose, rather than one that hit CTest's default and might simply need longer on
a slow runner.

## From CI

```bash
ctest --output-on-failure --output-junit results.xml || TESTS_FAILED=1
npx xunit-viewer --results=results.xml --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 "CTest — $PRESET @ $GITHUB_SHA" '{title: $t, html: $html}')"

exit "${TESTS_FAILED:-0}"
```

Publish before you exit, and run the step with `if: always()` (GitHub Actions),
`when: always` (GitLab, CircleCI) or `post { always { … } }` (Jenkins).

Use one report id per CMake preset or toolchain. A C++ project's interesting
failures are usually configuration-specific — Debug-only, ASan-only,
MSVC-only — and a per-configuration URL makes that visible instead of leaving
someone to correlate build numbers.

## What review adds

- **Anchored threads** on the failing test — see
  [commenting on HTML](/comment-on-html).
- **Revisions**, so an intermittent timeout has a history.
- **Access per report** — private, team, domain-gated or named reviewers. See
  the [sharing model](/docs/sharing).

## Limits

- **Entry HTML: 5 MB.**
- **60 requests/minute per token.**

## Try it

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

**[Publish a test report →](https://commareports.com/)**

### Related

- [Share a GoogleTest report](/share-gtest-report) · [Share a Catch2 report](/share-catch2-report)
- [Share a JUnit report](/share-junit-report) · [Share a coverage report](/share-coverage-report)
- [Share a Doxygen doc](/share-doxygen-docs) · [Publish from CI](/docs/ci)
