1212
1313import static org .apiguardian .api .API .Status .INTERNAL ;
1414import static org .junit .platform .engine .TestExecutionResult .successful ;
15+ import static org .junit .vintage .engine .Constants .PARALLEL_EXECUTION_ENABLED ;
16+ import static org .junit .vintage .engine .Constants .PARALLEL_POOL_SIZE ;
1517import static org .junit .vintage .engine .descriptor .VintageTestDescriptor .ENGINE_ID ;
1618
19+ import java .util .ArrayList ;
1720import java .util .Iterator ;
21+ import java .util .List ;
1822import java .util .Optional ;
23+ import java .util .concurrent .CompletableFuture ;
24+ import java .util .concurrent .ExecutionException ;
25+ import java .util .concurrent .ExecutorService ;
26+ import java .util .concurrent .Executors ;
27+ import java .util .concurrent .TimeUnit ;
1928
2029import org .apiguardian .api .API ;
30+ import org .junit .platform .commons .logging .Logger ;
31+ import org .junit .platform .commons .logging .LoggerFactory ;
32+ import org .junit .platform .commons .util .ExceptionUtils ;
2133import org .junit .platform .engine .EngineDiscoveryRequest ;
2234import org .junit .platform .engine .EngineExecutionListener ;
2335import org .junit .platform .engine .ExecutionRequest ;
3749@ API (status = INTERNAL , since = "4.12" )
3850public final class VintageTestEngine implements TestEngine {
3951
52+ private static final Logger logger = LoggerFactory .getLogger (VintageTestEngine .class );
53+
54+ private static final int DEFAULT_THREAD_POOL_SIZE = Runtime .getRuntime ().availableProcessors ();
55+ private static final int SHUTDOWN_TIMEOUT_SECONDS = 30 ;
56+
4057 @ Override
4158 public String getId () {
4259 return ENGINE_ID ;
@@ -69,11 +86,73 @@ public void execute(ExecutionRequest request) {
6986 EngineExecutionListener engineExecutionListener = request .getEngineExecutionListener ();
7087 VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor ) request .getRootTestDescriptor ();
7188 engineExecutionListener .executionStarted (engineDescriptor );
72- executeAllChildren (engineDescriptor , engineExecutionListener );
89+ executeAllChildren (engineDescriptor , engineExecutionListener , request );
7390 engineExecutionListener .executionFinished (engineDescriptor , successful ());
7491 }
7592
7693 private void executeAllChildren (VintageEngineDescriptor engineDescriptor ,
94+ EngineExecutionListener engineExecutionListener , ExecutionRequest request ) {
95+ boolean parallelExecutionEnabled = getParallelExecutionEnabled (request );
96+
97+ if (parallelExecutionEnabled ) {
98+ if (executeInParallel (engineDescriptor , engineExecutionListener , request )) {
99+ Thread .currentThread ().interrupt ();
100+ }
101+ }
102+ else {
103+ executeSequentially (engineDescriptor , engineExecutionListener );
104+ }
105+ }
106+
107+ private boolean executeInParallel (VintageEngineDescriptor engineDescriptor ,
108+ EngineExecutionListener engineExecutionListener , ExecutionRequest request ) {
109+ ExecutorService executorService = Executors .newFixedThreadPool (getThreadPoolSize (request ));
110+ RunnerExecutor runnerExecutor = new RunnerExecutor (engineExecutionListener );
111+
112+ List <CompletableFuture <Void >> futures = new ArrayList <>();
113+ for (Iterator <TestDescriptor > iterator = engineDescriptor .getModifiableChildren ().iterator (); iterator .hasNext ();) {
114+ TestDescriptor descriptor = iterator .next ();
115+ CompletableFuture <Void > future = CompletableFuture .runAsync (() -> {
116+ runnerExecutor .execute ((RunnerTestDescriptor ) descriptor );
117+ }, executorService );
118+
119+ futures .add (future );
120+ iterator .remove ();
121+ }
122+
123+ CompletableFuture <Void > allOf = CompletableFuture .allOf (futures .toArray (new CompletableFuture <?>[0 ]));
124+ boolean wasInterrupted = false ;
125+ try {
126+ allOf .get ();
127+ }
128+ catch (InterruptedException e ) {
129+ logger .warn (e , () -> "Interruption while waiting for parallel test execution to finish" );
130+ wasInterrupted = true ;
131+ }
132+ catch (ExecutionException e ) {
133+ throw ExceptionUtils .throwAsUncheckedException (e .getCause ());
134+ }
135+ finally {
136+ shutdownExecutorService (executorService );
137+ }
138+ return wasInterrupted ;
139+ }
140+
141+ private void shutdownExecutorService (ExecutorService executorService ) {
142+ try {
143+ executorService .shutdown ();
144+ if (!executorService .awaitTermination (SHUTDOWN_TIMEOUT_SECONDS , TimeUnit .SECONDS )) {
145+ logger .warn (() -> "Executor service did not terminate within the specified timeout" );
146+ executorService .shutdownNow ();
147+ }
148+ }
149+ catch (InterruptedException e ) {
150+ logger .warn (e , () -> "Interruption while waiting for executor service to shut down" );
151+ Thread .currentThread ().interrupt ();
152+ }
153+ }
154+
155+ private void executeSequentially (VintageEngineDescriptor engineDescriptor ,
77156 EngineExecutionListener engineExecutionListener ) {
78157 RunnerExecutor runnerExecutor = new RunnerExecutor (engineExecutionListener );
79158 for (Iterator <TestDescriptor > iterator = engineDescriptor .getModifiableChildren ().iterator (); iterator .hasNext ();) {
@@ -82,4 +161,21 @@ private void executeAllChildren(VintageEngineDescriptor engineDescriptor,
82161 }
83162 }
84163
164+ private boolean getParallelExecutionEnabled (ExecutionRequest request ) {
165+ return request .getConfigurationParameters ().getBoolean (PARALLEL_EXECUTION_ENABLED ).orElse (false );
166+ }
167+
168+ private int getThreadPoolSize (ExecutionRequest request ) {
169+ Optional <String > poolSize = request .getConfigurationParameters ().get (PARALLEL_POOL_SIZE );
170+ if (poolSize .isPresent ()) {
171+ try {
172+ return Integer .parseInt (poolSize .get ());
173+ }
174+ catch (NumberFormatException e ) {
175+ logger .warn (() -> "Invalid value for parallel pool size: " + poolSize .get ());
176+ }
177+ }
178+ return DEFAULT_THREAD_POOL_SIZE ;
179+ }
180+
85181}
0 commit comments