Development Workflow#

Ship features, tests, documentation and refactors together, in small verifiable slices.

Documentation Rule#

The Sphinx site is the project handbook, not just API output: setup and local execution, CI workflow behavior, test strategy, and architecture notes for new features. Every non-trivial feature updates it in the same change:

  • getting_started when user-facing setup or runtime usage changes

  • overview when architecture or capability boundaries change

  • a new dedicated page when the feature needs operational guidance, examples, or design notes

Test Strategy#

The Suites#

Four C++ suites link AccelerANTgine and import its modules, so they test the library itself; a fifth, Test/python, tests the Python bindings.

Suite

Binary

What it covers

Test/commit

commitTestSuite

The C API; the TOML and JSON config parsers, every rejection path included; the ONNX engine on models the tests generate (Relu, Add, two outputs, and a Reshape that turns the input into raw YOLO output); YOLO decoding (thresholds, classes, NMS, scaling, NaN and infinity, rows shorter than the class count); GStreamer frames from videotestsrc and appsrc through appsink, callbacks and state; the WebRTC stream config, its validation and a start against a refusing signalling server; both exports of the Rust crate, through cxx and extern "C"

Test/compile

compileTestSuite

The embedder contract: the C header compiles as C17 and links, import kataglyphis.c_api reaches the same symbol, every module imports into one translation unit, the resource owners are move-only, the error codes keep their numbers, the defaults agree

Test/fuzz

fuzzTestSuite

FuzzTest properties over the untrusted inputs: the settings JSON and the inference TOML on arbitrary bytes, unsigned fields that round-trip or are rejected, the stream-name mapping, the COCO table, and the YOLO decoder on arbitrary floats

Test/perf

perfTestSuite

Google Benchmark over the hot paths: both parsers, run_inference at three sizes, YOLO detection with 0, 64 and 512 candidate boxes (NMS is quadratic), and pull_sample

No model or media file is checked in for them: Test/common/kataglyphis_test_support.h writes the ONNX protobuf itself, and the GStreamer tests use videotestsrc and appsrc. A test that needs a GStreamer element the install lacks skips and names it; the WebRTC ones need webrtcsink (gst-plugins-rs), which the Linux image has and the Windows image has not.

Top-level CMake selects the suites by build type: commit, compile and fuzz in Debug; perf in RelWithDebInfo; none in Release unless -DKATAGLYPHIS_RELEASE_TESTS=ON, which builds all four and which both Windows lanes set (Build-Windows.ps1 -StageTests). FuzzTest needs Clang or clang-cl. Every lane runs them:

Lane

Commit + compile

Fuzz (unit mode)

Perf

Linux x64, arm64 (clang)

ctest, ASan + UBSan with llvm-cov, then again under TSan

yes

ci-profile-bench.sh

Linux x64, arm64 (GCC)

ctest, ASan + UBSan with gcovr

— (FuzzTest is Clang-only)

ci-profile-bench.sh

Linux riscv64

ctest under QEMU, no sanitizers

yes

— (QEMU timing)

Windows x64

Debug: ctest in the container (ASan), then on the runner host; Release: staged, on the host

yes, Debug and Release

Profile build, container and host; Release, by exit code

Windows arm64

Release, on windows-11-arm

yes

yes, by exit code

Run The Suites Locally#

In the family image:

cmake --preset linux-debug-clang && cmake --build build -j
ctest --test-dir build --no-tests=error --output-on-failure
./build/fuzzTestSuite                       # about a second per property
FUZZTEST_FUZZ_FOR=60s ./build/fuzzTestSuite  # longer, still unit mode

Coverage#

scripts/linux/ci-coverage.sh reports llvm-cov on the clang lane (over lib/libAccelerANTgine.so) and gcovr on the GCC lane, where linux-debug-GNU now builds with myproject_ENABLE_COVERAGE. The HTML lands in docs/coverage/.

Python Tests#

The Python bindings test suite (Test/python) runs against both installed wheels, GIL and free-threaded (see Python Bindings). test_threading.py calls one engine, detector and config from eight threads at once.

Where A New Test Goes#

The change

The test

Library logic and boundary cases

small deterministic tests in the debug suites Test/compile and Test/commit

Spans CLI to library interactions, optional dependency discovery, packaging/runtime path behavior, or C API to library boundaries

integration coverage

Takes inputs that are difficult to enumerate manually: configuration files, CLI argument combinations, parser inputs, externally provided model metadata or request payloads

Test/fuzz

Claims measurable runtime impact: inference startup time, repeated execution performance, pipeline setup costs, serialization or preprocessing hot paths

Test/perf

Profiling#

ci-profile-bench.sh is the profiling and benchmark lane, and it is what CI runs:

bash scripts/linux/ci-profile-bench.sh
  • gperftools: configure links libprofiler (google-perftools, libgoogle-perftools-dev) when it finds it, and the profiled window writes logs/profile.prof; a host without it falls back to -pg. The image carries it (2.18.1, checked 2026-10-16), so CI’s window writes the profile on both arches. Reading it takes Go’s pprof (plus graphviz for its graphs), which the lane itself never calls.

  • On arm64 the script picks gperftools’ generic_fp stack walker. The default, libgcc, unwinds from inside the profiler’s signal handler and crashed the workload with a segfault in 6 of 140 starts at 4 kHz on a Pi 5 (one CI run on 2026-10-16), while generic_fp crashed in none of 210 and gave slightly deeper stacks, since clang keeps arm64’s frame records at -O3. x86_64 keeps the default, where clang drops frame pointers. Setting TCMALLOC_STACKTRACE_METHOD yourself overrides the choice.

  • callgrind: valgrind --tool=callgrind is reached through bash scripts/linux/ci-release.sh --callgrind.

  • perf: perf record needs a perf binary (Ubuntu 26.04: the linux-perf package; linux-tools-common no longer ships one) plus permission to open perf events. The CI image has perf, but CI cannot open perf events, so the lane warns, runs the workload for a short window without a recorder, then runs the benchmarks. The profiled workload, the CLI’s WebRTC producer, streams only while a signalling server answers, so the script starts one on 127.0.0.1:18443 for the window (--signalling-port moves it) unless --profile-args names a --server of its own. Why, with the measurements: AGENTS.md § 4.

Installing the tools is not this project’s subject; the upstream install instructions for gperftools, pprof, valgrind and perf are the ones that stay correct.

Refactoring Cadence#

Refactor periodically, but only tested code. Prefer refactors that:

  • reduce duplication in frequently touched code paths

  • clarify runtime or dependency boundaries

  • simplify test setup

  • improve naming and control flow without changing behavior

Avoid broad cleanup passes that are not tied to active work unless there is clear payoff and sufficient coverage.

Validation Targets#

Choose the smallest validation path that still proves the change: a debug build for correctness and fuzz targets, a profile build for benchmarks, a release build for packaging validation.

  • Linux debug correctness: cmake --preset linux-debug-clang

  • Linux TSan validation: cmake --preset linux-debug-clang-tsan

  • Linux profile benchmarks: cmake --preset linux-profile-clang

  • Windows debug/profile/release container builds: .\scripts\windows\Invoke-ContainerBuild.ps1

  • Windows host execution: .\scripts\windows\Start-Windows.ps1 -Config Debug|Profile|Release

The GitHub workflows are the reference for how the project builds and validates (Build Matrix Summary); keep local verification on those same paths so CI remains a confirmation step rather than a discovery step.

Static Analysis And Formatting#

One entry point per platform, and CI runs exactly these:

bash scripts/linux/run-static-analysis-format.sh
pwsh -NoProfile -File .\scripts\windows\Build-Windows.ps1
  • What runs: cmake-format and clang-format everywhere, plus clang-tidy and scan-build on the Linux clang lane and clang-tidy in the Windows clangcl-debug build, over the files the project actually compiles (.ixx, .cppm and .mxx included). clang++ --analyze runs only when asked for (--direct-analyze).

  • Rewrite or check: cmake-format rewrites in place, and so does clang-format on Windows. On Linux clang-format checks and does not rewrite: it covers every C/C++ file under Src/, Test/, Bindings/ and cmake/, and fails the lane when one of them deviates from .clang-format.

  • One clang-format release: the Linux gate grades with the clang-format of the hub’s LLVM_RELEASE (23.1.3), because each release formats differently. The hub’s code_quality_require_pinned_clang_format looks in /usr/local/llvm-target/bin, then on PATH, and fails the gate when neither has that release. To fix a file, run the clang-format -i the gate’s error names; the image’s clang-format is that release.

  • Every tool is a gate: a tool that is not installed fails the lane rather than being skipped.

  • Configs: .clang-format and .cmake-format.yaml here are shared configs, and scripts/linux/run-lint-gates.sh checks them against ANTfrastructure’s copies (the rows of .antfrastructure-shared.manifest): edit them upstream, not here. .clang-tidy, gcovr.cfg and .pre-commit-config.yaml are this project’s own on purpose; the manifest’s header says why. What each tool does and how to run one of them alone is in ANTfrastructure’s code-quality-tooling.md, and the configs themselves are described in its shared/config/README.md.

The pre-commit hook installs from the same requirements.txt:

uv venv --python 3.14   # the docs theme in requirements.txt needs it
source .venv/bin/activate # .venv/Scripts/activate on pwsh
uv pip install -r requirements.txt
pre-commit install
pre-commit run --all-files   # once, optional

Useful Tools#

Benchmarking:

VSCode extensions:

Literature#

Rust:

CMake/C++: