# SlipCover measures Python coverage by rewriting bytecodes instead of tracing

> SlipCover is an Apache licensed Python code coverage tool from the PLASMA lab at UMass Amherst that instruments bytecodes just in time and removes the instrumentation as the program runs, and on Python 3.12 and later it uses the sys.monitoring API instead. It suits a test suite that Coverage.py has made too slow to run, and it trades some completeness for that speed on Python versions where tracing is the only option.

**plasma-umass/slipcover** — Near Zero-Overhead Python Code Coverage

- Repository: https://github.com/plasma-umass/slipcover
- Stars: 587 · Forks: 30
- Language: Python
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/plasma-umass-slipcover

## The cost of tracing is what the project attacks

SlipCover is a fast code coverage tool from Juan Altmayer Pizzorno and Emery Berger at the PLASMA lab at UMass Amherst. It tracks a Python program as it runs and reports on the parts that executed and those that did not, which can help guide testing, debugging, fuzzing, or finding dead code.

The framing of the problem is precise. Past code coverage tools can make programs significantly slower, and it is not uncommon for them to take twice as long to execute. The comparison point is Coverage.py, which relies on Python's tracing facilities, that is sys.settrace, and that adds significant overhead.

The project's answer is just-in-time instrumentation and de-instrumentation. When SlipCover gathers coverage information, it modifies the program's Python bytecodes, inserting instructions that let it keep track of the lines executed. As the program executes, it gradually removes instrumentation that is no longer needed, so those parts run at full speed.

That is the core idea and it is a good one. A coverage tool that is slow gets turned off, and a coverage tool that is off tells you nothing. The instrumentation is a cost paid only on the paths a program actually takes, and the de-instrumentation means a long-running loop does not carry the tracking overhead for its whole life.

## Python 3.12 and later use sys.monitoring instead of bytecodes

There is a second mechanism, and it matters more than the first one for anyone on a recent Python.

On Python 3.12 and later, rather than rewrite bytecode, SlipCover uses the sys.monitoring API to collect coverage information.

That distinction is a real behavioural difference between versions, not an implementation detail behind a curtain. On 3.11 and earlier the tool rewrites bytecodes, and on 3.12 and later it asks the interpreter for events through the monitoring API. The same command produces the same report on both, but the mechanism, and therefore the edge cases, differ.

The project also notes that the choice of mechanism interacts with the workload. More Python-intensive programs, such as sudoku and those from the benchmark suite, with a larger proportion of execution time spent in Python rather than in native code, generate more tracing events, causing more overhead in Coverage.py. While each program's structure can affect SlipCover's ability to de-instrument, its running time stays relatively close to the original.

So the claim is not that the overhead is zero everywhere. It is that the overhead is small, and that the way it scales differs from the tool it is replacing.

## The reported speedups, and what they are measured against

The performance section gives numbers, and they are worth reading with their baseline in mind.

On CPython 3.10.5, the speedup relative to Coverage.py ranges from 1.1x to 3.4x. The first two benchmarks are the test suites for scikit-learn and Flask, sudoku runs Peter Norvig's Sudoku solver, and the others were derived from the Python Benchmark Suite.

On PyPy 3.9, the speedup ranges from 2.1x to 104.9x. Since it is so high for some of the benchmarks, the project plots it on a logarithmic scale.

There is also a proof-of-concept integration with a property-based testing package, where SlipCover sped up coverage-based testing 22x.

Two observations. First, the comparison is against Coverage.py, not against no coverage, so the numbers describe the gap between the two tools rather than the absolute cost of measuring coverage. Second, the spread is wide, and the project explains why: the programs that spend most of their time in Python produce the most tracing events, and those are the ones where Coverage.py suffers. A workload that is mostly native code, such as heavy numeric work in a compiled library, will show a much smaller difference.

For an adopter, the relevant question is what your own suite looks like on that scale, and the answer depends on the same ratio of Python time to native time.

## Accuracy was checked against Coverage.py and against a tracing script

A fast coverage tool that reports the wrong lines is worse than no tool, so the accuracy story is the part to read carefully.

The project verified SlipCover's accuracy against Coverage.py and against a simple script of their own that collects coverage using Python tracing, in the tools/ directory of the repository. The result is described as accurate, in fact more accurate in certain cases, with a link to a Coverage.py issue discussing it.

That is a stronger claim than the usual, because the oracle is not only the tool being replaced but an independent implementation of the same tracing approach. The extra accuracy comes from a known weakness in tracing: a line that raises an exception can be recorded as executed by one method and not by the other, and a method that reports a line as hit when it was never reached is a coverage report that sends a developer to the wrong place.

The counterweight is that the two mechanisms have different edge cases, and the claim is qualified accordingly. De-instrumentation depends on the program's structure, since the tool can only remove instrumentation it is sure is no longer needed, so a program with unusual control flow is where a mismatch would show up first.

## Running it under a test harness, including in parallel

SlipCover is available from PyPI and installs like any other Python module.

```console
pip3 install slipcover
```

And you run your script through it.

```console
python3 -m slipcover myscript.py
```

The interesting part is the test harness case, where it executes a Python module for you.

```console
python3 -m slipcover -m pytest -x -v
```

That starts pytest, passing it any options after the module name, and the documentation is explicit that no plug-in is required for pytest.

It also works with pytest-xdist for parallel test execution.

```console
python3 -m slipcover -m pytest -n auto
```

SlipCover activates in each worker process and automatically merges the coverage collected by every worker into a single report. That merging is the part that matters for parallel runs, because without it a distributed suite produces one partial report per worker and the numbers are meaningless.

The package metadata also registers a pytest11 entry point pointing at slipcover.pytest_plugin, so the plug-in path exists alongside the wrapper path even though the wrapper needs no plug-in.

The console script is slipcover = slipcover.__main__:main, so an installed SlipCover also gives you a slipcover command.

## Configuration lives in pyproject.toml, with a coverage.py compatible exception

Options can be stored in the project's pyproject.toml under a [tool.slipcover] section, and SlipCover automatically discovers the nearest pyproject.toml by walking up from the current working directory.

```toml
[tool.slipcover]
branch = true
source = "src"        # or ["src", "lib"]
omit = "tests/*"       # or ["tests/*", "*.pyc"]
fail-under = 80.0
format = "json"        # "text" (default), "json", "xml", or "lcov"
pretty-print = true
skip-covered = true
immediate = false
out = "coverage.json"
threshold = 75
missing-width = 120
xml-package-depth = 3
```

Most command line flags have a matching key, using hyphens, and source and omit also accept a TOML array instead of a single comma-separated string. The three format flags, --json, --xml and --lcov, map to the single format key rather than one key per flag. And --merge, the module option, the script argument, --version and --help are per-invocation choices rather than settings, so they are not configurable this way. Command line arguments always take precedence over values in pyproject.toml, so any setting can be overridden on a per-run basis.

The one exception is exclude-lines and exclude-also, the coverage.py-style regex-based line and block exclusion such as # pragma: no cover. Those are configurable only via pyproject.toml, with no command line flag, matching coverage.py's own design.

```toml
[tool.slipcover]
exclude-lines = ["# pragma: no cover", "if DEBUG:"]  # replaces the built-in defaults
exclude-also = ["# my-custom-marker"]                # adds to whichever list is active
```

The distinction between replaces and adds is the kind of detail that matters when you migrate an existing configuration: exclude-lines replaces the built-in defaults, while exclude-also adds to whichever list is active.

## The implementation is a C extension, and the build shows it

The repository layout and the build files explain how the bytecodes are actually rewritten.

The top level holds a src/ directory, a tests/ directory, a benchmarks/ directory, a tools/ directory, a docs/ directory, a tox.ini, a Makefile, a setup.py and a pyproject.toml, plus a JustUnit submodule, which is a Google test framework for C++.

The setup.py is where the native part shows. It reads the version from src/slipcover/version.py, handles a tomli fallback for Python versions before 3.11, and contains a platform_compile_args function that sets the compiler flags per platform. On macOS the default is a multi-arch build with -arch x86_64, -arch arm64 and -arch arm64e, and on Windows it uses /MT to avoid creating Visual Studio dependencies.

The Makefile has a pyptr_test target that builds a C++ test with clang++ against a JustUnit submodule, compiling src/pyptr.h with the Python C API flags, and then runs the resulting binary. That is a pointer-level test of the bytecode rewriting machinery, which tells you the project tests its scariest code at the level where a mistake would be silent.

The build system is setuptools with a src layout, the package requires Python 3.9 or later and below 3.15, and the runtime dependencies are deliberately minimal: tabulate for the report output and tomli as a tomllib alternative before 3.11. A coverage tool that pulls in a web framework would be a problem, and this one does not.

There is a CLI entry point as well as the module invocation, and the documentation notes the project supports Python 3.9 through 3.14, with Linux, macOS and Windows classifiers.

## Conclusion

SlipCover fits a team whose coverage runs have become too slow to keep in the loop, since the project reports a speedup over Coverage.py of between 1.1x and 3.4x on CPython and 2.1x to 104.9x on PyPy, and it also fits anyone who wants coverage for a single script without a plugin. It does not fit a team that wants to keep coverage.py's exact configuration surface, since exclude-lines and exclude-also exist only in pyproject.toml with no command line flag, and it does not fit a project that needs the same numbers on every Python version without checking which mechanism is in use. Before adopting it, run the same test suite under both tools and compare the reports, because the project claims accuracy against Coverage.py and against its own tracing script, and check the fail-under and threshold settings you carry over so the coverage gate you had before still means the same thing.

## FAQ

### How do I install and run SlipCover on a Python script?

Install it with pip3 install slipcover, then run your script through it with python3 -m slipcover myscript.py. The project is on PyPI and supports Python 3.9 through 3.14.

### How does SlipCover differ from Coverage.py?

Coverage.py uses Python's tracing facilities, which add significant overhead, while SlipCover uses just-in-time bytecode instrumentation that it removes as the program runs, and on Python 3.12 and later it uses the sys.monitoring API. The project reports a speedup over Coverage.py of 1.1x to 3.4x on CPython 3.10.5 and 2.1x to 104.9x on PyPy 3.9.

### Can SlipCover run a pytest suite in parallel?

Yes. Running python3 -m slipcover -m pytest -n auto works with pytest-xdist, and SlipCover activates in each worker process and merges the coverage from every worker into a single report. No plug-in is required for pytest.

### How do I configure exclude-lines and exclude-also in SlipCover?

Only through pyproject.toml under [tool.slipcover], because there is no command line flag for them, matching coverage.py's own design. exclude-lines replaces the built-in defaults and exclude-also adds to whichever list is active.

## Sources

- [Issues](https://github.com/plasma-umass/slipcover/issues)
- [License: Apache-2.0](https://github.com/plasma-umass/slipcover/blob/main/LICENSE)
- [plasma-umass/slipcover on GitHub](https://github.com/plasma-umass/slipcover)
- [README](https://github.com/plasma-umass/slipcover/blob/main/README.md)
- [Releases](https://github.com/plasma-umass/slipcover/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/plasma-umass-slipcover
