Skip to content

Commit 3bc823c

Browse files
authored
Merge pull request #24 from CleverTap/find-missing-tests-on-rerun
Do not skip tests on rerun if maven run exited prematurely or was forcibly killed
2 parents d1c3e44 + 91865de commit 3bc823c

File tree

8 files changed

+471
-51
lines changed

8 files changed

+471
-51
lines changed

pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<groupId>com.clevertap</groupId>
88
<artifactId>supertest-maven-plugin</artifactId>
99
<packaging>maven-plugin</packaging>
10-
<version>1.11</version>
10+
<version>1.12</version>
1111
<description>A wrapper for Maven's Surefire Plugin, with advanced re-run capabilities.
1212
</description>
1313
<name>supertest-maven-plugin</name>
@@ -43,6 +43,11 @@
4343
<version>2.2.1</version>
4444
<scope>provided</scope>
4545
</dependency>
46+
<dependency>
47+
<groupId>org.apache.maven.plugins</groupId>
48+
<artifactId>maven-surefire-plugin</artifactId>
49+
<version>2.22.1</version>
50+
</dependency>
4651
<dependency>
4752
<groupId>org.junit.jupiter</groupId>
4853
<artifactId>junit-jupiter</artifactId>

src/main/java/com/clevertap/maven/plugins/supertest/RunResult.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,19 @@
55

66
public class RunResult {
77

8-
final String className;
9-
final List<String> testCases;
8+
private final String className;
9+
private final List<String> failedTestCases = new ArrayList<>();
1010

1111
public RunResult(String className) {
1212
this.className = className;
13-
this.testCases = new ArrayList<>();
1413
}
1514

16-
public void addTestCase(final String testCase) {
17-
testCases.add(testCase);
15+
public void addFailedTestCase(final String testCase) {
16+
failedTestCases.add(testCase);
1817
}
1918

20-
public List<String> getTestCases() {
21-
return testCases;
19+
public List<String> getFailedTestCases() {
20+
return failedTestCases;
2221
}
2322

2423
public String getClassName() {

src/main/java/com/clevertap/maven/plugins/supertest/SuperTestMavenPlugin.java

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,23 @@
88
import java.io.PrintWriter;
99
import java.io.StringWriter;
1010
import java.util.HashMap;
11+
import java.util.HashSet;
1112
import java.util.List;
1213
import java.util.Map;
14+
import java.util.Set;
1315
import java.util.StringTokenizer;
1416
import java.util.concurrent.CountDownLatch;
1517
import java.util.concurrent.ExecutorService;
1618
import java.util.concurrent.Executors;
1719
import java.util.concurrent.Future;
1820
import java.util.concurrent.TimeUnit;
1921
import java.util.concurrent.atomic.AtomicLong;
22+
import java.util.regex.Matcher;
23+
import java.util.regex.Pattern;
2024
import javax.xml.parsers.ParserConfigurationException;
2125
import org.apache.maven.plugin.AbstractMojo;
2226
import org.apache.maven.plugin.MojoExecutionException;
27+
import org.apache.maven.plugin.MojoFailureException;
2328
import org.apache.maven.plugins.annotations.Mojo;
2429
import org.apache.maven.plugins.annotations.Parameter;
2530
import org.apache.maven.project.MavenProject;
@@ -30,6 +35,8 @@ public class SuperTestMavenPlugin extends AbstractMojo {
3035
// this is the max time to wait in seconds for process termination after the stdout read is
3136
// finished or terminated
3237
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);
3340

3441
private ExecutorService pool;
3542

@@ -49,8 +56,13 @@ public class SuperTestMavenPlugin extends AbstractMojo {
4956
@Parameter(property = "shellNoActivityTimeout", readonly = true, defaultValue = "300")
5057
Integer shellNoActivityTimeout;
5158

52-
public void execute() throws MojoExecutionException {
59+
@Parameter(property = "includes" )
60+
List<String> includes;
5361

62+
@Parameter(property = "excludes" )
63+
List<String> excludes;
64+
65+
public void execute() throws MojoExecutionException, MojoFailureException {
5466
if (mvnTestOpts == null) {
5567
mvnTestOpts = "";
5668
}
@@ -69,6 +81,14 @@ public void execute() throws MojoExecutionException {
6981
final String groupId = project.getGroupId();
7082

7183
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));
7292

7393
int exitCode;
7494
final String command = "mvn test " + buildProcessedMvnTestOpts(artifactId, groupId);
@@ -83,7 +103,7 @@ public void execute() throws MojoExecutionException {
83103
}
84104

85105
// 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, "");
87107

88108
for (int retryRunNumber = 1; retryRunNumber <= retryRunCount; retryRunNumber++) {
89109
final File[] xmlFileList = getXmlFileList(baseDir);
@@ -97,10 +117,11 @@ public void execute() throws MojoExecutionException {
97117
throw new MojoExecutionException(
98118
"Failed to parse surefire report! file=" + file, e);
99119
}
100-
classnameToTestcaseList.put(runResult.getClassName(), runResult.getTestCases());
120+
classnameToTestcaseList.put(
121+
runResult.getClassName(), runResult.getFailedTestCases());
101122
}
102123

103-
final String runCommand = createRerunCommand(classnameToTestcaseList);
124+
final String runCommand = createRerunCommand(allTestClasses, classnameToTestcaseList);
104125

105126
// previous run exited with code > 0, but all tests were actually run successfully
106127
if (runCommand == null) {
@@ -134,6 +155,15 @@ public void execute() throws MojoExecutionException {
134155
}
135156
}
136157

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+
137167
private StringBuilder buildProcessedMvnTestOpts(String artifactId, String groupId) {
138168
final StringBuilder processedMvnTestOpts = new StringBuilder(" ");
139169
processedMvnTestOpts.append(mvnTestOpts);
@@ -251,31 +281,58 @@ public File[] getXmlFileList(File baseDir) {
251281
* @param classnameToTestcaseList map of classname and list of failed test cases
252282
* @return rerunCommand
253283
*/
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) {
256286
final StringBuilder retryRun = new StringBuilder("mvn test");
287+
Set<String> incompleteTests = new HashSet<>(allTestClasses);
288+
257289
retryRun.append(" -Dtest=");
290+
int emptyRetryRunLen = retryRun.length();
291+
258292
// TODO: 04/02/2022 replace with Java 8 streams
259293
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);
260296
List<String> failedTestCaseList = classnameToTestcaseList.get(className);
297+
261298
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("+");
277335
}
278336
}
279-
return hasTestsAppended ? retryRun.toString() : null;
280337
}
281338
}

src/main/java/com/clevertap/maven/plugins/supertest/SurefireReportParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public RunResult parse() throws ParserConfigurationException, IOException, SAXEx
5050
uniqueNames.add(name);
5151
}
5252
}
53-
uniqueNames.forEach(result::addTestCase);
53+
uniqueNames.forEach(result::addFailedTestCase);
5454
return result;
5555
}
5656

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.clevertap.maven.plugins.supertest;
2+
3+
import java.io.File;
4+
import java.util.ArrayList;
5+
import java.util.Collections;
6+
import java.util.List;
7+
import java.util.function.Function;
8+
import java.util.stream.Collectors;
9+
import org.apache.maven.plugin.MojoFailureException;
10+
import org.apache.maven.plugin.surefire.util.DirectoryScanner;
11+
12+
public class TestListResolver {
13+
private static final String[] DEFAULT_INCLUDES = new String[] {
14+
"**/Test*.java", "**/*Test.java", "**/*Tests.java", "**/*TestCase.java"};
15+
private static final String[] DEFAULT_EXCLUDES = new String[] {"**/*$*"};
16+
17+
private final DirectoryScanner scanner;
18+
19+
public TestListResolver(
20+
List<String> includes, List<String> excludes, String test, String testClassDir)
21+
throws MojoFailureException {
22+
scanner = new DirectoryScanner(
23+
new File(testClassDir),
24+
new org.apache.maven.surefire.testset.TestListResolver(
25+
getIncludeList(test, includes), getExcludeList(test, excludes)));
26+
}
27+
28+
public List<String> scanDirectories() {
29+
return scanner.scan().getClasses();
30+
}
31+
32+
private List<String> getIncludeList(String test, List<String> includes)
33+
throws MojoFailureException {
34+
return getFilterList(test, includes, DEFAULT_INCLUDES, x -> x.split(","));
35+
}
36+
37+
private List<String> getExcludeList(String test, List<String> excludes)
38+
throws MojoFailureException {
39+
return getFilterList(test, excludes, DEFAULT_EXCLUDES, x -> new String[] {});
40+
}
41+
42+
private List<String> getFilterList(
43+
String test,
44+
List<String> filterData,
45+
String[] defaultFilterData,
46+
Function<String, String[]> parseTestFunc) throws MojoFailureException {
47+
List<String> filterList = new ArrayList<>();
48+
49+
if (isSpecificTestSpecified(test)) {
50+
Collections.addAll(filterList, parseTestFunc.apply(test));
51+
} else {
52+
if (filterData != null) {
53+
filterList.addAll(filterData);
54+
checkMethodFilterInIncludesExcludes(filterList);
55+
}
56+
57+
if (filterList.isEmpty()) {
58+
Collections.addAll(filterList, defaultFilterData);
59+
}
60+
}
61+
62+
return filterNulls(filterList);
63+
}
64+
65+
private static boolean isSpecificTestSpecified(String test) {
66+
return test != null && !test.isEmpty();
67+
}
68+
69+
private static void checkMethodFilterInIncludesExcludes(Iterable<String> patterns)
70+
throws MojoFailureException {
71+
for (String pattern : patterns) {
72+
if (pattern != null && pattern.contains( "#" )) {
73+
throw new MojoFailureException(
74+
"Method filter prohibited in includes|excludes parameter: " + pattern);
75+
}
76+
}
77+
}
78+
79+
private static List<String> filterNulls(List<String> toFilter) {
80+
return toFilter.stream()
81+
.filter(x -> x != null && !x.trim().isEmpty())
82+
.collect(Collectors.toList());
83+
}
84+
}

0 commit comments

Comments
 (0)