Run only the tests affected by your code changes.
Project description
Pintest
Run only the tests affected by your code changes - automatically!
Pintest integrates with your git workflow as a pre-commit hook, running only tests that cover the code you've changed. This dramatically speeds up your development cycle while ensuring quality.
๐ฏ Key Features
- ๐ Fast: Run only affected tests, not the entire suite
- ๐ Safe: Blocks commits if affected tests fail
- ๐ Intelligent: Uses SQLite-based test-to-code mapping for instant lookups
- ๐ Incremental Coverage: Combines coverage from test runs
- โจ New Test Detection: Always runs newly added tests
- ๐ง Auto-Discovery: Detects unmapped tests and builds mapping automatically
- ๐ฃ Pre-commit Hook: Automatic integration with git workflow
- ๐ณ Docker Integration: Ensures containers are running before tests
- ๐ง Preflight Checks: Validates database connections (postgres, neo4j)
- ๐ฆ Git LFS Support: Shares pre-built mapping database across team (30-60min savings)
- ๐ Auto-Push: Automatically pushes commits with updated mapping database
โก Quick Start (Pre-Commit Hook)
cd ~/workspace/myproject
# Install the pre-commit hook
~/workspace/all/pintest/scripts/install_pre_commit.sh
# That's it! Now every commit will:
# 1. Find tests affected by your changes
# 2. Run only those tests
# 3. Block commit if tests fail
# 4. Combine coverage with existing data
๐ How It Works
Pre-Commit Flow
Developer commits changes
โ
Pre-commit hook triggered
โ
Compare staged changes vs development branch
โ
Query SQLite mapping: "Which tests cover these lines?"
โ
Detect unmapped tests (dry-run pytest --collect-only)
โ
Run unmapped tests all-at-once (build mapping)
โ
Run mapped affected tests with coverage
โ
Tests pass? โ Combine coverage โ Allow commit โ
Tests fail? โ Block commit โ
Auto-Discovery of Unmapped Tests
The pre-commit hook automatically detects tests that exist in your codebase but aren't in the mapping database yet:
- Collect all tests: Runs
pytest --collect-onlyto find all available tests - Compare with mapping: Queries
.test_mapping.dbto see which tests are already mapped - Find delta: Identifies tests that exist but have never been run with coverage
- Run all-at-once: Executes all unmapped tests together with
--cov-context=test(fastest) - Update mapping: After completion, updates the mapping database with new coverage
Why this matters:
- New tests you write are automatically added to the mapping
- Tests added by teammates get mapped when you first commit
- No need to manually regenerate the entire mapping
- Mapping database grows organically over time
- All tests run in a single pytest invocation (fastest possible)
Example:
$ git commit -m "Fix bug in auth.py"
๐ Found 1 changed Python file
๐ Mapping DB: 1234 tests, 567 files, 45678 mappings
๐ Collected 1250 total tests from pytest
โ ๏ธ Found 16 unmapped test(s)
================================================================================
Building coverage mapping for 16 unmapped test(s)
================================================================================
Running unmapped test: unit_tests/test_new_feature.py::test_case_1
โ Mapping updated for: unit_tests/test_new_feature.py::test_case_1
Running unmapped test: unit_tests/test_new_feature.py::test_case_2
โ Mapping updated for: unit_tests/test_new_feature.py::test_case_2
...
โ Successfully mapped 16 test(s)
================================================================================
Running 3 affected test(s)...
================================================================================
...
Mapping Database
The mapping is stored in .test_mapping.db (SQLite) at your repo root:
CREATE TABLE test_coverage (
test_name TEXT,
file_path TEXT,
line_number INTEGER,
PRIMARY KEY (test_name, file_path, line_number)
);
Fast lookups: "Which tests cover file X, line Y?"
๐ง Installation
Prerequisites
- Python 3.8+
- pytest >= 7.0
- pytest-cov >= 4.0
- Git repository
Step 1: Install Package
cd ~/workspace/all/pintest
pip install -e .
Step 2: Build Mapping Database
cd ~/workspace/myproject
# Build mapping database (resumable)
delta build-mapping --repo-root ~/workspace/myproject --verbose
If interrupted, resume with:
delta build-mapping --repo-root ~/workspace/myproject --resume --verbose
Step 3: Option for Batch Build from Existing Coverage (Advanced)
If you already have a .coverage file generated with --cov-context=test, you can build/update the database directly from it:
# Update mapping database using existing .coverage
delta update-mapping --repo-root ~/workspace/myproject --verbose
๐ See ITERATIVE_SETUP_GUIDE.md for detailed guide
Option C: Use Pre-built Database (Git LFS)
If your repo uses Git LFS to store .test_mapping.db, you can skip building:
# Install Git LFS (one-time)
brew install git-lfs # macOS
# or: sudo apt-get install git-lfs # Linux
# Initialize in repo
cd ~/workspace/myproject
git lfs install
# Pull the pre-built database
git lfs pull
Benefits:
- โ Instant - no rebuild needed
- โ Shared across team
- โ Always available after clone/pull
See GIT_LFS_SETUP.md in your repo.
Step 4: Install Pre-Commit Hook
cd ~/workspace/myproject
~/workspace/all/pintest/scripts/install_pre_commit.sh
Done! Now every commit will run only affected tests.
๐ Usage
Pre-Commit Hook (Automatic)
Just commit normally:
git add src/my_module.py
git commit -m "Fix bug in authentication"
# Hook runs automatically:
# - Finds tests covering src/my_module.py
# - Runs only those tests
# - Blocks commit if tests fail
# - Combines coverage on success
Manual Test Running
# Show what tests would run
pintest run --repo-root ~/workspace/myproject --dry-run --verbose
# Run affected tests manually
pintest run --repo-root ~/workspace/myproject
# Compare against different branch
pintest run --repo-root ~/workspace/myproject --base-branch develop
# Pass pytest arguments
pintest run --repo-root ~/workspace/myproject -- -x --pdb
Update Mapping
Update the mapping database when you add new tests or change coverage significantly:
# Build/update mapping database
delta build-mapping --repo-root ~/workspace/myproject --verbose
After adding many tests (iterative resume):
# Resume where you left off (only runs unmapped tests)
delta build-mapping --repo-root ~/workspace/myproject --resume --verbose
When to update:
- After adding new test files
- After significant refactoring
- Weekly (recommended for active projects)
- When mapping feels "stale" (tests not being selected correctly)
๐๏ธ Commands
pintest run
Run affected tests based on changes.
pintest run [OPTIONS] [-- PYTEST_ARGS]
Options:
--repo-root PATH Repository root (default: current directory)
--base-branch BRANCH Branch to compare against (default: master)
--coverage-file PATH Path to .coverage file
--dry-run Show tests without running
--min-tests N Minimum tests required
-v, --verbose Detailed output
pintest update-mapping
Update test mapping database from coverage data.
pintest update-mapping [OPTIONS]
Options:
--repo-root PATH Repository root (default: current directory)
--coverage-file PATH Source .coverage file
--mapping-db PATH Target mapping database path
-v, --verbose Detailed output
๐ How Test Selection Works
1. Changed Files Detection
git diff --cached development...HEAD
Identifies:
- Which Python files changed
- Which lines were added/modified
- Which files are test files
2. Test Mapping Lookup
SELECT DISTINCT test_name
FROM test_coverage
WHERE file_path = 'src/auth.py'
AND line_number IN (42, 43, 44)
Fast O(1) lookup using SQLite indexes.
3. Test Categories
| Category | Always Run? | Example |
|---|---|---|
| New tests | โ Yes | test_new_feature.py (added in commit) |
| Modified tests | โ Yes | test_auth.py (changed in commit) |
| Coverage-based | โ Yes | Tests that cover changed lines |
| Unaffected | โ No | Tests not covering any changes |
4. Coverage Combining
# Existing coverage from previous runs
.coverage (before)
# New coverage from affected tests
.coverage.new
# Combined result
.coverage (after) = .coverage (before) + .coverage.new
Uses coverage combine to merge data safely.
๐ก Examples
Example 1: Bug Fix
# You fix a bug in src/auth.py line 42
vim src/auth.py
git add src/auth.py
git commit -m "Fix login timeout bug"
# Hook runs:
# ๐ Found 1 changed Python file
# ๐ Mapping DB: 1234 tests, 567 files
# ๐ src/auth.py: 3 test(s) cover changed lines
# - unit_tests/test_auth.py::test_login_timeout
# - unit_tests/test_auth.py::test_login_retry
# - integration_tests/test_full_auth_flow.py::test_complete_flow
#
# Running 3 affected test(s)...
# โ
All tests passed. Commit allowed.
Example 2: New Test Added
# You add a new test file
vim unit_tests/test_new_feature.py
git add unit_tests/test_new_feature.py
git commit -m "Add tests for new feature"
# Hook runs:
# โจ New test files (always run): 1
# + unit_tests/test_new_feature.py
#
# Running 1 test file...
# โ
All tests passed. Commit allowed.
Example 3: Refactoring
# You refactor a utility function used everywhere
vim src/utils/helpers.py
git add src/utils/helpers.py
git commit -m "Refactor helper function"
# Hook runs:
# ๐ src/utils/helpers.py: 47 test(s) cover changed lines
#
# Running 47 affected test(s)...
# (Only runs 47, not all 1234 tests!)
๐ซ Bypassing the Hook
For urgent commits or when hook is broken:
git commit --no-verify -m "Urgent hotfix"
๐ง Configuration
Change Base Branch
Edit .git/hooks/pre-commit:
python3 -m delta.pre_commit_hook \
--repo-root "$REPO_ROOT" \
--base-branch main \ # Changed from development
--verbose
Customize Coverage Options
The hook runs pytest with:
pytest --cov=. --cov-context=test --cov-append --cov-report=
To customize, edit delta/pre_commit_hook.py.
๐ Statistics
Typical time savings:
| Project Size | Total Tests | Avg Affected | Time Before | Time After | Savings |
|---|---|---|---|---|---|
| Small (500 tests) | 500 | ~15 | 2 min | 10 sec | 92% |
| Medium (2000 tests) | 2000 | ~30 | 8 min | 30 sec | 94% |
| Large (5000 tests) | 5000 | ~50 | 20 min | 1 min | 95% |
Actual savings depend on test distribution and change scope.
๐ Troubleshooting
"Mapping database not found"
delta build-mapping --verbose
"No tests selected"
- Check if
.delta/test_mapping.dbexists and is recent - Update mapping:
delta build-mapping - Run with
--verboseto see what's happening
"Tests that should run aren't selected"
Regenerate mapping:
delta build-mapping --verbose
"Hook runs all tests"
Check that mapping database exists:
ls -lh .delta/test_mapping.db
If missing, generate it with delta build-mapping.
๐ Files Created
.delta/test_mapping.db- SQLite database with test-to-code mappings (gitignored).coverage- pytest coverage data (gitignored).git/hooks/pre-commit- Pre-commit hook script
๐ค Contributing
Delta is an open-source developer productivity tool.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deltatest_cli-0.4.23.tar.gz.
File metadata
- Download URL: deltatest_cli-0.4.23.tar.gz
- Upload date:
- Size: 58.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3eb4d1fe43e30154a795f0d5f2a2fd1a4b5007c8a0ddd97ba6196618a3c9142c
|
|
| MD5 |
7aa1f59238271b950cc7ee1e9b968ea0
|
|
| BLAKE2b-256 |
ef4fd09cfa574bb7afd116020268e5cc3277b4455f7f4f3fb3e1afedb006c201
|
File details
Details for the file deltatest_cli-0.4.23-py3-none-any.whl.
File metadata
- Download URL: deltatest_cli-0.4.23-py3-none-any.whl
- Upload date:
- Size: 54.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15ae04271839b04f7ebd4619349e4c34b021bb103aac4fdb534b50c627a9c732
|
|
| MD5 |
fd2e719d223584b9b33a0984314c8b5f
|
|
| BLAKE2b-256 |
6ddecd506fd2d63a0c581d7c5e6f1dd9e86d94f0434d26bcc8f4bfad44e1ffc9
|