Skip to content
Latchkey

Test with coverage workflow (sefcom/oxidizer)

The Test with coverage workflow from sefcom/oxidizer, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get caching, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: sefcom/oxidizer.github/workflows/coverage.ymlLicense BSD-2-ClauseView source

What it does

This is the Test with coverage workflow from the sefcom/oxidizer repository, a real project running GitHub Actions. It is shown here with attribution under its BSD-2-Clause license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: Test with coverage

on:
  push:
    branches:
      - master
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  SKIP_SLOW_TESTS: 1
  RUSTFLAGS: "-C instrument-coverage -C codegen-units=1 -C link-dead-code"
  LLVM_PROFILE_FILE: "%p-%m.profraw"
  CFLAGS: "--coverage -O0 -g"
  LDFLAGS: "--coverage -g"

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
      - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v6
        with:
          python-version: "3.10"
      - name: Restore test durations cache
        uses: actions/cache/restore@v5
        with:
          path: test_durations.json
          key: cov-test-durations-${{ github.sha }}
          restore-keys: cov-test-durations-
      - name: Build
        run: uv sync -v -p 3.10 --all-groups
      - name: Archive environment
        run: tar -cpf /tmp/env.tzst --absolute-names --zstd $PWD $UV_CACHE_DIR $UV_PYTHON_INSTALL_DIR
      - name: Upload environment artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
        with:
          name: env
          path: /tmp/env.tzst
          if-no-files-found: error
          compression-level: 0

  test:
    needs: [build]
    name: Test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        runner_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    steps:
      - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          name: env
          path: .
      - name: Extract environment
        run: |
          tar -xpf $PWD/env.tzst -C /
          rm env.tzst
      - name: Download test binaries
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
        with:
          repository: angr/binaries
          path: binaries
      - name: Relocate binaries directory
        run: mv binaries ..
      - name: Run tests
        run: |
          source .venv/bin/activate
          pytest -vv \
            -n auto --splits 10 --group ${{ matrix.runner_id }} --splitting-algorithm=least_duration \
            --cov=angr --cov=tests --cov-report=xml \
            --junitxml=junit.xml -o junit_family=legacy \
            --store-durations --clean-durations --durations-path=test_durations.json \
            || [[ $? -lt 2 ]]  # Accept success and test failures, fail on infrastructure problems (exit codes >1)
          gcovr -r native --print-summary --xml-pretty -o coverage-native.xml
      - name: Check for results
        run: |
          [[ -e junit.xml && -e coverage.xml && -e coverage-native.xml && -e test_durations.json ]]
      - name: Upload results artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: results-${{ matrix.runner_id }}
          if-no-files-found: error
          path: |
            ./junit.xml
            ./coverage.xml
            ./coverage-native.xml
            ./test_durations.json
            ./*.profraw

  test_rust:
    name: Test Rust packages
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
      - uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1
      - uses: taiki-e/install-action@710817a1645ef40daad5bcde7431ceccf6cc3528 # v2
        with:
          tool: cargo-llvm-cov
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5
        with:
          python-version: "3.10"
      - name: Run tests
        run: |
          cargo test --release
          cargo llvm-cov --lcov --output-path lcov.info
      - name: Upload results artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: results-rust
          if-no-files-found: error
          path: lcov.info

  report:
      name: Report
      runs-on: ubuntu-latest
      permissions:
        id-token: write
        checks: write
      needs: [test, test_rust]
      steps:
      - name: Download environment
        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          name: env
          path: .
      - name: Extract environment
        run: |
          tar -xpf $PWD/env.tzst -C /
          rm env.tzst
      - name: Download results
        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          pattern: "results-*"
          path: results
      - name: Combine Rust coverage
        run: |
          find results

          # XXX: Workaround slow `Processing triggers for man-db...`
          echo 'set man-db/auto-update false' | sudo debconf-communicate
          sudo dpkg-reconfigure man-db

          sudo apt-get install -qq llvm-20 llvm-20-tools
          echo "Merging coverage..."
          llvm-profdata-20 merge -sparse $(find . -name '*.profraw') -o default.profdata
          echo "Generating report..."
          llvm-cov-20 report ./angr/rustylib*.so -instr-profile=default.profdata --sources $(find native -name "*.rs")
          llvm-cov-20 export ./angr/rustylib*.so -instr-profile=default.profdata --sources $(find native -name "*.rs") --format=lcov > lcov-py.info
      - name: Collect results
        id: files
        run: |
          coverage=$(find . -type f \( -name 'coverage*.xml' -o -name 'lcov*.info' \) | paste -sd, -)
          echo "coverage=$coverage" >> "$GITHUB_OUTPUT"
          junit=$(find . -type f -name 'junit.xml' | paste -sd, -)
          echo "junit=$junit" >> "$GITHUB_OUTPUT"
      - name: Upload test coverage to Codecov
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
        with:
          use_oidc: true
          verbose: true
          files: ${{ steps.files.outputs.coverage }}
      - name: Upload test results to Codecov
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
        with:
          use_oidc: true
          fail_ci_if_error: true
          verbose: true
          files: ${{ steps.files.outputs.junit }}
          report_type: test_results
      - name: Combine test durations
        run: jq -Ss 'add' results/**/test_durations.json > test_durations.json
      - name: Upload combined test durations artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: test_durations
          path: ./test_durations.json
          if-no-files-found: error
      - name: Update test durations cache
        uses: actions/cache/save@v5
        with:
          path: test_durations.json
          key: cov-test-durations-${{ github.sha }}
      - name: Publish test results
        uses: EnricoMi/publish-unit-test-result-action@27d65e188ec43221b20d26de30f4892fad91df2f # v2
        with:
          files: "results/**/junit.xml"
          action_fail_on_inconclusive: true
          comment_mode: off  # codecov already leaves a comment, and more details
                             # from this action can be found in the check it creates.

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: Test with coverage
 
on:
  push:
    branches:
      - master
  pull_request:
  workflow_dispatch:
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
env:
  SKIP_SLOW_TESTS: 1
  RUSTFLAGS: "-C instrument-coverage -C codegen-units=1 -C link-dead-code"
  LLVM_PROFILE_FILE: "%p-%m.profraw"
  CFLAGS: "--coverage -O0 -g"
  LDFLAGS: "--coverage -g"
 
jobs:
  build:
    timeout-minutes: 30
    name: Build
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
      - uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v6
        with:
          python-version: "3.10"
      - name: Restore test durations cache
        uses: actions/cache/restore@v5
        with:
          path: test_durations.json
          key: cov-test-durations-${{ github.sha }}
          restore-keys: cov-test-durations-
      - name: Build
        run: uv sync -v -p 3.10 --all-groups
      - name: Archive environment
        run: tar -cpf /tmp/env.tzst --absolute-names --zstd $PWD $UV_CACHE_DIR $UV_PYTHON_INSTALL_DIR
      - name: Upload environment artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
        with:
          name: env
          path: /tmp/env.tzst
          if-no-files-found: error
          compression-level: 0
 
  test:
    timeout-minutes: 30
    needs: [build]
    name: Test
    runs-on: latchkey-small
    strategy:
      matrix:
        runner_id: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    steps:
      - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          name: env
          path: .
      - name: Extract environment
        run: |
          tar -xpf $PWD/env.tzst -C /
          rm env.tzst
      - name: Download test binaries
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
        with:
          repository: angr/binaries
          path: binaries
      - name: Relocate binaries directory
        run: mv binaries ..
      - name: Run tests
        run: |
          source .venv/bin/activate
          pytest -vv \
            -n auto --splits 10 --group ${{ matrix.runner_id }} --splitting-algorithm=least_duration \
            --cov=angr --cov=tests --cov-report=xml \
            --junitxml=junit.xml -o junit_family=legacy \
            --store-durations --clean-durations --durations-path=test_durations.json \
            || [[ $? -lt 2 ]]  # Accept success and test failures, fail on infrastructure problems (exit codes >1)
          gcovr -r native --print-summary --xml-pretty -o coverage-native.xml
      - name: Check for results
        run: |
          [[ -e junit.xml && -e coverage.xml && -e coverage-native.xml && -e test_durations.json ]]
      - name: Upload results artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: results-${{ matrix.runner_id }}
          if-no-files-found: error
          path: |
            ./junit.xml
            ./coverage.xml
            ./coverage-native.xml
            ./test_durations.json
            ./*.profraw
 
  test_rust:
    timeout-minutes: 30
    name: Test Rust packages
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
      - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
      - uses: actions-rust-lang/setup-rust-toolchain@1780873c7b576612439a134613cc4cc74ce5538c # v1
      - uses: taiki-e/install-action@710817a1645ef40daad5bcde7431ceccf6cc3528 # v2
        with:
          tool: cargo-llvm-cov
      - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5
        with:
          cache: 'pip'
          python-version: "3.10"
      - name: Run tests
        run: |
          cargo test --release
          cargo llvm-cov --lcov --output-path lcov.info
      - name: Upload results artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: results-rust
          if-no-files-found: error
          path: lcov.info
 
  report:
    timeout-minutes: 30
      name: Report
      runs-on: latchkey-small
      permissions:
        id-token: write
        checks: write
      needs: [test, test_rust]
      steps:
      - name: Download environment
        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          name: env
          path: .
      - name: Extract environment
        run: |
          tar -xpf $PWD/env.tzst -C /
          rm env.tzst
      - name: Download results
        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          pattern: "results-*"
          path: results
      - name: Combine Rust coverage
        run: |
          find results
 
          # XXX: Workaround slow `Processing triggers for man-db...`
          echo 'set man-db/auto-update false' | sudo debconf-communicate
          sudo dpkg-reconfigure man-db
 
          sudo apt-get install -qq llvm-20 llvm-20-tools
          echo "Merging coverage..."
          llvm-profdata-20 merge -sparse $(find . -name '*.profraw') -o default.profdata
          echo "Generating report..."
          llvm-cov-20 report ./angr/rustylib*.so -instr-profile=default.profdata --sources $(find native -name "*.rs")
          llvm-cov-20 export ./angr/rustylib*.so -instr-profile=default.profdata --sources $(find native -name "*.rs") --format=lcov > lcov-py.info
      - name: Collect results
        id: files
        run: |
          coverage=$(find . -type f \( -name 'coverage*.xml' -o -name 'lcov*.info' \) | paste -sd, -)
          echo "coverage=$coverage" >> "$GITHUB_OUTPUT"
          junit=$(find . -type f -name 'junit.xml' | paste -sd, -)
          echo "junit=$junit" >> "$GITHUB_OUTPUT"
      - name: Upload test coverage to Codecov
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
        with:
          use_oidc: true
          verbose: true
          files: ${{ steps.files.outputs.coverage }}
      - name: Upload test results to Codecov
        uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
        with:
          use_oidc: true
          fail_ci_if_error: true
          verbose: true
          files: ${{ steps.files.outputs.junit }}
          report_type: test_results
      - name: Combine test durations
        run: jq -Ss 'add' results/**/test_durations.json > test_durations.json
      - name: Upload combined test durations artifact
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
        with:
          name: test_durations
          path: ./test_durations.json
          if-no-files-found: error
      - name: Update test durations cache
        uses: actions/cache/save@v5
        with:
          path: test_durations.json
          key: cov-test-durations-${{ github.sha }}
      - name: Publish test results
        uses: EnricoMi/publish-unit-test-result-action@27d65e188ec43221b20d26de30f4892fad91df2f # v2
        with:
          files: "results/**/junit.xml"
          action_fail_on_inconclusive: true
          comment_mode: off  # codecov already leaves a comment, and more details
                             # from this action can be found in the check it creates.

What changed

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 4 jobs (13 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow