8
8
import java .io .PrintWriter ;
9
9
import java .io .StringWriter ;
10
10
import java .util .HashMap ;
11
+ import java .util .HashSet ;
11
12
import java .util .List ;
12
13
import java .util .Map ;
14
+ import java .util .Set ;
13
15
import java .util .StringTokenizer ;
14
16
import java .util .concurrent .CountDownLatch ;
15
17
import java .util .concurrent .ExecutorService ;
16
18
import java .util .concurrent .Executors ;
17
19
import java .util .concurrent .Future ;
18
20
import java .util .concurrent .TimeUnit ;
19
21
import java .util .concurrent .atomic .AtomicLong ;
22
+ import java .util .regex .Matcher ;
23
+ import java .util .regex .Pattern ;
20
24
import javax .xml .parsers .ParserConfigurationException ;
21
25
import org .apache .maven .plugin .AbstractMojo ;
22
26
import org .apache .maven .plugin .MojoExecutionException ;
27
+ import org .apache .maven .plugin .MojoFailureException ;
23
28
import org .apache .maven .plugins .annotations .Mojo ;
24
29
import org .apache .maven .plugins .annotations .Parameter ;
25
30
import org .apache .maven .project .MavenProject ;
@@ -30,6 +35,8 @@ public class SuperTestMavenPlugin extends AbstractMojo {
30
35
// this is the max time to wait in seconds for process termination after the stdout read is
31
36
// finished or terminated
32
37
private static final int STDOUT_POST_READ_WAIT_TIMEOUT = 10 ;
38
+ private static final String TEST_REGEX = "-Dtest=(.*?)(\\ s|$)" ;
39
+ private static final Pattern TEST_REGEX_PATTERN = Pattern .compile (TEST_REGEX );
33
40
34
41
private ExecutorService pool ;
35
42
@@ -49,8 +56,13 @@ public class SuperTestMavenPlugin extends AbstractMojo {
49
56
@ Parameter (property = "shellNoActivityTimeout" , readonly = true , defaultValue = "300" )
50
57
Integer shellNoActivityTimeout ;
51
58
52
- public void execute () throws MojoExecutionException {
59
+ @ Parameter (property = "includes" )
60
+ List <String > includes ;
53
61
62
+ @ Parameter (property = "excludes" )
63
+ List <String > excludes ;
64
+
65
+ public void execute () throws MojoExecutionException , MojoFailureException {
54
66
if (mvnTestOpts == null ) {
55
67
mvnTestOpts = "" ;
56
68
}
@@ -69,6 +81,14 @@ public void execute() throws MojoExecutionException {
69
81
final String groupId = project .getGroupId ();
70
82
71
83
pool = Executors .newFixedThreadPool (1 );
84
+ String testClassesDir = project .getBuild ().getTestOutputDirectory ();
85
+
86
+ Set <String > allTestClasses = new HashSet <>(
87
+ new TestListResolver (
88
+ includes , excludes , getTest (), testClassesDir ).scanDirectories ());
89
+
90
+ getLog ().info ("Test classes dir: " + testClassesDir );
91
+ getLog ().debug ("Test classes found: " + String .join ("," , allTestClasses ));
72
92
73
93
int exitCode ;
74
94
final String command = "mvn test " + buildProcessedMvnTestOpts (artifactId , groupId );
@@ -83,7 +103,7 @@ public void execute() throws MojoExecutionException {
83
103
}
84
104
85
105
// Strip -Dtest=... from the Maven opts if specified, since these were valid for the very first run only.
86
- mvnTestOpts = mvnTestOpts .replaceAll ("-Dtest=(.*?)( \\ s|$)" , "" );
106
+ mvnTestOpts = mvnTestOpts .replaceAll (TEST_REGEX , "" );
87
107
88
108
for (int retryRunNumber = 1 ; retryRunNumber <= retryRunCount ; retryRunNumber ++) {
89
109
final File [] xmlFileList = getXmlFileList (baseDir );
@@ -97,10 +117,11 @@ public void execute() throws MojoExecutionException {
97
117
throw new MojoExecutionException (
98
118
"Failed to parse surefire report! file=" + file , e );
99
119
}
100
- classnameToTestcaseList .put (runResult .getClassName (), runResult .getTestCases ());
120
+ classnameToTestcaseList .put (
121
+ runResult .getClassName (), runResult .getFailedTestCases ());
101
122
}
102
123
103
- final String runCommand = createRerunCommand (classnameToTestcaseList );
124
+ final String runCommand = createRerunCommand (allTestClasses , classnameToTestcaseList );
104
125
105
126
// previous run exited with code > 0, but all tests were actually run successfully
106
127
if (runCommand == null ) {
@@ -134,6 +155,15 @@ public void execute() throws MojoExecutionException {
134
155
}
135
156
}
136
157
158
+ public String getTest () {
159
+ if (mvnTestOpts == null ) {
160
+ return "" ;
161
+ }
162
+
163
+ Matcher matcher = TEST_REGEX_PATTERN .matcher (mvnTestOpts );
164
+ return matcher .find () ? matcher .group (1 ) : "" ;
165
+ }
166
+
137
167
private StringBuilder buildProcessedMvnTestOpts (String artifactId , String groupId ) {
138
168
final StringBuilder processedMvnTestOpts = new StringBuilder (" " );
139
169
processedMvnTestOpts .append (mvnTestOpts );
@@ -251,31 +281,58 @@ public File[] getXmlFileList(File baseDir) {
251
281
* @param classnameToTestcaseList map of classname and list of failed test cases
252
282
* @return rerunCommand
253
283
*/
254
- public String createRerunCommand (Map < String , List < String >> classnameToTestcaseList ) {
255
- boolean hasTestsAppended = false ;
284
+ public String createRerunCommand (
285
+ Set < String > allTestClasses , Map < String , List < String >> classnameToTestcaseList ) {
256
286
final StringBuilder retryRun = new StringBuilder ("mvn test" );
287
+ Set <String > incompleteTests = new HashSet <>(allTestClasses );
288
+
257
289
retryRun .append (" -Dtest=" );
290
+ int emptyRetryRunLen = retryRun .length ();
291
+
258
292
// TODO: 04/02/2022 replace with Java 8 streams
259
293
for (String className : classnameToTestcaseList .keySet ()) {
294
+ // if a test class is in the surefire report, it means that all its tests were executed
295
+ incompleteTests .remove (className );
260
296
List <String > failedTestCaseList = classnameToTestcaseList .get (className );
297
+
261
298
if (!failedTestCaseList .isEmpty ()) {
262
- retryRun .append (className );
263
- hasTestsAppended = true ;
264
- if (failedTestCaseList .contains ("" )) {
265
- retryRun .append ("," );
266
- continue ;
267
- }
268
- retryRun .append ("#" );
269
- for (int i = 0 ; i < failedTestCaseList .size (); i ++) {
270
- retryRun .append (failedTestCaseList .get (i )).append ("*" );
271
- if (i == failedTestCaseList .size () - 1 ) {
272
- retryRun .append ("," );
273
- } else {
274
- retryRun .append ("+" );
275
- }
276
- }
299
+ appendFailedTestCases (className , failedTestCaseList , retryRun );
300
+ } else {
301
+ // passing tests will not be re-run anymore
302
+ allTestClasses .remove (className );
303
+ }
304
+ }
305
+
306
+ if (retryRun .length () != emptyRetryRunLen
307
+ && retryRun .charAt (retryRun .length () - 1 ) != ','
308
+ && !incompleteTests .isEmpty ()) {
309
+ retryRun .append ("," );
310
+ }
311
+
312
+ retryRun .append (String .join ("," , incompleteTests ));
313
+
314
+ return retryRun .length () != emptyRetryRunLen ? retryRun .toString () : null ;
315
+ }
316
+
317
+ private void appendFailedTestCases (
318
+ String className , List <String > failedTestCaseList , StringBuilder retryRun ) {
319
+ retryRun .append (className );
320
+
321
+ if (failedTestCaseList .contains ("" )) {
322
+ retryRun .append ("," );
323
+ return ;
324
+ }
325
+
326
+ retryRun .append ("#" );
327
+
328
+ for (int i = 0 ; i < failedTestCaseList .size (); i ++) {
329
+ retryRun .append (failedTestCaseList .get (i )).append ("*" );
330
+
331
+ if (i == failedTestCaseList .size () - 1 ) {
332
+ retryRun .append ("," );
333
+ } else {
334
+ retryRun .append ("+" );
277
335
}
278
336
}
279
- return hasTestsAppended ? retryRun .toString () : null ;
280
337
}
281
338
}
0 commit comments