-
Notifications
You must be signed in to change notification settings - Fork 25
Testing and CI
This page covers wiRedPanda's test framework, how to write and run tests, coverage analysis, sanitizers, and the CI/CD pipelines.
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
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 |
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);
}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);
}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...
}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 ...
)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; }},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")cmake --build --preset debug
./build/test_wiredpanda TestMyElement# 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 --verboseImportant: 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
# 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 ubsanThe project uses GitHub Actions for continuous integration:
- 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.pyafter every build - Artifacts: uploads build logs on failure
- Generates distribution artifacts:
- Linux: AppImage
- Windows: ZIP
- macOS: DMG
- Integrates Sentry for crash reporting
- Automatic debug symbol upload
| Workflow | Purpose |
|---|---|
codeql.yml |
Code security analysis (CodeQL) |
coverage.yml |
Code coverage tracking; uploads to Codecov |
translate.yml |
Translation management |
wasm.yml |
WebAssembly build |
- Element System — The elements being tested
- Simulation Engine Internals — Simulation behavior under test
- Developer Guide — Tips, common pitfalls, and starter tasks
- Development Setup — Setting up the build environment
Made with ❤️ by the wiRedPanda Community
Licensed under GPL-3.0 | © 2026 GIBIS-UNIFESP