diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java index 56621d50ba..852e24e72b 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java @@ -23,7 +23,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.TimeUnit; /** * Abstract parallel scheduling strategy in private package. @@ -43,7 +43,7 @@ abstract class AbstractThreadPoolStrategy private final Collection> futureResults; - private final AtomicBoolean canSchedule = new AtomicBoolean( true ); + private volatile boolean isDestroyed; AbstractThreadPoolStrategy( ExecutorService threadPool ) { @@ -66,11 +66,6 @@ protected final Collection> getFutureResults() return futureResults; } - protected final void disable() - { - canSchedule.set( false ); - } - @Override public void schedule( Runnable task ) { @@ -87,7 +82,7 @@ public void schedule( Runnable task ) @Override protected boolean stop() { - boolean wasRunning = canSchedule.getAndSet( false ); + boolean wasRunning = disable(); if ( threadPool.isShutdown() ) { wasRunning = false; @@ -102,7 +97,7 @@ protected boolean stop() @Override protected boolean stopNow() { - boolean wasRunning = canSchedule.getAndSet( false ); + boolean wasRunning = disable(); if ( threadPool.isShutdown() ) { wasRunning = false; @@ -114,6 +109,9 @@ protected boolean stopNow() return wasRunning; } + /** + * @see Scheduler.ShutdownHandler + */ @Override protected void setDefaultShutdownHandler( Scheduler.ShutdownHandler handler ) { @@ -125,9 +123,21 @@ protected void setDefaultShutdownHandler( Scheduler.ShutdownHandler handler ) } } - @Override - public final boolean canSchedule() + public boolean destroy() { - return canSchedule.get(); + try + { + if ( !isDestroyed )//just an optimization + { + disable(); + threadPool.shutdown(); + this.isDestroyed |= threadPool.awaitTermination( Long.MAX_VALUE, TimeUnit.NANOSECONDS ); + } + return isDestroyed; + } + catch ( InterruptedException e ) + { + return false; + } } } \ No newline at end of file diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Destroyable.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Destroyable.java new file mode 100644 index 0000000000..284ce5a43f --- /dev/null +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Destroyable.java @@ -0,0 +1,38 @@ +package org.apache.maven.surefire.junitcore.pc; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Destroys the embedded thread-pool. + * + * @author Tibor Digana (tibor17) + * @see ParallelComputerBuilder + * @since 2.18 + */ +public interface Destroyable +{ + /** + * Calling {@link java.util.concurrent.ThreadPoolExecutor#shutdown()} + * and {@link java.util.concurrent.ThreadPoolExecutor#awaitTermination(long, java.util.concurrent.TimeUnit)}. + * + * @return {@code true} if not interrupted in current thread + */ + boolean destroy(); +} \ No newline at end of file diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java index 4dd7f10a37..06c328d070 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java @@ -21,7 +21,6 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicBoolean; /** * The sequentially executing strategy in private package. @@ -33,7 +32,6 @@ final class InvokerStrategy extends SchedulingStrategy { - private final AtomicBoolean canSchedule = new AtomicBoolean( true ); private final Queue activeThreads = new ConcurrentLinkedQueue(); @@ -58,13 +56,13 @@ public void schedule( Runnable task ) @Override protected boolean stop() { - return canSchedule.getAndSet( false ); + return disable(); } @Override protected boolean stopNow() { - final boolean stopped = stop(); + final boolean stopped = disable(); for ( Thread activeThread; ( activeThread = activeThreads.poll() ) != null; ) { activeThread.interrupt(); @@ -79,14 +77,13 @@ public boolean hasSharedThreadPool() } @Override - public boolean canSchedule() + public boolean finished() + throws InterruptedException { - return canSchedule.get(); + return disable(); } - @Override - public boolean finished() - throws InterruptedException + public boolean destroy() { return stop(); } diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java index df80ad917b..9fa1e6ba28 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java @@ -47,16 +47,9 @@ public boolean hasSharedThreadPool() public boolean finished() throws InterruptedException { - boolean wasRunning = canSchedule(); + boolean wasRunning = disable(); getThreadPool().shutdown(); - try - { - getThreadPool().awaitTermination( Long.MAX_VALUE, TimeUnit.NANOSECONDS ); - return wasRunning; - } - finally - { - disable(); - } + getThreadPool().awaitTermination( Long.MAX_VALUE, TimeUnit.NANOSECONDS ); + return wasRunning; } } diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java index bf28c70e51..483e5d98ad 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java @@ -59,45 +59,8 @@ public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds this.timeoutForcedNanos = secondsToNanos( timeoutForcedInSeconds ); } - private static long secondsToNanos( double seconds ) - { - double nanos = seconds > 0 ? seconds * 1E9 : 0; - return Double.isInfinite( nanos ) || nanos >= Long.MAX_VALUE ? 0 : (long) nanos; - } - - private static long minTimeout( long timeout1, long timeout2 ) - { - if ( timeout1 == 0 ) - { - return timeout2; - } - else if ( timeout2 == 0 ) - { - return timeout1; - } - else - { - return Math.min( timeout1, timeout2 ); - } - } - - private static void printShutdownHook( Collection executedTests, - Future> testsBeforeShutdown ) - throws ExecutionException, InterruptedException - { - if ( testsBeforeShutdown != null ) - { - for ( final Description executedTest : testsBeforeShutdown.get() ) - { - if ( executedTest != null && executedTest.getDisplayName() != null ) - { - executedTests.add( executedTest.getDisplayName() ); - } - } - } - } - - public abstract Collection shutdown( boolean shutdownNow ); + protected abstract Collection describeStopped( boolean shutdownNow ); + abstract boolean shutdownThreadPoolsAwaitingKilled(); protected final void beforeRunQuietly() { @@ -109,6 +72,7 @@ protected final boolean afterRunQuietly() { shutdownStatus.tryFinish(); forcedShutdownStatus.tryFinish(); + boolean notInterrupted = true; if ( shutdownScheduler != null ) { shutdownScheduler.shutdownNow(); @@ -123,10 +87,11 @@ protected final boolean afterRunQuietly() } catch ( InterruptedException e ) { - return false; + notInterrupted = false; } } - return true; + notInterrupted &= shutdownThreadPoolsAwaitingKilled(); + return notInterrupted; } public String describeElapsedTimeout() @@ -203,7 +168,7 @@ public Collection call() throws Exception { boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout(); - return stampedStatusWithTimeout ? ParallelComputer.this.shutdown( false ) : null; + return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( false ) : null; } }; } @@ -216,7 +181,7 @@ public Collection call() throws Exception { boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout(); - return stampedStatusWithTimeout ? ParallelComputer.this.shutdown( true ) : null; + return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( true ) : null; } }; } @@ -235,4 +200,42 @@ private boolean hasTimeoutForced() { return timeoutForcedNanos > 0; } + + private static long secondsToNanos( double seconds ) + { + double nanos = seconds > 0 ? seconds * 1E9 : 0; + return Double.isInfinite( nanos ) || nanos >= Long.MAX_VALUE ? 0 : (long) nanos; + } + + private static long minTimeout( long timeout1, long timeout2 ) + { + if ( timeout1 == 0 ) + { + return timeout2; + } + else if ( timeout2 == 0 ) + { + return timeout1; + } + else + { + return Math.min( timeout1, timeout2 ); + } + } + + private static void printShutdownHook( Collection executedTests, + Future> testsBeforeShutdown ) + throws ExecutionException, InterruptedException + { + if ( testsBeforeShutdown != null ) + { + for ( final Description executedTest : testsBeforeShutdown.get() ) + { + if ( executedTest != null && executedTest.getDisplayName() != null ) + { + executedTests.add( executedTest.getDisplayName() ); + } + } + } + } } diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java index ca7cc60f54..9353349846 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java @@ -64,10 +64,10 @@ * {@link ParallelComputerBuilder#useOnePool(int)} must be greater than the number of concurrent suites and classes * altogether. *

- * The Computer can be shutdown in a separate thread. Pending tests will be interrupted if the argument is + * The Computer can be stopped in a separate thread. Pending tests will be interrupted if the argument is * true. *

- * computer.shutdown(true);
+ * computer.describeStopped(true);
  * 
* * @author Tibor Digana (tibor17) @@ -254,17 +254,29 @@ private PC() } @Override - public Collection shutdown( boolean shutdownNow ) + protected Collection describeStopped( boolean shutdownNow ) { - Collection startedTests = notThreadSafeTests.shutdown( shutdownNow ); - final Scheduler m = this.master; + Collection startedTests = notThreadSafeTests.describeStopped( shutdownNow ); + final Scheduler m = master; if ( m != null ) { - startedTests.addAll( m.shutdown( shutdownNow ) ); + startedTests.addAll( m.describeStopped( shutdownNow ) ); } return startedTests; } + @Override + boolean shutdownThreadPoolsAwaitingKilled() + { + boolean notInterrupted = notThreadSafeTests.shutdownThreadPoolsAwaitingKilled(); + final Scheduler m = master; + if ( m != null ) + { + notInterrupted &= m.shutdownThreadPoolsAwaitingKilled(); + } + return notInterrupted; + } + @Override public Runner getSuite( RunnerBuilder builder, Class[] cls ) throws InitializationError diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java index a16f1d23ec..8feeb0211a 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java @@ -22,6 +22,7 @@ import org.junit.runner.Description; import org.junit.runners.model.RunnerScheduler; +import java.util.Arrays; import java.util.Collection; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; @@ -45,6 +46,9 @@ public class Scheduler implements RunnerScheduler { + private static final Collection UNUSED_DESCRIPTIONS = + Arrays.asList( null, Description.TEST_MECHANISM, Description.EMPTY ); + private final Balancer balancer; private final SchedulingStrategy strategy; @@ -202,51 +206,93 @@ protected void logQuietly( String msg ) * Attempts to stop all actively executing tasks and immediately returns a collection * of descriptions of those tasks which have started prior to this call. *

- * This scheduler and other registered schedulers will shutdown, see {@link #register(Scheduler)}. + * This scheduler and other registered schedulers will stop, see {@link #register(Scheduler)}. * If shutdownNow is set, waiting methods will be interrupted via {@link Thread#interrupt}. * - * @param shutdownNow if true interrupts waiting methods + * @param stopNow if true interrupts waiting test methods * @return collection of descriptions started before shutting down */ - public Collection shutdown( boolean shutdownNow ) + protected Collection describeStopped( boolean stopNow ) { - shutdown = true; - Collection activeChildren = new ConcurrentLinkedQueue(); + Collection executedTests = new ConcurrentLinkedQueue(); + stop( executedTests, false, stopNow ); + return executedTests; + } - if ( started && description != null ) + /** + * Stop/Shutdown/Interrupt scheduler and its children (if any). + * + * @param executedTests Started tests which have finished normally or abruptly till called this method. + * @param tryCancelFutures Useful to set to {@code false} if a timeout is specified in plugin config. + * When the runner of + * {@link ParallelComputer#getSuite(org.junit.runners.model.RunnerBuilder, Class[])} + * is finished in + * {@link org.junit.runners.Suite#run(org.junit.runner.notification.RunNotifier)} + * all the thread-pools created by {@link ParallelComputerBuilder.PC} are already dead. + * See the unit test ParallelComputerBuilder#timeoutAndForcedShutdown(). + * @param stopNow Interrupting tests by {@link java.util.concurrent.ExecutorService#shutdownNow()} or + * {@link java.util.concurrent.Future#cancel(boolean) Future#cancel(true)} or + * {@link Thread#interrupt()}. + */ + private void stop( Collection executedTests, boolean tryCancelFutures, boolean stopNow ) + { + shutdown = true; + try { - activeChildren.add( description ); - } + if ( executedTests != null && started && !UNUSED_DESCRIPTIONS.contains( description ) ) + { + executedTests.add( description ); + } - for ( Controller slave : slaves ) + for ( Controller slave : slaves ) + { + slave.stop( executedTests, tryCancelFutures, stopNow ); + } + } + finally { try { - activeChildren.addAll( slave.shutdown( shutdownNow ) ); + balancer.releaseAllPermits(); } - catch ( Throwable t ) + finally { - logQuietly( t ); + if ( stopNow ) + { + strategy.stopNow(); + } + else if ( tryCancelFutures ) + { + strategy.stop(); + } + else + { + strategy.disable(); + } } } + } - try - { - balancer.releaseAllPermits(); - } - finally + protected boolean shutdownThreadPoolsAwaitingKilled() + { + if ( masterController == null ) { - if ( shutdownNow ) + stop( null, true, false ); + boolean isNotInterrupted = true; + if ( strategy != null ) { - strategy.stopNow(); + isNotInterrupted = strategy.destroy(); } - else + for ( Controller slave : slaves ) { - strategy.stop(); + isNotInterrupted &= slave.destroy(); } + return isNotInterrupted; + } + else + { + throw new UnsupportedOperationException( "cannot call this method if this is not a master scheduler" ); } - - return activeChildren; } protected void beforeExecute() @@ -277,7 +323,7 @@ else if ( canSchedule() && strategy.canSchedule() ) } catch ( RejectedExecutionException e ) { - shutdown( false ); + stop( null, true, false ); } catch ( Throwable t ) { @@ -297,13 +343,6 @@ public void finished() { logQuietly( e ); } - finally - { - for ( Controller slave : slaves ) - { - slave.awaitFinishedQuietly(); - } - } } private Runnable wrapTask( final Runnable task ) @@ -357,21 +396,17 @@ boolean canSchedule() return Scheduler.this.canSchedule(); } - void awaitFinishedQuietly() + void stop( Collection executedTests, boolean tryCancelFutures, boolean shutdownNow ) { - try - { - slave.finished(); - } - catch ( Throwable t ) - { - slave.logQuietly( t ); - } + slave.stop( executedTests, tryCancelFutures, shutdownNow ); } - Collection shutdown( boolean shutdownNow ) + /** + * @see org.apache.maven.surefire.junitcore.pc.Destroyable#destroy() + */ + boolean destroy() { - return slave.shutdown( shutdownNow ); + return slave.strategy.destroy(); } @Override @@ -387,6 +422,14 @@ public boolean equals( Object o ) } } + /** + * There is a way to shutdown the hierarchy of schedulers. You can do it in master scheduler via + * {@link #shutdownThreadPoolsAwaitingKilled()} which kills the current master and children recursively. + * If alternatively a shared {@link java.util.concurrent.ExecutorService} used by the master and children + * schedulers is shutdown from outside, then the {@link ShutdownHandler} is a hook calling current + * {@link #describeStopped(boolean)}. The method {@link #describeStopped(boolean)} is again shutting down children + * schedulers recursively as well. + */ public class ShutdownHandler implements RejectedExecutionHandler { @@ -406,7 +449,7 @@ public void rejectedExecution( Runnable r, ThreadPoolExecutor executor ) { if ( executor.isShutdown() ) { - shutdown( false ); + Scheduler.this.stop( null, true, false ); } final RejectedExecutionHandler poolHandler = this.poolHandler; if ( poolHandler != null ) diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java index f419cb7f61..1ce744de72 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java @@ -22,6 +22,7 @@ import org.junit.runners.model.RunnerScheduler; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; /** * Specifies the strategy of scheduling whether sequential, or parallel. @@ -36,8 +37,11 @@ * @since 2.16 */ public abstract class SchedulingStrategy + implements Destroyable { + private final AtomicBoolean canSchedule = new AtomicBoolean( true ); + /** * Schedules tasks if {@link #canSchedule()}. * @@ -92,6 +96,16 @@ protected boolean stopNow() return stop(); } + /** + * Persistently disables this strategy. Atomically ignores {@link Balancer} to acquire a new permit.

+ * The method {@link #canSchedule()} atomically returns {@code false}. + * @return {@code true} if {@link #canSchedule()} has return {@code true} on the beginning of this method call. + */ + protected boolean disable() + { + return canSchedule.getAndSet( false ); + } + protected void setDefaultShutdownHandler( Scheduler.ShutdownHandler handler ) { } @@ -103,7 +117,15 @@ protected void setDefaultShutdownHandler( Scheduler.ShutdownHandler handler ) protected abstract boolean hasSharedThreadPool(); /** - * @return true unless stopped or finished. + * @return true unless stopped, finished or disabled. */ - protected abstract boolean canSchedule(); + protected boolean canSchedule() + { + return canSchedule.get(); + } + + protected void logQuietly( Throwable t ) + { + t.printStackTrace( System.out ); + } } diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java index 88907e69e5..cfe6faa7fa 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java @@ -50,7 +50,7 @@ public boolean hasSharedThreadPool() public boolean finished() throws InterruptedException { - boolean wasRunningAll = canSchedule(); + boolean wasRunningAll = disable(); for ( Future futureResult : getFutureResults() ) { try @@ -60,19 +60,23 @@ public boolean finished() catch ( InterruptedException e ) { // after called external ExecutorService#shutdownNow() - // or ExecutorService#shutdown() wasRunningAll = false; } catch ( ExecutionException e ) { - // test throws exception + // JUnit core throws exception. + if ( e.getCause() != null ) + { + logQuietly( e.getCause() ); + } } catch ( CancellationException e ) { - // cannot happen because not calling Future#cancel() + /** + * Cancelled by {@link Future#cancel(boolean)} in {@link stop()} and {@link stopNow()}. + */ } } - disable(); return wasRunningAll; } @@ -90,12 +94,11 @@ protected final boolean stopNow() private boolean stop( boolean interrupt ) { - final boolean wasRunning = canSchedule(); + final boolean wasRunning = disable(); for ( Future futureResult : getFutureResults() ) { futureResult.cancel( interrupt ); } - disable(); return wasRunning; } } \ No newline at end of file diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java index 79b3197060..42a5c59f6b 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java @@ -22,7 +22,9 @@ import org.junit.runner.Description; import org.junit.runners.model.RunnerScheduler; +import java.util.Arrays; import java.util.Collection; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; @@ -39,32 +41,46 @@ */ final class SingleThreadScheduler { + private static final Collection UNUSED_DESCRIPTIONS = + Arrays.asList( null, Description.TEST_MECHANISM, Description.EMPTY ); + private final ExecutorService pool = newPool(); private final Scheduler master = new Scheduler( null, SchedulingStrategies.createParallelSharedStrategy( pool ) ); + private static ExecutorService newPool() + { + final ThreadFactory factory = new ThreadFactory() + { + public Thread newThread( Runnable r ) + { + return new Thread( r, "maven-surefire-plugin@NotThreadSafe" ); + } + }; + return new ThreadPoolExecutor( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), factory ); + } + RunnerScheduler newRunnerScheduler() { return new Scheduler( null, master, SchedulingStrategies.createParallelSharedStrategy( pool ) ); } /** - * @see Scheduler#shutdown(boolean) + * @see Scheduler#describeStopped(boolean) */ - Collection shutdown( boolean shutdownNow ) + Collection describeStopped( boolean shutdownNow ) { - return master.shutdown( shutdownNow ); + Collection activeChildren = + new ConcurrentLinkedQueue( master.describeStopped( shutdownNow ) ); + activeChildren.removeAll( UNUSED_DESCRIPTIONS ); + return activeChildren; } - private static ExecutorService newPool() + /** + * @see Scheduler#shutdownThreadPoolsAwaitingKilled() + */ + boolean shutdownThreadPoolsAwaitingKilled() { - final ThreadFactory factory = new ThreadFactory() - { - public Thread newThread( Runnable r ) - { - return new Thread( r, "maven-surefire-plugin@NotThreadSafe" ); - } - }; - return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), factory); + return master.shutdownThreadPoolsAwaitingKilled(); } } \ No newline at end of file diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java index 322d44344e..455874cfa5 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java @@ -49,9 +49,15 @@ final class ThreadResourcesBalancer * @param numPermits number of permits to acquire when maintaining concurrency on tests. * Must be >0 and < {@link Integer#MAX_VALUE}. * @param fair true guarantees the waiting schedulers to wake up in order they acquired a permit + * @throws IllegalArgumentException if numPermits is not positive number */ ThreadResourcesBalancer( int numPermits, boolean fair ) { + if ( numPermits <= 0 ) + { + throw new IllegalArgumentException( + String.format( "numPermits=%d should be positive number", numPermits ) ); + } balancer = new Semaphore( numPermits, fair ); this.numPermits = numPermits; } diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java index cad3062cdc..ce6ef44b43 100644 --- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java +++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java @@ -20,11 +20,13 @@ */ import net.jcip.annotations.NotThreadSafe; +import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.RunWith; @@ -33,8 +35,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; import java.util.Iterator; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; import static org.hamcrest.core.AnyOf.anyOf; import static org.hamcrest.core.Is.is; @@ -48,6 +54,8 @@ */ public class ParallelComputerBuilderTest { + private static final Object class1Lock = new Object(); + private static volatile boolean beforeShutdown; private static volatile Runnable shutdownTask; @@ -506,6 +514,37 @@ public void inheritanceWithNotThreadSafe() assertThat( computer.poolCapacity, is( 10 ) ); } + @Test + public void beforeAfterThreadChanges() + throws InterruptedException + { + Collection expectedThreads = jvmThreads(); + ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder(); + parallelComputerBuilder.parallelMethods( 3 ); + ParallelComputer computer = parallelComputerBuilder.buildComputer(); + Result result = new JUnitCore().run( computer, TestWithBeforeAfter.class ); + System.out.println( new Date() + " finished test run" ); + assertTrue( result.wasSuccessful() ); + assertThat( jvmThreads(), is( expectedThreads ) ); + } + + private static Collection jvmThreads() + { + Thread[] t = new Thread[1000]; + Thread.enumerate( t ); + ArrayList appThreads = new ArrayList( t.length ); + Collections.addAll( appThreads, t ); + appThreads.removeAll( Collections.singleton( null ) ); + Collections.sort( appThreads, new Comparator() + { + public int compare( Thread t1, Thread t2 ) + { + return (int) Math.signum( t1.getId() - t2.getId() ); + } + } ); + return appThreads; + } + private static class ShutdownTest { Result run( final boolean useInterrupt ) @@ -522,7 +561,7 @@ Result run( final boolean useInterrupt ) { public void run() { - Collection startedTests = computer.shutdown( useInterrupt ); + Collection startedTests = computer.describeStopped( useInterrupt ); assertThat( startedTests.size(), is( not( 0 ) ) ); } }; @@ -540,10 +579,10 @@ public static class Class1 public void test1() throws InterruptedException { - synchronized ( Class1.class ) + synchronized ( class1Lock ) { ++concurrentMethods; - Class1.class.wait( 500 ); + class1Lock.wait( 500 ); maxConcurrentMethods = Math.max( maxConcurrentMethods, concurrentMethods-- ); } } @@ -764,4 +803,52 @@ public static void afterSuite() assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) ); } } + + public static class TestWithBeforeAfter + { + @BeforeClass + public static void beforeClass() + throws InterruptedException + { + System.out.println( new Date() + " BEG: beforeClass" ); + TimeUnit.SECONDS.sleep( 1 ); + System.out.println( new Date() + " END: beforeClass" ); + } + + @Before + public void before() + throws InterruptedException + { + System.out.println( new Date() + " BEG: before" ); + TimeUnit.SECONDS.sleep( 1 ); + System.out.println( new Date() + " END: before" ); + } + + @Test + public void test() + throws InterruptedException + { + System.out.println( new Date() + " BEG: test" ); + TimeUnit.SECONDS.sleep( 1 ); + System.out.println( new Date() + " END: test" ); + } + + @After + public void after() + throws InterruptedException + { + System.out.println( new Date() + " BEG: after" ); + TimeUnit.SECONDS.sleep( 1 ); + System.out.println( new Date() + " END: after" ); + } + + @AfterClass + public static void afterClass() + throws InterruptedException + { + System.out.println( new Date() + " BEG: afterClass" ); + TimeUnit.SECONDS.sleep( 1 ); + System.out.println( new Date() + " END: afterClass" ); + } + } } diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java index e094e116e8..3b92750cd9 100644 --- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java +++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java @@ -995,7 +995,7 @@ public void shutdown() long timeSpent = runtime.stop(); long deltaTime = 500L; - assertEquals( 2500L, timeSpent, deltaTime ); + assertEquals( 5000L, timeSpent, deltaTime ); String description = pc.describeElapsedTimeout(); assertTrue( description.contains( "The test run has finished abruptly after timeout of 2.5 seconds.") ); assertTrue( description.contains( "These tests were executed in prior to the shutdown operation:\n" @@ -1029,7 +1029,9 @@ public void forcedShutdown() public void timeoutAndForcedShutdown() throws TestSetFailedException, ExecutionException, InterruptedException { - // The JUnitCore returns after 2.5s and the test-methods in TestClass are interrupted after 3.5s. + // The JUnitCore returns after 3.5s and the test-methods in TestClass are timed out after 2.5s. + // No new test methods are scheduled for execution after 2.5s. + // Interruption of test methods after 3.5s. Properties properties = new Properties(); properties.setProperty( PARALLEL_KEY, "methods" ); properties.setProperty( THREADCOUNTMETHODS_KEY, "2" ); @@ -1042,13 +1044,37 @@ public void timeoutAndForcedShutdown() long timeSpent = runtime.stop(); long deltaTime = 500L; - assertEquals( 2500L, timeSpent, deltaTime ); + assertEquals( 3500L, timeSpent, deltaTime ); String description = pc.describeElapsedTimeout(); assertTrue( description.contains( "The test run has finished abruptly after timeout of 2.5 seconds.") ); assertTrue( description.contains( "These tests were executed in prior to the shutdown operation:\n" + TestClass.class.getName() ) ); } + @Test + public void forcedTimeoutAndShutdown() + throws TestSetFailedException, ExecutionException, InterruptedException + { + // The JUnitCore returns after 3.5s and the test-methods in TestClass are interrupted after 3.5s. + Properties properties = new Properties(); + properties.setProperty( PARALLEL_KEY, "methods" ); + properties.setProperty( THREADCOUNTMETHODS_KEY, "2" ); + properties.setProperty( PARALLEL_TIMEOUTFORCED_KEY, Double.toString( 3.5d ) ); + properties.setProperty( PARALLEL_TIMEOUT_KEY, Double.toString( 4.0d ) ); + JUnitCoreParameters params = new JUnitCoreParameters( properties ); + ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder( params ); + ParallelComputer pc = pcBuilder.buildComputer(); + new JUnitCore().run( pc, TestClass.class ); + long timeSpent = runtime.stop(); + long deltaTime = 500L; + + assertEquals( 3500L, timeSpent, deltaTime ); + String description = pc.describeElapsedTimeout(); + assertTrue( description.contains( "The test run has finished abruptly after timeout of 3.5 seconds.") ); + assertTrue( description.contains( "These tests were executed in prior to the shutdown operation:\n" + + TestClass.class.getName() ) ); + } + public static class TestClass { @Test