| 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. |
| */ |
| |
| import org.apache.maven.surefire.api.testset.TestSetFailedException; |
| import org.apache.maven.surefire.api.util.internal.DaemonThreadFactory; |
| import org.junit.runner.Computer; |
| import org.junit.runner.Description; |
| |
| import java.util.Collection; |
| import java.util.TreeSet; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.concurrent.ThreadFactory; |
| |
| import static java.util.concurrent.TimeUnit.NANOSECONDS; |
| |
| /** |
| * ParallelComputer extends JUnit {@link Computer} and has a shutdown functionality. |
| * |
| * @author Tibor Digana (tibor17) |
| * @see ParallelComputerBuilder |
| * @since 2.16 |
| */ |
| public abstract class ParallelComputer |
| extends Computer |
| { |
| private static final ThreadFactory DAEMON_THREAD_FACTORY = DaemonThreadFactory.newDaemonThreadFactory(); |
| |
| private static final double NANOS_IN_A_SECOND = 1E9; |
| |
| private final ShutdownStatus shutdownStatus = new ShutdownStatus(); |
| |
| private final ShutdownStatus forcedShutdownStatus = new ShutdownStatus(); |
| |
| private final long timeoutNanos; |
| |
| private final long timeoutForcedNanos; |
| |
| private ScheduledExecutorService shutdownScheduler; |
| |
| public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds ) |
| { |
| this.timeoutNanos = secondsToNanos( timeoutInSeconds ); |
| this.timeoutForcedNanos = secondsToNanos( timeoutForcedInSeconds ); |
| } |
| |
| protected abstract ShutdownResult describeStopped( boolean shutdownNow ); |
| |
| protected abstract boolean shutdownThreadPoolsAwaitingKilled(); |
| |
| protected final void beforeRunQuietly() |
| { |
| shutdownStatus.setDescriptionsBeforeShutdown( hasTimeout() ? scheduleShutdown() : null ); |
| forcedShutdownStatus.setDescriptionsBeforeShutdown( hasTimeoutForced() ? scheduleForcedShutdown() : null ); |
| } |
| |
| protected final boolean afterRunQuietly() |
| { |
| shutdownStatus.tryFinish(); |
| forcedShutdownStatus.tryFinish(); |
| boolean notInterrupted = true; |
| if ( shutdownScheduler != null ) |
| { |
| shutdownScheduler.shutdownNow(); |
| /** |
| * Clear <i>interrupted status</i> of the (main) Thread. |
| * Could be previously interrupted by {@link InvokerStrategy} after triggering immediate shutdown. |
| */ |
| Thread.interrupted(); |
| try |
| { |
| shutdownScheduler.awaitTermination( Long.MAX_VALUE, NANOSECONDS ); |
| } |
| catch ( InterruptedException e ) |
| { |
| notInterrupted = false; |
| } |
| } |
| notInterrupted &= shutdownThreadPoolsAwaitingKilled(); |
| return notInterrupted; |
| } |
| |
| public String describeElapsedTimeout() |
| throws TestSetFailedException |
| { |
| final StringBuilder msg = new StringBuilder(); |
| final boolean isShutdownTimeout = shutdownStatus.isTimeoutElapsed(); |
| final boolean isForcedShutdownTimeout = forcedShutdownStatus.isTimeoutElapsed(); |
| if ( isShutdownTimeout || isForcedShutdownTimeout ) |
| { |
| msg.append( "The test run has finished abruptly after timeout of " ); |
| msg.append( nanosToSeconds( minTimeout( timeoutNanos, timeoutForcedNanos ) ) ); |
| msg.append( " seconds.\n" ); |
| |
| try |
| { |
| final TreeSet<String> executedTests = new TreeSet<>(); |
| final TreeSet<String> incompleteTests = new TreeSet<>(); |
| |
| if ( isShutdownTimeout ) |
| { |
| printShutdownHook( executedTests, incompleteTests, shutdownStatus.getDescriptionsBeforeShutdown() ); |
| } |
| |
| if ( isForcedShutdownTimeout ) |
| { |
| printShutdownHook( executedTests, incompleteTests, |
| forcedShutdownStatus.getDescriptionsBeforeShutdown() ); |
| } |
| |
| if ( !executedTests.isEmpty() ) |
| { |
| msg.append( "These tests were executed in prior to the shutdown operation:\n" ); |
| for ( String executedTest : executedTests ) |
| { |
| msg.append( executedTest ).append( '\n' ); |
| } |
| } |
| |
| if ( !incompleteTests.isEmpty() ) |
| { |
| msg.append( "These tests are incomplete:\n" ); |
| for ( String incompleteTest : incompleteTests ) |
| { |
| msg.append( incompleteTest ).append( '\n' ); |
| } |
| } |
| } |
| catch ( InterruptedException e ) |
| { |
| throw new TestSetFailedException( "Timed termination was interrupted.", e ); |
| } |
| catch ( ExecutionException e ) |
| { |
| throw new TestSetFailedException( e.getLocalizedMessage(), e.getCause() ); |
| } |
| } |
| return msg.toString(); |
| } |
| |
| private Future<ShutdownResult> scheduleShutdown() |
| { |
| return getShutdownScheduler().schedule( createShutdownTask(), timeoutNanos, NANOSECONDS ); |
| } |
| |
| private Future<ShutdownResult> scheduleForcedShutdown() |
| { |
| return getShutdownScheduler().schedule( createForcedShutdownTask(), timeoutForcedNanos, NANOSECONDS ); |
| } |
| |
| private ScheduledExecutorService getShutdownScheduler() |
| { |
| if ( shutdownScheduler == null ) |
| { |
| shutdownScheduler = Executors.newScheduledThreadPool( 2, DAEMON_THREAD_FACTORY ); |
| } |
| return shutdownScheduler; |
| } |
| |
| private Callable<ShutdownResult> createShutdownTask() |
| { |
| return new Callable<ShutdownResult>() |
| { |
| @Override |
| public ShutdownResult call() |
| throws Exception |
| { |
| boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout(); |
| return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( false ) : null; |
| } |
| }; |
| } |
| |
| private Callable<ShutdownResult> createForcedShutdownTask() |
| { |
| return new Callable<ShutdownResult>() |
| { |
| @Override |
| public ShutdownResult call() |
| throws Exception |
| { |
| boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout(); |
| return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped( true ) : null; |
| } |
| }; |
| } |
| |
| private double nanosToSeconds( long nanos ) |
| { |
| return (double) nanos / NANOS_IN_A_SECOND; |
| } |
| |
| private boolean hasTimeout() |
| { |
| return timeoutNanos > 0; |
| } |
| |
| private boolean hasTimeoutForced() |
| { |
| return timeoutForcedNanos > 0; |
| } |
| |
| private static long secondsToNanos( double seconds ) |
| { |
| double nanos = seconds > 0 ? seconds * NANOS_IN_A_SECOND : 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<String> executedTests, Collection<String> incompleteTests, |
| Future<ShutdownResult> testsBeforeShutdown ) |
| throws ExecutionException, InterruptedException |
| { |
| if ( testsBeforeShutdown != null ) |
| { |
| final ShutdownResult shutdownResult = testsBeforeShutdown.get(); |
| if ( shutdownResult != null ) |
| { |
| for ( final Description test : shutdownResult.getTriggeredTests() ) |
| { |
| if ( test != null && test.getDisplayName() != null ) |
| { |
| executedTests.add( test.getDisplayName() ); |
| } |
| } |
| |
| for ( final Description test : shutdownResult.getIncompleteTests() ) |
| { |
| if ( test != null && test.getDisplayName() != null ) |
| { |
| incompleteTests.add( test.getDisplayName() ); |
| } |
| } |
| } |
| } |
| } |
| } |