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: Generate Initial Coverage
cd ~/workspace/myproject
# Run all tests with coverage context enabled
pytest --cov=. --cov-context=test --cov-report=
This creates .coverage file with test-to-code mappings.
Step 3: Create Mapping Database
Choose one of two methods:
Option A: Iterative (Recommended for large test suites )
# Build mapping by running tests one-by-one (resumable)
pintest build-mapping --repo-root ~/workspace/myproject --verbose
# If interrupted, resume with:
pintest build-mapping --repo-root ~/workspace/myproject --resume --verbose
Benefits:
- โ Resumable after interruption or failure
- โ Progress saved every 10 tests
- โ Safe for 20,000+ test suites
- โ Continues even if some tests fail
Time: ~2-4 hours , but can be interrupted and resumed anytime.
Option B: Batch (Faster, but not resumable)
# Run all tests with coverage at once
pytest --cov=. --cov-context=test --cov-report=
# Build mapping from coverage
pintest update-mapping --repo-root ~/workspace/myproject --verbose
Benefits:
- โ Faster (45-90 minutes )
- โ Single command
Drawbacks:
- โ Not resumable if interrupted
- โ Stops on first failure
- โ Must regenerate from scratch
๐ 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:
Regular updates (batch):
# Run tests with coverage
pytest --cov=. --cov-context=test --cov-report=
# Update mapping database
pintest update-mapping --repo-root ~/workspace/myproject --verbose
After adding many tests (iterative):
# Resume where you left off (only runs unmapped tests)
pintest 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 pintest.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 pintest/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"
pintest update-mapping --repo-root ~/workspace/myproject
"No tests selected"
- Check if
.coveragefile exists and is recent - Update mapping:
pintest update-mapping - Run with
--verboseto see what's happening
"Tests that should run aren't selected"
Regenerate coverage and mapping:
pytest --cov=. --cov-context=test --cov-report=
pintest update-mapping --verbose
"Hook runs all tests"
Check that mapping database exists:
ls -lh .test_mapping.db
If missing, generate it with update-mapping.
๐ Files Created
.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
Pintest 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.2.tar.gz.
File metadata
- Download URL: deltatest_cli-0.4.2.tar.gz
- Upload date:
- Size: 52.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6f19616a1ab4260aca0dfae741809e938bf56485f9fd55c6f01f6c0ac0213f9
|
|
| MD5 |
fe4bac17db79928d1a3c9c44395a609b
|
|
| BLAKE2b-256 |
5f510a88e9437b06da51d7dc6fce211bd4d7c512f3070e6c9a805c0938d3d0da
|
File details
Details for the file deltatest_cli-0.4.2-py3-none-any.whl.
File metadata
- Download URL: deltatest_cli-0.4.2-py3-none-any.whl
- Upload date:
- Size: 51.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7094f16e8764887aa88e524ab61d8c90bf5f87832cb04544dd3a13ded1a0ec7d
|
|
| MD5 |
193738527aa19d38c2ab13204c56d8aa
|
|
| BLAKE2b-256 |
43d2f1191e4529173fc2733d84dd6167b7216e3ce45754ded4c71d99eaca68ce
|