Developer guide¶
Development setup¶
Clone the repository and install in development mode:
git clone <repo-url>
cd pytest-cocotb
python -m venv .venv
source .venv/bin/activate
uv pip install -e ".[dev,docs]"
Running tests¶
Unit tests (no simulator needed):
pytest tests/test_plugin.py
NFS lock and guard tests:
pytest tests/test_nfs_lock.py tests/test_guard.py
End-to-end tests (requires Verilator on PATH):
pytest tests/e2e/test_counter.py --simulator verilator \
--hdl-toplevel counter --sources tests/e2e/rtl/counter.sv
Full suite:
pytest
Simulator-dependent tests auto-skip if Verilator is not found.
Building documentation¶
uv pip install -e ".[docs]"
sphinx-build docs/source docs/build/html
Open docs/build/html/index.html in a browser to view the result.
Plugin architecture¶
The plugin registers fixtures with the following dependency graph:
testrun_uid (session)
│
▼
sim_build_dir (session)
│
├──────────────┐
▼ ▼
build_dir test_session (function)
(session) │
│ │
▼ ▼
runner ──────► test_session.run()
(session)
testrun_uidgenerates a timestamp string.sim_build_dircreates the base output directory (with optional--regresstimestamped subdirectory).build_dircreates thebuild/orbuild_waves/subdirectory.runnercompiles HDL once at session scope using the build directory.test_sessioncreates a unique per-test directory and yields aTestSessiondataclass.
HPC mixin pattern¶
The HPC runner classes use Python’s MRO to override execution without duplicating simulator logic:
class HpcVerilator(HpcExecutorMixin, Verilator):
pass
HpcExecutorMixin overrides _execute_cmds() to submit commands through
hpc-runner’s scheduler. All simulator-specific logic (build commands, test
commands, argument formatting) is inherited from the cocotb base class.
The mixin also overrides _simulator_in_path() to skip local PATH checks,
since the simulator binary is only available after module loading on the
compute node.
Code organisation¶
File |
Purpose |
|---|---|
|
Pytest plugin entry point: CLI options, fixtures |
|
|
|
HPC-enabled runner classes and |
|
|
|
|
|
|
|
Unit and pytester integration tests |
|
Tests for |
|
Tests for |
|
End-to-end test with real RTL and cocotb testbench |
How to add a new simulator¶
Create the HPC runner class in
runners.py:from cocotb_tools.runner import NewSim from .mixin import HpcExecutorMixin class HpcNewSim(HpcExecutorMixin, NewSim): """NewSim runner with HPC job submission."""
Register it in the
_HPC_RUNNERSdict inrunners.py:_HPC_RUNNERS: dict[str, type] = { ... "newsim": HpcNewSim, }
Override build commands if needed. For example,
HpcVerilatoroverrides_build_command()to fix the executable path for remote execution.Test with the new simulator:
pytest --simulator newsim --hdl-toplevel top --sources rtl/top.sv
Test suite structure¶
Unit tests (tests/test_plugin.py):
Tests for
_sanitise_name()with various pytest node ID formats.Tests for
TestSession(single-use guard, managed keys, defaults).Pytester-based integration tests that exercise CLI options and fixture wiring without a real simulator.
The
_needs_verilatorfixture auto-skips simulator-dependent tests when Verilator is not available.
NFS/guard tests (tests/test_nfs_lock.py, tests/test_guard.py):
Lock acquisition, release, timeout, and stale lock detection.
CallOncesuccess, failure, and re-execution semantics.
End-to-end tests (tests/e2e/):
Real RTL (
counter.sv) compiled and simulated with Verilator.cocotb testbench (
cocotb_counter.py) with@cocotb.test()coroutines.Pytest test (
test_counter.py) using thetest_sessionfixture.