Skip to content

Commit 9d7ceb8

Browse files
committed
Support suite level thread pools for data provider
Closes testng-team#2980 We can now configure TestNG such that it uses a Suite level thread pool when running data driven Tests in parallel. This can be enabled via the configuration “-shareThreadPoolForDataProviders” with a value of “true” Alternatively one can also use the suite level attribute “share-thread-pool-for-data-providers”
1 parent d623d0c commit 9d7ceb8

16 files changed

+332
-4
lines changed

CHANGES.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Current
22
Fixed: GITHUB-3006: ITestResult injected at @AfterMethod incorrect when a configuration method failed (Krishnan Mahadevan)
3+
Fixed: GITHUB-2980: Data Provider Threads configuration in the suite don't match the documentation (Krishnan Mahadevan)
34
Fixed: GITHUB-3003: BeforeClass|AfterClass with inheritedGroups triggers cyclic dependencies (Krishnan Mahadevan)
45
New: Added @Inherited to the Listeners annotation, allowing it to be used in forming meta-annotations. (Pavlo Glushchenko)
56
Fixed: GITHUB-2991: Suite attributes map should be thread safe (Krishnan Mahadevan)

testng-core-api/src/main/java/org/testng/xml/XmlSuite.java

+14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Locale;
77
import java.util.Map;
88
import java.util.Set;
9+
import java.util.UUID;
910
import org.testng.ITestObjectFactory;
1011
import org.testng.collections.Lists;
1112
import org.testng.collections.Maps;
@@ -123,6 +124,9 @@ public String toString() {
123124
private String m_parentModule = "";
124125
private String m_guiceStage = "";
125126

127+
/** Represents a unique id for this suite. Can be used for uniquely identifying the xml suite. */
128+
public final UUID SUITE_ID = UUID.randomUUID();
129+
126130
/** Whether to SKIP or CONTINUE to re-attempt failed configuration methods. */
127131
public static final FailurePolicy DEFAULT_CONFIG_FAILURE_POLICY = FailurePolicy.SKIP;
128132

@@ -139,6 +143,8 @@ public String toString() {
139143
public static final Boolean DEFAULT_SKIP_FAILED_INVOCATION_COUNTS = Boolean.FALSE;
140144
private Boolean m_skipFailedInvocationCounts = DEFAULT_SKIP_FAILED_INVOCATION_COUNTS;
141145

146+
private boolean shareThreadPoolForDataProviders = false;
147+
142148
/** The thread count. */
143149
public static final Integer DEFAULT_THREAD_COUNT = 5;
144150

@@ -243,6 +249,14 @@ public Class<? extends ITestObjectFactory> getObjectFactoryClass() {
243249
return m_objectFactoryClass;
244250
}
245251

252+
public void setShareThreadPoolForDataProviders(boolean shareThreadPoolForDataProviders) {
253+
this.shareThreadPoolForDataProviders = shareThreadPoolForDataProviders;
254+
}
255+
256+
public boolean isShareThreadPoolForDataProviders() {
257+
return shareThreadPoolForDataProviders;
258+
}
259+
246260
@Deprecated
247261
public void setObjectFactory(ITestObjectFactory objectFactory) {
248262
setObjectFactoryClass(objectFactory.getClass());

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

+9
Original file line numberDiff line numberDiff line change
@@ -274,4 +274,13 @@ public class CommandLineArgs {
274274
names = GENERATE_RESULTS_PER_SUITE,
275275
description = "Should TestNG consider failures in Data Providers as test failures.")
276276
public Boolean generateResultsPerSuite = false;
277+
278+
public static final String SHARE_THREAD_POOL_FOR_DATA_PROVIDERS =
279+
"-shareThreadPoolForDataProviders";
280+
281+
@Parameter(
282+
names = SHARE_THREAD_POOL_FOR_DATA_PROVIDERS,
283+
description =
284+
"Should TestNG use a global Shared ThreadPool (At suite level) for running data providers.")
285+
public Boolean shareThreadPoolForDataProviders = false;
277286
}

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

+15
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.testng.internal.ExitCode;
3232
import org.testng.internal.IConfiguration;
3333
import org.testng.internal.ListenerOrderDeterminer;
34+
import org.testng.internal.ObjectBag;
3435
import org.testng.internal.OverrideProcessor;
3536
import org.testng.internal.ReporterConfig;
3637
import org.testng.internal.RuntimeBehavior;
@@ -609,6 +610,14 @@ public boolean isPropagateDataProviderFailureAsTestFailure() {
609610
return this.m_configuration.isPropagateDataProviderFailureAsTestFailure();
610611
}
611612

613+
public void shareThreadPoolForDataProviders(boolean flag) {
614+
this.m_configuration.shareThreadPoolForDataProviders(flag);
615+
}
616+
617+
public boolean isShareThreadPoolForDataProviders() {
618+
return this.m_configuration.isShareThreadPoolForDataProviders();
619+
}
620+
612621
/**
613622
* Set the suites file names to be run by this TestNG object. This method tries to load and parse
614623
* the specified TestNG suite xml files. If a file is missing, it is ignored.
@@ -1082,6 +1091,7 @@ public void run() {
10821091
m_end = System.currentTimeMillis();
10831092

10841093
if (null != suiteRunners) {
1094+
suiteRunners.forEach(ObjectBag::cleanup);
10851095
generateReports(suiteRunners);
10861096
}
10871097

@@ -1186,6 +1196,9 @@ public List<ISuite> runSuitesLocally() {
11861196
// First initialize the suite runners to ensure there are no configuration issues.
11871197
// Create a map with XmlSuite as key and corresponding SuiteRunner as value
11881198
for (XmlSuite xmlSuite : m_suites) {
1199+
if (m_configuration.isShareThreadPoolForDataProviders()) {
1200+
xmlSuite.setShareThreadPoolForDataProviders(true);
1201+
}
11891202
createSuiteRunners(suiteRunnerMap, xmlSuite);
11901203
}
11911204

@@ -1454,6 +1467,8 @@ public static TestNG privateMain(String[] argv, ITestListener listener) {
14541467
* @param cla The command line parameters
14551468
*/
14561469
protected void configure(CommandLineArgs cla) {
1470+
Optional.ofNullable(cla.shareThreadPoolForDataProviders)
1471+
.ifPresent(this::shareThreadPoolForDataProviders);
14571472
Optional.ofNullable(cla.propagateDataProviderFailureAsTestFailure)
14581473
.ifPresent(value -> propagateDataProviderFailureAsTestFailure());
14591474
setReportAllDataDrivenTestsAsSkipped(cla.includeAllDataDrivenTestsWhenSkipping);

testng-core/src/main/java/org/testng/internal/Configuration.java

+12
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class Configuration implements IConfiguration {
2323
private ITestObjectFactory m_objectFactory;
2424
private IHookable m_hookable;
2525
private IConfigurable m_configurable;
26+
27+
private boolean shareThreadPoolForDataProviders = false;
2628
private final Map<Class<? extends IExecutionListener>, IExecutionListener> m_executionListeners =
2729
Maps.newLinkedHashMap();
2830
private final Map<Class<? extends IConfigurationListener>, IConfigurationListener>
@@ -168,4 +170,14 @@ public void propagateDataProviderFailureAsTestFailure() {
168170
public boolean isPropagateDataProviderFailureAsTestFailure() {
169171
return propagateDataProviderFailureAsTestFailure;
170172
}
173+
174+
@Override
175+
public boolean isShareThreadPoolForDataProviders() {
176+
return this.shareThreadPoolForDataProviders;
177+
}
178+
179+
@Override
180+
public void shareThreadPoolForDataProviders(boolean flag) {
181+
this.shareThreadPoolForDataProviders = flag;
182+
}
171183
}

testng-core/src/main/java/org/testng/internal/IConfiguration.java

+4
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,8 @@ default boolean getReportAllDataDrivenTestsAsSkipped() {
5959
void propagateDataProviderFailureAsTestFailure();
6060

6161
boolean isPropagateDataProviderFailureAsTestFailure();
62+
63+
boolean isShareThreadPoolForDataProviders();
64+
65+
void shareThreadPoolForDataProviders(boolean flag);
6266
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.testng.internal;
2+
3+
import java.io.Closeable;
4+
import java.io.IOException;
5+
import java.util.Map;
6+
import java.util.Optional;
7+
import java.util.UUID;
8+
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.function.Supplier;
10+
import org.testng.ISuite;
11+
import org.testng.log4testng.Logger;
12+
13+
/**
14+
* A simple bean bag that is intended to help share objects during the lifetime of TestNG without
15+
* needing it to be a singleton.
16+
*/
17+
public final class ObjectBag {
18+
19+
private static final Logger logger = Logger.getLogger(ObjectBag.class);
20+
private final Map<Class<?>, Object> bag = new ConcurrentHashMap<>();
21+
22+
private static final Map<UUID, ObjectBag> instances = new ConcurrentHashMap<>();
23+
24+
public static ObjectBag getInstance(ISuite suite) {
25+
return instances.computeIfAbsent(suite.getXmlSuite().SUITE_ID, k -> new ObjectBag());
26+
}
27+
28+
public static void cleanup(ISuite suite) {
29+
UUID uid = suite.getXmlSuite().SUITE_ID;
30+
Optional.ofNullable(instances.get(uid)).ifPresent(ObjectBag::cleanup);
31+
instances.remove(uid);
32+
}
33+
34+
/**
35+
* @param type - The type of the object to be created
36+
* @param supplier - A {@link Supplier} that should be used to produce a new instance
37+
* @return - Either the newly produced instance or the existing instance.
38+
*/
39+
public Object createIfRequired(Class<?> type, Supplier<Object> supplier) {
40+
return bag.computeIfAbsent(type, t -> supplier.get());
41+
}
42+
43+
public void cleanup() {
44+
bag.values().stream()
45+
.filter(it -> it instanceof Closeable)
46+
.map(it -> (Closeable) it)
47+
.forEach(
48+
it -> {
49+
try {
50+
it.close();
51+
} catch (IOException e) {
52+
logger.debug("Could not clean-up " + it, e);
53+
}
54+
});
55+
bag.clear();
56+
}
57+
}

testng-core/src/main/java/org/testng/internal/PoolService.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.testng.internal;
22

3+
import java.io.Closeable;
4+
import java.io.IOException;
35
import java.util.List;
46
import java.util.concurrent.Callable;
57
import java.util.concurrent.ExecutionException;
@@ -15,12 +17,18 @@
1517
import org.testng.internal.thread.ThreadUtil;
1618

1719
/** Simple wrapper for an ExecutorCompletionService. */
18-
public class PoolService<FutureType> {
20+
public class PoolService<FutureType> implements Closeable {
1921

2022
private final ExecutorCompletionService<FutureType> m_completionService;
2123
private final ExecutorService m_executor;
2224

25+
private final boolean shutdownAfterExecution;
26+
2327
public PoolService(int threadPoolSize) {
28+
this(threadPoolSize, true);
29+
}
30+
31+
public PoolService(int threadPoolSize, boolean shutdownAfterExecution) {
2432

2533
ThreadFactory threadFactory =
2634
new ThreadFactory() {
@@ -35,6 +43,7 @@ public Thread newThread(@Nonnull Runnable r) {
3543
};
3644
m_executor = Executors.newFixedThreadPool(threadPoolSize, threadFactory);
3745
m_completionService = new ExecutorCompletionService<>(m_executor);
46+
this.shutdownAfterExecution = shutdownAfterExecution;
3847
}
3948

4049
public List<FutureType> submitTasksAndWait(List<? extends Callable<FutureType>> tasks) {
@@ -53,7 +62,16 @@ public List<FutureType> submitTasksAndWait(List<? extends Callable<FutureType>>
5362
}
5463
}
5564

56-
m_executor.shutdown();
65+
if (shutdownAfterExecution) {
66+
m_executor.shutdown();
67+
}
5768
return result;
5869
}
70+
71+
@Override
72+
public void close() throws IOException {
73+
if (!shutdownAfterExecution) {
74+
m_executor.shutdown();
75+
}
76+
}
5977
}

testng-core/src/main/java/org/testng/internal/invokers/MethodRunner.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.testng.ITestResult;
88
import org.testng.collections.CollectionUtils;
99
import org.testng.collections.Lists;
10+
import org.testng.internal.ObjectBag;
1011
import org.testng.internal.Parameters;
1112
import org.testng.internal.PoolService;
1213
import org.testng.internal.invokers.ITestInvoker.FailureContext;
@@ -129,7 +130,18 @@ public List<ITestResult> runInParallel(
129130
// testng387: increment the param index in the bag.
130131
parametersIndex += 1;
131132
}
132-
PoolService<List<ITestResult>> ps = new PoolService<>(suite.getDataProviderThreadCount());
133+
134+
ObjectBag objectBag = ObjectBag.getInstance(context.getSuite());
135+
boolean sharedThreadPool = context.getSuite().getXmlSuite().isShareThreadPoolForDataProviders();
136+
137+
@SuppressWarnings("unchecked")
138+
PoolService<List<ITestResult>> ps =
139+
sharedThreadPool
140+
? (PoolService<List<ITestResult>>)
141+
objectBag.createIfRequired(
142+
PoolService.class,
143+
() -> new PoolService<>(suite.getDataProviderThreadCount(), false))
144+
: new PoolService<>(suite.getDataProviderThreadCount());
133145
List<List<ITestResult>> r = ps.submitTasksAndWait(workers);
134146
for (List<ITestResult> l2 : r) {
135147
result.addAll(l2);

testng-core/src/main/java/org/testng/xml/TestNGContentHandler.java

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.List;
1919
import java.util.Map;
2020
import java.util.Objects;
21+
import java.util.Optional;
2122
import java.util.Stack;
2223
import org.testng.ITestObjectFactory;
2324
import org.testng.TestNGException;
@@ -267,6 +268,15 @@ private void xmlSuite(boolean start, Attributes attributes) {
267268
if (null != dataProviderThreadCount) {
268269
m_currentSuite.setDataProviderThreadCount(Integer.parseInt(dataProviderThreadCount));
269270
}
271+
272+
String shareThreadPoolForDataProviders =
273+
attributes.getValue("share-thread-pool-for-data-providers");
274+
Optional.ofNullable(shareThreadPoolForDataProviders)
275+
.ifPresent(
276+
it ->
277+
m_currentSuite.setShareThreadPoolForDataProviders(
278+
Boolean.parseBoolean(shareThreadPoolForDataProviders)));
279+
270280
String timeOut = attributes.getValue("time-out");
271281
if (null != timeOut) {
272282
m_currentSuite.setTimeOut(timeOut);

testng-core/src/main/resources/testng-1.0.dtd

+4-1
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@ Cedric Beust & Alexandru Popescu
5656
@attr skipfailedinvocationcounts Whether to skip failed invocations.
5757
@attr data-provider-thread-count An integer giving the size of the thread pool to use
5858
for parallel data providers.
59+
@attr share-thread-pool-for-data-providers - Whether TestNG should use a common thread pool
60+
for running parallel data providers. (Works only with TestNG versions 7.9.0 or higher)
5961
@attr object-factory A class that implements IObjectFactory that will be used to
6062
instantiate the test objects.
6163
@attr allow-return-values If true, tests that return a value will be run as well
6264
-->
63-
<!ATTLIST suite
65+
<!ATTLIST suite
6466
name CDATA #REQUIRED
6567
junit (true | false) "false"
6668
verbose CDATA #IMPLIED
@@ -73,6 +75,7 @@ Cedric Beust & Alexandru Popescu
7375
time-out CDATA #IMPLIED
7476
skipfailedinvocationcounts (true | false) "false"
7577
data-provider-thread-count CDATA "10"
78+
share-thread-pool-for-data-providers (true | false) "false"
7679
object-factory CDATA #IMPLIED
7780
group-by-instances (true | false) "false"
7881
preserve-order (true | false) "true"

0 commit comments

Comments
 (0)