Skip to content

Commit d01a4f1

Browse files
committed
Shared Threadpool for normal/datadriven tests.
Closes #2019
1 parent 34c7ba1 commit d01a4f1

19 files changed

+243
-36
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Current
2+
Fixed: GITHUB-2019: Total thread count in testng parallel tests with dataproviders (Krishnan Mahadevan)
23
Fixed: GITHUB-3006: ITestResult injected at @AfterMethod incorrect when a configuration method failed (Krishnan Mahadevan)
34
Fixed: GITHUB-2980: Data Provider Threads configuration in the suite don't match the documentation (Krishnan Mahadevan)
45
Fixed: GITHUB-3003: BeforeClass|AfterClass with inheritedGroups triggers cyclic dependencies (Krishnan Mahadevan)

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ public String toString() {
145145

146146
private boolean shareThreadPoolForDataProviders = false;
147147

148+
private boolean useGlobalThreadPool = false;
149+
148150
/** The thread count. */
149151
public static final Integer DEFAULT_THREAD_COUNT = 5;
150152

@@ -253,6 +255,14 @@ public void setShareThreadPoolForDataProviders(boolean shareThreadPoolForDataPro
253255
this.shareThreadPoolForDataProviders = shareThreadPoolForDataProviders;
254256
}
255257

258+
public boolean useGlobalThreadPool() {
259+
return this.useGlobalThreadPool;
260+
}
261+
262+
public void shouldUseGlobalThreadPool(boolean flag) {
263+
this.useGlobalThreadPool = flag;
264+
}
265+
256266
public boolean isShareThreadPoolForDataProviders() {
257267
return shareThreadPoolForDataProviders;
258268
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,12 @@ public class CommandLineArgs {
283283
description =
284284
"Should TestNG use a global Shared ThreadPool (At suite level) for running data providers.")
285285
public Boolean shareThreadPoolForDataProviders = false;
286+
287+
public static final String USE_GLOBAL_THREAD_POOL = "-useGlobalThreadPool";
288+
289+
@Parameter(
290+
names = USE_GLOBAL_THREAD_POOL,
291+
description =
292+
"Should TestNG use a global Shared ThreadPool (At suite level) for running regular and data driven tests.")
293+
public Boolean useGlobalThreadPool = false;
286294
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,14 @@ public boolean isShareThreadPoolForDataProviders() {
616616
return this.m_configuration.isShareThreadPoolForDataProviders();
617617
}
618618

619+
public boolean useGlobalThreadPool() {
620+
return this.m_configuration.useGlobalThreadPool();
621+
}
622+
623+
public void shouldUseGlobalThreadPool(boolean flag) {
624+
this.m_configuration.shouldUseGlobalThreadPool(flag);
625+
}
626+
619627
/**
620628
* Set the suites file names to be run by this TestNG object. This method tries to load and parse
621629
* the specified TestNG suite xml files. If a file is missing, it is ignored.
@@ -1203,6 +1211,9 @@ public List<ISuite> runSuitesLocally() {
12031211
if (m_configuration.isShareThreadPoolForDataProviders()) {
12041212
xmlSuite.setShareThreadPoolForDataProviders(true);
12051213
}
1214+
if (m_configuration.useGlobalThreadPool()) {
1215+
xmlSuite.shouldUseGlobalThreadPool(true);
1216+
}
12061217
createSuiteRunners(suiteRunnerMap, xmlSuite);
12071218
}
12081219

@@ -1457,6 +1468,7 @@ public static TestNG privateMain(String[] argv, ITestListener listener) {
14571468
* @param cla The command line parameters
14581469
*/
14591470
protected void configure(CommandLineArgs cla) {
1471+
Optional.ofNullable(cla.useGlobalThreadPool).ifPresent(this::shouldUseGlobalThreadPool);
14601472
Optional.ofNullable(cla.shareThreadPoolForDataProviders)
14611473
.ifPresent(this::shareThreadPoolForDataProviders);
14621474
Optional.ofNullable(cla.propagateDataProviderFailureAsTestFailure)

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import java.util.concurrent.ExecutorService;
66
import java.util.concurrent.ThreadPoolExecutor;
77
import java.util.concurrent.TimeUnit;
8+
import java.util.function.Supplier;
89
import org.testng.internal.IConfiguration;
10+
import org.testng.internal.ObjectBag;
911
import org.testng.internal.RuntimeBehavior;
1012
import org.testng.internal.Utils;
1113
import org.testng.internal.thread.TestNGThreadFactory;
@@ -47,8 +49,7 @@ public TestTaskExecutor(
4749

4850
public void execute() {
4951
String name = "test-" + xmlTest.getName();
50-
int threadCount = xmlTest.getThreadCount();
51-
threadCount = Math.max(threadCount, 1);
52+
int threadCount = Math.max(xmlTest.getThreadCount(), 1);
5253
if (RuntimeBehavior.favourCustomThreadPoolExecutor()) {
5354
IExecutorFactory execFactory = configuration.getExecutorFactory();
5455
ITestNGThreadPoolExecutor executor =
@@ -65,14 +66,22 @@ public void execute() {
6566
executor.run();
6667
service = executor;
6768
} else {
68-
service =
69-
new ThreadPoolExecutor(
70-
threadCount,
71-
threadCount,
72-
0,
73-
TimeUnit.MILLISECONDS,
74-
queue,
75-
new TestNGThreadFactory(name));
69+
boolean reUse = xmlTest.getSuite().useGlobalThreadPool();
70+
Supplier<Object> supplier =
71+
() ->
72+
new ThreadPoolExecutor(
73+
threadCount,
74+
threadCount,
75+
0,
76+
TimeUnit.MILLISECONDS,
77+
queue,
78+
new TestNGThreadFactory(name));
79+
if (reUse) {
80+
ObjectBag bag = ObjectBag.getInstance(xmlTest.getSuite());
81+
service = (ExecutorService) bag.createIfRequired(ExecutorService.class, supplier);
82+
} else {
83+
service = (ExecutorService) supplier.get();
84+
}
7685
GraphOrchestrator<ITestNGMethod> executor =
7786
new GraphOrchestrator<>(service, factory, graph, comparator);
7887
executor.run();

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class Configuration implements IConfiguration {
3939

4040
private boolean propagateDataProviderFailureAsTestFailure;
4141

42+
private boolean useGlobalThreadPool = false;
43+
4244
public Configuration() {
4345
init(new JDK15AnnotationFinder(new DefaultAnnotationTransformer()));
4446
}
@@ -180,4 +182,14 @@ public boolean isShareThreadPoolForDataProviders() {
180182
public void shareThreadPoolForDataProviders(boolean flag) {
181183
this.shareThreadPoolForDataProviders = flag;
182184
}
185+
186+
@Override
187+
public boolean useGlobalThreadPool() {
188+
return this.useGlobalThreadPool;
189+
}
190+
191+
@Override
192+
public void shouldUseGlobalThreadPool(boolean flag) {
193+
this.useGlobalThreadPool = flag;
194+
}
183195
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,8 @@ default boolean getReportAllDataDrivenTestsAsSkipped() {
6363
boolean isShareThreadPoolForDataProviders();
6464

6565
void shareThreadPoolForDataProviders(boolean flag);
66+
67+
boolean useGlobalThreadPool();
68+
69+
void shouldUseGlobalThreadPool(boolean flag);
6670
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.function.Supplier;
99
import org.testng.ISuite;
1010
import org.testng.log4testng.Logger;
11+
import org.testng.xml.XmlSuite;
1112

1213
/**
1314
* A simple bean bag that is intended to help share objects during the lifetime of TestNG without
@@ -21,7 +22,11 @@ public final class ObjectBag {
2122
private static final Map<UUID, ObjectBag> instances = new ConcurrentHashMap<>();
2223

2324
public static ObjectBag getInstance(ISuite suite) {
24-
return instances.computeIfAbsent(suite.getXmlSuite().SUITE_ID, k -> new ObjectBag());
25+
return getInstance(suite.getXmlSuite());
26+
}
27+
28+
public static ObjectBag getInstance(XmlSuite xmlSuite) {
29+
return instances.computeIfAbsent(xmlSuite.SUITE_ID, k -> new ObjectBag());
2530
}
2631

2732
public static void cleanup(ISuite suite) {

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.concurrent.Executors;
1313
import java.util.concurrent.ThreadFactory;
1414
import java.util.concurrent.atomic.AtomicInteger;
15+
import java.util.concurrent.atomic.AtomicReference;
16+
import java.util.function.Supplier;
1517
import java.util.stream.Collectors;
1618
import org.testng.ITestContext;
1719
import org.testng.ITestResult;
@@ -21,7 +23,7 @@
2123
import org.testng.internal.Parameters;
2224
import org.testng.internal.invokers.ITestInvoker.FailureContext;
2325
import org.testng.internal.invokers.TestMethodArguments.Builder;
24-
import org.testng.internal.thread.ThreadUtil;
26+
import org.testng.internal.thread.TestNGThreadFactory;
2527
import org.testng.xml.XmlSuite;
2628

2729
public class MethodRunner implements IMethodRunner {
@@ -107,9 +109,9 @@ public List<ITestResult> runInParallel(
107109
XmlSuite suite = context.getSuite().getXmlSuite();
108110
int parametersIndex = 0;
109111
ObjectBag objectBag = ObjectBag.getInstance(context.getSuite());
110-
boolean reUse = suite.isShareThreadPoolForDataProviders();
112+
boolean reUse = suite.isShareThreadPoolForDataProviders() || suite.useGlobalThreadPool();
111113

112-
ExecutorService service = getOrCreate(reUse, suite.getDataProviderThreadCount(), objectBag);
114+
ExecutorService service = getOrCreate(reUse, suite, objectBag);
113115
List<CompletableFuture<List<ITestResult>>> all = new ArrayList<>();
114116
for (Object[] next : CollectionUtils.asIterable(allParamValues)) {
115117
if (next == null) {
@@ -163,18 +165,20 @@ public List<ITestResult> runInParallel(
163165
return result;
164166
}
165167

166-
private static ExecutorService getOrCreate(boolean reUse, int count, ObjectBag objectBag) {
168+
private static ExecutorService getOrCreate(boolean reUse, XmlSuite suite, ObjectBag objectBag) {
169+
AtomicReference<Integer> count = new AtomicReference<>();
170+
count.set(suite.getDataProviderThreadCount());
171+
Supplier<Object> supplier = () -> Executors.newFixedThreadPool(count.get(), threadFactory());
167172
if (reUse) {
168-
return (ExecutorService)
169-
objectBag.createIfRequired(
170-
ExecutorService.class, () -> Executors.newFixedThreadPool(count, threadFactory()));
173+
if (suite.useGlobalThreadPool()) {
174+
count.set(suite.getThreadCount());
175+
}
176+
return (ExecutorService) objectBag.createIfRequired(ExecutorService.class, supplier);
171177
}
172-
return Executors.newFixedThreadPool(count, threadFactory());
178+
return (ExecutorService) supplier.get();
173179
}
174180

175181
private static ThreadFactory threadFactory() {
176-
AtomicInteger threadNumber = new AtomicInteger(0);
177-
return r ->
178-
new Thread(r, ThreadUtil.THREAD_NAME + "-PoolService-" + threadNumber.getAndIncrement());
182+
return new TestNGThreadFactory("PoolService");
179183
}
180184
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,13 @@ private void xmlSuite(boolean start, Attributes attributes) {
277277
m_currentSuite.setShareThreadPoolForDataProviders(
278278
Boolean.parseBoolean(shareThreadPoolForDataProviders)));
279279

280+
String useGlobalThreadPool = attributes.getValue("use-global-thread-pool");
281+
Optional.ofNullable(useGlobalThreadPool)
282+
.ifPresent(
283+
it ->
284+
m_currentSuite.shouldUseGlobalThreadPool(
285+
Boolean.parseBoolean(useGlobalThreadPool)));
286+
280287
String timeOut = attributes.getValue("time-out");
281288
if (null != timeOut) {
282289
m_currentSuite.setTimeOut(timeOut);

0 commit comments

Comments
 (0)