Skip to content

[FIX] Make the dependent comparison audit archive-safe #204

[FIX] Make the dependent comparison audit archive-safe

[FIX] Make the dependent comparison audit archive-safe #204

Workflow file for this run

name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# ---------------------------------------------------------------------------
# Documentation must be self-contained and must match the code it describes.
# Both checks are cheap and need no build, so they run first on every push.
# ---------------------------------------------------------------------------
comment-style:
name: Comment style
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for unresolvable reference codes
run: python3 tools/check_comment_style.py
- name: Check the threading page against the code
run: python3 tools/verify_threading_doc.py
- name: Check dependent member comparisons
run: |
python3 tools/check_dependent_comparisons.py
python3 tools/check_dependent_comparisons.py --self-test
header-audit:
name: Header audit - ${{ matrix.compiler }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
compiler: [g++, clang++]
steps:
- uses: actions/checkout@v4
- name: Compile every installed header and umbrella profile
run: >
python3 tools/check_header_self_sufficiency.py
--compiler ${{ matrix.compiler }} --std c++20 --warnings-as-errors
- name: Prove the missing-include control
run: python3 tools/check_header_self_sufficiency.py --self-test-negative-control
# ---------------------------------------------------------------------------
# Build + run the public conformance suite on every platform
# ---------------------------------------------------------------------------
conformance:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { name: "Windows MSVC x64", os: windows-latest, compiler: msvc, msvc_arch: x64 }
# Native Windows-on-ARM: the one target where MSVC compiles the
# NEON kernel paths (its arm64_neon.h, not the GCC/Clang one).
- { name: "Windows MSVC arm64", os: windows-11-arm, compiler: msvc, msvc_arch: arm64 }
- { name: "Linux GCC x64", os: ubuntu-latest, compiler: g++ }
- { name: "Linux Clang x64", os: ubuntu-latest, compiler: clang++ }
- { name: "Linux GCC arm64", os: ubuntu-24.04-arm, compiler: g++ }
- { name: "macOS Clang arm64", os: macos-latest, compiler: clang++ }
steps:
- uses: actions/checkout@v4
- name: Set up MSVC environment
if: matrix.compiler == 'msvc'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc_arch }}
- name: Build (MSVC)
if: matrix.compiler == 'msvc'
shell: cmd
run: cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . conformance\dspark_conformance.cpp /Fe:dspark_conformance.exe
- name: Build (GCC/Clang)
if: matrix.compiler != 'msvc'
run: ${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . conformance/dspark_conformance.cpp -o dspark_conformance
- name: Run conformance suite
shell: bash
run: ./dspark_conformance*
# Default x64 builds stay on the SSE2 baseline, so the AVX2/FMA kernel
# paths would never execute in CI without a second pass. All GitHub
# hosted x64 runners provide AVX2.
- name: Build + run conformance on the AVX2/FMA paths (MSVC)
if: matrix.name == 'Windows MSVC x64'
shell: cmd
run: |
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /arch:AVX2 /I . conformance\dspark_conformance.cpp /Fe:dspark_conformance_avx2.exe
dspark_conformance_avx2.exe
- name: Build + run conformance on the AVX2/FMA paths (GCC/Clang)
if: matrix.name == 'Linux GCC x64' || matrix.name == 'Linux Clang x64'
run: |
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -march=x86-64-v3 -I . conformance/dspark_conformance.cpp -o dspark_conformance_avx2
./dspark_conformance_avx2
- name: Build example plugin (VST3+CLAP) + smoke hosts (MSVC)
if: matrix.name == 'Windows MSVC x64'
shell: cmd
run: |
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_saturator\saturator.cpp /Fe:DSParkSaturator.vst3
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . tools\vst3_smoke_host.cpp /Fe:vst3_smoke_host.exe
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . tools\clap_smoke_host.cpp /Fe:clap_smoke_host.exe
vst3_smoke_host.exe DSParkSaturator.vst3
copy DSParkSaturator.vst3 DSParkSaturator.clap
clap_smoke_host.exe DSParkSaturator.clap
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_template\plugin_template.cpp /Fe:DSParkTemplate.vst3
vst3_smoke_host.exe DSParkTemplate.vst3
copy DSParkTemplate.vst3 DSParkTemplate.clap
clap_smoke_host.exe DSParkTemplate.clap
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_ducker\ducker.cpp /Fe:DSParkDucker.vst3
vst3_smoke_host.exe DSParkDucker.vst3 --expect-sidechain
copy DSParkDucker.vst3 DSParkDucker.clap
clap_smoke_host.exe DSParkDucker.clap --expect-sidechain
rem Host-contract probe: transport, offline, sample-accurate
rem automation, MIDI + pitch bend, latency change, mono, presets.
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . tools\plugin_probe.cpp /Fe:DSParkProbe.vst3
vst3_smoke_host.exe DSParkProbe.vst3 --probe
copy DSParkProbe.vst3 DSParkProbe.clap
clap_smoke_host.exe DSParkProbe.clap --probe
rem Instrument reference: no audio in, note in, pitch + decay.
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_synth\synth.cpp /Fe:DSParkSynth.vst3
vst3_smoke_host.exe DSParkSynth.vst3 --expect-instrument
copy DSParkSynth.vst3 DSParkSynth.clap
clap_smoke_host.exe DSParkSynth.clap --expect-instrument
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /LD /I . examples\plugin_webview_editor\webview_saturator.cpp /Fe:DSParkWebSaturator.vst3
vst3_smoke_host.exe DSParkWebSaturator.vst3
copy DSParkWebSaturator.vst3 DSParkWebSaturator.clap
clap_smoke_host.exe DSParkWebSaturator.clap
cl /nologo /std:c++20 /EHsc /W4 /WX /O2 /wd4324 /I . tools\vst3_editor_host.cpp /Fe:vst3_editor_host.exe user32.lib
cmake -S examples\plugin_webview_files -B build_webgain
cmake --build build_webgain --config Release
vst3_smoke_host.exe build_webgain\DSParkWebGain.vst3\Contents\x86_64-win\DSParkWebGain.vst3
clap_smoke_host.exe build_webgain\DSParkWebGain.clap
- name: Build example plugin (VST3+CLAP) + smoke hosts (GCC/Clang)
if: matrix.compiler != 'msvc'
shell: bash
run: |
EXTRA=""
if [[ "$RUNNER_OS" == "macOS" ]]; then
# The AU macro in the shared translation unit references
# AudioToolbox symbols on Apple platforms.
EXTRA="-framework AudioToolbox -framework CoreFoundation"
fi
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_saturator/saturator.cpp -o DSParkSaturator.vst3 $EXTRA
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/vst3_smoke_host.cpp -o vst3_smoke_host -ldl
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/clap_smoke_host.cpp -o clap_smoke_host -ldl
./vst3_smoke_host ./DSParkSaturator.vst3
cp DSParkSaturator.vst3 DSParkSaturator.clap
./clap_smoke_host ./DSParkSaturator.clap
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_template/plugin_template.cpp -o DSParkTemplate.vst3
./vst3_smoke_host ./DSParkTemplate.vst3
cp DSParkTemplate.vst3 DSParkTemplate.clap
./clap_smoke_host ./DSParkTemplate.clap
# Sidechain reference plugin: bus layout + functional ducking proof
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_ducker/ducker.cpp -o DSParkDucker.vst3 $EXTRA
./vst3_smoke_host ./DSParkDucker.vst3 --expect-sidechain
cp DSParkDucker.vst3 DSParkDucker.clap
./clap_smoke_host ./DSParkDucker.clap --expect-sidechain
# Host-contract probe: transport, offline render mode,
# sample-accurate automation, MIDI + pitch bend, the
# latency-changed notification, mono negotiation, presets.
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . tools/plugin_probe.cpp -o DSParkProbe.vst3
./vst3_smoke_host ./DSParkProbe.vst3 --probe
cp DSParkProbe.vst3 DSParkProbe.clap
./clap_smoke_host ./DSParkProbe.clap --probe
# Instrument reference: no audio in, note in, pitch + decay.
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_synth/synth.cpp -o DSParkSynth.vst3 $EXTRA
./vst3_smoke_host ./DSParkSynth.vst3 --expect-instrument
cp DSParkSynth.vst3 DSParkSynth.clap
./clap_smoke_host ./DSParkSynth.clap --expect-instrument
# WebView editor example: WKWebView objc glue on macOS, stub on Linux
WEBVIEW_EXTRA=""
if [[ "$RUNNER_OS" == "macOS" ]]; then
WEBVIEW_EXTRA="-lobjc"
fi
${{ matrix.compiler }} -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fPIC -shared -I . examples/plugin_webview_editor/webview_saturator.cpp -o DSParkWebSaturator.vst3 $WEBVIEW_EXTRA
./vst3_smoke_host ./DSParkWebSaturator.vst3
cp DSParkWebSaturator.vst3 DSParkWebSaturator.clap
./clap_smoke_host ./DSParkWebSaturator.clap
# CMake plugin pipeline: separate-files UI embedding + bundle layouts
cmake -S examples/plugin_webview_files -B build_webgain \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }}
cmake --build build_webgain
if [[ "$RUNNER_OS" == "macOS" ]]; then
WEBGAIN_VST3=build_webgain/DSParkWebGain.vst3/Contents/MacOS/DSParkWebGain
WEBGAIN_CLAP=build_webgain/DSParkWebGain.clap/Contents/MacOS/DSParkWebGain
elif [[ "$(uname -m)" == "aarch64" ]]; then
WEBGAIN_VST3=build_webgain/DSParkWebGain.vst3/Contents/aarch64-linux/DSParkWebGain.so
WEBGAIN_CLAP=build_webgain/DSParkWebGain.clap
else
WEBGAIN_VST3=build_webgain/DSParkWebGain.vst3/Contents/x86_64-linux/DSParkWebGain.so
WEBGAIN_CLAP=build_webgain/DSParkWebGain.clap
fi
./vst3_smoke_host "$WEBGAIN_VST3"
./clap_smoke_host "$WEBGAIN_CLAP"
- name: X11 WebView editor smoke (Linux)
if: matrix.name == 'Linux GCC x64'
shell: bash
run: |
sudo apt-get update -q
sudo apt-get install -y -q xvfb libwebkit2gtk-4.1-0 \
|| sudo apt-get install -y -q xvfb libwebkit2gtk-4.0-37
g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/x11_editor_smoke.cpp \
-o x11_editor_smoke -ldl
# No GPU/DRI on CI runners: keep WebKit on the software paths.
export WEBKIT_DISABLE_COMPOSITING_MODE=1
export WEBKIT_DISABLE_DMABUF_RENDERER=1
xvfb-run -a ./x11_editor_smoke ./DSParkSaturator.vst3 --expect-no-editor
xvfb-run -a ./x11_editor_smoke ./DSParkWebSaturator.vst3 --expect-editor 560x330
- name: AUv2 bundle + auval (macOS)
if: matrix.name == 'macOS Clang arm64'
shell: bash
run: |
clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -bundle -I . \
examples/plugin_saturator/saturator.cpp \
-framework AudioToolbox -framework CoreFoundation \
-o DSParkSaturatorAU
COMP="$HOME/Library/Audio/Plug-Ins/Components/DSParkSaturator.component"
mkdir -p "$COMP/Contents/MacOS"
cp DSParkSaturatorAU "$COMP/Contents/MacOS/DSParkSaturator"
cp examples/plugin_saturator/au/Info.plist "$COMP/Contents/Info.plist"
codesign --force -s - "$COMP"
killall -9 AudioComponentRegistrar 2>/dev/null || true
auval -v aufx DSst DSpk
# The sidechain reference plugin: a second input element (auval
# validates the element/stream-format/render contract for both).
clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -bundle -I . \
examples/plugin_ducker/ducker.cpp \
-framework AudioToolbox -framework CoreFoundation \
-o DSParkDuckerAU
COMP2="$HOME/Library/Audio/Plug-Ins/Components/DSParkDucker.component"
mkdir -p "$COMP2/Contents/MacOS"
cp DSParkDuckerAU "$COMP2/Contents/MacOS/DSParkDucker"
cp examples/plugin_ducker/au/Info.plist "$COMP2/Contents/Info.plist"
codesign --force -s - "$COMP2"
killall -9 AudioComponentRegistrar 2>/dev/null || true
auval -v aufx DSdk DSpk
# And the CMake-built example (.component comes assembled + signed).
cp -R build_webgain/DSParkWebGain.component \
"$HOME/Library/Audio/Plug-Ins/Components/"
killall -9 AudioComponentRegistrar 2>/dev/null || true
auval -v aufx DSwg DSpk
# The instrument reference: an aumu music device (no input
# elements, MusicDevice MIDI selectors, factory presets).
clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -bundle -I . \
examples/plugin_synth/synth.cpp \
-framework AudioToolbox -framework CoreFoundation \
-o DSParkSynthAU
COMP3="$HOME/Library/Audio/Plug-Ins/Components/DSParkSynth.component"
mkdir -p "$COMP3/Contents/MacOS"
cp DSParkSynthAU "$COMP3/Contents/MacOS/DSParkSynth"
cp examples/plugin_synth/au/Info.plist "$COMP3/Contents/Info.plist"
codesign --force -s - "$COMP3"
killall -9 AudioComponentRegistrar 2>/dev/null || true
auval -v aumu DSsy DSpk
# WebView editor through the AU Cocoa contract: factory class, real
# WKWebView attach, bridge handshake, both teardown orders.
clang++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . tools/au_editor_smoke.cpp \
-framework AudioToolbox -framework CoreFoundation -framework AppKit \
-lobjc -o au_editor_smoke
./au_editor_smoke aufx DSst DSpk --expect-no-editor
./au_editor_smoke aufx DSwg DSpk --expect-editor 300x360
- name: Official validators (pluginval + clap-validator)
if: matrix.name == 'Windows MSVC x64' || matrix.name == 'Linux GCC x64' || matrix.name == 'macOS Clang arm64'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -e
mkdir -p validators
case "$RUNNER_OS" in
Windows) PV_PAT="*Windows*"; CV_PAT="*windows*" ;;
macOS) PV_PAT="*macOS*"; CV_PAT="*macos*" ;;
*) PV_PAT="*Linux*"; CV_PAT="*ubuntu*"
sudo apt-get update -q && sudo apt-get install -y -q xvfb ;;
esac
# Validator versions are PINNED so a third-party release cannot turn
# this gate red on its own. clap-validator's "0.4.1" (2026-07-22) is a
# development snapshot (assets are tagged 0.4.1-127-g152b982 and the
# files inside them are still named 0.3.2) that also repackaged Linux
# and macOS as a zip WRAPPING a tar.gz, which leaves the extraction
# below with no binary to find. Moving to it is a deliberate upgrade
# with its own findings to work through, not a drift.
gh release download v1.0.4 --repo Tracktion/pluginval --pattern "$PV_PAT" --dir validators
gh release download 0.3.2 --repo free-audio/clap-validator --pattern "$CV_PAT" --dir validators
for a in validators/*.zip validators/*.tar.gz; do
[ -e "$a" ] || continue
case "$a" in
*.zip)
# Git Bash ships GNU tar (no zip support); Windows' own
# bsdtar handles zips fine. unzip is present elsewhere.
if [[ "$RUNNER_OS" == "Windows" ]]; then
/c/Windows/System32/tar.exe -xf "$a" -C validators
else
unzip -oq "$a" -d validators
fi ;;
*.tar.gz) tar -xf "$a" -C validators ;;
esac
done
# Release archives differ in layout per OS; locate the binaries.
CLAPVAL=$(find validators -type f \( -name "clap-validator" -o -name "clap-validator.exe" \) | head -1)
if [[ "$RUNNER_OS" == "Windows" ]]; then
PLUGINVAL=validators/pluginval.exe
VST3S="DSParkSaturator.vst3 DSParkWebSaturator.vst3 DSParkDucker.vst3 DSParkSynth.vst3 DSParkProbe.vst3 build_webgain/DSParkWebGain.vst3"
CLAPS="DSParkSaturator.clap DSParkWebSaturator.clap DSParkDucker.clap DSParkSynth.clap DSParkProbe.clap build_webgain/DSParkWebGain.clap"
PREFIX=""
elif [[ "$RUNNER_OS" == "macOS" ]]; then
PLUGINVAL=validators/pluginval.app/Contents/MacOS/pluginval
chmod +x "$CLAPVAL"
# Plain-file modules are a Windows/Linux convenience; on macOS
# only the CMake-built proper bundles are loadable by validators.
VST3S="build_webgain/DSParkWebGain.vst3"
CLAPS="build_webgain/DSParkWebGain.clap"
PREFIX=""
else
PLUGINVAL=validators/pluginval
VST3S="build_webgain/DSParkWebGain.vst3"
CLAPS="DSParkSaturator.clap DSParkWebSaturator.clap DSParkDucker.clap DSParkSynth.clap DSParkProbe.clap build_webgain/DSParkWebGain.clap"
PREFIX="xvfb-run -a"
fi
for p in $VST3S; do
$PREFIX "$PLUGINVAL" --strictness-level 8 --skip-gui-tests --validate "$p"
done
"$CLAPVAL" validate $CLAPS
- name: Generate metrics table (KPI K3)
if: matrix.name == 'Linux GCC x64'
shell: bash
run: ./dspark_conformance* --metrics metrics.md
- name: Upload metrics table
if: matrix.name == 'Linux GCC x64'
uses: actions/upload-artifact@v4
with:
name: dspark-metrics-table
path: metrics.md
# ---------------------------------------------------------------------------
# Build + run the test suite on every platform
#
# Driven through CMake/CTest rather than a direct compiler invocation: it is
# the same path a contributor uses locally, so this job also proves the CMake
# integration itself. The suite builds with optimisations but WITHOUT NDEBUG
# (see tests/CMakeLists.txt) so the framework's own assertions stay live.
# ---------------------------------------------------------------------------
tests:
name: Tests - ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- { name: "Windows MSVC x64", os: windows-latest, msvc_arch: x64 }
- { name: "Windows MSVC arm64", os: windows-11-arm, msvc_arch: arm64 }
- { name: "Linux GCC x64", os: ubuntu-latest, cxx: g++ }
- { name: "Linux Clang x64", os: ubuntu-latest, cxx: clang++ }
- { name: "Linux GCC arm64", os: ubuntu-24.04-arm, cxx: g++ }
- { name: "macOS Clang arm64", os: macos-latest, cxx: clang++ }
steps:
- uses: actions/checkout@v4
- name: Set up MSVC environment
if: matrix.msvc_arch != ''
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc_arch }}
# Windows resolves to the Visual Studio generator, which is multi-config:
# the target architecture is stated explicitly rather than inherited.
- name: Configure (MSVC)
if: matrix.msvc_arch != ''
shell: bash
run: >
cmake -S . -B build
-A ${{ matrix.msvc_arch == 'arm64' && 'ARM64' || 'x64' }}
-DDSPARK_BUILD_TESTS=ON
-DDSPARK_BUILD_CONFORMANCE=OFF
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON
- name: Configure (GCC/Clang)
if: matrix.msvc_arch == ''
run: >
cmake -S . -B build
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }}
-DDSPARK_BUILD_TESTS=ON
-DDSPARK_BUILD_CONFORMANCE=OFF
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON
- name: Build
shell: bash
run: cmake --build build --config Release --parallel
# --test-output-size-failed 0: do not truncate the output of a failing
# test, so the case the runner died on is always identifiable.
- name: Test
shell: bash
run: >
ctest --test-dir build --build-config Release
--output-on-failure --test-output-size-failed 0
# ---------------------------------------------------------------------------
# TestIO process isolation and its real fixed-name collision control
# ---------------------------------------------------------------------------
testio-concurrency:
name: TestIO concurrent process isolation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run three complete suites from one working directory
run: >
python3 tools/run_concurrent_test_suites.py
--copies 3 --working-directory shared --expect-all-pass
- name: Prove fixed names collide under the same runner
run: >
python3 tools/run_concurrent_test_suites.py
--copies 3 --working-directory shared
--mutant fixed-testio-names --expect-collision
# ---------------------------------------------------------------------------
# AddressSanitizer + UndefinedBehaviorSanitizer + float-cast-overflow
# ---------------------------------------------------------------------------
sanitizers:
name: ASan + UBSan + float-cast-overflow (${{ matrix.name }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { name: GCC, compiler: g++ }
- { name: Clang, compiler: clang++ }
steps:
- uses: actions/checkout@v4
- name: Build with sanitizers
run: >
${{ matrix.compiler }} -std=c++20 -O1 -g
-Wall -Wextra -Wpedantic -Werror
-fsanitize=address,undefined,float-cast-overflow
-fno-sanitize-recover=all
-I . conformance/dspark_conformance.cpp -o dspark_conformance_san
- name: Run under sanitizers
run: ./dspark_conformance_san
- name: Prove float-cast-overflow instrumentation and flag dependence
run: |
python3 tools/verify_float_cast_overflow_sanitizer.py --compiler ${{ matrix.compiler }}
python3 tools/verify_float_cast_overflow_sanitizer.py --compiler ${{ matrix.compiler }} --self-test-flag-removal
# The suite reaches far more of the framework than the conformance
# checks do, so it is where a stray read or an unsigned overflow is
# most likely to surface.
- name: Build + run the test suite under sanitizers
run: |
cmake -S . -B build-san \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_COMPILER=${{ matrix.compiler }} \
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON \
-DDSPARK_BUILD_TESTS=ON -DDSPARK_BUILD_CONFORMANCE=ON \
-DDSPARK_VERIFY_FLOAT_CAST_OVERFLOW_SANITIZER=ON \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined,float-cast-overflow -fno-sanitize-recover=all"
cmake --build build-san --parallel
ctest --test-dir build-san --output-on-failure --test-output-size-failed 0
# ---------------------------------------------------------------------------
# ThreadSanitizer (Linux GCC) — data-race detection for the lock-free code
# (SpectrumAnalyzer triple buffer, SpscQueue, atomic parameter handoff). TSan
# models the C++11 memory model precisely, so it is the correct oracle for
# these paths; it CANNOT be combined with ASan/UBSan, hence a separate build.
# (It is unavailable on the local audit sandbox, where the kernel blocks the
# personality() syscall TSan needs at startup; it runs normally on the CI
# Linux runner.)
# ---------------------------------------------------------------------------
tsan:
name: ThreadSanitizer (Linux GCC)
runs-on: ubuntu-latest
# Bounds a pathological hang; a healthy full run fits well inside it
# (run 30712892449: tests 1-143 of the suite took ~8.6 min, dominated by
# the two seqlock stress cases; the remaining 547 cases are
# single-threaded and lighter).
timeout-minutes: 150
env:
TSAN_OPTIONS: halt_on_error=1
steps:
- uses: actions/checkout@v4
- name: Build with ThreadSanitizer
run: >
g++ -std=c++20 -O1 -g -Wall -Wextra -Wpedantic -Werror -Wno-error=tsan
-fsanitize=thread
-I . conformance/dspark_conformance.cpp -o dspark_conformance_tsan
- name: Run under ThreadSanitizer
run: ./dspark_conformance_tsan
# What this step actually proves concurrently: the suite's seven
# cross-thread cases over six hand-offs (SpinLock contention, SpscQueue
# producer/consumer, FIRFilter and Biquad seqlock publication,
# SpectrumAnalyzer triple buffer - counted once as a hand-off but
# covered by TWO cases, tear-free readout and paired-getter pointer
# lifetime -, AnalogRandom readout publication). Every other component
# runs single-threaded here, so for those TSan checks execution, not
# concurrency. The list above IS the per-hand-off statement: a hand-off
# that no threaded case exercises concurrently is unproven, and gets
# reported as unproven rather than presented as proven.
#
# Timeout: per-test wall times are wildly non-uniform under TSan. In
# run 30712892449 the first 143 cases took ~8.6 min, almost all of it
# in the two seqlock stress cases (atomic-dense, contended, the exact
# thing TSan instruments hardest); the 547 cases that followed them in
# THAT run held no threaded code and ~19x the plain numeric work of the
# first 143. The suite has since grown to 694 cases, four of them
# concurrency-related (three concurrency pins and a lock-free census),
# and one threaded case now sits past that boundary (the AnalogRandom
# readout pin). A fifth such case, the single-threaded prepare()
# exception-safety pin, is no longer part of the suite binary: it
# installs replacement allocation functions, which are program-wide, so
# it now builds and runs as its own executable (ctest test
# alloc_failure) to keep the sanitizers' allocation checks live for the
# suite. This step runs it too, at a cost of milliseconds. 5400 s still
# covers the derived worst case (~35 min) with >2x margin: every
# one of them is a bounded-loop case far lighter than the seqlock
# stress pair. Never trim the suite to fit the budget: raise the
# budget.
#
# ctest -V, not --output-on-failure: on success
# --output-on-failure suppresses the harness's per-case listing, so a
# green log was not self-evidencing. -V surfaces every
# "[ i/N ] case_name" line and the final "N tests | N passed" summary,
# making the green verdict checkable against the case inventory.
- name: Build + run the test suite under ThreadSanitizer
run: |
cmake -S . -B build-tsan \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DDSPARK_BUILD_TESTS=ON -DDSPARK_BUILD_CONFORMANCE=OFF \
-DCMAKE_CXX_FLAGS="-fsanitize=thread -Wno-error=tsan"
cmake --build build-tsan --parallel
ctest --test-dir build-tsan -V --timeout 5400
# ---------------------------------------------------------------------------
# WebAssembly (Emscripten) — compile and execute with node
# ---------------------------------------------------------------------------
wasm:
name: WebAssembly (Emscripten)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: mymindstorm/setup-emsdk@v14
- name: Build
run: >
em++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I .
-sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=4MB
conformance/dspark_conformance.cpp -o dspark_conformance.js
- name: Run with node
run: node dspark_conformance.js
# Second pass with Wasm SIMD128: Emscripten lowers the SSE2 intrinsic
# paths of SimdOps to v128 when built with -msimd128 -msse2.
- name: Build (Wasm SIMD128 through the SSE2 mapping)
run: >
em++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . -msimd128 -msse2
-sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=4MB
conformance/dspark_conformance.cpp -o dspark_conformance_simd.js
- name: Run with node (SIMD128)
run: node dspark_conformance_simd.js
# ---------------------------------------------------------------------------
# Embedded profile: -fno-exceptions -fno-rtti, no file IO
# ---------------------------------------------------------------------------
embedded-profile:
name: Embedded profile (no exceptions / no RTTI / no file IO)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Compile umbrella with embedded flags
run: |
cat > embedded_check.cpp << 'EOF'
#define DSPARK_NO_FILE_IO
#include "DSPark.h"
int main()
{
dspark::AudioSpec spec { 48000.0, 256, 2 };
dspark::Compressor<float> comp; comp.prepare(spec);
dspark::FilterEngine<float> filt; filt.prepare(spec);
dspark::AlgorithmicReverb<float> rev; rev.prepare(spec);
dspark::AudioBuffer<float> buf; buf.resize(2, 256);
comp.processBlock(buf.toView());
filt.processBlock(buf.toView());
rev.processBlock(buf.toView());
return buf.getChannel(0)[0] == buf.getChannel(0)[0] ? 0 : 1;
}
EOF
g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -fno-exceptions -fno-rtti -I . embedded_check.cpp -o embedded_check
./embedded_check
# ---------------------------------------------------------------------------
# Single-header amalgamation: generate, compile, publish artifact
# ---------------------------------------------------------------------------
amalgamate:
name: Single-header amalgamation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate
run: python3 tools/amalgamate.py dspark_single_header.hpp
- name: Compile the amalgamated header
run: |
cat > amalgam_check.cpp << 'EOF'
#include "dspark_single_header.hpp"
int main()
{
dspark::AudioSpec spec { 48000.0, 256, 2 };
dspark::Equalizer<float> eq; eq.prepare(spec);
dspark::AudioBuffer<float> buf; buf.resize(2, 256);
eq.processBlock(buf.toView());
return 0;
}
EOF
g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror amalgam_check.cpp -o amalgam_check
./amalgam_check
- uses: actions/upload-artifact@v4
with:
name: dspark_single_header
path: dspark_single_header.hpp
# ---------------------------------------------------------------------------
# Examples and benchmarks stay buildable
# ---------------------------------------------------------------------------
examples:
name: Examples + bench (compile)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build examples
run: |
for src in examples/*.cpp; do
echo "== $src"
g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . "$src" -o /dev/null -fsyntax-only
done
- name: Build and smoke-run bench
run: |
g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . bench/dspark_bench.cpp -o dspark_bench
./dspark_bench
- name: AVX2-without-FMA build check (FMA gating regression)
run: |
# -mavx2 alone must build: DSPARK_SIMD_FMA may only engage together
# with the fma target feature (or under MSVC, which permits the FMA
# intrinsics with /arch:AVX2). GCC hard-errors if this regresses.
g++ -std=c++20 -O1 -mavx2 -c -I . conformance/dspark_conformance.cpp -o /dev/null
# ---------------------------------------------------------------------------
# EBU R128 conformance against the official EBU loudness test set
# (downloaded from tech.ebu.ch with caching; never redistributed in-repo)
# ---------------------------------------------------------------------------
ebu-r128:
name: EBU R128 conformance (official test set)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache EBU test set
id: cache-ebu
uses: actions/cache@v4
with:
path: ebu-test-set
key: ebu-loudness-test-set-v05
- name: Download EBU test set (best effort)
if: steps.cache-ebu.outputs.cache-hit != 'true'
run: |
# tech.ebu.ch sits behind Cloudflare, which rejects curl's default
# User-Agent — and often datacenter IPs altogether. Best effort:
# when the download fails, the suite still runs and the EBU cases
# report SKIP (the vectors are verified locally and whenever the
# cache holds a copy). Never redistributed in-repo (EBU license).
curl -fL --retry 3 -A "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36" \
-o ebu.zip "https://tech.ebu.ch/files/live/sites/tech/files/shared/testmaterial/ebu-loudness-test-setv05.zip" \
&& unzip -tq ebu.zip \
&& mkdir -p ebu-test-set \
&& unzip -q ebu.zip -d ebu-test-set \
|| echo "::warning::EBU test set unavailable from this runner; EBU cases will be skipped"
- name: Build conformance suite
run: g++ -std=c++20 -O2 -Wall -Wextra -Wpedantic -Werror -I . conformance/dspark_conformance.cpp -o dspark_conformance
- name: Run conformance (EBU cases skip cleanly when the set is absent)
run: |
if [ -d ebu-test-set ] && ls ebu-test-set/**/*.wav >/dev/null 2>&1; then
DSPARK_EBU_TEST_SET=ebu-test-set ./dspark_conformance
else
./dspark_conformance
fi