Testing and CI
Pytest is the only game in town. Every new feature ships with tests. Never commit broken code. Those are house rules, not suggestions.
For the numbers that our stress tests produce, see Performance Story. For the knobs used in dev, see Tracing and Profiling and Environment variables.
Layout
Everything lives under tests/:
tests/
conftest.py # global fixtures; forces DECNET_DEVELOPER_TRACING=false
api/ # FastAPI + repository integration
docker/ # image-build smoke (marker: docker)
live/ # subprocess-backed service tests (marker: live)
perf/ # per-request profiling harness + README
service_testing/ # protocol-level decoys (SSH, SMB, RDP, ...)
stress/ # Locust-driven load tests (marker: stress)
test_*.py # unit tests, flat layout
tests/conftest.py pins a few env vars for determinism:
DECNET_DEVELOPER=trueDECNET_DEVELOPER_TRACING=false(never trace in tests)DECNET_DB_TYPE=sqlite
Running the fast suite
# activate the venv first — see ANTI's rule
source .venv/bin/activate
pytest
pyproject.toml sets the default opt-outs:
addopts = "-m 'not fuzz and not live and not stress and not bench and not docker' \
-v -q -x -n logical --dist loadscope"
Meaning: the default run skips fuzz, live, stress, bench,
and docker. Everything else runs in parallel with xdist,
distributed by test module (--dist loadscope).
Markers
All markers are declared in pyproject.toml:
| Marker | Meaning | Opt in |
|---|---|---|
fuzz |
Hypothesis-based fuzz tests, slow | pytest -m fuzz |
live |
Live subprocess service tests | pytest -m live |
live_docker |
Live Docker containers (needs DECNET_LIVE_DOCKER=1) |
pytest -m live_docker |
stress |
Locust load tests | pytest -m stress |
bench |
pytest-benchmark micro-benchmarks | pytest -m bench |
docker |
Tests that build and run Docker images | pytest -m docker |
Run everything, including the slow markers:
pytest -m ''
Run a single marker and nothing else:
pytest -m stress tests/stress/ -v -x -n0 -s
Stress tests (Locust)
The stress suite is how we produced every number in Performance Story.
Invocation:
STRESS_USERS=2000 STRESS_SPAWN_RATE=200 \
pytest -m stress tests/stress/ -v -x -n0 -s
Notes:
-n0— no xdist parallelism. Locust wants to own the workers.-s— do not capture stdout; you want to see the RPS/latency stream.-x— stop on first failure. A locust failure here means the API regressed.STRESS_USERSandSTRESS_SPAWN_RATEare the knobs you tune. 2000 users at spawn rate 200 is our standard "can the API hold" check.
Output CSVs land in development/profiles/ (or wherever the harness
is configured to write) — the same CSV format parsed into the perf
table on Performance Story.
Before you run stress tests against a live deploy: turn tracing
off. Profiles with DECNET_DEVELOPER_TRACING=true are not
production-representative. The 1500-user comparison proves this.
Project rules (non-negotiable)
From CLAUDE.md at repo root:
- Every new feature must have pytests. No exceptions. If you add a collector, a parser, a service decoy, or a repository method, ship tests alongside it.
- Never pass broken code. "Broken" here means: does not run, does not pass 100% of tests, leaves the suite in a red state. If the bar is red when you commit, you are violating the rule.
- 100% pass before commit. Green suite, then
git commit. Not the other way around.
There is also a live rule worth calling out because it has bitten more than one contributor:
No scapy sniff threads in TestClient lifespan tests. Scapy's
sniff()spawns a thread that does not clean up when FastAPI's TestClient tears down the lifespan context. Result: pytest hangs forever at teardown. If you need to verify sniffer behaviour under a live API, use static source inspection (import the module, assert on its attributes / dispatch table) rather than spinning up the real capture loop.
This is why tests/test_sniffer_*.py reaches into module internals
instead of just hitting an endpoint.
Quick recipe for adding a feature
- Write the failing test (pick the right marker — probably none, i.e. default suite).
- Implement the feature.
pytestuntil green.- If the feature touches the storage layer, use
get_repository()or the FastAPIget_repodependency — never importSQLiteRepositorydirectly. See the DI rule inCLAUDE.md. - If the feature has a performance story, run the stress suite with
tracing off and archive the CSV under
development/profiles/. git commit.
Related: Design overview · Performance Story · Tracing and Profiling · Environment variables · Logging.
DECNET
User docs
- Quick-Start
- Installation
- Requirements-and-Python-Versions
- CLI-Reference
- INI-Config-Format
- Custom-Services
- Services-Catalog
- Service-Personas
- Archetypes
- Distro-Profiles
- OS-Fingerprint-Spoofing
- Networking-MACVLAN-IPVLAN
- Deployment-Modes
- SWARM-Mode
- MazeNET
- Remote-Updates
- Environment-Variables
- Teardown-and-State
- Database-Drivers
- Systemd-Setup
- Logging-and-Syslog
- Service-Bus
- Web-Dashboard
- REST-API-Reference
- Mutation-and-Randomization
- Troubleshooting
Developer docs
DECNET — honeypot deception-network framework. Pre-1.0, active development — use with caution. See Sponsors to support the project. Contact: samuel@securejump.cl