Skip to main content

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:

  1. Collect all tests: Runs pytest --collect-only to find all available tests
  2. Compare with mapping: Queries .test_mapping.db to see which tests are already mapped
  3. Find delta: Identifies tests that exist but have never been run with coverage
  4. Run all-at-once: Executes all unmapped tests together with --cov-context=test (fastest)
  5. 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 .coverage file exists and is recent
  • Update mapping: pintest update-mapping
  • Run with --verbose to 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deltatest_cli-0.4.2.tar.gz (52.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deltatest_cli-0.4.2-py3-none-any.whl (51.3 kB view details)

Uploaded Python 3

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

Hashes for deltatest_cli-0.4.2.tar.gz
Algorithm Hash digest
SHA256 e6f19616a1ab4260aca0dfae741809e938bf56485f9fd55c6f01f6c0ac0213f9
MD5 fe4bac17db79928d1a3c9c44395a609b
BLAKE2b-256 5f510a88e9437b06da51d7dc6fce211bd4d7c512f3070e6c9a805c0938d3d0da

See more details on using hashes here.

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

Hashes for deltatest_cli-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7094f16e8764887aa88e524ab61d8c90bf5f87832cb04544dd3a13ded1a0ec7d
MD5 193738527aa19d38c2ab13204c56d8aa
BLAKE2b-256 43d2f1191e4529173fc2733d84dd6167b7216e3ce45754ded4c71d99eaca68ce

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page