You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Maven plugin that tracks which production classes each test touches at the bytecode level, then uses Git to detect what changed and runs only the affected tests. Zero annotations. Zero config changes to your tests. Just add the plugin and watch your feedback loop shrink.
3
+
A Maven plugin that tracks which production classes each test touches at the bytecode level, then uses Git to detect what changed and runs only the affected tests. No annotations, no config changes to your tests.
4
4
5
5
## Why?
6
6
7
-
Large Maven projects waste minutes (or hours) re-running thousands of tests when only a handful of source files changed. This plugin fixes that:
7
+
Large Maven projects spend a lot of time re-running tests when only a few source files changed. This plugin addresses that:
8
8
9
-
-**Bytecode-level precision**: instruments every method entry via a Java agent, so it catches dependencies that static analysis misses (reflection, polymorphism, lambdas).
10
-
-**Git-aware**: diffs your working tree against the last commit, last tag, or last full run to find changed files.
11
-
-**Zero test changes**: works with JUnit 4, JUnit 5, and TestNG out of the box. No annotations, no base classes, no test rewrites.
12
-
-**Safe by default**: when in doubt, runs everything. Missing coverage data? Full run. Git error? Full run. The plugin never silently skips tests.
13
-
-**Multi-module ready**: supports reactors with shared coverage maps and concurrent-safe writes under `mvn -T`.
9
+
-Instruments every method entry via a Java agent, catching dependencies that static analysis misses (reflection, polymorphism, lambdas)
10
+
-Diffs your working tree against the last commit, last tag, or last full run to find changed files
11
+
-Works with JUnit 4, JUnit 5, and TestNG without requiring annotations, base classes, or test rewrites
12
+
-Falls back to a full run when something is uncertain (missing coverage data, git errors, etc.)
13
+
-Supports multi-module reactors with shared coverage maps and concurrent writes under `mvn -T`
14
14
15
15
## Quick start
16
16
@@ -39,27 +39,13 @@ Then run your build as usual:
39
39
mvn verify
40
40
```
41
41
42
-
**First run:** all tests execute and coverage is recorded. **Every run after:** only tests affected by your changes are selected. That's it.
42
+
On the first run all tests execute and coverage is recorded. On subsequent runs only tests affected by your changes are selected.
1.**Collect**: attaches a Java agent to Surefire's forked JVM. The agent instruments every method entry in your production and test classes using ASM bytecode rewriting.
61
-
2.**Select**: uses JGit to detect changed `.java` files, resolves them to compiled classes (including inner classes), looks up the coverage map to find which tests touch those classes, and sets Surefire's `test` filter.
62
-
3.**Report**: merges the per-module coverage dump into the shared coverage map (JSON) and writes a human-readable summary.
46
+
1.**Collect** (`process-test-classes`): attaches a Java agent to Surefire's forked JVM. The agent instruments method entries in your production and test classes using ASM.
47
+
2.**Select** (`process-test-classes`): uses JGit to detect changed `.java` files, resolves them to compiled classes (including inner classes), looks up the coverage map to find which tests touch those classes, and sets Surefire's `test` filter.
48
+
3.**Report** (`verify`): merges the per-module coverage dump into the shared coverage map (JSON) and prints a summary.
63
49
64
50
## Configuration
65
51
@@ -86,64 +72,60 @@ mvn verify
86
72
| Parameter | Default | Description |
87
73
|-----------|---------|-------------|
88
74
|`baseline`|`lastCommit`| Git baseline for change detection. `lastCommit` = HEAD vs HEAD~1, `lastTag` = HEAD vs most recent tag, `lastFullRun` = working tree vs the commit recorded in the coverage map. |
89
-
|`fullRunInterval`|`50`| Force a periodic full run every N incremental builds. Set to `0` to disable. |
90
-
|`includes`|*(auto)*| Comma-separated package prefixes to instrument. Auto-detected from your source roots if omitted. |
75
+
|`fullRunInterval`|`50`| Force a full run every N incremental builds. Set to `0` to disable. |
76
+
|`includes`|*(auto)*| Comma-separated package prefixes to instrument. Auto-detected from source roots if omitted. |
91
77
|`excludes`|*(empty)*| Comma-separated package prefixes to exclude from instrumentation. |
92
78
|`failOnEmptySelection`|`false`| If `true`, fail the build when no tests match the changed classes instead of falling back to a full run. |
93
79
|`coverageMapPath`|`<reactor-root>/target/.test-impact/coverage.json`| Override the coverage map location. |
94
80
95
81
## Baseline strategies
96
82
97
-
Choose the right baseline for your workflow:
98
-
99
-
| Strategy | Best for | How it works |
83
+
| Strategy | Use case | How it works |
100
84
|----------|----------|--------------|
101
-
|`lastCommit`|**CI / pull requests**| Diffs HEAD against HEAD~1. Each push re-evaluates. |
102
-
|`lastTag`|**Release pipelines**| Diffs HEAD against the most recent Git tag by timestamp. |
103
-
|`lastFullRun`|**Local development**| Diffs the working tree against the commit hash recorded in the coverage map from the last full test run. |
85
+
|`lastCommit`| CI / pull requests | Diffs HEAD against HEAD~1. Each push re-evaluates. |
86
+
|`lastTag`| Release pipelines | Diffs HEAD against the most recent Git tag by timestamp. |
87
+
|`lastFullRun`| Local development | Diffs the working tree against the commit hash recorded in the coverage map from the last full test run. |
104
88
105
89
## Multi-module reactors
106
90
107
-
The plugin works out of the box with multi-module Maven projects, including parallel builds (`mvn -T`):
91
+
The plugin supports multi-module Maven projects, including parallel builds (`mvn -T`):
108
92
109
-
-**Shared coverage map** at the reactor root (`target/.test-impact/coverage.json`)
110
-
-**Per-module dumps**: each module's Surefire JVM writes its own binary dump
111
-
-**Concurrency-safe merges**: `report` uses a JVM monitor + OS-level `FileLock` for safe concurrent writes
112
-
-**Dependency-aware filtering**: `select` uses `MavenSession.getProjectDependencyGraph()` to only consider changes in upstream modules
93
+
- Shared coverage map at the reactor root (`target/.test-impact/coverage.json`)
94
+
-Each module's Surefire JVM writes its own binary dump
95
+
-`report` uses a JVM monitor + OS-level `FileLock` for concurrent writes
96
+
-`select` uses `MavenSession.getProjectDependencyGraph()` to only consider changes in upstream modules
113
97
114
-
## Safety guarantees
98
+
## Fallback behaviour
115
99
116
-
The plugin is designed to **never silently skip tests**. It falls back to a full run when:
100
+
The plugin falls back to a full run when:
117
101
118
102
- No coverage map exists (first run)
119
103
- Coverage map version doesn't match the plugin version
120
104
- Coverage map is older than `fullRunInterval` builds
121
105
- Git change detection fails
122
-
- The change set is empty (ambiguous state)
106
+
- The change set is empty
123
107
- No tests intersect with the changed classes
124
108
125
-
This means you can adopt the plugin incrementally with confidence. The worst case is running all tests, same as without the plugin.
109
+
The worst case is running all tests, same as without the plugin.
0 commit comments