Skip to content

Testing and CI

Rodrigo Torres edited this page Apr 23, 2026 · 1 revision

This page covers wiRedPanda's test framework, how to write and run tests, coverage analysis, sanitizers, and the CI/CD pipelines.


Test Organization

wiRedPanda has a comprehensive test suite with 175 test classes. All tests compile into a single binary: build/test_wiredpanda. Each test class is a QObject with private slots as test methods.

Tests/
├── Unit/                      # Individual unit tests
│   ├── CodeGen/               #   Code generation tests
│   ├── Commands/              #   Undo/redo tests
│   ├── Common/                #   Utility tests
│   ├── Core/                  #   Core type tests
│   ├── Elements/              #   Individual element tests
│   ├── Factory/               #   ElementFactory tests
│   ├── Logic/                 #   Logic gate tests
│   ├── Nodes/                 #   Port and connection tests
│   ├── Scene/                 #   QGraphicsScene tests
│   ├── Serialization/         #   Serialization tests
│   ├── Simulation/            #   Simulation engine tests
│   └── Ui/                    #   UI component tests
├── Integration/               # Integration tests
│   ├── IC/                    #   Hierarchical IC tests (9 levels)
│   │   ├── Components/        #     .panda IC fixture files
│   │   ├── Generators/        #     Python scripts that generate fixtures
│   │   └── Tests/
│   │       ├── Cpu/           #     CPU-level integration tests
│   │       ├── TestLevel1*    #     Basic sequential elements
│   │       ├── TestLevel2*    #     Combinational circuits
│   │       ├── ...
│   │       └── TestLevel9*    #     Complete CPUs
│   ├── Logic/                 #   Mux/Demux tests
│   ├── Arduino/               #   Arduino codegen .ino fixtures
│   ├── SystemVerilog/         #   SystemVerilog codegen .sv fixtures
│   ├── TestArduino.cpp        #   Arduino code generation
│   ├── TestFeedback.cpp       #   Feedback loop tests
│   ├── TestFileDialogProvider.cpp  # File dialog provider tests
│   ├── TestFiles.cpp          #   .panda file loading
│   ├── TestIc.cpp             #   IC integration tests
│   ├── TestICInline.cpp       #   Inline IC tests
│   ├── TestMainWindowGui.cpp  #   Main window GUI tests
│   ├── TestSimulation.cpp     #   Simulation engine
│   ├── TestSystemVerilogExport.cpp  # SystemVerilog export
│   ├── TestWorkspace.cpp      #   Workspace operations
│   └── TestWorkspaceFileops.cpp  #  Workspace file operations
├── System/                    # System-level tests
│   ├── TestWaveform.cpp       #   BeWavedDolphin waveform tests
│   └── TestBewavedDolphinGui.cpp  #   BeWavedDolphin GUI tests
├── BackwardCompatibility/     # Old format compatibility
├── Resources/                 # Icon/resource validation
│   └── TestIcons.cpp
├── Common/                    # Shared utilities
│   ├── TestUtils.h/cpp        #   Test environment setup
│   ├── ICTestHelpers.h        #   IC test helpers
│   └── StubFileDialogProvider.h  # Testable file dialog stub
├── Fixtures/                  # Test .panda files
└── Runners/
    ├── RunnerUtils.h          # Test suite runner helpers
    └── TestWiredpanda.cpp     # Unified test entry point

Test Utilities and CircuitBuilder

Tests/Common/TestUtils.h provides everything you need to build and test circuits:

// Environment setup (call once per test class)
TestUtils::setupTestEnvironment();  // Headless mode, suppress dialogs
TestUtils::configureApp();          // QApplication settings

// Create a fresh workspace with a scene
WorkSpace workspace;

// CircuitBuilder reduces wiring boilerplate
CircuitBuilder builder(workspace.scene());
InputSwitch sw1, sw2;
And andGate;
Led led;
builder.add(&sw1, &sw2, &andGate, &led);
builder.connect(&sw1, 0, &andGate, 0);  // sw1 output 0 -> AND input 0
builder.connect(&sw2, 0, &andGate, 1);  // sw2 output 0 -> AND input 1
builder.connect(&andGate, 0, &led, 0);  // AND output 0 -> LED input 0
auto *sim = builder.initSimulation();

// Set inputs and simulate
sw1.setOn(true);
sw2.setOn(false);
sim->update();

// Read outputs
bool result = TestUtils::getInputStatus(&led);  // false (1 AND 0 = 0)

Useful helpers:

Function Purpose
TestUtils::createSwitches(n) Create n input switches
TestUtils::setInputValues(switches, bools) Set multiple switches at once
TestUtils::setMultiBitInput(switches, int) Decompose an integer into bits
TestUtils::readMultiBitOutput<T>(elms, port) Combine multiple outputs into an integer
TestUtils::clockCycle(sim, clk) Full clock pulse: LOW -> HIGH -> LOW
TestUtils::clockToggle(sim, clk) Single clock edge toggle
TestUtils::getInputStatus(elm) Read input port 0 as bool
TestUtils::getOutputStatus(elm) Read output port 0 as bool

Writing Your First Test

Here is a complete, minimal test for an AND gate:

Tests/Unit/Elements/TestMyGate.h:

#pragma once
#include <QObject>

class TestMyGate : public QObject {
    Q_OBJECT
private slots:
    void testAndTruthTable();
};

Tests/Unit/Elements/TestMyGate.cpp:

#include "TestMyGate.h"
#include "Tests/Common/TestUtils.h"
#include "App/Element/GraphicElements/And.h"
#include "App/Element/GraphicElements/InputSwitch.h"
#include "App/Element/GraphicElements/Led.h"

void TestMyGate::testAndTruthTable()
{
    WorkSpace workspace;
    CircuitBuilder builder(workspace.scene());

    InputSwitch sw0, sw1;
    And gate;
    Led led;

    builder.add(&sw0, &sw1, &gate, &led);
    builder.connect(&sw0, 0, &gate, 0);
    builder.connect(&sw1, 0, &gate, 1);
    builder.connect(&gate, 0, &led, 0);

    auto *sim = builder.initSimulation();

    // 0 AND 0 = 0
    sw0.setOn(false); sw1.setOn(false); sim->update();
    QCOMPARE(TestUtils::getInputStatus(&led), false);

    // 0 AND 1 = 0
    sw0.setOn(false); sw1.setOn(true); sim->update();
    QCOMPARE(TestUtils::getInputStatus(&led), false);

    // 1 AND 0 = 0
    sw0.setOn(true); sw1.setOn(false); sim->update();
    QCOMPARE(TestUtils::getInputStatus(&led), false);

    // 1 AND 1 = 1
    sw0.setOn(true); sw1.setOn(true); sim->update();
    QCOMPARE(TestUtils::getInputStatus(&led), true);
}

Testing Sequential Logic

Sequential elements need clock edges:

void TestMyFlipFlop::testDFlipFlop()
{
    WorkSpace workspace;
    CircuitBuilder builder(workspace.scene());

    InputSwitch swD, swClk;
    DFlipFlop dff;
    Led ledQ, ledNQ;

    builder.add(&swD, &swClk, &dff, &ledQ, &ledNQ);
    builder.connect(&swD, 0, &dff, 0);       // D input
    builder.connect(&swClk, 0, &dff, 1);     // Clock input
    builder.connect(&dff, 0, &ledQ, 0);      // Q output
    builder.connect(&dff, 1, &ledNQ, 0);     // ~Q output

    auto *sim = builder.initSimulation();

    // Set D = 1, then pulse clock
    swD.setOn(true);
    sim->update();
    TestUtils::clockCycle(sim, &swClk);       // LOW -> HIGH -> LOW

    QCOMPARE(TestUtils::getInputStatus(&ledQ), true);
    QCOMPARE(TestUtils::getInputStatus(&ledNQ), false);

    // Set D = 0, then pulse clock
    swD.setOn(false);
    sim->update();
    TestUtils::clockCycle(sim, &swClk);

    QCOMPARE(TestUtils::getInputStatus(&ledQ), false);
    QCOMPARE(TestUtils::getInputStatus(&ledNQ), true);
}

How To: Add a New Test

Step 1: Create the Test Files

Tests/Unit/Elements/TestMyElement.h:

#pragma once
#include <QObject>

class TestMyElement : public QObject {
    Q_OBJECT
private slots:
    void testProperties();
    void testBehavior();
};

Tests/Unit/Elements/TestMyElement.cpp:

#include "TestMyElement.h"
#include "Tests/Common/TestUtils.h"
// Include the elements you need...

void TestMyElement::testProperties() {
    MyElement elm;
    QCOMPARE(elm.elementType(), ElementType::MyElement);
    // Verify port counts, group, etc.
}

void TestMyElement::testBehavior() {
    WorkSpace workspace;
    CircuitBuilder builder(workspace.scene());
    // Build circuit, simulate, assert results...
}

Step 2: Register in CMakeSources.cmake

Add both files to the TEST_WIREDPANDA_SOURCES list in alphabetical order:

set(TEST_WIREDPANDA_SOURCES
    # ... existing entries ...
    Tests/Unit/Elements/TestMyElement.cpp
    Tests/Unit/Elements/TestMyElement.h
    # ... existing entries ...
)

Step 3: Register in Tests/Runners/TestWiredpanda.cpp

Add an #include at the top with the other includes, then add an entry to the tests vector in main():

#include "Tests/Unit/Elements/TestMyElement.h"
{"TestMyElement", []() -> QObject * { return new TestMyElement; }},

Step 4: Register in CMakeLists.txt

Add an add_test() call and label it so CTest picks it up:

add_test(NAME TestMyElement COMMAND test_wiredpanda TestMyElement WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
set_tests_properties(TestMyElement PROPERTIES LABELS "unit")

Step 5: Run

cmake --build --preset debug
./build/test_wiredpanda TestMyElement

Running Tests

# All tests (parallel execution)
ctest --preset debug

# A specific test class
./build/test_wiredpanda TestLogicGates

# List all available test classes
./build/test_wiredpanda -functions

# Verbose output
ctest --preset debug --verbose

Important: Always run tests from the project root directory. Never cd build/.

Conventions:

  • One test file per class being tested
  • Method names start with test
  • Use QVERIFY(condition) for boolean assertions
  • Use QCOMPARE(actual, expected) for comparisons
  • Use a try/catch with QVERIFY2(exceptionThrown, "message") for exception tests
  • Always prefer fixing code over changing tests to accept incorrect behavior

Coverage and Sanitizers

# Code coverage
cmake --preset coverage
cmake --build --preset coverage
ctest --preset coverage
# Generates report with lcov/gcov

# Address Sanitizer (buffer overflows, use-after-free, etc.)
cmake --preset asan
cmake --build --preset asan
ctest --preset asan

# Thread Sanitizer (data races)
cmake --preset tsan
cmake --build --preset tsan
ctest --preset tsan

# Undefined Behavior Sanitizer
cmake --preset ubsan
cmake --build --preset ubsan
ctest --preset ubsan

CI/CD and Workflows

The project uses GitHub Actions for continuous integration:

build.yml — Build and Tests

  • Platform matrix: Ubuntu, Windows, macOS
  • Qt versions: 6.2.4, 6.5.3, 6.8.3, 6.9.3
  • Timeout: 10 minutes for tests
  • MCP integration test: runs MCP/Client/run_tests.py after every build
  • Artifacts: uploads build logs on failure

deploy.yml — Releases

  • Generates distribution artifacts:
    • Linux: AppImage
    • Windows: ZIP
    • macOS: DMG
  • Integrates Sentry for crash reporting
  • Automatic debug symbol upload

Other Workflows

Workflow Purpose
codeql.yml Code security analysis (CodeQL)
coverage.yml Code coverage tracking; uploads to Codecov
translate.yml Translation management
wasm.yml WebAssembly build

See Also

Clone this wiki locally