Table of Contents

  1. CDash Overview
  2. CTest Dashboard Modes
  3. Configuration
  4. Submitting Results
  5. Coverage Reporting
  6. Memory Checking
  7. Setting Up CDash Server
  8. GitHub Actions Integration
  9. Conclusion & Next Steps
Back to CMake Mastery Series

Part 27: Testing Dashboards with CDash

June 4, 2026 Wasil Zafar 35 min read

Submit build, test, and coverage results to Kitware's CDash dashboards. Configure Experimental, Nightly, and Continuous testing modes with memory checking and CI/CD pipeline integration.

CDash Overview

CDash is Kitware's open-source testing dashboard that aggregates build, test, coverage, and dynamic analysis results from CTest submissions. It provides a web-based view of project health across multiple platforms, compilers, and configurations. See the CTest dashboard client documentation for the official reference.

Key Insight: CDash doesn't replace your CI system — it complements it. While GitHub Actions or GitLab CI orchestrate builds, CDash provides long-term trend analysis, cross-platform comparison, and historical regression tracking that CI dashboards lack.

Dashboard Anatomy

A CDash dashboard displays submissions organized by date and group:

CDash Data Flow
        flowchart LR
            A[Developer Machine] -->|ctest -D Experimental| D[CDash Server]
            B[Nightly CI Job] -->|ctest -D Nightly| D
            C[Commit Hook] -->|ctest -D Continuous| D
            D --> E[Build Warnings/Errors]
            D --> F[Test Pass/Fail]
            D --> G[Coverage %]
            D --> H[Memory Leaks]
            E --> I[Web Dashboard]
            F --> I
            G --> I
            H --> I
    

CTest Dashboard Modes

Experimental Mode

Experimental submissions are ad-hoc — developers run them locally to check their changes before committing. They appear in a separate group on the dashboard and don't affect the project's health indicators.

# Quick experimental submission from build directory
cd build
ctest -D Experimental

# This performs: start → configure → build → test → submit
# All in one command with default settings

Nightly Mode

Nightly builds run at a scheduled time (default 2:00 AM project time), check out the latest code, and submit comprehensive results. They form the baseline for project health.

# Nightly submission — typically from a scheduled CI job
ctest -D Nightly

# The nightly start time is configured in CTestConfig.cmake
# CTest updates source to the nightly timestamp before building

Continuous Mode

# Continuous mode — triggered by commits
ctest -D Continuous

# Only builds/tests if source has changed since last submission
# Efficient for post-commit hooks or frequent polling

Configuration

CTestConfig.cmake

# CTestConfig.cmake — placed in project root alongside CMakeLists.txt
set(CTEST_PROJECT_NAME "MyProject")
set(CTEST_NIGHTLY_START_TIME "02:00:00 UTC")

# CDash server connection
set(CTEST_DROP_METHOD "https")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=MyProject")
set(CTEST_DROP_SITE_CDASH TRUE)

# Optional: authentication token for private dashboards
set(CTEST_AUTH_TOKEN "$ENV{CDASH_AUTH_TOKEN}")

CTest Scripts

For fine-grained control, use CTest scripting mode with ctest -S script.cmake:

# dashboard.cmake — full CTest dashboard script
set(CTEST_SOURCE_DIRECTORY "/path/to/source")
set(CTEST_BINARY_DIRECTORY "/path/to/build")
set(CTEST_CMAKE_GENERATOR "Ninja")
set(CTEST_BUILD_CONFIGURATION "Release")

# Configure build name for dashboard identification
set(CTEST_SITE "github-actions-ubuntu")
set(CTEST_BUILD_NAME "Linux-GCC12-Release")

# Execute the dashboard steps
ctest_start("Nightly")
ctest_update()
ctest_configure()
ctest_build()
ctest_test(PARALLEL_LEVEL 8)
ctest_coverage()
ctest_memcheck()
ctest_submit()
Lab Exercise Local CDash Submission

Objective: Submit your first test results to a CDash dashboard.

Use the public CDash instance at https://open.cdash.org to create a test project. Add CTestConfig.cmake to your project, add a few tests with add_test(), then run ctest -D Experimental. Verify your submission appears on the dashboard within seconds.

CDash CTest dashboard

Submitting Results

Each ctest_* command generates XML files in build/Testing/. The ctest_submit() command uploads them to CDash. You can submit partial results — for example, build + test without coverage:

# Selective submission — only build and test results
ctest_start("Experimental")
ctest_configure()
ctest_build(NUMBER_ERRORS num_errors NUMBER_WARNINGS num_warnings)
ctest_test(RETURN_VALUE test_result)

# Submit only specific parts
ctest_submit(PARTS Configure Build Test)

# Check results in script
if(num_errors GREATER 0)
    message(WARNING "Build had ${num_errors} errors")
endif()
if(NOT test_result EQUAL 0)
    message(WARNING "Some tests failed")
endif()
# Submit with retry logic for unreliable networks
ctest -D Experimental --submit-index 3

# View what would be submitted without actually sending
ctest -D Experimental --no-submit

# Submit previously generated results
ctest -D ExperimentalSubmit

Coverage Reporting

CDash displays line-by-line coverage data. Configure your project for coverage collection with gcov or llvm-cov, then ctest_coverage() gathers and submits the results.

# CMakeLists.txt — enable coverage flags
option(ENABLE_COVERAGE "Enable code coverage" OFF)

if(ENABLE_COVERAGE)
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
        add_compile_options(--coverage -O0 -g)
        add_link_options(--coverage)
    endif()
endif()

add_executable(myapp main.cpp utils.cpp)
add_test(NAME unit_tests COMMAND myapp --test)
# coverage-dashboard.cmake — CTest script with coverage
set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}")
set(CTEST_BINARY_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/build-cov")
set(CTEST_CMAKE_GENERATOR "Ninja")
set(CTEST_BUILD_CONFIGURATION "Debug")
set(CTEST_COVERAGE_COMMAND "gcov")

# Configure with coverage enabled
set(CTEST_CONFIGURE_OPTIONS "-DENABLE_COVERAGE=ON")

ctest_start("Experimental")
ctest_configure()
ctest_build()
ctest_test()
ctest_coverage()
ctest_submit()
Tip: For LLVM-based coverage (Clang), set CTEST_COVERAGE_COMMAND to llvm-cov and CTEST_COVERAGE_EXTRA_FLAGS to "gcov" for gcov-compatible output that CDash understands.

Memory Checking

CDash tracks memory errors (leaks, invalid reads, uninitialized values) from tools like Valgrind, AddressSanitizer, and Dr. Memory. The ctest_memcheck() command runs tests under a memory checker and uploads results.

# memcheck-dashboard.cmake — Valgrind memory checking
set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}")
set(CTEST_BINARY_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/build-memcheck")
set(CTEST_CMAKE_GENERATOR "Ninja")
set(CTEST_BUILD_CONFIGURATION "Debug")

# Valgrind configuration
set(CTEST_MEMORYCHECK_COMMAND "/usr/bin/valgrind")
set(CTEST_MEMORYCHECK_COMMAND_OPTIONS
    "--leak-check=full --show-leak-kinds=all --track-origins=yes")
set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE
    "${CTEST_SOURCE_DIRECTORY}/valgrind.supp")

ctest_start("Experimental")
ctest_configure()
ctest_build()
ctest_memcheck(PARALLEL_LEVEL 4)
ctest_submit()
# Run memory check locally without submitting
ctest -T MemCheck

# View memory check results
cat build/Testing/Temporary/MemoryChecker.*.log

# Use AddressSanitizer instead of Valgrind (faster)
cmake -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" ..
ctest -T Test
# ASan output appears in test logs, visible on CDash
Lab Exercise Memory Leak Detection Pipeline

Objective: Create a project with an intentional memory leak and detect it via CDash.

Write a test that allocates memory without freeing it. Configure ctest_memcheck() with Valgrind, submit to CDash, and observe the "Dynamic Analysis" tab showing the leak location, size, and stack trace. Then fix the leak and verify CDash shows zero defects on the next submission.

Valgrind memory leaks dynamic analysis

Setting Up CDash Server

For private projects, host your own CDash instance. CDash is a PHP/MySQL application deployed via Docker:

# Pull and run the official CDash Docker image
docker pull kitware/cdash:latest

docker run -d \
    --name cdash \
    -p 8080:80 \
    -v cdash-data:/var/lib/mysql \
    -e CDASH_DB_HOST=localhost \
    -e CDASH_DB_NAME=cdash \
    -e CDASH_DB_LOGIN=cdash \
    -e CDASH_DB_PASS=secure_password \
    kitware/cdash:latest

# Access dashboard at http://localhost:8080
# Create admin account on first visit
# Then create a project and note the submit URL
CDash Server Architecture
        flowchart TD
            A[CI Runner 1] -->|HTTPS POST| B[CDash Web Server]
            C[CI Runner 2] -->|HTTPS POST| B
            D[Developer] -->|HTTPS POST| B
            B --> E[PHP Application]
            E --> F[(MySQL Database)]
            E --> G[File Storage - XML/Logs]
            B --> H[Web Dashboard UI]
            H --> I[Build Summary]
            H --> J[Test Results]
            H --> K[Coverage Trends]
            H --> L[Memory Analysis]
    

GitHub Actions Integration

Automate CDash submissions from CI pipelines. This workflow submits Nightly results every day and Experimental results on every push:

# .github/workflows/cdash.yml
name: CDash Dashboard

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'  # Nightly at 2 AM UTC

jobs:
  dashboard:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install -y ninja-build valgrind lcov

      - name: Configure
        run: |
          cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug \
                -DENABLE_COVERAGE=ON -S . -B build

      - name: Submit to CDash
        env:
          CDASH_AUTH_TOKEN: ${{ secrets.CDASH_TOKEN }}
        run: |
          cd build
          if [ "${{ github.event_name }}" = "schedule" ]; then
            ctest -D Nightly
          else
            ctest -D Experimental
          fi
# Alternative: Use a CTest script for more control
# .github/scripts/dashboard.cmake
set(CTEST_SOURCE_DIRECTORY "$ENV{GITHUB_WORKSPACE}")
set(CTEST_BINARY_DIRECTORY "$ENV{GITHUB_WORKSPACE}/build")
set(CTEST_CMAKE_GENERATOR "Ninja")
set(CTEST_SITE "github-actions")
set(CTEST_BUILD_NAME "$ENV{GITHUB_REF_NAME}-$ENV{GITHUB_SHA}")
set(CTEST_BUILD_CONFIGURATION "Debug")
set(CTEST_COVERAGE_COMMAND "gcov")

set(dashboard_model "$ENV{CDASH_MODEL}")
if(NOT dashboard_model)
    set(dashboard_model "Experimental")
endif()

ctest_start(${dashboard_model})
ctest_configure()
ctest_build(NUMBER_ERRORS errors)
ctest_test(PARALLEL_LEVEL $ENV{NPROC})
ctest_coverage()
ctest_submit()
Lab Exercise Full CI/CDash Pipeline

Objective: Set up a complete GitHub Actions workflow that submits to CDash on every push.

Create a repository with tests and coverage enabled, configure CTestConfig.cmake pointing to open.cdash.org, add the GitHub Actions workflow above, and push. Verify that each commit creates a new dashboard entry with build, test, and coverage data.

GitHub Actions CI/CD automation

Conclusion & Next Steps

CDash transforms isolated CI results into a unified project health dashboard with historical trends, cross-platform comparisons, and actionable regression alerts. Combined with coverage and memory-checking, it provides comprehensive quality metrics that improve team accountability and code reliability.

Next in the Series

In Part 28: Porting Projects to CMake, we'll tackle the practical challenge of migrating existing projects from Makefiles, Autotools, and qmake to modern CMake — covering incremental strategies, common pitfalls, and validation techniques.