Skip to content

Commit 4c7d903

Browse files
authored
Support running partial test names from suite file
Closes #2897 1. Add new boolean option '-ignoreMissedTestNames' to work with the option '-testnames'. 2. When -testnames is given, and '-ignoreMissedTestNames true' is also given, then in case any missed test names not found in the suite, only warning message will be printed, TestNG will continue to run other test names which are existing in the suite. 3. Users who are going to use the new option '-ignoreMissedTestNames' should be aware of that the logging level should be properly configured to make sure the warning message is visible in output or console, rather than missed the notification of the missed test names, if any.
1 parent a766113 commit 4c7d903

File tree

9 files changed

+304
-33
lines changed

9 files changed

+304
-33
lines changed

CHANGES.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Current
2+
New: GITHUB-2897: Not exception but warning if some (not all) of the given test names are not found in suite files. (Bruce Wen)
23
New: Added assertListContains and assertListContainsObject methods to check if specific object present in List (Dmytro Budym)
34
Fixed: GITHUB-2888: Skipped Tests with DataProvider appear as failed (Joaquin Moreira)
45
Fixed: GITHUB-2884: Discrepancies with DataProvider and Retry of failed tests (Krishnan Mahadevan)

testng-ant/src/main/java/org/testng/TestNGAntTask.java

+6
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ public class TestNGAntTask extends Task {
161161
private String m_methods;
162162
private Mode mode = Mode.testng;
163163
private boolean forkJvm = true;
164+
private boolean m_ignoreMissedTestNames;
164165

165166
public enum Mode {
166167
// lower-case to better look in build scripts
@@ -360,6 +361,10 @@ public void setTestNames(String testNames) {
360361
m_testNames = testNames;
361362
}
362363

364+
public void setIgnoreMissedTestNames(boolean ignoreMissedTestNames) {
365+
m_ignoreMissedTestNames = ignoreMissedTestNames;
366+
}
367+
363368
/**
364369
* Sets the suite runner class to invoke
365370
*
@@ -578,6 +583,7 @@ protected List<String> createArguments() {
578583
addStringIfNotBlank(argv, CommandLineArgs.SUITE_NAME, m_suiteName);
579584
addStringIfNotBlank(argv, CommandLineArgs.TEST_NAME, m_testName);
580585
addStringIfNotBlank(argv, CommandLineArgs.TEST_NAMES, m_testNames);
586+
addBooleanIfTrue(argv, CommandLineArgs.IGNORE_MISSED_TEST_NAMES, m_ignoreMissedTestNames);
581587
addStringIfNotBlank(argv, CommandLineArgs.METHODS, m_methods);
582588
addReporterConfigs(argv);
583589
addIntegerIfNotNull(argv, CommandLineArgs.SUITE_THREAD_POOL_SIZE, m_suiteThreadPoolSize);

testng-collections/src/main/java/org/testng/util/Strings.java

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.testng.util;
22

3+
import java.util.List;
34
import java.util.Map;
45
import java.util.Optional;
56
import java.util.stream.Collectors;
@@ -18,6 +19,23 @@ public static boolean isNotNullAndNotEmpty(String string) {
1819
return !isNullOrEmpty(string);
1920
}
2021

22+
/**
23+
* Check if the given string list is null or empty or all elements are null or empty or blank.
24+
*
25+
* @param list A list instance with String elements.
26+
* @return true if the given string list is null or empty or all elements are null or empty or
27+
* blank; otherwise false.
28+
*/
29+
public static boolean isBlankStringList(List<String> list) {
30+
if (list == null) {
31+
return true;
32+
}
33+
if (list.isEmpty()) {
34+
return true;
35+
}
36+
return list.stream().allMatch(t -> t == null || t.isBlank());
37+
}
38+
2139
private static final Map<String, String> ESCAPE_HTML_MAP = Maps.newLinkedHashMap();
2240

2341
static {

testng-core/src/main/java/org/testng/CommandLineArgs.java

+8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ public class CommandLineArgs {
142142
@Parameter(names = TEST_NAMES, description = "The list of test names to run")
143143
public String testNames;
144144

145+
public static final String IGNORE_MISSED_TEST_NAMES = "-ignoreMissedTestNames";
146+
147+
@Parameter(
148+
names = IGNORE_MISSED_TEST_NAMES,
149+
description =
150+
"Ignore missed test names given by '-testnames' and continue to run existing tests, if any.")
151+
public boolean ignoreMissedTestNames = false;
152+
145153
public static final String TEST_JAR = "-testjar";
146154

147155
@Parameter(names = TEST_JAR, description = "A jar file containing the tests")

testng-core/src/main/java/org/testng/JarFileUtils.java

+38-8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
class JarFileUtils {
2424
private final IPostProcessor processor;
2525
private final String xmlPathInJar;
26+
private final boolean ignoreMissedTestNames;
2627
private final List<String> testNames;
2728
private final List<XmlSuite> suites = Lists.newLinkedList();
2829
private final XmlSuite.ParallelMode mode;
@@ -36,10 +37,28 @@ class JarFileUtils {
3637
String xmlPathInJar,
3738
List<String> testNames,
3839
XmlSuite.ParallelMode mode) {
40+
this(processor, xmlPathInJar, testNames, mode, false);
41+
}
42+
43+
JarFileUtils(
44+
IPostProcessor processor,
45+
String xmlPathInJar,
46+
List<String> testNames,
47+
boolean ignoreMissedTestNames) {
48+
this(processor, xmlPathInJar, testNames, XmlSuite.ParallelMode.NONE, ignoreMissedTestNames);
49+
}
50+
51+
JarFileUtils(
52+
IPostProcessor processor,
53+
String xmlPathInJar,
54+
List<String> testNames,
55+
XmlSuite.ParallelMode mode,
56+
boolean ignoreMissedTestNames) {
3957
this.processor = processor;
4058
this.xmlPathInJar = xmlPathInJar;
4159
this.testNames = testNames;
4260
this.mode = mode == null ? XmlSuite.ParallelMode.NONE : mode;
61+
this.ignoreMissedTestNames = ignoreMissedTestNames;
4362
}
4463

4564
List<XmlSuite> extractSuitesFrom(File jarFile) {
@@ -69,6 +88,7 @@ private boolean testngXmlExistsInJar(File jarFile, List<String> classes) throws
6988
Enumeration<JarEntry> entries = jf.entries();
7089
File file = java.nio.file.Files.createTempDirectory("testngXmlPathInJar-").toFile();
7190
String suitePath = null;
91+
7292
while (entries.hasMoreElements()) {
7393
JarEntry je = entries.nextElement();
7494
String jeName = je.getName();
@@ -87,24 +107,34 @@ private boolean testngXmlExistsInJar(File jarFile, List<String> classes) throws
87107
classes.add(constructClassName(je));
88108
}
89109
}
110+
90111
if (Strings.isNullOrEmpty(suitePath)) {
112+
Utils.log("TestNG", 1, String.format("Not found '%s' in '%s'.", xmlPathInJar, jarFile));
91113
return false;
92114
}
115+
93116
Collection<XmlSuite> parsedSuites = Parser.parse(suitePath, processor);
94117
delete(file);
118+
boolean addedSuite = false;
95119
for (XmlSuite suite : parsedSuites) {
96-
// If test names were specified, only run these test names
97-
if (testNames != null) {
98-
TestNamesMatcher testNamesMatcher = new TestNamesMatcher(suite, testNames);
99-
testNamesMatcher.validateMissMatchedTestNames();
100-
suites.addAll(testNamesMatcher.getSuitesMatchingTestNames());
101-
} else {
120+
if (testNames == null) {
102121
suites.add(suite);
122+
addedSuite = true;
123+
} else {
124+
TestNamesMatcher testNamesMatcher =
125+
new TestNamesMatcher(suite, testNames, ignoreMissedTestNames);
126+
boolean validationResult = testNamesMatcher.validateMissMatchedTestNames();
127+
if (validationResult) {
128+
suites.addAll(testNamesMatcher.getSuitesMatchingTestNames());
129+
addedSuite = true;
130+
} else {
131+
Utils.error(String.format("None of '%s' found in '%s'.", testNames, suite));
132+
}
103133
}
104-
return true;
105134
}
135+
136+
return addedSuite;
106137
}
107-
return false;
108138
}
109139

110140
private void delete(File f) throws IOException {

testng-core/src/main/java/org/testng/TestNG.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ private Collection<XmlSuite> processCommandLineArgs(Collection<XmlSuite> allSuit
353353
continue;
354354
}
355355
// If test names were specified, only run these test names
356-
TestNamesMatcher testNamesMatcher = new TestNamesMatcher(s, m_testNames);
356+
TestNamesMatcher testNamesMatcher =
357+
new TestNamesMatcher(s, m_testNames, m_ignoreMissedTestNames);
357358
testNamesMatcher.validateMissMatchedTestNames();
358359
result.addAll(testNamesMatcher.getSuitesMatchingTestNames());
359360
}
@@ -417,7 +418,8 @@ public void initializeSuitesAndJarFile() {
417418
File jarFile = new File(m_jarPath);
418419

419420
JarFileUtils utils =
420-
new JarFileUtils(getProcessor(), m_xmlPathInJar, m_testNames, m_parallelMode);
421+
new JarFileUtils(
422+
getProcessor(), m_xmlPathInJar, m_testNames, m_parallelMode, m_ignoreMissedTestNames);
421423

422424
Collection<XmlSuite> allSuites = utils.extractSuitesFrom(jarFile);
423425
allSuites.forEach(this::processParallelModeCommandLineArgs);
@@ -799,6 +801,8 @@ public List<ISuiteListener> getSuiteListeners() {
799801
/** The list of test names to run from the given suite */
800802
private List<String> m_testNames;
801803

804+
private boolean m_ignoreMissedTestNames;
805+
802806
private Integer m_suiteThreadPoolSize = CommandLineArgs.SUITE_THREAD_POOL_SIZE_DEFAULT;
803807

804808
private boolean m_randomizeSuites = Boolean.FALSE;
@@ -1475,6 +1479,7 @@ protected void configure(CommandLineArgs cla) {
14751479

14761480
if (cla.testNames != null) {
14771481
setTestNames(Arrays.asList(cla.testNames.split(",")));
1482+
setIgnoreMissedTestNames(cla.ignoreMissedTestNames);
14781483
}
14791484

14801485
// Note: can't use a Boolean field here because we are allowing a boolean
@@ -1574,6 +1579,10 @@ protected void configure(CommandLineArgs cla) {
15741579
alwaysRunListeners(cla.alwaysRunListeners);
15751580
}
15761581

1582+
private void setIgnoreMissedTestNames(boolean ignoreMissedTestNames) {
1583+
m_ignoreMissedTestNames = ignoreMissedTestNames;
1584+
}
1585+
15771586
public void setSuiteThreadPoolSize(Integer suiteThreadPoolSize) {
15781587
m_suiteThreadPoolSize = suiteThreadPoolSize;
15791588
}

testng-core/src/main/java/org/testng/xml/internal/TestNamesMatcher.java

+49-8
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,35 @@
33
import java.util.List;
44
import org.testng.TestNGException;
55
import org.testng.collections.Lists;
6+
import org.testng.log4testng.Logger;
7+
import org.testng.util.Strings;
68
import org.testng.xml.XmlSuite;
79
import org.testng.xml.XmlTest;
810

9-
/** The class to work with "-testnames" */
11+
/**
12+
* The class to work with "-testnames", "-ignoreMissedTestNames", and VM argument
13+
* "-Dtestng.ignore.missed.testnames". If both "-ignoreMissedTestNames" and VM argument
14+
* "-Dtestng.ignore.missed.testnames" are set, then either of them has "true" value will enable the
15+
* feature to ingore partially missed test names and run those existing test names.
16+
*/
1017
public final class TestNamesMatcher {
1118

19+
private static final Logger LOGGER = Logger.getLogger(TestNamesMatcher.class);
20+
1221
private final List<XmlSuite> cloneSuites = Lists.newArrayList();
1322
private final List<String> matchedTestNames = Lists.newArrayList();
1423
private final List<XmlTest> matchedTests = Lists.newArrayList();
1524
private final List<String> testNames;
25+
private final boolean ignoreMissedTestNames;
1626

1727
public TestNamesMatcher(XmlSuite xmlSuite, List<String> testNames) {
28+
this(xmlSuite, testNames, false);
29+
}
30+
31+
public TestNamesMatcher(
32+
XmlSuite xmlSuite, List<String> testNames, boolean ignoreMissedTestNames) {
1833
this.testNames = testNames;
34+
this.ignoreMissedTestNames = ignoreMissedTestNames;
1935
cloneIfContainsTestsWithNamesMatchingAny(xmlSuite, this.testNames);
2036
}
2137

@@ -26,7 +42,7 @@ public TestNamesMatcher(XmlSuite xmlSuite, List<String> testNames) {
2642
* @param testNames The list of testnames to iterate through
2743
*/
2844
private void cloneIfContainsTestsWithNamesMatchingAny(XmlSuite xmlSuite, List<String> testNames) {
29-
if (testNames == null || testNames.isEmpty()) {
45+
if (Strings.isBlankStringList(testNames)) {
3046
throw new TestNGException("Please provide a valid list of names to check.");
3147
}
3248

@@ -43,13 +59,38 @@ public List<XmlSuite> getSuitesMatchingTestNames() {
4359
return cloneSuites;
4460
}
4561

46-
public void validateMissMatchedTestNames() {
47-
List<String> tmpTestNames = Lists.newArrayList();
48-
tmpTestNames.addAll(testNames);
49-
tmpTestNames.removeIf(matchedTestNames::contains);
50-
if (!tmpTestNames.isEmpty()) {
51-
throw new TestNGException("The test(s) <" + tmpTestNames + "> cannot be found in suite.");
62+
/**
63+
* Do validation for testNames and notify users if any testNames are missed in suite. This method
64+
* is also used to decide how to run test suite when test names are given. In legacy logic, if
65+
* test names are given and exist in suite, then run them; if any of them do not exist in suite,
66+
* then throw exception and exit. After ignoreMissedTestNames is introduced, if
67+
* ignoreMissedTestNames is enabled, then any of the given test names exist in suite will be run,
68+
* and print warning message to tell those test names do not exist in suite.
69+
*
70+
* @return boolean if ignoreMissedTestNames disabled, then return true if no missed test names in
71+
* suite, otherwise throw TestNGException; if ignoreMissedTestNames enabled, then return true
72+
* if any test names exist in suite, otehrwise (all given test names are missed) throw
73+
* TestNGException.
74+
*/
75+
public boolean validateMissMatchedTestNames() {
76+
final List<String> missedTestNames = getMissedTestNames();
77+
if (!missedTestNames.isEmpty()) {
78+
final String errMsg = "The test(s) <" + missedTestNames + "> cannot be found in suite.";
79+
if (ignoreMissedTestNames && !matchedTestNames.isEmpty()) {
80+
LOGGER.warn(errMsg);
81+
return true;
82+
} else {
83+
throw new TestNGException(errMsg);
84+
}
5285
}
86+
return missedTestNames.isEmpty() && !matchedTestNames.isEmpty();
87+
}
88+
89+
public List<String> getMissedTestNames() {
90+
List<String> missedTestNames = Lists.newArrayList();
91+
missedTestNames.addAll(testNames);
92+
missedTestNames.removeIf(matchedTestNames::contains);
93+
return missedTestNames;
5394
}
5495

5596
public List<XmlTest> getMatchedTests() {

0 commit comments

Comments
 (0)