Skip to content

Implement Automated Line and MC/DC Coverage CI Workflows - #219165

Open
tapiwagonga wants to merge 26 commits into
llvm:mainfrom
tapiwagonga:libc-coverage-bots
Open

tapiwagonga wants to merge 26 commits into
llvm:mainfrom
tapiwagonga:libc-coverage-bots

Conversation

@tapiwagonga

@tapiwagonga tapiwagonga commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

I previously wrote an RFC regarding Enabling Code Coverage in LLVM-libc to address the circular dependencies and link-time failures caused by standard compiler-rt profiling in hermetic (-nostdlib) testing environments. After incorporating community feedback, the core freestanding instrumentation was merged in Code Coverage via Linux Syscalls.

Following that integration, I evaluated how best to surface this coverage data automatically within the monorepo. This resulted in a second RFC proposing an Automated Code Coverage Infrastructure utilising GitHub Actions to track coverage on commit. This pull request implements that infrastructure.

Implementation Details
The architecture of this infrastructure is explicitly decoupled into execution workflows and zero-dependency analytical scripts. The pipeline is entirely self-contained within the monorepo. The GitHub Actions workflows compile the test suite, generate the raw coverage payloads using llvm-cov, and pipe that data into the Python analysers to render native Markdown summaries.
This submission consists of 8 cohesive files:

GitHub Actions Workflows (.github/workflows/):
Execution Model:These workflows leverage the official ghcr.io/llvm/libc-ubuntu-24.04 container. By using a pre-installed toolchain and building exclusively at the runtimes layer (-DLLVM_ENABLE_RUNTIMES=libc), they eliminate the overhead of compiling Clang from source.

Pre-commit bots (libc-patch-coverage.yml, libc-patch-mcdc.yml) that evaluate patch diffs and provide actionable diagnostics regarding unexecuted lines and unverified boolean conditions on pull requests.

Post-commit bots (libc-full-coverage.yml, libc-full-mcdc.yml) that aggregate repository-wide metrics across all directories on a daily schedule.

In-Tree Reporting Analysers (libc/utils/coverage/):
diff_coverage.py: A Python utility that intercepts llvm-cov JSON exports, correlates them against unified Git diffs, filters out non-executable code lines (comments, macros, braces), and strictly evaluates MC/DC bitmasks to determine patch completeness.

codebase_coverage.py: Aggregates full repository execution segments into top-level directories (e.g., src/math, src/string), It implements explicit path filtering to drop internal mock dependencies and test infrastructure (libc/test/), ensuring the final metrics accurately reflect the core library implementation.

Unit Tests
The Python reporting scripts are supported by fast-executing unit test suites that verify the underlying analysis algorithms using synthetic, in-memory data structures:

test_diff_coverage.py: Verifies the integrity of the patch evaluation pipeline, including Git diff parsing, C++ heuristic filtering, segment intersection math, and MC/DC boolean extraction.

test_codebase_coverage.py: Verifies the global repository tracking logic, specifically asserting directory-level metric aggregation and infrastructure path exclusion.

@github-actions

Copy link
Copy Markdown

Hello @tapiwagonga 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@tapiwagonga
tapiwagonga marked this pull request as ready for review September 2, 2026 12:44
@tapiwagonga
tapiwagonga requested a review from a team as a code owner September 2, 2026 12:44
@llvmorg-github-actions

llvmorg-github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

@llvm/pr-subscribers-github-workflow

@llvm/pr-subscribers-libc

Author: Tapiwa Gonga (tapiwagonga)

Changes

I previously wrote an RFC regarding Enabling Code Coverage in LLVM-libc to address the circular dependencies and link-time failures caused by standard compiler-rt profiling in hermetic (-nostdlib) testing environments. After incorporating community feedback, the core freestanding instrumentation was merged in Code Coverage via Linux Syscalls.

Following that integration, I evaluated how best to surface this coverage data automatically within the monorepo. This resulted in a second RFC proposing an Automated Code Coverage Infrastructure utilising GitHub Actions to track coverage on commit. This pull request implements that infrastructure.

Developer instructions on how to run coverage locally have been published in How To Run Code Coverage.

Implementation Details
The architecture of this infrastructure is explicitly decoupled into execution workflows and zero-dependency analytical scripts. The pipeline is entirely self-contained within the monorepo. The GitHub Actions workflows compile the test suite, generate the raw coverage payloads using llvm-cov, and pipe that data into the Python analysers to render native Markdown summaries.
This submission consists of 8 cohesive files:

GitHub Actions Workflows (.github/workflows/):
Execution Model:These workflows leverage the official ghcr.io/llvm/libc-ubuntu-24.04 container. By using a pre-installed toolchain and building exclusively at the runtimes layer (-DLLVM_ENABLE_RUNTIMES=libc), they eliminate the overhead of compiling Clang from source.

Pre-commit bots (libc-patch-coverage.yml, libc-patch-mcdc.yml) that evaluate patch diffs and provide actionable diagnostics regarding unexecuted lines and unverified boolean conditions on pull requests.

Post-commit bots (libc-full-coverage.yml, libc-full-mcdc.yml) that aggregate repository-wide metrics across all directories on a daily schedule.

In-Tree Reporting Analysers (libc/utils/coverage/):
diff_coverage.py: A Python utility that intercepts llvm-cov JSON exports, correlates them against unified Git diffs, filters out non-executable code lines (comments, macros, braces), and strictly evaluates MC/DC bitmasks to determine patch completeness.

codebase_coverage.py: Aggregates full repository execution segments into top-level directories (e.g., src/math, src/string), It implements explicit path filtering to drop internal mock dependencies and test infrastructure (libc/test/), ensuring the final metrics accurately reflect the core library implementation.

Unit Tests
The Python reporting scripts are supported by fast-executing unit test suites that verify the underlying analysis algorithms using synthetic, in-memory data structures:

test_diff_coverage.py: Verifies the integrity of the patch evaluation pipeline, including Git diff parsing, C++ heuristic filtering, segment intersection math, and MC/DC boolean extraction.

test_codebase_coverage.py: Verifies the global repository tracking logic, specifically asserting directory-level metric aggregation and infrastructure path exclusion.


Patch is 95.24 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/219165.diff

8 Files Affected:

  • (added) .github/workflows/libc-full-coverage.yml (+143)
  • (added) .github/workflows/libc-full-mcdc.yml (+149)
  • (added) .github/workflows/libc-patch-coverage.yml (+362)
  • (added) .github/workflows/libc-patch-mcdc.yml (+370)
  • (added) libc/utils/coverage/codebase_coverage.py (+308)
  • (added) libc/utils/coverage/diff_coverage.py (+661)
  • (added) libc/utils/coverage/test_codebase_coverage.py (+82)
  • (added) libc/utils/coverage/test_diff_coverage.py (+256)
diff --git a/.github/workflows/libc-full-coverage.yml b/.github/workflows/libc-full-coverage.yml
new file mode 100644
index 0000000000000..ebaa9f2a39fb6
--- /dev/null
+++ b/.github/workflows/libc-full-coverage.yml
@@ -0,0 +1,143 @@
+name: Libc Full Codebase Coverage
+
+permissions:
+  contents: write # Required to publish live HTML dashboard to gh-pages branch
+
+env:
+  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
+
+on:
+  # Daily overnight run at 02:00 UTC
+  schedule:
+    - cron: '0 2 * * *'
+
+  # Allow manual on-demand execution from GitHub Actions UI
+  workflow_dispatch:
+
+  # Trigger on pushes to main / libc-coverage-ci-bots / post-commit-bot
+  push:
+    branches:
+      - main
+      - libc-coverage-ci-bots
+      - post-commit-bot
+    paths:
+      - 'libc/**'
+      - '.github/workflows/libc-full-coverage.yml'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  full-coverage:
+    timeout-minutes: 60
+    name: libc-full-coverage
+    runs-on: ubuntu-24.04
+    container:
+      image: ghcr.io/llvm/libc-ubuntu-24.04:latest@sha256:8fee4c9ce5a1fd095686593cd36e032c48a31b6ae575378c82accd1d86a08d59
+      options: --privileged
+    defaults:
+      run:
+        shell: bash
+    steps:
+    - name: Checkout Code
+      uses: actions/checkout@v4
+      with:
+        fetch-depth: 1
+        persist-credentials: false
+
+    - name: Setup Compiler Cache (sccache)
+      uses: hendrikmuhs/ccache-action@v1.2.23
+      with:
+        max-size: 1G
+        key: libc_coverage_unified_v2_x86_64
+        variant: sccache
+
+    - name: Configure CMake
+      run: |
+        echo "=== [STAGE 1/4] Starting CMake Configuration at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        START_TIME=$(date +%s)
+
+        export CMAKE_FLAGS="
+          -G Ninja
+          -S runtimes
+          -B build-cov
+          -DCMAKE_C_COMPILER=clang-23
+          -DCMAKE_CXX_COMPILER=clang++-23
+          -DCMAKE_BUILD_TYPE=Debug
+          -DCMAKE_C_COMPILER_LAUNCHER=sccache
+          -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
+          -DLLVM_USE_LINKER=lld-23
+          -DLLVM_ENABLE_RUNTIMES=libc
+          -DLLVM_LIBC_FULL_BUILD=ON
+          -DLIBC_ENABLE_COVERAGE=ON
+          -DLIBC_TEST_UNIT_TEST_ONLY=ON
+          -DLIBC_TEST_SKIP_DEATH_TESTS=ON
+          -DLIBC_TEST_SKIP_SHARED_TESTS=ON
+        "
+        cmake $CMAKE_FLAGS
+
+        END_TIME=$(date +%s)
+        echo "=== [STAGE 1/4] CMake Configuration Completed in $((END_TIME - START_TIME))s ==="
+
+    - name: Run Full Codebase Unit Tests
+      run: |
+        echo "=== [STAGE 2/4] Running Full Unit Test Suite at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        START_TIME=$(date +%s)
+
+        export LLVM_PROFILE_FILE="libc_cov_%p.profraw"
+        ninja -k 0 -C build-cov libc-unit-tests || true
+
+        END_TIME=$(date +%s)
+        echo "=== [STAGE 2/4] libc-unit-tests Completed in $((END_TIME - START_TIME))s ==="
+
+    - name: Merge Profiles
+      run: |
+        echo "=== [STAGE 3/4] Merging Raw Profiles at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        find . build-cov -name "libc_cov_*.profraw" > profraw_list.txt
+        NUM_PROFS=$(wc -l < profraw_list.txt || echo 0)
+        echo "[LOG] Discovered $NUM_PROFS raw profile data files."
+        llvm-profdata-23 merge -sparse --input-files=profraw_list.txt -o libc_full.profdata
+        echo "[LOG] Merged full codebase profile into libc_full.profdata."
+
+    - name: Generate Reports and Summary
+      env:
+        COMMIT_SHA: ${{ github.sha }}
+        BRANCH_REF: ${{ github.ref_name }}
+      run: |
+        echo "=== [STAGE 4/4] Generating Full Codebase Coverage Reports at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        EXECUTABLES=($(find build-cov -type f -executable -name "*__build__"))
+        OBJECTS=("${EXECUTABLES[@]:1}")
+        OBJECTS=("${OBJECTS[@]/#/-object=}")
+
+        echo "[LOG] Exporting coverage data across ${#EXECUTABLES[@]} test binaries."
+
+        # 1. Generate JSON export for Full Coverage Analyzer
+        llvm-cov-23 export -format=text -instr-profile=libc_full.profdata "${EXECUTABLES[0]}" "${OBJECTS[@]}" > coverage.json
+
+        # 2. Generate HTML Coverage Report with directory hierarchy and branch tracking
+        llvm-cov-23 show -format=html \
+          -output-dir=coverage_html \
+          -instr-profile=libc_full.profdata \
+          "${EXECUTABLES[0]}" "${OBJECTS[@]}" \
+          --show-directory-coverage \
+          --show-branches=count \
+          --compilation-dir=. \
+          --path-equivalence="$GITHUB_WORKSPACE,." \
+          -ignore-filename-regex=".*(test|utils).*"
+        touch coverage_html/.nojekyll
+
+        # 3. Run Codebase Coverage Analyzer
+        PYTHONPATH="libc/utils/coverage" python3 libc/utils/coverage/codebase_coverage.py coverage.json "$COMMIT_SHA" "$BRANCH_REF" >> $GITHUB_STEP_SUMMARY
+        echo "[LOG] Post-commit summary report written to GITHUB_STEP_SUMMARY."
+
+    - name: Deploy Coverage Dashboard to GitHub Pages
+      if: github.repository != 'llvm/llvm-project'
+      continue-on-error: true
+      uses: peaceiris/actions-gh-pages@v4
+      with:
+        github_token: ${{ secrets.GITHUB_TOKEN }}
+        publish_dir: ./coverage_html
+        keep_files: true
+        enable_jekyll: false
+        force_orphan: false
diff --git a/.github/workflows/libc-full-mcdc.yml b/.github/workflows/libc-full-mcdc.yml
new file mode 100644
index 0000000000000..fdebc1c66f399
--- /dev/null
+++ b/.github/workflows/libc-full-mcdc.yml
@@ -0,0 +1,149 @@
+name: Libc Full Codebase MC/DC Coverage
+
+permissions:
+  contents: write
+
+env:
+  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
+
+on:
+  # Daily overnight run at 02:30 UTC (staggered by 30 mins from baseline sweep)
+  schedule:
+    - cron: '30 2 * * *'
+
+  # Allow manual on-demand execution from GitHub Actions UI ("Run workflow" button)
+  workflow_dispatch:
+
+  # Trigger on pushes to main / libc-mcdc-coverage / post-commit-bot
+  push:
+    branches:
+      - main
+      - libc-mcdc-coverage
+      - post-commit-bot
+    paths:
+      - 'libc/**'
+      - '.github/workflows/libc-full-mcdc.yml'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  full-mcdc-coverage:
+    timeout-minutes: 60
+    name: libc-full-mcdc-coverage
+    runs-on: ubuntu-24.04
+    container:
+      image: ghcr.io/llvm/libc-ubuntu-24.04:latest@sha256:8fee4c9ce5a1fd095686593cd36e032c48a31b6ae575378c82accd1d86a08d59
+      options: --privileged
+    defaults:
+      run:
+        shell: bash
+    steps:
+      - name: Checkout Code
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 1
+          persist-credentials: false
+
+      - name: Setup Compiler Cache (sccache)
+        uses: hendrikmuhs/ccache-action@v1.2.23
+        with:
+          max-size: 1G
+          key: libc_mcdc_coverage_unified_x86_64
+          variant: sccache
+
+      - name: Configure CMake with MC/DC
+        run: |
+          echo "=== [STAGE 1/4] Starting CMake Configuration (MC/DC) at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+          START_TIME=$(date +%s)
+
+          export CMAKE_FLAGS="
+            -G Ninja
+            -S runtimes
+            -B build-cov
+            -DCMAKE_C_COMPILER=clang-23
+            -DCMAKE_CXX_COMPILER=clang++-23
+            -DCMAKE_BUILD_TYPE=Debug
+            -DCMAKE_C_COMPILER_LAUNCHER=sccache
+            -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
+            -DLLVM_USE_LINKER=lld-23
+            -DLLVM_ENABLE_RUNTIMES=libc
+            -DLLVM_LIBC_FULL_BUILD=ON
+            -DLIBC_ENABLE_COVERAGE=ON
+            -DLIBC_ENABLE_MCDC=ON
+            -DLIBC_TEST_UNIT_TEST_ONLY=ON
+            -DLIBC_TEST_SKIP_DEATH_TESTS=ON
+            -DLIBC_TEST_SKIP_SHARED_TESTS=ON
+          "
+          cmake $CMAKE_FLAGS
+
+          END_TIME=$(date +%s)
+          echo "=== [STAGE 1/4] CMake Configuration Completed in $((END_TIME - START_TIME))s ==="
+
+      - name: Run Full Codebase Unit Tests
+        run: |
+          echo "=== [STAGE 2/4] Running Full Unit Test Suite at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+          START_TIME=$(date +%s)
+
+          export LLVM_PROFILE_FILE="libc_cov_%p.profraw"
+          ninja -k 0 -C build-cov libc-unit-tests || true
+
+          END_TIME=$(date +%s)
+          echo "=== [STAGE 2/4] libc-unit-tests Completed in $((END_TIME - START_TIME))s ==="
+
+      - name: Merge Profiles
+        run: |
+          echo "=== [STAGE 3/4] Merging Raw Profiles at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+          find build-cov -name "libc_cov_*.profraw" > profraw_list.txt
+          NUM_PROFS=$(wc -l < profraw_list.txt || echo 0)
+          echo "[LOG] Discovered $NUM_PROFS raw profile data files."
+          llvm-profdata-23 merge -sparse --input-files=profraw_list.txt -o libc_full.profdata
+          echo "[LOG] Merged full codebase profile into libc_full.profdata."
+
+      - name: Generate Reports and Summary
+        env:
+          COMMIT_SHA: ${{ github.sha }}
+          BRANCH_REF: ${{ github.ref_name }}
+        run: |
+          echo "=== [STAGE 4/4] Generating Full Codebase MC/DC Coverage Reports at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+          EXECUTABLES=($(find build-cov -type f -executable -name "*__build__"))
+          OBJECTS=("${EXECUTABLES[@]:1}")
+          OBJECTS=("${OBJECTS[@]/#/-object=}")
+
+          echo "[LOG] Exporting coverage data across ${#EXECUTABLES[@]} test binaries."
+
+          # 1. Generate JSON export for Full Coverage Analyzer (contains mcdc_records)
+          llvm-cov-23 export -format=text -instr-profile=libc_full.profdata "${EXECUTABLES[0]}" "${OBJECTS[@]}" > coverage.json
+
+          # 2. Generate HTML Coverage Report with directory hierarchy, branch tracking, and MC/DC truth tables
+          llvm-cov-23 show -format=html \
+            -output-dir=coverage_mcdc_html \
+            -instr-profile=libc_full.profdata \
+            "${EXECUTABLES[0]}" "${OBJECTS[@]}" \
+            --show-directory-coverage \
+            --show-branches=count \
+            --show-mcdc \
+            --show-mcdc-summary \
+            --compilation-dir=. \
+            --path-equivalence="$GITHUB_WORKSPACE,." \
+            -ignore-filename-regex=".*(test|utils).*"
+          touch coverage_mcdc_html/.nojekyll
+
+          # 3. Run Codebase Coverage Analyzer
+          PYTHONPATH="libc/utils/coverage" python3 libc/utils/coverage/codebase_coverage.py coverage.json "$COMMIT_SHA" "$BRANCH_REF" >> $GITHUB_STEP_SUMMARY
+          echo "[LOG] Post-commit summary report written to GITHUB_STEP_SUMMARY."
+
+      - name: Deploy MC/DC Coverage Report to GitHub Pages
+        if: github.repository != 'llvm/llvm-project'
+        continue-on-error: true
+        uses: peaceiris/actions-gh-pages@v4
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          publish_dir: ./coverage_mcdc_html
+          destination_dir: mcdc
+          keep_files: true
+          enable_jekyll: false
+          force_orphan: false
+
+
diff --git a/.github/workflows/libc-patch-coverage.yml b/.github/workflows/libc-patch-coverage.yml
new file mode 100644
index 0000000000000..5cd2b723862e1
--- /dev/null
+++ b/.github/workflows/libc-patch-coverage.yml
@@ -0,0 +1,362 @@
+name: Libc Patch Code Coverage
+
+permissions:
+  contents: read
+  pull-requests: write # Required to post/update patch coverage comments
+
+env:
+  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
+
+on:
+  workflow_dispatch:
+  push:
+    branches:
+      - pre-commit-bot
+      - libc-coverage-ci-bots
+      - main
+    paths:
+      - 'libc/src/**'
+      - 'libc/include/**'
+      - 'libc/test/**'
+      - 'libc/CMakeLists.txt'
+      - '.github/workflows/libc-patch-coverage.yml'
+      - '!libc/docs/**'
+      - '!libc/benchmarks/**'
+      - '!libc/fuzzing/**'
+      - '!libc/utils/**'
+      - '!**.md'
+  pull_request:
+    paths:
+      - 'libc/src/**'
+      - 'libc/include/**'
+      - 'libc/test/**'
+      - 'libc/CMakeLists.txt'
+      - '.github/workflows/libc-patch-coverage.yml'
+      - '!libc/docs/**'
+      - '!libc/benchmarks/**'
+      - '!libc/fuzzing/**'
+      - '!libc/utils/**'
+      - '!**.md'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  pre-commit-coverage:
+    timeout-minutes: 60
+    name: libc-pre-commit-coverage
+    runs-on: ubuntu-24.04
+    container:
+      image: ghcr.io/llvm/libc-ubuntu-24.04:latest@sha256:8fee4c9ce5a1fd095686593cd36e032c48a31b6ae575378c82accd1d86a08d59
+      options: >-
+        --privileged
+    defaults:
+      run:
+        shell: bash
+    steps:
+    - name: Checkout Code
+      uses: actions/checkout@v4
+      with:
+        ref: ${{ github.event.pull_request.head.sha || github.sha }}
+        fetch-depth: 2
+        persist-credentials: false
+
+    - name: Setup Compiler Cache (sccache)
+      uses: hendrikmuhs/ccache-action@v1.2.23
+      with:
+        max-size: 1G
+        key: libc_coverage_unified_v2_x86_64
+        variant: sccache
+
+    - name: Configure CMake
+      run: |
+        echo "=== [STAGE 1/4] Starting CMake Configuration at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        START_TIME=$(date +%s)
+
+        export CMAKE_FLAGS="
+          -G Ninja
+          -S runtimes
+          -B build-cov
+          -DCMAKE_C_COMPILER=clang-23
+          -DCMAKE_CXX_COMPILER=clang++-23
+          -DCMAKE_BUILD_TYPE=Debug
+          -DCMAKE_C_COMPILER_LAUNCHER=sccache
+          -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
+          -DLLVM_USE_LINKER=lld-23
+          -DLLVM_ENABLE_RUNTIMES=libc
+          -DLLVM_LIBC_FULL_BUILD=ON
+          -DLIBC_ENABLE_COVERAGE=ON
+          -DLIBC_TEST_UNIT_TEST_ONLY=ON
+          -DLIBC_TEST_SKIP_DEATH_TESTS=ON
+          -DLIBC_TEST_SKIP_SHARED_TESTS=ON
+        "
+        cmake $CMAKE_FLAGS
+
+        END_TIME=$(date +%s)
+        echo "=== [STAGE 1/4] CMake Configuration Completed in $((END_TIME - START_TIME))s ==="
+
+    - name: Build and Run Targeted Tests
+      env:
+        EVENT_NAME: ${{ github.event_name }}
+        PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+        PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+        PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
+        PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
+        PR_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }}
+        PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+        COMMIT_SHA: ${{ github.sha }}
+        BRANCH_REF: ${{ github.ref_name }}
+        REPO_NAME: ${{ github.repository }}
+      run: |
+        echo "=== [STAGE 2/4] Starting Targeted Test Discovery at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        START_TIME=$(date +%s)
+
+        # Ensure upstream llvm-project remote is configured
+        git remote add upstream https://github.com/llvm/llvm-project.git 2>/dev/null || true
+
+        # 1. Deterministically resolve Base and Head references against upstream llvm-project
+        if [ "$EVENT_NAME" == "pull_request" ]; then
+          BASE_SHA="$PR_BASE_SHA"
+          HEAD_SHA="$PR_HEAD_SHA"
+          BASE_REF="$PR_BASE_REF"
+          HEAD_REF="$PR_HEAD_REF"
+          BASE_REPO="${PR_BASE_REPO:-llvm/llvm-project}"
+          HEAD_REPO="${PR_HEAD_REPO:-$REPO_NAME}"
+          git fetch upstream "$BASE_SHA" --depth=1 2>/dev/null || git fetch origin "$BASE_SHA" --depth=1 2>/dev/null || git fetch origin "$BASE_REF" --depth=1 2>/dev/null || true
+          DIFF_BASE="$BASE_SHA"
+        else
+          # For direct pushes, compare the pushed commit against its parent (HEAD~1)
+          BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || echo "HEAD~1")
+          HEAD_SHA="$COMMIT_SHA"
+          BASE_REF="main"
+          HEAD_REF="$BRANCH_REF"
+          BASE_REPO="llvm/llvm-project"
+          HEAD_REPO="$REPO_NAME"
+          DIFF_BASE="HEAD~1"
+        fi
+
+        # Persist resolved commit references for all downstream steps
+        echo "DIFF_BASE=$DIFF_BASE" >> $GITHUB_ENV
+        echo "BASE_SHA=$BASE_SHA" >> $GITHUB_ENV
+        echo "HEAD_SHA=$HEAD_SHA" >> $GITHUB_ENV
+        echo "BASE_REF=$BASE_REF" >> $GITHUB_ENV
+        echo "HEAD_REF=$HEAD_REF" >> $GITHUB_ENV
+        echo "BASE_REPO=$BASE_REPO" >> $GITHUB_ENV
+        echo "HEAD_REPO=$HEAD_REPO" >> $GITHUB_ENV
+
+        echo "[LOG] Resolved Base: ${BASE_REF} (${BASE_SHA:0:7}) in ${BASE_REPO}"
+        echo "[LOG] Resolved Head: ${HEAD_REF} (${HEAD_SHA:0:7}) in ${HEAD_REPO}"
+
+        MODIFIED_FILES=$(git diff --name-only "$DIFF_BASE" HEAD -- libc/src/ || true)
+        echo "[LOG] Modified files in libc/src/:"
+        echo "$MODIFIED_FILES"
+
+        if [ -z "$MODIFIED_FILES" ]; then
+          echo "[LOG] No source files modified in libc/src/. Exiting successfully."
+          echo "TARGETS=" >> $GITHUB_ENV
+
+          echo "## LLVM-libc Patch Coverage Report" > coverage_report.md
+          echo "" >> coverage_report.md
+          echo "- **Base Branch:** [\`${BASE_REF}\` (${BASE_SHA:0:7})](https://github.com/${BASE_REPO}/commit/${BASE_SHA})" >> coverage_report.md
+          echo "- **Head Commit:** [\`${HEAD_REF}\` (${HEAD_SHA:0:7})](https://github.com/${HEAD_REPO}/commit/${HEAD_SHA})" >> coverage_report.md
+          echo "---" >> coverage_report.md
+          echo "" >> coverage_report.md
+          echo "**Coverage Validated**" >> coverage_report.md
+          echo "No \`.cpp\` source files in \`libc/src/\` were modified in this patch." >> coverage_report.md
+
+          cat coverage_report.md >> $GITHUB_STEP_SUMMARY
+          exit 0
+        fi
+
+        # Get all valid ninja targets
+        ninja -C build-cov -t targets > all_targets.txt
+
+        TARGETS=""
+
+        for FILE in $MODIFIED_FILES; do
+          # Skip core support files from direct target mapping (tested transitively)
+          if [[ "$FILE" == *"__support/"* ]]; then
+            echo "[LOG] -> Support file ($FILE) will be tested transitively."
+            continue
+          fi
+
+          if [[ "$FILE" =~ ^libc/src/([^/]+)/(.*/)?([^/]+)\.cpp$ ]]; then
+            DIR="${BASH_REMATCH[1]}"
+            FUNC="${BASH_REMATCH[3]}"
+            ALL_MATCHES=$(grep -oE "^libc\.test\.src\.${DIR}\.([a-zA-Z0-9_]+\.)*${FUNC}_test\.__unit__" all_targets.txt || true)
+            FOUND_TARGET=$(echo "$ALL_MATCHES" | head -n 1 | tr -d '\r\n ' || true)
+            if [ -n "$FOUND_TARGET" ]; then
+              echo "[LOG] -> Matched $FILE -> $FOUND_TARGET"
+              TARGETS="$TARGETS $FOUND_TARGET"
+            else
+              echo "[LOG] -> Notice: No direct unit test target found for $FILE."
+            fi
+          fi
+        done
+
+        # Remove duplicate targets
+        TARGETS=$(echo "$TARGETS" | xargs -n1 | sort -u | xargs || true)
+        echo "[LOG] Executing Ninja targets: $TARGETS"
+
+        if [ -n "$TARGETS" ]; then
+          export LLVM_PROFILE_FILE="libc_cov_%p.profraw"
+          ninja -C build-cov $TARGETS
+          echo "TARGETS=$TARGETS" >> $GITHUB_ENV
+        else
+          echo "[LOG] No standalone unit test targets to run."
+          echo "TARGETS=" >> $GITHUB_ENV
+
+          echo "## LLVM-libc Patch Coverage Report" > coverage_report.md
+          echo "" >> coverage_report.md
+          echo "- **Base Branch:** [\`${BASE_REF}\` (${BASE_SHA:0:7})](https://github.com/${BASE_REPO}/commit/${BASE_SHA})" >> coverage_report.md
+          echo "- **Head Commit:** [\`${HEAD_REF}\` (${HEAD_SHA:0:7})](https://github.com/${HEAD_REPO}/commit/${HEAD_SHA})" >> coverage_report.md
+          echo "---" >> coverage_report.md
+          echo "" >> coverage_report.md
+          echo "**Coverage Validated**" >> coverage_report.md
+          echo "Modified files do not have standalone unit test targets." >> coverage_report.md
+
+          cat coverage_report.md >> $GITHUB_STEP_SUMMARY
+        fi
+
+        END_TIME=$(date +%s)
+        echo "=== [STAGE 2/4] Targeted Tests Completed in $((END_TIME - START_TIME))s ==="
+
+    - name: Merge Profiles
+      if: env.TARGETS != ''
+      run: |
+        echo "=== [STAGE 3/4] Merging Raw Profiles at $(date -u '+%Y-%m-%d %H:%M:%S UTC') ==="
+        find build-cov -name "libc_cov_*.profraw" > profraw_list.txt
+        NUM_PROFS=$(wc -l < profraw_li...
[truncated]

echo "[LOG] Post-commit summary report written to GITHUB_STEP_SUMMARY."

- name: Deploy Coverage Dashboard to GitHub Pages
if: github.repository != 'llvm/llvm-project'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a better solution than just using the GH Pages branch of the monorepo. For now we should probably just upload the report as an artifact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Aiden, I've removed the GitHub Pages deployment step entirely and replaced it with actions/upload-artifact to upload the report.

Replace the third-party peaceiris/actions-gh-pages deployment action with
standard actions/upload-artifact@v4 in both full coverage workflows. This
aligns with LLVM CI security best practices by removing unnecessary
contents: write repository permissions and untrusted third-party actions.

Harden raw profile discovery and report generation with defensive guards.
Configure overwrite: true on artifact uploads to ensure robustness across
workflow re-runs. Update codebase_coverage.py and associated unit tests to
direct users to the workflow Artifacts section.

Assisted-by: Automated tooling, human reviewed.
Comment thread .github/workflows/libc-full-coverage.yml Fixed
Comment thread .github/workflows/libc-full-mcdc.yml Fixed
Comment thread .github/workflows/libc-patch-coverage.yml Fixed
Comment thread .github/workflows/libc-patch-mcdc.yml Fixed
Comment thread .github/workflows/libc-full-coverage.yml Fixed
Comment thread .github/workflows/libc-full-coverage.yml Fixed
Comment thread .github/workflows/libc-full-coverage.yml Fixed
Comment thread .github/workflows/libc-full-mcdc.yml Fixed
Comment thread .github/workflows/libc-full-mcdc.yml Fixed
Comment thread .github/workflows/libc-patch-coverage.yml Fixed
Comment thread .github/workflows/libc-patch-coverage.yml Fixed
Comment thread .github/workflows/libc-patch-mcdc.yml Fixed
Comment thread .github/workflows/libc-patch-mcdc.yml Fixed
Comment thread .github/workflows/libc-patch-mcdc.yml Fixed
@github-actions

github-actions Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

✅ With the latest revision this PR passed the Python code formatter.

runs-on: ubuntu-24.04
container:
image: ghcr.io/llvm/libc-ubuntu-24.04:latest@sha256:8fee4c9ce5a1fd095686593cd36e032c48a31b6ae575378c82accd1d86a08d59
options: --privileged

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need this --privileged flags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The --privileged flag was a leftover from earlier container experiments and wasn't actually needed for these tests. I've removed it.

Comment on lines +80 to +81
END_TIME=$(date +%s)
echo "=== [STAGE 1/4] CMake Configuration Completed in $((END_TIME - START_TIME))s ==="

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to track how long this step takes? GtiHub already tracks this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally added that to measure stage timings while benchmarking build performance and caching in local container runs. I've removed it now.

Pin workflow actions to 40-character commit hashes to comply with
Zizmor security policy.

Format codebase and diff coverage Python utilities and unit test suites
to conform to Black/darker standards (88-character limit, standard spacing).

Remove unnecessary container --privileged flag and redundant manual step
timing logic from coverage workflows.

Assisted-by: Automated tooling, human reviewed.
Comment thread .github/workflows/libc-full-mcdc.yml Outdated
Comment on lines +21 to +22
- libc-mcdc-coverage
- post-commit-bot

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these branches for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those were leftover branches used for testing the workflows during development on my fork. I've removed them so the push triggers only target main.

@tstellar

tstellar commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

@tapiwagonga Was this patch originally generated using AI or have you only been using AI for the updates?

@tapiwagonga

Copy link
Copy Markdown
Contributor Author

@tapiwagonga Was this patch originally generated using AI or have you only been using AI for the updates?

The original patch wasn't generated by AI, I wrote the initial implementation myself. I've mainly been using AI as an assistant to help turn around review comments and formatting updates faster, but I review, test, and verify all the changes myself before committing and pushing.

@boomanaiden154

Copy link
Copy Markdown
Contributor

Understood, for a detailed description of the approach, please associate a issue with this pull request. The merged commit message will be associated with the pull request number.

This is not necessary. There was already an RFC. Please stop writing low-quality comments on PRs and only post when you're confident what you're saying is correct/helpful.

Remove development and testing branch filters from coverage workflows so
they only trigger on upstream main and pull requests targeting main.

Assisted-by: Automated tooling, human reviewed.

@tstellar tstellar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this is split into 4 separate workflows instead of just 1 or 2?

@tapiwagonga

tapiwagonga commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Can you explain why this is split into 4 separate workflows instead of just 1 or 2?

The overall idea was to track both standard statement/branch coverage and MC/DC (Modified Condition / Decision Coverage), and to do so across two distinct levels of scope: incrementally on every pull request commit so developers and contributors get immediate visibility into the coverage of their commits directly on their pull requests, and globally across the entire codebase as a recurring baseline.

To support this, I set up a dedicated bot for each of the four configurations:

  • On-Commit Bots (libc-patch-coverage & libc-patch-mcdc)
    The first two bots evaluate incoming pull requests on each commit, but they measure fundamentally different levels of verification: libc-patch-coverage tracks whether modified lines and branches were executed, while libc-patch-mcdc evaluates Modified Condition / Decision Coverage to verify that each boolean sub-condition in compound logic independently affects the decision outcome. I separated these into standalone workflows because MC/DC requires dedicated compiler instrumentation (-fcoverage-mcdc), meaning the two analyses cannot share the same instrumented test binaries. Running them independently guarantees failure isolation: if an intricate boolean expression or macro encounters an issue during MC/DC analysis, it will not fail the entire check or prevent the contributor from receiving standard line coverage feedback.

  • Full Codebase Bots (libc-full-coverage & libc-full-mcdc)
    The remaining two bots measure total coverage across the entire codebase on a recurring schedule rather than evaluating individual pull requests. libc-full-coverage builds the full unit test suite to measure statement and branch coverage across all library entrypoints, while libc-full-mcdc builds the full test suite with -fcoverage-mcdc to measure boolean condition coverage across all decision statements. I separated these into standalone workflows because compiling with and without -fcoverage-mcdc produces different binaries that cannot be combined into a single build. Running them independently keeps the builds separate and allows each workflow to publish its own summary table and HTML artifact without interfering with the other.

@tapiwagonga
tapiwagonga requested a review from tstellar September 4, 2026 11:24
…plify CI summaries

Streamline test_diff_coverage.py by 41% (reducing 360+ lines of fixture
boilerplate) while preserving 100% statement and branch coverage.

Harden diff and codebase coverage utilities against C++ syntax edge cases,
including closing braces with namespace comments, C++ access specifiers,
and compile-time declarations (static_assert, friend). Filter non-source files
from patch diff evaluations and normalize repository URLs for GitHub links.

Remove the redundant sticky PR comment step from patch workflows in favor of
standard GITHUB_STEP_SUMMARY and artifact uploads, restricting workflow
permissions strictly to contents: read to avoid fork token permission failures.

Add downloadable artifact packaging with if-no-files-found guards, merge-base
diff resolution, and executable verification guards across CI.

Assisted-by: Automated tooling, human reviewed.


- name: Upload Patch Coverage Artifacts
if: always() && hashFiles('coverage_report.md')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tstellar

tstellar commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

@tapiwagonga libc-full-* files look almost identical. Would it be possible to merged those into one workflow and use a matrix? Same thing for the libc-patch-* files. It looks like there is a lot of cut and paste duplication.

Merge full and patch coverage workflows into matrix jobs across line and
MC/DC coverage, eliminating duplicate workflow definitions. Rename unit
test files to patch_coverage_test.py and full_coverage_test.py, and remove
synthetic commit SHAs, dummy branch names, and mock repository fixtures
from test cases. Remove redundant Node.js environment settings and ensure
strict compliance with darker, Black, and git-clang-format.

Assisted-by: Automated tooling, human reviewed.
@tapiwagonga

Copy link
Copy Markdown
Contributor Author

@tapiwagonga libc-full-* files look almost identical. Would it be possible to merged those into one workflow and use a matrix? Same thing for the libc-patch-* files. It looks like there is a lot of cut and paste duplication.

@tstellar I have consolidated both sets of workflows into single matrix pipelines. The libc-full-* workflows are now merged into libc-full-coverage.yml, and the libc-patch-* workflows into libc-patch-coverage.yml. I used a matrix to parameterise standard branch coverage versus MC/DC. Each workflow uses strategy.matrix.include to define two parallel jobs, one standard and one MC/DC. All the common steps are shared in a single pipeline, while the matrix swaps in the specific CMake options, compiler flags, and llvm-cov report options for each run.

Tapiwa Gonga and others added 14 commits September 8, 2026 12:44
Assisted-by: Automated tooling, human reviewed.
Temporarily enable patch coverage workflow runs on libc-coverage-bots for testing purposes.

Assisted-by: Automated tooling, human reviewed.
Assisted-by: Automated tooling, human reviewed.
…e on isalpha

Assisted-by: Automated tooling, human reviewed.
…files

Assisted-by: Automated tooling, human reviewed.
…ild directory

Assisted-by: Automated tooling, human reviewed.
…date isdigit for testing patch coverage

This commit updates commit reference resolution, adds diagnostic workflow logging,
and modifies isdigit for testing purposes to validate the patch coverage pipeline.

Assisted-by: Automated tooling, human reviewed.
…sdigit

This commit switches the patch coverage bot to overlay build mode to resolve
profile runtime linker errors, adds LIBC_ENABLE_MCDC support, and updates
isdigit for testing purposes to validate the patch coverage pipeline.

Assisted-by: Automated tooling, human reviewed.
…porting

Replace cryptic MC/DC summaries with structured condition diagnostics and actionable test
guidance. Ensure clean unified diff formatting and add GitHub Flavored Markdown summary callouts.

Assisted-by: Automated tooling, human reviewed.
…testing

Update local bounds checking variables in isdigit to validate the coverage artifact
formatting pipeline on GitHub Actions.

Assisted-by: Automated tooling, human reviewed.
Remove alert callouts in diff_coverage.py and package interactive HTML.
Synchronize patch_coverage_test.py unit tests with updated report structure.
Update isalpha bounds checking to trigger and exercise the coverage bot.

Assisted-by: Automated tooling, human reviewed.
Align full codebase and patch coverage workflows with Clang continuous
profiling mode. Add repository owner guards, isolate sccache keys, persist
modified file lists across workflow steps, and correct trigger path filters.
Re-add Clang compiler checks and remove compiler-rt profile path-guessing
in CMakeLists. Revert temporary testing modifications in ctype source files.

Assisted-by: Automated tooling, human reviewed.
Configure libc-full-coverage and libc-patch-coverage workflows to strictly
use full-build mode (LLVM_LIBC_FULL_BUILD=ON) and target libc-hermetic-tests.
Align CMake continuous profiling options with LIBC_ENABLE_COVERAGE_MCDC and
use -fprofile-instr-generate=libc_cov_%p.profraw.

Assisted-by: Automated tooling, human reviewed.
@michaelrj-google

Copy link
Copy Markdown
Member

@tstellar PTAL

@tstellar tstellar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to review the workflow structure as best as I can. I don't really understand what this is doing and the code is hard for me to follow. You should have someone more familiar with libc take a look at this too.

Comment on lines +75 to +91
export CMAKE_FLAGS="
-G Ninja
-S runtimes
-B build-cov
-DCMAKE_C_COMPILER=clang-23
-DCMAKE_CXX_COMPILER=clang++-23
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_C_COMPILER_LAUNCHER=sccache
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache
-DLLVM_USE_LINKER=lld-23
-DLLVM_ENABLE_RUNTIMES=libc
-DLLVM_LIBC_FULL_BUILD=ON
-DLIBC_ENABLE_COVERAGE=ON
-DLIBC_ENABLE_COVERAGE_MCDC=${{ matrix.enable_mcdc }}
-DLIBC_TEST_SKIP_DEATH_TESTS=ON
-DLIBC_TEST_SKIP_SHARED_TESTS=ON
"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use an environment variable here? Can't you just pass this directly to cmake?

-DLIBC_TEST_SKIP_DEATH_TESTS=ON
-DLIBC_TEST_SKIP_SHARED_TESTS=ON
"
cmake $CMAKE_FLAGS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here.

REPO_NAME: ${{ github.repository }}
run: |
# Ensure upstream llvm-project remote is configured
git remote add upstream https://github.com/llvm/llvm-project.git 2>/dev/null || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants