Skip to content

Commit 3b243fc

Browse files
committed
2 parents 26c3fb8 + 03f0468 commit 3b243fc

21 files changed

+1656
-196
lines changed

.github/workflows/codeql.yml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ name: "CodeQL"
33
on:
44
push:
55
branches: [ "main" ]
6+
paths:
7+
- src/**
8+
- tests/**
69
pull_request:
710
branches: [ "main" ]
11+
paths:
12+
- src/**
13+
- tests/**
814
schedule:
915
- cron: "42 19 * * 1"
16+
workflow_dispatch:
1017

1118
jobs:
1219
analyze:
@@ -20,23 +27,23 @@ jobs:
2027
strategy:
2128
fail-fast: false
2229
matrix:
23-
language: [ cpp, python ]
30+
language: [ python ]
2431

2532
steps:
2633
- name: Checkout
27-
uses: actions/checkout@v3
34+
uses: actions/checkout@v4
2835

2936
- name: Initialize CodeQL
30-
uses: github/codeql-action/init@v2
37+
uses: github/codeql-action/init@v3
3138
with:
3239
languages: ${{ matrix.language }}
3340
queries: +security-and-quality
3441

3542
- name: Autobuild
36-
uses: github/codeql-action/autobuild@v2
43+
uses: github/codeql-action/autobuild@v3
3744
if: ${{ matrix.language == 'cpp' || matrix.language == 'python' }}
3845

3946
- name: Perform CodeQL Analysis
40-
uses: github/codeql-action/analyze@v2
47+
uses: github/codeql-action/analyze@v3
4148
with:
4249
category: "/language:${{ matrix.language }}"

.github/workflows/tests.yml

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,10 @@ jobs:
1717
run-tests:
1818
runs-on: ${{ matrix.os }}
1919
strategy:
20+
fail-fast: false
2021
matrix:
2122
os: [ ubuntu-latest, macos-latest, windows-latest ]
22-
python: [ '3.8', '3.9', 'pypy-3.9', '3.10', '3.11', '3.12', '3.13' ]
23-
include:
24-
- os: macos-13
25-
python: '3.8'
26-
- os: macos-13
27-
python: '3.9'
28-
- os: macos-13
29-
python: 'pypy-3.9'
30-
- os: macos-13
31-
python: '3.10'
32-
- os: macos-13
33-
python: '3.11'
23+
python: [ '3.8', '3.9', 'pypy-3.9', '3.10', 'pypy-3.10', '3.11', '3.12', '3.13', '3.14' ]
3424
exclude:
3525
- os: macos-latest # Python 3.8 unavailable on macos-14
3626
python: '3.8'
@@ -54,16 +44,19 @@ jobs:
5444
python-version: ${{ matrix.python }}
5545
allow-prereleases: true
5646

57-
- name: install SlipCover and pytest
47+
- name: install prereqs
5848
run: |
59-
python3 -m pip install --upgrade pip # to avoid warnings
60-
python3 -m pip install .
49+
python3 -m pip install -U pip # to avoid warnings
6150
python3 -m pip install pytest
6251
6352
- name: install Unix dependencies
6453
if: matrix.os != 'windows-latest'
6554
run: python3 -m pip install pytest-forked
6655

56+
- name: install SlipCover
57+
run: |
58+
python3 -m pip install .
59+
6760
- name: run tests
6861
run: python3 -m pytest
6962

@@ -73,7 +66,7 @@ jobs:
7366
container: ${{ matrix.container }}
7467
strategy:
7568
matrix:
76-
python_tag: ['cp311', 'pp39']
69+
python_tag: ['cp311'] # PyPy not available in manylinux container; tested via run-tests job
7770
include:
7871
- os: ubuntu-latest
7972
container: quay.io/pypa/manylinux_2_28_x86_64 # https://github.com/pypa/manylinux
@@ -86,16 +79,17 @@ jobs:
8679
cat $GITHUB_PATH
8780
- name: install dependencies
8881
run: |
89-
python3 -m pip install --upgrade pip # to avoid warnings
90-
python3 -m pip install wheel
82+
python3 -m pip install -U pip # avoids warnings
83+
python3 -m pip install wheel build
84+
9185
- name: build sdist
92-
run: |
93-
python3 setup.py sdist
86+
run: python3 -m build --sdist
87+
9488
- name: try to use it
9589
run: |
9690
tar xzf dist/slipcover*.tar.gz
9791
cd slipcover*
98-
python3 setup.py build
92+
python3 -m build --wheel
9993
10094
# Run a manylinux wheel build to verify build configuration
10195
test-build-wheel-manylinux:
@@ -114,13 +108,15 @@ jobs:
114108
run: |
115109
PYV=`echo "${{ matrix.python_version }}" | tr -d "."`; ls -d -1 /opt/python/cp$PYV*/bin | head -n 1 >> $GITHUB_PATH
116110
cat $GITHUB_PATH
111+
117112
- name: install dependencies
118113
run: |
119-
python3 -m pip install --upgrade pip # otherwise building 'cryptography' may fail
120-
python3 -m pip install setuptools wheel twine
114+
python3 -m pip install -U pip # avoids GitHub warnings
115+
python3 -m pip install twine build
116+
121117
- name: build wheel
122-
run: |
123-
python3 setup.py bdist_wheel
118+
run: python3 -m build --wheel
119+
124120
- name: run auditwheel for manylinux
125121
run: |
126122
auditwheel repair dist/*.whl

CLAUDE.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
SlipCover is a fast, near-zero-overhead Python code coverage tool. Unlike traditional coverage tools that use Python's tracing facilities, SlipCover uses just-in-time bytecode instrumentation (Python <3.12) or the `sys.monitoring` API (Python 3.12+) to track executed code with minimal overhead.
8+
9+
## Build and Development Commands
10+
11+
```bash
12+
# Install in development/editable mode
13+
pip install -e .
14+
15+
# Run all tests
16+
pytest
17+
18+
# Run a single test file
19+
pytest tests/test_coverage.py
20+
21+
# Run a specific test
22+
pytest tests/test_coverage.py::test_function_name
23+
24+
# Run with pytest-forked (Unix only, useful for isolation)
25+
pytest --forked
26+
27+
# Clean build artifacts
28+
make clean
29+
30+
# Run benchmarks
31+
make bench
32+
```
33+
34+
## Running SlipCover
35+
36+
```bash
37+
# Run a script with coverage
38+
python -m slipcover myscript.py
39+
40+
# Run with a module (e.g., pytest)
41+
python -m slipcover -m pytest
42+
43+
# Enable branch coverage
44+
python -m slipcover --branch myscript.py
45+
46+
# Output JSON format
47+
python -m slipcover --json --out coverage.json myscript.py
48+
```
49+
50+
## Architecture
51+
52+
### Core Components
53+
54+
- **`src/slipcover/slipcover.py`**: Main `Slipcover` class that manages instrumentation and coverage collection. Contains version-specific code paths for Python <3.12 (bytecode rewriting) vs 3.12+ (sys.monitoring).
55+
56+
- **`src/slipcover/bytecode.py`**: Bytecode manipulation utilities (`Editor` class) for inserting probe calls into Python bytecode. Only used on Python <3.12.
57+
58+
- **`src/slipcover/branch.py`**: AST-based pre-instrumentation for branch coverage. The `preinstrument()` function inserts branch markers before compilation.
59+
60+
- **`src/slipcover/importer.py`**: Custom import machinery (`ImportManager`, `SlipcoverMetaPathFinder`, `SlipcoverLoader`) that intercepts module loading to instrument code. Also contains `FileMatcher` for source file filtering and `wrap_pytest()` for pytest integration.
61+
62+
- **`src/probe.cxx`**: C++ extension module providing low-overhead probe signaling for Python <3.12. Not used on 3.12+ (pure Python there).
63+
64+
### Python Version Handling
65+
66+
The codebase has significant branching based on Python version:
67+
- **Python 3.12+**: Uses `sys.monitoring` API for coverage (no bytecode rewriting, no C++ extension)
68+
- **Python <3.12**: Uses bytecode instrumentation via the `probe` C++ extension
69+
70+
Many functions have `if sys.version_info >= (3,12):` blocks with different implementations.
71+
72+
### Coverage Flow
73+
74+
1. **Script/module execution**: `__main__.py` parses args, creates `Slipcover` and `FileMatcher` instances
75+
2. **Import interception**: `ImportManager` installs a meta path finder that wraps module loaders
76+
3. **Instrumentation**: When matching modules load, their bytecode is instrumented via `Slipcover.instrument()`
77+
4. **Branch coverage** (optional): AST pre-instrumentation via `branch.preinstrument()` adds branch markers before compilation
78+
5. **Collection**: Probes signal line/branch execution to the `Slipcover` instance
79+
6. **De-instrumentation** (Python <3.12): Once coverage is recorded, probes can be disabled to reduce overhead
80+
7. **Reporting**: `get_coverage()` returns JSON-compatible coverage data; output can be text, JSON, or XML
81+
82+
### Test Structure
83+
84+
Tests are in `tests/` and use pytest:
85+
- `test_coverage.py`: End-to-end coverage functionality tests
86+
- `test_instrumentation.py`: Bytecode instrumentation tests
87+
- `test_bytecode.py`: Low-level bytecode editor tests
88+
- `test_branch.py`: Branch coverage and AST pre-instrumentation tests
89+
- `test_importer.py`: Import machinery tests

Makefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ all:
22
python3 -m pip install -e .
33

44
# obtained with e.g. "brew install python@3.10"
5-
HOMEBREW_PYTHON=/opt/homebrew/opt/python@
65
test:
76
- rm -f .coverage
8-
@ for V in 3.8 3.9 3.10 3.11 3.12 3.13; do \
9-
P=$$(command -v ${HOMEBREW_PYTHON}$$V/bin/python3 || command -v python$$V); \
7+
@ for V in python3.8 python3.9 pypy3.9 python3.10 pypy3.10 python3.11 python3.12 python3.13; do \
8+
P=$$(command -v $$V); \
109
if ! [ -z $$P ]; then \
1110
$$P --version; \
1211
$$P -O -m pip uninstall -y slipcover; \

pyproject.toml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@ classifiers = [
1414
"Programming Language :: Python :: 3.11",
1515
"Programming Language :: Python :: 3.12",
1616
"Programming Language :: Python :: 3.13",
17+
"Programming Language :: Python :: 3.14",
1718
"License :: OSI Approved :: Apache Software License",
1819
"Operating System :: POSIX :: Linux",
1920
"Operating System :: MacOS :: MacOS X",
2021
"Operating System :: Microsoft :: Windows :: Windows 10"
2122
]
22-
requires-python = ">=3.8,<3.14"
23+
requires-python = ">=3.8,<3.15"
2324
dependencies = [
2425
"tabulate"
2526
]
2627

2728
[project.scripts]
2829
slipcover = "slipcover.__main__:main"
2930

31+
[project.entry-points.pytest11]
32+
slipcover = "slipcover.pytest_plugin"
33+
3034
[project.urls]
3135
"Homepage" = "https://github.com/plasma-umass/slipcover"
3236
"Repository" = "https://github.com/plasma-umass/slipcover"
@@ -35,10 +39,19 @@ slipcover = "slipcover.__main__:main"
3539
# see https://peps.python.org/pep-0508/#environment-markers for conditional syntax
3640
requires = [
3741
"setuptools>61",
42+
"setuptools<72.2; implementation_name == 'pypy'", # https://github.com/pypa/distutils/issues/283
3843
"wheel",
3944
"tomli; python_version < '3.11'" # tomllib alternative
4045
]
4146
build-backend = "setuptools.build_meta"
4247

4348
[tool.setuptools.package-dir]
4449
"" = "src"
50+
51+
[project.optional-dependencies]
52+
test = [
53+
'pytest',
54+
'pytest-forked',
55+
'pytest-xdist'
56+
]
57+

src/slipcover/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .version import __version__
2-
from .slipcover import Slipcover, merge_coverage, print_coverage
2+
from .slipcover import Slipcover, merge_coverage, print_coverage, print_xml
33
from .importer import FileMatcher, ImportManager, wrap_pytest
44
from .fuzz import wrap_function

0 commit comments

Comments
 (0)