Skip to content

Commit cf6cddd

Browse files
committed
Allow custom thread pool executors to be wired in.
Closes #3066 Following should be done. * Implement `org.testng.IExecutorServiceFactory` * plugin the fully qualified class name of the above implementation via the configuration parameter "-threadpoolfactoryclass" If using Maven surefire plugin then it can be wired in as below: <configuration> <properties> <property> <name>threadpoolfactoryclass</name> <value>test.thread.MyExecutorServiceFactory</value> </property> </properties> </configuration> If using TestNG API, then it can be wired in as below: TestNG testng = new TestNG(); testng.setExecutorServiceFactory(new MyExecutorServiceFactory());
1 parent ee22dc0 commit cf6cddd

19 files changed

+288
-257
lines changed

CHANGES.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Current (7.10.0)
2-
New: GITHUB-2916: Allow users to define ordering for TestNG listeners (Krishnan Mahadevan)
2+
Fixed: GITHUB-3066: How to dynamically adjust the number of TestNG threads after IExecutorFactory is deprecated? (Krishnan Mahadevan)
3+
New: GITHUB-2874: Allow users to define ordering for TestNG listeners (Krishnan Mahadevan)
34
Fixed: GITHUB-3033: Moved ant support under own repository https://github.com/testng-team/testng-ant (Julien Herr)
45
Fixed: GITHUB-3064: TestResult lost if failure creating RetryAnalyzer (Krishnan Mahadevan)
56
Fixed: GITHUB-3048: ConcurrentModificationException when injecting values (Krishnan Mahadevan)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.testng;
2+
3+
import java.util.concurrent.BlockingQueue;
4+
import java.util.concurrent.ExecutorService;
5+
import java.util.concurrent.ThreadFactory;
6+
import java.util.concurrent.TimeUnit;
7+
8+
/**
9+
* Represents the capability to create a custom {@link ExecutorService} by downstream consumers. The
10+
* implementation can be plugged in via the configuration parameter <code>-threadpoolfactoryclass
11+
* </code>
12+
*/
13+
@FunctionalInterface
14+
public interface IExecutorServiceFactory {
15+
16+
/**
17+
* @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless
18+
* {@code allowCoreThreadTimeOut} is set
19+
* @param maximumPoolSize the maximum number of threads to allow in the pool
20+
* @param keepAliveTime when the number of threads is greater than the core, this is the maximum
21+
* time that excess idle threads will wait for new tasks before terminating.
22+
* @param unit the time unit for the {@code keepAliveTime} argument
23+
* @param workQueue the queue to use for holding tasks before they are executed. This queue will
24+
* hold only the {@code Runnable} tasks submitted by the {@code execute} method.
25+
* @param threadFactory the factory to use when the executor creates a new thread *
26+
* @return - An implementation of {@link ExecutorService}
27+
*/
28+
ExecutorService create(
29+
int corePoolSize,
30+
int maximumPoolSize,
31+
long keepAliveTime,
32+
TimeUnit unit,
33+
BlockingQueue<Runnable> workQueue,
34+
ThreadFactory threadFactory);
35+
}

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,11 @@ private void runInParallelTestMode() {
439439
}
440440

441441
ThreadUtil.execute(
442-
"tests", tasks, xmlSuite.getThreadCount(), xmlSuite.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS));
442+
configuration,
443+
"tests",
444+
tasks,
445+
xmlSuite.getThreadCount(),
446+
xmlSuite.getTimeOut(XmlTest.DEFAULT_TIMEOUT_MS));
443447
}
444448

445449
private class SuiteWorker implements Runnable {

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

+10-24
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22

33
import java.util.concurrent.BlockingQueue;
44
import java.util.concurrent.ExecutorService;
5-
import java.util.concurrent.ThreadPoolExecutor;
65
import java.util.concurrent.TimeUnit;
76
import org.testng.internal.IConfiguration;
87
import org.testng.internal.RuntimeBehavior;
98
import org.testng.internal.Utils;
109
import org.testng.internal.thread.TestNGThreadFactory;
1110
import org.testng.internal.thread.graph.GraphOrchestrator;
1211
import org.testng.log4testng.Logger;
13-
import org.testng.thread.IExecutorFactory;
14-
import org.testng.thread.ITestNGThreadPoolExecutor;
1512
import org.testng.thread.IThreadWorkerFactory;
1613

1714
class SuiteTaskExecutor {
@@ -42,29 +39,18 @@ public SuiteTaskExecutor(
4239
public void execute() {
4340
String name = "suites-";
4441
if (RuntimeBehavior.favourCustomThreadPoolExecutor()) {
45-
IExecutorFactory execFactory = configuration.getExecutorFactory();
46-
ITestNGThreadPoolExecutor executor =
47-
execFactory.newSuiteExecutor(
48-
name,
49-
graph,
50-
factory,
51-
threadPoolSize,
52-
threadPoolSize,
53-
Integer.MAX_VALUE,
54-
TimeUnit.MILLISECONDS,
55-
queue,
56-
null);
57-
executor.run();
58-
service = executor;
42+
throw new UnsupportedOperationException("This is NO LONGER Supported in TestNG");
5943
} else {
6044
service =
61-
new ThreadPoolExecutor(
62-
threadPoolSize,
63-
threadPoolSize,
64-
Integer.MAX_VALUE,
65-
TimeUnit.MILLISECONDS,
66-
queue,
67-
new TestNGThreadFactory(name));
45+
this.configuration
46+
.getExecutorServiceFactory()
47+
.create(
48+
threadPoolSize,
49+
threadPoolSize,
50+
Integer.MAX_VALUE,
51+
TimeUnit.MILLISECONDS,
52+
queue,
53+
new TestNGThreadFactory(name));
6854
GraphOrchestrator<ISuite> executor = new GraphOrchestrator<>(service, factory, graph, null);
6955
executor.run();
7056
}

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

+11-36
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.Collections;
1717
import java.util.List;
1818
import java.util.Map;
19+
import java.util.Objects;
1920
import java.util.Optional;
2021
import java.util.ServiceLoader;
2122
import java.util.Set;
@@ -58,7 +59,6 @@
5859
import org.testng.reporters.VerboseReporter;
5960
import org.testng.reporters.XMLReporter;
6061
import org.testng.reporters.jq.Main;
61-
import org.testng.thread.IExecutorFactory;
6262
import org.testng.thread.IThreadWorkerFactory;
6363
import org.testng.util.Strings;
6464
import org.testng.xml.IPostProcessor;
@@ -151,8 +151,6 @@ public class TestNG {
151151
private final Map<Class<? extends IDataProviderInterceptor>, IDataProviderInterceptor>
152152
m_dataProviderInterceptors = Maps.newLinkedHashMap();
153153

154-
private IExecutorFactory m_executorFactory = null;
155-
156154
public static final Integer DEFAULT_VERBOSE = 1;
157155

158156
// Command line suite parameters
@@ -843,10 +841,9 @@ public void setVerbose(int verbose) {
843841
m_verbose = verbose;
844842
}
845843

846-
/** This method stands deprecated as of TestNG <code>v7.9.0</code>. */
847-
@Deprecated
848-
public void setExecutorFactoryClass(String clazzName) {
849-
this.m_executorFactory = createExecutorFactoryInstanceUsing(clazzName);
844+
public void setExecutorServiceFactory(IExecutorServiceFactory factory) {
845+
Objects.requireNonNull(factory);
846+
m_configuration.setExecutorServiceFactory(factory);
850847
}
851848

852849
public void setListenerFactory(ITestNGListenerFactory factory) {
@@ -857,31 +854,6 @@ public void setGenerateResultsPerSuite(boolean generateResultsPerSuite) {
857854
this.m_generateResultsPerSuite = generateResultsPerSuite;
858855
}
859856

860-
private IExecutorFactory createExecutorFactoryInstanceUsing(String clazzName) {
861-
Class<?> cls = ClassHelper.forName(clazzName);
862-
Object instance = m_objectFactory.newInstance(cls);
863-
if (instance instanceof IExecutorFactory) {
864-
return (IExecutorFactory) instance;
865-
}
866-
throw new IllegalArgumentException(
867-
clazzName + " does not implement " + IExecutorFactory.class.getName());
868-
}
869-
870-
/** This method stands deprecated as of TestNG <code>v7.9.0</code>. */
871-
@Deprecated
872-
public void setExecutorFactory(IExecutorFactory factory) {
873-
this.m_executorFactory = factory;
874-
}
875-
876-
/** This method stands deprecated as of TestNG <code>v7.9.0</code>. */
877-
@Deprecated
878-
public IExecutorFactory getExecutorFactory() {
879-
if (this.m_executorFactory == null) {
880-
this.m_executorFactory = createExecutorFactoryInstanceUsing(DEFAULT_THREADPOOL_FACTORY);
881-
}
882-
return this.m_executorFactory;
883-
}
884-
885857
private void initializeCommandLineSuites() {
886858
if (m_commandLineTestClasses != null || m_commandLineMethods != null) {
887859
if (null != m_commandLineMethods) {
@@ -1018,7 +990,6 @@ private void initializeConfiguration() {
1018990
m_configuration.setConfigurable(m_configurable);
1019991
m_configuration.setObjectFactory(factory);
1020992
m_configuration.setAlwaysRunListeners(this.m_alwaysRun);
1021-
m_configuration.setExecutorFactory(getExecutorFactory());
1022993
}
1023994

1024995
private void addListeners(XmlSuite s) {
@@ -1514,9 +1485,13 @@ protected void configure(CommandLineArgs cla) {
15141485
m_objectFactory.newInstance((Class<IInjectorFactory>) clazz));
15151486
}
15161487
}
1517-
if (cla.threadPoolFactoryClass != null) {
1518-
setExecutorFactoryClass(cla.threadPoolFactoryClass);
1519-
}
1488+
Optional.ofNullable(cla.threadPoolFactoryClass)
1489+
.map(ClassHelper::forName)
1490+
.filter(IExecutorServiceFactory.class::isAssignableFrom)
1491+
.map(it -> m_objectFactory.newInstance(it))
1492+
.map(it -> (IExecutorServiceFactory) it)
1493+
.ifPresent(this::setExecutorServiceFactory);
1494+
15201495
setOutputDirectory(cla.outputDirectory);
15211496

15221497
String testClasses = cla.testClass;

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

+11-24
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.Comparator;
44
import java.util.concurrent.BlockingQueue;
55
import java.util.concurrent.ExecutorService;
6-
import java.util.concurrent.ThreadPoolExecutor;
76
import java.util.concurrent.TimeUnit;
87
import java.util.function.Supplier;
98
import org.testng.internal.IConfiguration;
@@ -13,8 +12,6 @@
1312
import org.testng.internal.thread.TestNGThreadFactory;
1413
import org.testng.internal.thread.graph.GraphOrchestrator;
1514
import org.testng.log4testng.Logger;
16-
import org.testng.thread.IExecutorFactory;
17-
import org.testng.thread.ITestNGThreadPoolExecutor;
1815
import org.testng.thread.IThreadWorkerFactory;
1916
import org.testng.xml.XmlTest;
2017

@@ -51,31 +48,21 @@ public void execute() {
5148
String name = "test-" + xmlTest.getName();
5249
int threadCount = Math.max(xmlTest.getThreadCount(), 1);
5350
if (RuntimeBehavior.favourCustomThreadPoolExecutor()) {
54-
IExecutorFactory execFactory = configuration.getExecutorFactory();
55-
ITestNGThreadPoolExecutor executor =
56-
execFactory.newTestMethodExecutor(
57-
name,
58-
graph,
59-
factory,
60-
threadCount,
61-
threadCount,
62-
0,
63-
TimeUnit.MILLISECONDS,
64-
queue,
65-
comparator);
66-
executor.run();
67-
service = executor;
51+
throw new UnsupportedOperationException("This is NO LONGER Supported in TestNG");
52+
6853
} else {
6954
boolean reUse = xmlTest.getSuite().useGlobalThreadPool();
7055
Supplier<Object> supplier =
7156
() ->
72-
new ThreadPoolExecutor(
73-
threadCount,
74-
threadCount,
75-
0,
76-
TimeUnit.MILLISECONDS,
77-
queue,
78-
new TestNGThreadFactory(name));
57+
configuration
58+
.getExecutorServiceFactory()
59+
.create(
60+
threadCount,
61+
threadCount,
62+
0,
63+
TimeUnit.MILLISECONDS,
64+
queue,
65+
new TestNGThreadFactory(name));
7966
if (reUse) {
8067
ObjectBag bag = ObjectBag.getInstance(xmlTest.getSuite());
8168
service = (ExecutorService) bag.createIfRequired(ExecutorService.class, supplier);

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

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import java.util.List;
44
import java.util.Map;
5+
import java.util.Objects;
6+
import java.util.concurrent.ThreadPoolExecutor;
57
import org.testng.IConfigurable;
68
import org.testng.IConfigurationListener;
79
import org.testng.IExecutionListener;
10+
import org.testng.IExecutorServiceFactory;
811
import org.testng.IHookable;
912
import org.testng.IInjectorFactory;
1013
import org.testng.ITestNGListenerFactory;
@@ -16,8 +19,6 @@
1619
import org.testng.internal.annotations.IAnnotationFinder;
1720
import org.testng.internal.annotations.JDK15AnnotationFinder;
1821
import org.testng.internal.objects.GuiceBackedInjectorFactory;
19-
import org.testng.internal.thread.DefaultThreadPoolExecutorFactory;
20-
import org.testng.thread.IExecutorFactory;
2122

2223
public class Configuration implements IConfiguration {
2324

@@ -34,7 +35,7 @@ public class Configuration implements IConfiguration {
3435
private final Map<Class<? extends IConfigurationListener>, IConfigurationListener>
3536
m_configurationListeners = Maps.newLinkedHashMap();
3637
private boolean alwaysRunListeners = true;
37-
private IExecutorFactory m_executorFactory = new DefaultThreadPoolExecutorFactory();
38+
private IExecutorServiceFactory executorServiceFactory = ThreadPoolExecutor::new;
3839

3940
private IInjectorFactory injectorFactory = new GuiceBackedInjectorFactory();
4041

@@ -145,13 +146,13 @@ public void setAlwaysRunListeners(boolean alwaysRunListeners) {
145146
}
146147

147148
@Override
148-
public void setExecutorFactory(IExecutorFactory factory) {
149-
this.m_executorFactory = factory;
149+
public void setExecutorServiceFactory(IExecutorServiceFactory executorServiceFactory) {
150+
this.executorServiceFactory = Objects.requireNonNull(executorServiceFactory);
150151
}
151152

152153
@Override
153-
public IExecutorFactory getExecutorFactory() {
154-
return this.m_executorFactory;
154+
public IExecutorServiceFactory getExecutorServiceFactory() {
155+
return executorServiceFactory;
155156
}
156157

157158
@Override

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import java.util.List;
44
import org.testng.*;
55
import org.testng.internal.annotations.IAnnotationFinder;
6-
import org.testng.thread.IExecutorFactory;
76

87
public interface IConfiguration {
98
IAnnotationFinder getAnnotationFinder();
@@ -46,11 +45,11 @@ default boolean addExecutionListenerIfAbsent(IExecutionListener l) {
4645

4746
void setAlwaysRunListeners(boolean alwaysRun);
4847

49-
void setExecutorFactory(IExecutorFactory factory);
48+
IInjectorFactory getInjectorFactory();
5049

51-
IExecutorFactory getExecutorFactory();
50+
IExecutorServiceFactory getExecutorServiceFactory();
5251

53-
IInjectorFactory getInjectorFactory();
52+
void setExecutorServiceFactory(IExecutorServiceFactory factory);
5453

5554
void setInjectorFactory(IInjectorFactory factory);
5655

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ private void invokeConfigurationMethod(
397397
targetInstance, params, configurableInstance, method.getMethod(), testResult);
398398
} else {
399399
MethodInvocationHelper.invokeMethodConsideringTimeout(
400-
tm, method, targetInstance, params, testResult);
400+
tm, method, targetInstance, params, testResult, m_configuration);
401401
}
402402
boolean testStatusRemainedUnchanged = testResult.isNotRunning();
403403
boolean throwException = !RuntimeBehavior.ignoreCallbackInvocationSkips();

0 commit comments

Comments
 (0)