ocaml-containers/COVERAGE_SETUP.md
Simon Cruanes 115b6276a3 Use instrumentation stanza for CBOR (cleaner approach)
Changed CBOR from 'pps bisect_ppx' to 'instrumentation (backend bisect_ppx)'
which is dune's recommended approach for coverage when not using custom
preprocessing.

Benefits:
- Cleaner dune syntax
- Use --instrument-with bisect_ppx flag (no env vars needed)
- Coverage files auto-managed in _build
- More idiomatic dune

Updated documentation to reflect new usage:
  dune runtest --instrument-with bisect_ppx
  bisect-ppx-report summary --coverage-path=_build

Core library still uses 'pps bisect_ppx' due to per-module preprocessing
requirements (cpp.exe for 3 modules).
2026-02-08 12:25:58 +00:00

205 lines
5.6 KiB
Markdown

# Coverage Instrumentation Setup
## Problem Solved
The ocaml-containers project uses a custom preprocessor (`cpp.exe`) for OCaml version-specific conditionals, which conflicts with dune's `(instrumentation ...)` stanza. We solved this using per-module preprocessing.
## Solution
### Core Library (`src/core/dune`)
```ocaml
(preprocess
(per_module
((action (run %{project_root}/src/core/cpp/cpp.exe %{input-file}))
CCAtomic CCList CCVector) ; These 3 modules need cpp
((pps bisect_ppx)))) ; All other modules get coverage
```
**Result:** 41 out of 44 core modules are instrumented (~93%)
### CBOR Library (`src/cbor/dune`)
```ocaml
(instrumentation (backend bisect_ppx))
```
**Result:** Full coverage instrumentation (100%)
**Note:** CBOR uses `instrumentation` stanza (cleaner) while core uses `pps`
(required for per-module preprocessing compatibility)
## Modules Excluded from Coverage
Only 3 modules require cpp preprocessing:
- **CCAtomic.ml** - Platform-specific atomic operations
- **CCList.ml** - Version-specific optimizations
- **CCVector.ml** - Version-specific features
These modules use `[@@@ifge 4.8]` style conditionals for OCaml version compatibility.
## Usage
### Generate Coverage Data
```bash
# Run tests with coverage using instrumentation
dune runtest --instrument-with bisect_ppx
# Or run specific test suite
dune runtest tests/cbor --instrument-with bisect_ppx
# Coverage files are written to _build/default/tests/*/*.coverage
```
### Generate Reports
```bash
# Summary
bisect-ppx-report summary --coverage-path=_build
# Per-file breakdown
bisect-ppx-report summary --coverage-path=_build --per-file
# HTML report
bisect-ppx-report html --coverage-path=_build -o _coverage/html
```
### View HTML Report
```bash
# Local
firefox _coverage/html/index.html
# Or serve it
cd _coverage/html && python3 -m http.server 8080
```
## Initial Coverage Results
### CBOR Module
From RFC test vectors + property tests:
```
Coverage: 203/232 (87.50%)
```
**Uncovered areas (29 points):**
- Some error handling paths
- Edge cases in indefinite-length encoding
- Specific integer encoding optimizations
### Next Steps for 100% Coverage
1. **Add tests for uncovered CBOR paths:**
- Indefinite-length byte strings
- Indefinite-length text strings
- Break codes in various contexts
- Simple values 20-25 (reserved range)
2. **Enable coverage for excluded modules:**
- Option 1: Modify cpp.exe to preserve bisect annotations
- Option 2: Use dune-workspace with separate coverage context
- Option 3: Replace cpp conditionals with dune's version checks
3. **Add coverage CI:**
- Generate coverage on each PR
- Track coverage trends
- Set coverage thresholds
## Coverage Best Practices
### Finding Gaps
```bash
# Generate detailed HTML report
bisect-ppx-report html --coverage-path=_coverage -o _coverage/html
# Open index.html and click through files marked in yellow/red
# Red lines = never executed
# Yellow lines = partially executed (e.g., one branch not tested)
```
### Improving Coverage
1. Look at red (uncovered) lines in HTML report
2. Write tests that exercise those paths
3. Re-run tests with coverage
4. Verify improvement
### Example Workflow
```bash
# Initial run
dune runtest tests/cbor --instrument-with bisect_ppx
bisect-ppx-report summary --coverage-path=_build --per-file
# View gaps
bisect-ppx-report html --coverage-path=_build -o _coverage/html
firefox _coverage/html/src/cbor/containers_cbor.ml.html
# Add tests to cover gaps
# ... edit tests/core/t_cbor.ml ...
# Re-run (dune automatically regenerates coverage)
dune clean
dune runtest tests/cbor --instrument-with bisect_ppx
bisect-ppx-report summary --coverage-path=_build
```
## Benefits Achieved
**Coverage instrumentation working** on 95% of codebase
**No performance impact** on regular builds (coverage is opt-in)
**Per-file coverage** visibility
**HTML reports** for detailed analysis
**Maintains compatibility** with version-specific code
## Technical Notes
### Why `pps bisect_ppx` instead of `instrumentation`?
The `instrumentation` stanza cannot be combined with `(preprocess (action ...))` in dune. Using `pps` in the preprocess field allows mixing:
- Preprocessors (bisect_ppx)
- Actions (cpp.exe)
via per-module configuration.
### Why mix `instrumentation` and `pps bisect_ppx`?
- **CBOR:** Uses `instrumentation` stanza (cleaner, dune's recommended approach)
- **Core:** Uses `pps bisect_ppx` in per-module preprocessing (works with action preprocessing)
The `instrumentation` stanza is preferred but cannot be used with `(preprocess (action ...))`.
We use it where possible (CBOR) and fall back to `pps` where needed (core).
### Alternative Approaches Considered
1. **Modify cpp.exe** to pass through bisect annotations ❌
- Complex, requires understanding cpp internals
- Maintenance burden
2. **Replace cpp with dune features**
- Would require refactoring existing conditionals
- Breaking change for the project
3. **Separate dune-workspace context**
- Adds complexity
- Harder to use
4. **Per-module preprocessing**
- Clean, minimal changes
- Works with existing infrastructure
- Easy to understand and maintain
## Maintenance
When adding new modules:
- **Default:** Will get bisect_ppx automatically
- **If needs cpp:** Add to the CCAtomic/CCList/CCVector list
When upgrading bisect_ppx:
- Test that per-module preprocessing still works
- Check HTML report generation
## Documentation
For more details see:
- Bisect_ppx docs: https://github.com/aantron/bisect_ppx
- Dune preprocessing: https://dune.readthedocs.io/en/stable/concepts/preprocessing.html