Skip to content

tia — Test Impact Analysis ​

Run only the tests that matter for a given diff.

greengate tia analyses which source files changed between two commits, parses the import statements of every test file in the repository using tree-sitter, and outputs only the test files whose dependency graph overlaps with the changed set.

The default output is newline-separated file paths — designed to be piped directly into your test runner.


Usage ​

bash
greengate tia [OPTIONS]
Options:
      --base <REF>     Diff base: commit, branch, or tag [default: HEAD~1]
      --staged         Diff staged changes instead of committed diff
      --format <FMT>   Output format: newline (default) | text | json
  -h, --help           Print help

Pipe-friendly output (default) ​

bash
# Run only affected pytest tests
pytest $(greengate tia --base main)

# Run only affected Jest tests
npx jest $(greengate tia --base main)

# Run only affected Go tests
go test $(greengate tia --base main --format newline)

# Staged changes (before committing)
pytest $(greengate tia --staged)

When no tests are affected the command exits 0 with empty output — the test runner receives no arguments and will typically exit cleanly or with a "no tests selected" message.


How it works ​

  1. Diff — Runs git diff <base>...HEAD --name-only to get the list of changed files.
  2. Partition — Splits changed files into source files (.ts, .tsx, .js, .jsx, .py, .go) and config/data files (everything else). Config changes produce a warning to consider running the full suite.
  3. Walk — Traverses the repository (respecting .gitignore) and collects all files matching the configured test_patterns globs.
  4. Parse — For each test file, uses tree-sitter to extract all import/require/from … import statements.
  5. Match — Resolves each import to an extension-free path and checks whether it overlaps with any changed source file (exact match, path-suffix match, or package-directory match for Go).
  6. Output — Prints the affected test file paths.

Supported languages ​

LanguageImport syntax detected
TypeScript / TSXimport … from '…', export … from '…', require('…')
JavaScript / JSXimport … from '…', export … from '…', require('…')
Pythonimport foo.bar, from foo.bar import …
Goimport "pkg/path"

Path matching ​

Three overlap checks are applied (first match wins):

CheckExample
Exactimport src/utils/helpers ↔ changed src/utils/helpers.ts
Suffiximport utils/helpers ↔ changed src/utils/helpers.ts
Package dir (Go)import github.com/org/repo/pkg/utils ↔ changed pkg/utils/helpers.go

Text output ​

bash
greengate tia --base main --format text
╔══ Test Impact Analysis ══════════════════════════════╗
  Changed source files : 3
  Total test files     : 48
  Affected tests       : 7
╚══════════════════════════════════════════════════════╝

src/utils/__tests__/helpers.test.ts
src/components/__tests__/Button.test.tsx
src/api/__tests__/client.test.ts
tests/test_helpers.py
tests/test_client.py
pkg/utils/helpers_test.go
pkg/api/client_test.go

✅ 7/48 tests selected.

JSON output ​

bash
greengate tia --base main --format json
json
{
  "changed_source_files": [
    "src/utils/helpers.ts",
    "src/api/client.ts"
  ],
  "affected_tests": [
    "src/utils/__tests__/helpers.test.ts",
    "src/api/__tests__/client.test.ts"
  ],
  "total_test_files": 48,
  "config_files_changed": []
}

Configuration ​

Configure test file detection under [tia] in .greengate.toml:

toml
[tia]
test_patterns = [
  "**/*.test.ts",
  "**/*.test.tsx",
  "**/*.test.js",
  "**/*.test.jsx",
  "**/*.spec.ts",
  "**/*.spec.tsx",
  "**/*.spec.js",
  "**/*.spec.jsx",
  "**/test_*.py",
  "**/*_test.py",
  "tests/**/*.py",
  "**/*_test.go",
]

All patterns use standard glob syntax with ** for recursive matching. The defaults above cover the standard conventions for TypeScript/JavaScript, Python, and Go projects. Override to match your project layout.


CI usage ​

Use tia to speed up PR pipelines without sacrificing correctness:

yaml
- name: Run affected tests only
  run: |
    TESTS=$(greengate tia --base "${{ github.event.pull_request.base.sha }}")
    if [ -n "$TESTS" ]; then
      pytest $TESTS
    else
      echo "No affected tests — skipping"
    fi

For Jest:

yaml
- name: Run affected tests only
  run: |
    TESTS=$(greengate tia --base "${{ github.event.pull_request.base.sha }}")
    if [ -n "$TESTS" ]; then
      npx jest $TESTS
    else
      echo "No affected tests — skipping"
    fi

Limitations ​

  • Static imports only — dynamic import() calls with non-literal paths (e.g. import(someVariable)) cannot be statically resolved and are not detected.
  • Re-exports — if module A re-exports module B, and a test imports A, changes to B will not automatically surface that test. Direct imports only.
  • Non-source changes — changes to package.json, Cargo.toml, .env, YAML configs, or other non-source files are reported as warnings but do not drive test selection. If a config change could affect test outcomes, run the full suite manually.
  • Monorepo package boundaries — cross-package imports using workspace aliases (e.g. @myorg/shared) are matched on the alias string rather than resolved to a file path. Ensure your alias paths overlap with the actual file paths for best results.

Exit codes ​

CodeMeaning
0Command ran successfully (may output zero affected tests)
1Git diff failed, glob compilation error, or other runtime error

Released under the MIT License.