Skip to main content

LLM-powered test selection for CI/CD pipelines

Project description

Testwise

LLM-powered test selection for CI/CD pipelines
Run only the tests that matter. Save CI time without sacrificing coverage.

CI PyPI Python License


Testwise analyzes your git diff and uses an LLM to classify every test as must_run, should_run, or skip — then executes only what's needed. It supports test-level granularity for languages with parser plugins and falls back to file-level selection for everything else.

Why Testwise?

Large test suites slow down CI. Most changes only affect a fraction of your tests, but running the full suite every time wastes minutes (or hours). Existing static-analysis approaches miss indirect dependencies and cross-cutting concerns. Testwise uses an LLM that actually understands your code changes and test structure to make smarter decisions — with a safe fallback to run everything if it's ever uncertain.

How It Works

git diff ─> Discover Tests ─> Parse with Plugins ─> LLM Classifies ─> Run Selected ─> Report
  1. Diff Analysis — Extracts the git diff between base and head refs
  2. Test Discovery — Finds all test files and parses individual test functions via parser plugins
  3. LLM Classification — Sends diff + test inventory to an LLM with structured output
  4. Selective Execution — Runs only selected tests and reports results with GitHub annotations

Features

  • Hybrid Granularity — Test-level selection for languages with parser plugins (pytest built-in), file-level fallback for others
  • Plugin Architecture — Extensible parser system via Python entry points. Write a parser for any test framework.
  • Any LLM Provider — Uses litellm to support Claude, GPT, Gemini, and 100+ other models
  • GitHub Actions — Ships as a composite action with step summary, annotations, and outputs
  • Safe Fallback — If the LLM fails or is uncertain, falls back to running all tests
  • Test Annotations — Supports @pytest.mark.covers() to explicitly map tests to code areas

Quick Start

Install

pip install smartselect

Configure

Create .testwise.yml in your repo root:

runners:
  - name: pytest
    command: pytest
    args: ["-v", "--tb=short"]
    test_patterns: ["tests/**/*.py", "test_*.py"]
    parser: pytest
    select_mode: test

llm:
  model: anthropic/claude-sonnet-4-20250514
  api_key_env: ANTHROPIC_API_KEY

Run

# Dry run — see what the LLM would select
testwise --dry-run

# Run selected tests
testwise

# Force all tests (bypass LLM)
testwise --fallback

GitHub Actions

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history needed for diff

      - uses: mattfrautnick/testwise@v1
        with:
          api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          run-level: should_run

The action writes a Markdown summary to $GITHUB_STEP_SUMMARY and emits ::error:: annotations for failing tests inline in your PR diff.

Test Annotations

Testwise's pytest parser understands standard markers and a custom @covers annotation that explicitly maps tests to code areas:

import pytest

@pytest.mark.covers("auth_module", "user.login")
def test_login_success(client, db):
    """Verify successful login flow."""
    ...

@pytest.mark.integration
@pytest.mark.covers("payment_service")
def test_checkout_flow(client):
    ...

@pytest.mark.parametrize("role", ["admin", "user", "guest"])
def test_permissions(role):
    ...

The parser also extracts imports and fixture references automatically — no annotation required for basic dependency mapping.

Parser Plugins

Testwise uses a plugin architecture for language-specific test parsing. Plugins are registered via Python entry points.

Built-in Parsers

Parser Language Granularity Features
pytest Python Test-level Markers, covers, parametrize, fixtures, imports
generic Any File-level Fallback for unsupported languages

Writing a Parser Plugin

Implement BaseParser and register it as an entry point:

from testwise.parsers import BaseParser
from testwise.models import ParsedTest, ParsedTestFile, RunnerConfig
from pathlib import Path

class JestParser(BaseParser):
    name = "jest"
    languages = ["javascript", "typescript"]
    file_patterns = ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"]

    def parse_test_file(self, file_path: Path, content: str) -> ParsedTestFile:
        # Parse describe/it blocks, extract test names
        ...

    def build_run_command(self, tests, runner_config, repo_root):
        # Build jest --testNamePattern command
        ...
# pyproject.toml
[project.entry-points."testwise.parsers"]
jest = "my_package.jest_parser:JestParser"

See CONTRIBUTING.md for a full guide on writing and testing parser plugins.

CLI Reference

testwise [OPTIONS]

Options:
  -c, --config PATH                    Path to .testwise.yml
  -b, --base-ref TEXT                  Base git ref to diff against
      --head-ref TEXT                  Head git ref (default: HEAD)
  -o, --output [text|json|github]      Output format (default: text)
      --output-file PATH               Write JSON report to file
      --dry-run                        Show selections without running tests
      --fallback                       Skip LLM, run all tests
      --run-level [must_run|should_run|all]  Minimum classification to run
  -v, --verbose                        Verbose logging
      --version                        Show version
      --help                           Show this message

Configuration Reference

See .testwise.example.yml for a fully commented example.

Key Type Default Description
runners[].name string required Runner identifier
runners[].command string required Test runner command
runners[].args list [] Additional arguments
runners[].test_patterns list [] Glob patterns for test files
runners[].parser string "generic" Parser plugin name
runners[].select_mode string "file" "test" or "file"
runners[].timeout_seconds int 300 Per-runner timeout
llm.model string "anthropic/claude-sonnet-4-20250514" LLM model (litellm format)
llm.api_key_env string "ANTHROPIC_API_KEY" Env var containing API key
llm.max_context_tokens int 100000 Token budget for context
llm.temperature float 0.0 LLM temperature
fallback_on_error bool true Run all tests if LLM fails
run_should_run bool true Also run "should_run" tests

Roadmap

Testwise is in early development. Here's what's planned:

  • Jest/Vitest parser plugin
  • Go test parser plugin
  • Caching layer — skip LLM call for identical diffs
  • Cost tracking — log token usage and estimated cost per run
  • Confidence threshold — auto-fallback below a configurable confidence
  • Test impact analysis — learn from historical runs which tests fail for which changes
  • GitLab CI integration

Have an idea? Open an issue or start a discussion.

Contributing

Contributions are welcome! Whether it's a bug fix, a new parser plugin, or documentation improvements — all contributions help.

See CONTRIBUTING.md for development setup, architecture overview, and the full guide to writing parser plugins.

Community

License

MIT

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

smartselect-0.1.3.tar.gz (27.7 kB view details)

Uploaded Source

Built Distribution

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

smartselect-0.1.3-py3-none-any.whl (30.3 kB view details)

Uploaded Python 3

File details

Details for the file smartselect-0.1.3.tar.gz.

File metadata

  • Download URL: smartselect-0.1.3.tar.gz
  • Upload date:
  • Size: 27.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smartselect-0.1.3.tar.gz
Algorithm Hash digest
SHA256 878f4338ffe40cf2d41592e761d989cd06c3329fcd1059ec7760a1bb7612377d
MD5 257a965ca4abfa3877c9a06f47320571
BLAKE2b-256 3c35abf043abbb0959b69a6c3785d18b511f17f9373ce2a30136db6c5d4b1a53

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartselect-0.1.3.tar.gz:

Publisher: publish.yml on mattfrautnick/testwise

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file smartselect-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: smartselect-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 30.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for smartselect-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 1f8df52b25f88a71945f6b8c4d0790b177d993f6d126a44eb131bc31735a5266
MD5 f59bda796298d93b3764d0522612437d
BLAKE2b-256 3ad21b043750fe4349e3125f84e2f97102b64a18503c06931ff3a981b556c014

See more details on using hashes here.

Provenance

The following attestation bundles were made for smartselect-0.1.3-py3-none-any.whl:

Publisher: publish.yml on mattfrautnick/testwise

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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