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: ```cpp // 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(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`**: ```cpp #pragma once #include class TestMyGate : public QObject { Q_OBJECT private slots: void testAndTruthTable(); }; ``` **`Tests/Unit/Elements/TestMyGate.cpp`**: ```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: ```cpp 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`**: ```cpp #pragma once #include class TestMyElement : public QObject { Q_OBJECT private slots: void testProperties(); void testBehavior(); }; ``` **`Tests/Unit/Elements/TestMyElement.cpp`**: ```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: ```cmake 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()`: ```cpp #include "Tests/Unit/Elements/TestMyElement.h" ``` ```cpp {"TestMyElement", []() -> QObject * { return new TestMyElement; }}, ``` ### Step 4: Register in `CMakeLists.txt` Add an `add_test()` call and label it so CTest picks it up: ```cmake add_test(NAME TestMyElement COMMAND test_wiredpanda TestMyElement WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) set_tests_properties(TestMyElement PROPERTIES LABELS "unit") ``` ### Step 5: Run ```bash cmake --build --preset debug ./build/test_wiredpanda TestMyElement ``` --- ## Running Tests ```bash # 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 ```bash # 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 - [Element System](Element-System.md) — The elements being tested - [Simulation Engine Internals](Simulation-Engine-Internals.md) — Simulation behavior under test - [Developer Guide](Developer-Guide.md) — Tips, common pitfalls, and starter tasks - [Development Setup](How-to-setup-environment.md) — Setting up the build environment