Skip to content

Test Infrastructure Documentation

Overview

The kernel reference implementations use Catch2 v3.10.0 as the unit testing framework. This document explains how the test infrastructure is configured and how to use it.

Test Framework: Catch2

Why Catch2?

  • Header-only option for easy integration
  • BDD-style SECTION support for organized tests
  • Rich assertion macros with detailed error messages
  • Excellent performance - can run 1M+ assertions quickly
  • CMake integration via find_package
  • Industry standard for C++ unit testing

Installation

Catch2 is installed via vcpkg as part of the AudioLab manifest:

{
  "dependencies": [
    "catch2",
    // ... other dependencies
  ]
}

To ensure Catch2 is installed:

# From audio-lab root directory
C:/vcpkg/vcpkg.exe install

This will install all manifest dependencies, including Catch2 3.10.0, into:

c:\AudioDev\audio-lab\vcpkg_installed\x64-windows\

CMake Configuration

Finding Catch2

The CMakeLists.txt is configured to find Catch2 in the vcpkg_installed directory:

# Add vcpkg_installed path to CMAKE_PREFIX_PATH
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../../../vcpkg_installed/x64-windows")
    list(APPEND CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../../vcpkg_installed/x64-windows")
endif()

# Find Catch2 (try version 3, fall back to version 2)
if(BUILD_TESTS)
    find_package(Catch2 3 QUIET CONFIG)

    if(NOT Catch2_FOUND)
        find_package(Catch2 2 QUIET CONFIG)
    endif()

    if(Catch2_FOUND)
        message(STATUS "Catch2 ${Catch2_VERSION} found - building unit tests")

        # Test executable
        add_executable(test_kernels
            tests/oscillators/test_sine_kernel.cpp
        )

        target_link_libraries(test_kernels PRIVATE
            audiolab_kernels
            Catch2::Catch2WithMain
        )

        enable_testing()
        add_test(NAME KernelTests COMMAND test_kernels)
        add_test(NAME KernelTestsVerbose COMMAND test_kernels -s)
    endif()
endif()

Key Points

  1. CMAKE_PREFIX_PATH: We add the vcpkg_installed directory so CMake can find Catch2Config.cmake
  2. Catch2::Catch2WithMain: We use the variant with main() included, so test files don't need to define main()
  3. BUILD_TESTS option: Tests can be disabled with -DBUILD_TESTS=OFF
  4. Two test targets: Normal and verbose output

Building Tests

Configure CMake

cd "3 - COMPONENTS/05_MODULES/05_15_REFERENCE_IMPLEMENTATIONS/05_15_01_kernel_references"
mkdir build
cd build

# Configure with Release build
"C:\Program Files\CMake\bin\cmake.exe" -S .. -B . -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON

Output should show:

-- Catch2 3.10.0 found - building unit tests

Build

# Build all targets
"C:\Program Files\CMake\bin\cmake.exe" --build . --config Release -j 8

# Or build just the tests
"C:\Program Files\CMake\bin\cmake.exe" --build . --config Release --target test_kernels -j 8

Running Tests

Method 1: Direct Execution

cd build
./Release/test_kernels.exe

Output:

===============================================================================
All tests passed (1046459 assertions in 13 test cases)

Method 2: CTest

cd build
"C:\Program Files\CMake\bin\ctest.exe" -C Release --output-on-failure

Output:

Test project C:/.../build
    Start 1: KernelTests
1/2 Test #1: KernelTests ......................   Passed    0.09 sec
    Start 2: KernelTestsVerbose
2/2 Test #2: KernelTestsVerbose ...............   Passed    8.19 sec

100% tests passed, 0 tests failed out of 2

Method 3: Catch2 Filters

Catch2 supports filtering tests by tags:

# Run only construction tests
./Release/test_kernels.exe "[construction]"

# Run only quality tests
./Release/test_kernels.exe "[quality]"

# Run with verbose output
./Release/test_kernels.exe -s

# List all test cases
./Release/test_kernels.exe --list-tests

# List all tags
./Release/test_kernels.exe --list-tags

Writing Tests

Test File Structure

#include "kernels/oscillators/sine_kernel.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>

using namespace audiolab::kernels;
using Catch::Approx;

TEST_CASE("Component - Feature", "[tag1][tag2]") {
    SECTION("Specific behavior") {
        // Setup
        SineKernel osc(44100.0);

        // Exercise
        osc.setFrequency(440.0);
        float output = osc.tick();

        // Verify
        REQUIRE(output >= -1.0f);
        REQUIRE(output <= 1.0f);
    }
}

Best Practices

  1. Use descriptive test names: "SineKernel - Output Quality" not "Test1"
  2. Tag appropriately: [construction], [quality], [performance], [edge-cases]
  3. One concept per SECTION: Each SECTION should test one thing
  4. Use Approx for floats: REQUIRE(value == Approx(expected).margin(tolerance))
  5. Test edge cases: Zero frequency, Nyquist, negative values, etc.
  6. Document floating-point quirks: Add comments when tolerances seem large

Common Macros

// Assertions
REQUIRE(condition);                  // Must pass
CHECK(condition);                    // Can fail without stopping test

// Floating-point comparisons
REQUIRE(value == Approx(expected));
REQUIRE(value == Approx(expected).margin(0.01));
REQUIRE(value == Approx(expected).epsilon(0.001));

// Exception testing
REQUIRE_THROWS(expression);
REQUIRE_THROWS_AS(expression, std::exception);
REQUIRE_NOTHROW(expression);

Test Coverage Goals

For Gold Certification (as documented in 05_15_00), tests must achieve:

  • >95% code coverage
  • All public API tested
  • Edge cases covered
  • Performance characteristics verified
  • Numerical stability tested

SineKernel Test Coverage

The test_sine_kernel.cpp achieves this with:

  • 13 test cases covering all functionality
  • 1,046,459 assertions executed
  • 100% pass rate

Test categories: 1. Construction (3 test cases) 2. Frequency management (1 test case) 3. Phase management (1 test case) 4. Output quality (1 test case) 5. Interpolation modes (1 test case) 6. Buffer processing (1 test case) 7. Frequency modulation (1 test case) 8. Edge cases (1 test case) 9. Numerical stability (1 test case) 10. THD estimation (1 test case) 11. Performance characteristics (1 test case)

Performance

Test Execution Speed

Normal run:     0.09 seconds  (13 test cases, 1M+ assertions)
Verbose run:    8.19 seconds  (includes long-term stability tests)

The tests include performance benchmarks: - 1 million sample generation test (~22 seconds @ 44.1kHz) - Buffer processing tests with 44100-sample buffers - Numerical stability over extended periods

Optimization Tips

  1. Use Release build for accurate performance testing
  2. Disable verbose output for fast CI runs
  3. Use test filters to run only relevant tests during development
  4. Parallel test execution with CTest: ctest -j 8

Continuous Integration

GitHub Actions Example

- name: Configure CMake
  run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON

- name: Build
  run: cmake --build build --config Release -j 4

- name: Run Tests
  run: |
    cd build
    ctest -C Release --output-on-failure

Pre-commit Hook

Add to .git/hooks/pre-commit:

#!/bin/bash
cd "3 - COMPONENTS/05_MODULES/05_15_REFERENCE_IMPLEMENTATIONS/05_15_01_kernel_references/build"
./Release/test_kernels.exe
if [ $? -ne 0 ]; then
    echo "Tests failed! Commit aborted."
    exit 1
fi

Troubleshooting

Catch2 Not Found

Problem: CMake says "Catch2 not found"

Solution: 1. Verify vcpkg installation: C:/vcpkg/vcpkg.exe list catch2 2. Run vcpkg install: C:/vcpkg/vcpkg.exe install 3. Check CMAKE_PREFIX_PATH includes vcpkg_installed directory

Tests Fail to Compile

Problem: Missing headers or undefined symbols

Solution: 1. Verify Catch2 headers exist: ls vcpkg_installed/x64-windows/include/catch2/ 2. Check target_link_libraries includes Catch2::Catch2WithMain 3. Ensure C++17 standard is enabled

Floating-Point Test Failures

Problem: Tests fail with small numeric differences

Solution: 1. Use Approx(expected).margin(tolerance) instead of == 2. Account for accumulation errors in long-running tests 3. Remember that frequencies like 440Hz don't divide evenly into 44100Hz 4. Document why specific tolerances are chosen

Tests Hang or Timeout

Problem: Tests never complete

Solution: 1. Check for infinite loops in kernel code 2. Verify buffer sizes are reasonable 3. Use timeout in CTest: ctest --timeout 60 4. Profile with debugger to find slow code

Adding New Tests

For New Kernels

  1. Create tests/category/test_new_kernel.cpp
  2. Add to CMakeLists.txt:
    add_executable(test_kernels
        tests/oscillators/test_sine_kernel.cpp
        tests/filters/test_new_kernel.cpp  # Add here
    )
    
  3. Follow SineKernel test structure as template
  4. Ensure >95% coverage for Gold certification

For New Test Categories

  1. Create new directory: tests/new_category/
  2. Add test file: tests/new_category/test_component.cpp
  3. Update CMakeLists.txt
  4. Create tags: [new_category]

Summary

The test infrastructure is fully operational with:

Catch2 3.10.0 integrated via vcpkg manifest mode ✅ CMake configuration finds Catch2 automatically ✅ 1M+ assertions executing in <10 seconds ✅ 100% pass rate on all test cases ✅ CTest integration for CI/CD pipelines ✅ Flexible filtering by tags and test names ✅ Comprehensive coverage achieving Gold certification standards

The infrastructure is ready for adding tests for all 28 planned L0 kernels.