Skip to content

Merge pull request #1245 from ThrowTheSwitch/perf-c-extractor-code-text #431

Merge pull request #1245 from ThrowTheSwitch/perf-c-extractor-code-text

Merge pull request #1245 from ThrowTheSwitch/perf-c-extractor-code-text #431

Workflow file for this run

# =========================================================================
# Ceedling - Test-Centered Build System for C
# ThrowTheSwitch.org
# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================
# -----------------------------------------------------------------------
# Continuous Integration Workflow
#
# Purpose:
# Run the full test suite and verify a clean gem build on every push.
# No releases are created here — publishing is handled by prerelease.yml
# and release.yml, which trigger only on intentional Git tags.
#
# Triggers:
# - Push to any branch
# - Pull request targeting any branch (a PR can be opened against any
# integration branch, not just master, as the project matures multiple
# branches at once)
# - Manual dispatch (workflow_dispatch)
#
# Skip-CI:
# Add any of the following to your commit message to skip this workflow:
# [skip ci] [ci skip] [no ci] [skip actions] [actions skip]
# Note: skip keywords have no effect on workflow_dispatch runs.
#
# Branch exclusions:
# To permanently exclude a branch pattern from CI, add a branches-ignore
# list under the push: trigger below (e.g. docs/**, wip/**).
# -----------------------------------------------------------------------
---
name: CI
# Triggers the workflow on push to any branch (tag pushes excluded — handled by
# prerelease.yml and release.yml), pull requests targeting any branch, or manual dispatch
on:
push:
branches:
- '**' # All branches; branches: ['**'] scopes push to refs/heads/ only,
# excluding refs/tags/ pushes — see Skip-CI above
pull_request:
# No branch restriction: a PR merging into any branch gets checks. The one
# exclusion is gh-pages, which only ever receives generated site output and
# is never itself the target of a real code review.
branches-ignore: [gh-pages]
workflow_dispatch:
# Cancel any in-progress run for the same unit of work when a new event arrives.
# A push to a branch that also has an open PR fires both the push and pull_request
# triggers for the very same commit; github.head_ref carries the bare source branch
# name for a pull_request event and is empty for a push, while github.ref_name carries
# that same bare branch name for a push (and an unrelated merge-ref name for a
# pull_request, which is why it's only ever reached as the fallback here) -- so
# `head_ref || ref_name` resolves to the identical string for both event types on the
# same branch, landing both runs in one group so the second to start cancels the first.
concurrency:
group: ci-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Job: Build MkDocs HTML documentation bundle for gem inclusion
generate-docs:
name: "Generate Local Docs Bundle for Gem Inclusion"
uses: ./.github/workflows/_generate-docs.yml
# Job: Linux test suite
tests-linux:
name: "Linux Test Suite"
needs: generate-docs
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby: ['3.0', '3.1', '3.2', '3.3', '3.4', '3.5']
steps:
# Checks out repository under $GITHUB_WORKSPACE — must come first to enable local action access
#
# checkout/cache/upload-artifact/download-artifact throughout this workflow are pinned
# to their latest majors (not v4) for Node 24 runner support -- GitHub Actions runners
# now force Node 24 regardless, and v4 still targets the deprecated Node 20.
- name: Checkout Latest Repo
uses: actions/checkout@v7
with:
submodules: recursive
# Common set up steps
- name: Set Up Ruby Test Environment
uses: ./.github/actions/setup
with:
ruby: ${{ matrix.ruby }}
# Workaround: Fix Ruby toolcache Gem directory permissions (Bundler exit 38)
# This is an environmental Ruby/toolcache issue, not a Bundler issue; the fix must
# run after setup-ruby (which populates the toolcache) but before bundle install.
# Issue: https://github.com/rubygems/rubygems/issues/7983
- name: Apply Workaround for Ruby toolcache Gem Directory Permissions
run: |
sudo chmod -R go-w /opt/hostedtoolcache/Ruby/**/**/lib/ruby/gems/**/gems || true
# Install Gem Dependencies
- name: Install Gem Dependencies
# Ensure local installation to prevent system directory permission problems
run: |
bundle install
# Run unit tests immediately after gem install — before the slower external tool
# installation steps — so unit failures surface as quickly as possible.
# Unit tests have no dependency on gdb, valgrind, cppcheck, gcovr, or ReportGenerator.
#
# Coverage instrumentation (CEEDLING_TEST_COVERAGE) only runs on one leg of this
# matrix -- Ruby 3.3, matching the other Linux-only singleton jobs' pinned version
# -- rather than a separate job, so the combined unit+system report reuses the
# tool installs this job already pays for instead of duplicating them. The value
# ('units' here, 'system' on the Run System Tests step below) tells spec_helper.rb
# which suite is instrumenting -- see spec_helper.rb's own comment for why a bare
# on/off flag isn't enough.
- name: Run Unit Tests
env:
CI_RSPEC_PROGRESS_FORMAT: true
CEEDLING_TEST_COVERAGE: ${{ matrix.ruby == '3.3' && 'units' || 'false' }}
run: rake specs:units
# Install gdb for backtrace feature testing
- name: Install gdb for Backtrace Feature Testing
run: |
sudo apt-get update -qq
sudo apt-get install --assume-yes --quiet gdb
# Install valgrind for Valgrind plugin testing
- name: Install valgrind for Valgrind Feature Testing
run: |
sudo apt-get update -qq
sudo apt-get install --assume-yes --quiet valgrind
# Build cppcheck from source at a pinned version so all plugin options
# (including SARIF, which requires 2.16+) are available for testing.
# The installed binary is cached by version to avoid rebuilding on every run.
#
# actions/cache's restore step runs unprivileged and needs write access to recreate
# cached paths on a fresh runner VM. /usr/local/bin is already writable on this runner
# image, but /usr/local/share is not -- extracting the cached cppcheck subtree into it
# fails with "Cannot mkdir: Permission denied" otherwise. This has to run before every
# restore, on every run, since each job starts on a fresh ephemeral VM with the base
# image's permissions reset -- nothing from a prior run's chown carries forward. Saving
# the cache itself only ever needed read access (root-owned files are world-readable),
# so this doesn't change how or where cppcheck ultimately gets installed below.
- name: Ensure cppcheck Cache Destination Is Writable
run: |
sudo mkdir -p /usr/local/share/cppcheck
sudo chown -R "$(id -u):$(id -g)" /usr/local/share/cppcheck
- name: Cache cppcheck binary and data files
id: cache-cppcheck
uses: actions/cache@v6
with:
path: |
/usr/local/bin/cppcheck
/usr/local/share/cppcheck
key: cppcheck-${{ env.CPPCHECK_VERSION }}-linux
env:
CPPCHECK_VERSION: '2.16.0'
- name: "Build and Install cppcheck for Tests of Ceedling Plugin: Cppcheck"
if: steps.cache-cppcheck.outputs.cache-hit != 'true'
env:
CPPCHECK_VERSION: '2.16.0'
run: |
sudo apt-get install --assume-yes --quiet cmake
wget -q https://github.com/danmar/cppcheck/archive/refs/tags/${CPPCHECK_VERSION}.tar.gz -O cppcheck.tar.gz
tar xzf cppcheck.tar.gz
cmake -S cppcheck-${CPPCHECK_VERSION} -B cppcheck-build -DCMAKE_BUILD_TYPE=Release -DUSE_MATCHCOMPILER=ON -DFILESDIR=/usr/local/share/cppcheck
cmake --build cppcheck-build --parallel $(nproc)
sudo cmake --install cppcheck-build
# --break-system-packages is required on Ubuntu 24.04+
# (PEP 668 prevents pip from installing to the system Python environment without explicit opt-in)
- name: "Install GCovr for Tests of Ceedling Plugin: Gcov"
run: |
pip install --break-system-packages gcovr
# Install ReportGenerator for Gcov plugin
# Fix PATH before tool installation
# https://stackoverflow.com/questions/59010890/github-action-how-to-restart-the-session
- name: "Install ReportGenerator for Tests of Ceedling Plugin: Gcov"
run: |
mkdir --parents $HOME/.dotnet/tools
echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
dotnet tool install --global dotnet-reportgenerator-globaltool
# Run system tests via the run-system-tests action (CI batch debug mode: failures only preserved)
- name: Run System Tests
uses: ./.github/actions/run-system-tests
with:
ruby: ${{ matrix.ruby }}
coverage: ${{ matrix.ruby == '3.3' && 'system' || 'false' }}
# Common plugin test steps. Runs before Generate Combined Coverage Report below --
# each plugin's own coverage/raw/system-*/ resultsets (see run-plugin-tests'
# own coverage input) must exist on disk before that step merges/formats the
# report, or they'd be silently excluded from it.
- name: Run Plugin Tests
uses: ./.github/actions/run-plugin-tests
with:
coverage: ${{ matrix.ruby == '3.3' && 'system' || 'false' }}
# Merges the resultset all prior coverage-instrumented steps accumulated (see
# Run Unit Tests, Run System Tests, and Run Plugin Tests above) into one HTML
# report and uploads it, downloadable from this run's own Actions summary page.
# Ruby-3.3-only, same leg those steps instrumented.
- name: Generate Combined Coverage Report
if: matrix.ruby == '3.3'
run: rake coverage:report
- name: Upload Combined Coverage Report
if: matrix.ruby == '3.3'
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: coverage/
if-no-files-found: error
# Job: Windows test suite
tests-windows:
name: "Windows Test Suite"
needs: generate-docs
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
# Ruby 3.5 not available on Windows runners [July 2026]
ruby: ['3.0', '3.1', '3.2', '3.3', '3.4']
steps:
# Checks out repository under $GITHUB_WORKSPACE — must come first to enable local action access
- name: Checkout Latest Repo
uses: actions/checkout@v7
with:
submodules: recursive
# Windows Defender real-time monitoring scans every file touched during gem installation
# and C compilation, adding 5–15 minutes of overhead per run. Disable it for CI performance.
- name: Disable Windows Defender real-time monitoring
shell: powershell
run: Set-MpPreference -DisableRealtimeMonitoring $true
# Common set up steps
- name: Set Up Ruby Test Environment
uses: ./.github/actions/setup
with:
ruby: ${{ matrix.ruby }}
# Install Gem Dependencies
- name: Install Gem Dependencies
# Ensure local installation to prevent system directory permission problems
run: |
bundle install
# Run unit tests immediately after gem install — before the slower external tool
# installation steps — so unit failures surface as quickly as possible.
# Unit tests have no dependency on gcovr, ReportGenerator, or cppcheck.
- name: Run Unit Tests
env:
CI_RSPEC_PROGRESS_FORMAT: true
run: rake specs:units
# Install GCovr for Gcov plugin test
- name: "Install GCovr for Tests of Ceedling Plugin: Gcov"
run: |
pip install gcovr
# Install ReportGenerator for Gcov plugin test
- name: "Install ReportGenerator for Tests of Ceedling Plugin: Gcov"
run: |
dotnet tool install --global dotnet-reportgenerator-globaltool
# Install cppcheck via Chocolatey (installs the MSI to C:\Program Files\Cppcheck\).
# Add the real install directory to PATH ahead of the Chocolatey shim so that
# cppcheck can locate its cfg/ directory (needed for std.cfg library lookups).
# Pinned to 2.16.0 (matches the version Linux CI builds from source):
# - Chocolatey's 2.14.0 package has a broken, winlibs-baked FILESDIR path
# and appears to have been delisted upstream.
# - Ceedling's cppcheck plugin defaults :xml_report_version to 3
# (plugins/cppcheck/config/defaults.yml), which cppcheck only supports
# starting at 2.16.0 -- anything older errors on --xml-version=3.
- name: "Install cppcheck for Tests of Ceedling Plugin: Cppcheck"
run: |
choco upgrade cppcheck -y --version=2.16.0
echo "C:\Program Files\Cppcheck" >> $env:GITHUB_PATH
# Run system tests via the run-system-tests action (CI batch debug mode: failures only preserved)
- name: Run System Tests
uses: ./.github/actions/run-system-tests
with:
ruby: ${{ matrix.ruby }}
# Common plugin test steps
- name: Run Plugin Tests
uses: ./.github/actions/run-plugin-tests
# Job: macOS test suite
# Targets Apple Clang (the gcc alias on macOS resolves to Xcode's LLVM-based Clang).
# Homebrew GCC is effectively covered by the Linux job, so no compiler matrix is needed.
# Ruby matrix starts at 3.1: macos-latest runs on Apple Silicon (ARM64), and ruby/setup-ruby
# has no pre-built binary for Ruby 3.0 on ARM64.
# Note for the future: Ruby 3.1 will itself be dropped starting with macOS 26.
tests-macos:
name: "macOS Test Suite"
needs: generate-docs
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
ruby: ['3.1', '3.2', '3.3', '3.4', '3.5']
steps:
# Checks out repository under $GITHUB_WORKSPACE — must come first to enable local action access
- name: Checkout Latest Repo
uses: actions/checkout@v7
with:
submodules: recursive
# Common set up steps
- name: Set Up Ruby Test Environment
uses: ./.github/actions/setup
with:
ruby: ${{ matrix.ruby }}
# No toolcache permissions workaround — that step targets /opt/hostedtoolcache (Linux-specific path)
# Install Gem Dependencies
# force_ruby_platform bypasses Bundler's multi-platform gem resolution, which fails on
# macOS ARM64 due to mixed versioned/generic platform strings in the lockfile.
# All Ceedling gem dependencies are pure Ruby, so this has no functional effect.
- name: Install Gem Dependencies
run: |
bundle config set --local force_ruby_platform true
bundle install
# Run unit tests immediately after gem install — before the slower external tool
# installation steps — so unit failures surface as quickly as possible.
# Unit tests have no dependency on cppcheck, gcovr, or ReportGenerator.
- name: Run Unit Tests
env:
CI_RSPEC_PROGRESS_FORMAT: true
run: rake specs:units
# gdb is not installed on macOS runners — backtrace specs detect absence and skip
# valgrind is unavailable on macOS — valgrind specs already skip via @valgrind_available guard
# Install cppcheck via Homebrew
#
# aws/tap is pre-tapped on GitHub's hosted macOS runner image (unrelated to anything
# this repo uses) -- newer Homebrew warns about every untrusted tap present on any
# `brew install`, not just ones involved in that install, so it's untapped here rather
# than trusted or having tap-trust checks disabled outright. `|| true` in case a future
# runner image doesn't have it pre-tapped.
- name: "Install cppcheck for Tests of Ceedling Plugin: Cppcheck"
run: |
brew untap aws/tap || true
brew install cppcheck
# --break-system-packages required on macOS 14+ (PEP 668 prevents pip from installing
# to the system Python environment without explicit opt-in, same as Ubuntu 24.04+)
- name: "Install GCovr for Tests of Ceedling Plugin: Gcov"
run: |
pip install --break-system-packages gcovr
# Install ReportGenerator for Gcov plugin
# Fix PATH before tool installation
# https://stackoverflow.com/questions/59010890/github-action-how-to-restart-the-session
- name: "Install ReportGenerator for Tests of Ceedling Plugin: Gcov"
run: |
mkdir -p $HOME/.dotnet/tools
echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
dotnet tool install --global dotnet-reportgenerator-globaltool
# Run system tests via the run-system-tests action (CI batch debug mode: failures only preserved)
- name: Run System Tests
uses: ./.github/actions/run-system-tests
with:
ruby: ${{ matrix.ruby }}
# Common plugin test steps
- name: Run Plugin Tests
uses: ./.github/actions/run-plugin-tests
# Job: Preprocessing locale encoding regression test
# Installs ja_JP.UTF-8 locale and runs the locale encoding spec under it.
# This replicates issue #1160's root cause: GCC under a non-C locale outputs
# localized preprocessor markers (e.g. <組み込み> for <built-in>) that Ceedling
# must handle without raising encoding errors.
# Note: No generate-docs dependency or docs download needed — system tests use
# a path-based Bundler deployment (deploy_gem) that does not require a gem build.
tests-linux-locale:
name: "Linux Test Suite (ja_JP.UTF-8 Locale)"
runs-on: ubuntu-latest
steps:
- uses: actions/cache@v6
with:
path: vendor/bundle
key: bundle-use-ruby-${{ runner.os }}-3.3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
bundle-use-ruby-${{ runner.os }}-3.3-
- name: Checkout Latest Repo
uses: actions/checkout@v7
with:
submodules: recursive
- name: Set Up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
# Workaround: fix Ruby toolcache gem dir permissions (Bundler exit 38)
# Issue: https://github.com/rubygems/rubygems/issues/7983
- name: Apply Workaround for Ruby toolcache Gem Directory Permissions
run: |
sudo chmod -R go-w /opt/hostedtoolcache/Ruby/**/**/lib/ruby/gems/**/gems || true
- name: Install Gem Dependencies
run: |
bundle install
- name: Install ja_JP.UTF-8 Locale
run: |
sudo locale-gen ja_JP.UTF-8
sudo update-locale
locale -a
- name: Run Preprocessing Locale Spec under ja_JP.UTF-8
env:
LC_ALL: ja_JP.UTF-8
LANG: ja_JP.UTF-8
CI_RSPEC_PROGRESS_FORMAT: true
run: |
rake spec:system:debug:preprocessing_locale
# Passing project directories are deleted immediately by done!; passing logs are never written.
# This step is retained as a safety net in case of any leftover pass artifacts.
- name: Remove passing system test artifacts before upload
if: failure()
shell: bash
run: |
rm -rf systests/proj/pass || true
rm -f systests/systest.pass.*.log || true
# Upload system test failure logs and temp project directories on test job failure
- name: Upload system test failure artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: systest-failures-locale-ruby-3.3
path: systests/
if-no-files-found: ignore
# Job: Default-encoding stress test against example projects
# Forces Ruby's default external encoding to US-ASCII (via LC_ALL=C) and exports +
# fully tests example projects with differing preprocessing configurations, catching
# regressions in Ceedling's handling of non-ASCII source content under a non-UTF-8
# platform default encoding.
# Note: No generate-docs dependency needed — system tests use a path-based Bundler
# deployment (deploy_gem) that does not require a gem build.
tests-linux-encoding-stress:
name: "Linux Encoding Stress Test (C/POSIX)"
runs-on: ubuntu-latest
steps:
- uses: actions/cache@v6
with:
path: vendor/bundle
key: bundle-use-ruby-${{ runner.os }}-3.3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
bundle-use-ruby-${{ runner.os }}-3.3-
- name: Checkout Latest Repo
uses: actions/checkout@v7
with:
submodules: recursive
- name: Set Up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
- name: Apply Workaround for Ruby toolcache Gem Directory Permissions
run: |
sudo chmod -R go-w /opt/hostedtoolcache/Ruby/**/**/lib/ruby/gems/**/gems || true
- name: Install Gem Dependencies
run: |
bundle install
# LC_ALL/LANG=C (POSIX) is the stress pick: it's always available (no locale-gen
# needed, unlike the ja_JP.UTF-8 job above) and it's the locale under which Ruby's
# Encoding.default_external resolves to US-ASCII rather than UTF-8 on Linux. That's
# the actual condition under test — source files here are UTF-8, but the platform
# default encoding is 7-bit ASCII, which is what surfaces bugs in code that reads
# or scans file content without accounting for the platform's default encoding.
# These env vars set the job-level default before RSpec boots; the spec itself
# additionally re-asserts them per-invocation since SystemContext#with_context
# otherwise forces en_US.UTF-8 ahead of each nested Ceedling call.
- name: Run Example Projects Under C/POSIX Default Encoding Stress
env:
LC_ALL: C
LANG: C
CI_RSPEC_PROGRESS_FORMAT: true
run: |
rake spec:system:debug:encoding_stress
# Passing project directories are deleted immediately by done!; passing logs are never written.
# This step is retained as a safety net in case of any leftover pass artifacts.
- name: Remove passing system test artifacts before upload
if: failure()
shell: bash
run: |
rm -rf systests/proj/pass || true
rm -f systests/systest.pass.*.log || true
# Upload system test failure logs and temp project directories on test job failure
- name: Upload system test failure artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: systest-failures-encoding-stress-ruby-3.3
path: systests/
if-no-files-found: ignore
# Job: Build the Ceedling gem once all tests pass
# Verifies a clean gem build is possible; the built gem is uploaded as a
# downloadable workflow artifact. No release is created here.
build-gem:
name: "Validate Ceedling Gem Build"
needs:
- tests-linux
- tests-windows
- tests-macos
- tests-linux-locale
- tests-linux-encoding-stress
runs-on: ubuntu-latest
steps:
# Use a cache for our tools to speed up builds
# No matrix here; Ruby version is hardcoded to match the cache key format used by test jobs
- uses: actions/cache@v6
with:
path: vendor/bundle
key: bundle-use-ruby-${{ runner.os }}-3.3-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
bundle-use-ruby-${{ runner.os }}-3.3-
- name: Checkout Latest Repo
uses: actions/checkout@v7
with:
submodules: recursive
- name: Set Up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
- name: Install Gem Dependencies
run: |
bundle install
# Download HTML docs bundle built by generate-docs job to validate docs ingestion in gem build
- name: Download HTML Documentation Bundle
uses: actions/download-artifact@v8
with:
name: gem-docs-site-local
path: site-local/
- name: Build Ceedling Gem
run: |
gem build ceedling.gemspec
# Upload the built gem as a workflow artifact (downloadable from the Actions UI)
- name: Upload Built Gem Artifact
uses: actions/upload-artifact@v7
with:
name: ceedling-gem
path: ceedling-*.gem
if-no-files-found: error