1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.junitcore.pc;
20
21 import java.util.Collection;
22 import java.util.TreeSet;
23 import java.util.concurrent.Callable;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.Future;
27 import java.util.concurrent.ScheduledExecutorService;
28 import java.util.concurrent.ThreadFactory;
29
30 import org.apache.maven.surefire.api.testset.TestSetFailedException;
31 import org.apache.maven.surefire.api.util.internal.DaemonThreadFactory;
32 import org.junit.runner.Computer;
33 import org.junit.runner.Description;
34
35 import static java.util.concurrent.TimeUnit.NANOSECONDS;
36
37
38
39
40
41
42
43
44 public abstract class ParallelComputer extends Computer {
45 private static final ThreadFactory DAEMON_THREAD_FACTORY = DaemonThreadFactory.newDaemonThreadFactory();
46
47 private static final double NANOS_IN_A_SECOND = 1E9;
48
49 private final ShutdownStatus shutdownStatus = new ShutdownStatus();
50
51 private final ShutdownStatus forcedShutdownStatus = new ShutdownStatus();
52
53 private final long timeoutNanos;
54
55 private final long timeoutForcedNanos;
56
57 private ScheduledExecutorService shutdownScheduler;
58
59 public ParallelComputer(double timeoutInSeconds, double timeoutForcedInSeconds) {
60 this.timeoutNanos = secondsToNanos(timeoutInSeconds);
61 this.timeoutForcedNanos = secondsToNanos(timeoutForcedInSeconds);
62 }
63
64 protected abstract ShutdownResult describeStopped(boolean shutdownNow);
65
66 protected abstract boolean shutdownThreadPoolsAwaitingKilled();
67
68 protected final void beforeRunQuietly() {
69 shutdownStatus.setDescriptionsBeforeShutdown(hasTimeout() ? scheduleShutdown() : null);
70 forcedShutdownStatus.setDescriptionsBeforeShutdown(hasTimeoutForced() ? scheduleForcedShutdown() : null);
71 }
72
73 protected final boolean afterRunQuietly() {
74 shutdownStatus.tryFinish();
75 forcedShutdownStatus.tryFinish();
76 boolean notInterrupted = true;
77 if (shutdownScheduler != null) {
78 shutdownScheduler.shutdownNow();
79
80
81
82
83 Thread.interrupted();
84 try {
85 shutdownScheduler.awaitTermination(Long.MAX_VALUE, NANOSECONDS);
86 } catch (InterruptedException e) {
87 notInterrupted = false;
88 }
89 }
90 notInterrupted &= shutdownThreadPoolsAwaitingKilled();
91 return notInterrupted;
92 }
93
94 public String describeElapsedTimeout() throws TestSetFailedException {
95 final StringBuilder msg = new StringBuilder();
96 final boolean isShutdownTimeout = shutdownStatus.isTimeoutElapsed();
97 final boolean isForcedShutdownTimeout = forcedShutdownStatus.isTimeoutElapsed();
98 if (isShutdownTimeout || isForcedShutdownTimeout) {
99 msg.append("The test run has finished abruptly after timeout of ");
100 msg.append(nanosToSeconds(minTimeout(timeoutNanos, timeoutForcedNanos)));
101 msg.append(" seconds.\n");
102
103 try {
104 final TreeSet<String> executedTests = new TreeSet<>();
105 final TreeSet<String> incompleteTests = new TreeSet<>();
106
107 if (isShutdownTimeout) {
108 printShutdownHook(executedTests, incompleteTests, shutdownStatus.getDescriptionsBeforeShutdown());
109 }
110
111 if (isForcedShutdownTimeout) {
112 printShutdownHook(
113 executedTests, incompleteTests, forcedShutdownStatus.getDescriptionsBeforeShutdown());
114 }
115
116 if (!executedTests.isEmpty()) {
117 msg.append("These tests were executed in prior to the shutdown operation:\n");
118 for (String executedTest : executedTests) {
119 msg.append(executedTest).append('\n');
120 }
121 }
122
123 if (!incompleteTests.isEmpty()) {
124 msg.append("These tests are incomplete:\n");
125 for (String incompleteTest : incompleteTests) {
126 msg.append(incompleteTest).append('\n');
127 }
128 }
129 } catch (InterruptedException e) {
130 throw new TestSetFailedException("Timed termination was interrupted.", e);
131 } catch (ExecutionException e) {
132 throw new TestSetFailedException(e.getLocalizedMessage(), e.getCause());
133 }
134 }
135 return msg.toString();
136 }
137
138 private Future<ShutdownResult> scheduleShutdown() {
139 return getShutdownScheduler().schedule(createShutdownTask(), timeoutNanos, NANOSECONDS);
140 }
141
142 private Future<ShutdownResult> scheduleForcedShutdown() {
143 return getShutdownScheduler().schedule(createForcedShutdownTask(), timeoutForcedNanos, NANOSECONDS);
144 }
145
146 private ScheduledExecutorService getShutdownScheduler() {
147 if (shutdownScheduler == null) {
148 shutdownScheduler = Executors.newScheduledThreadPool(2, DAEMON_THREAD_FACTORY);
149 }
150 return shutdownScheduler;
151 }
152
153 private Callable<ShutdownResult> createShutdownTask() {
154 return new Callable<ShutdownResult>() {
155 @Override
156 public ShutdownResult call() throws Exception {
157 boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout();
158 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped(false) : null;
159 }
160 };
161 }
162
163 private Callable<ShutdownResult> createForcedShutdownTask() {
164 return new Callable<ShutdownResult>() {
165 @Override
166 public ShutdownResult call() throws Exception {
167 boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout();
168 return stampedStatusWithTimeout ? ParallelComputer.this.describeStopped(true) : null;
169 }
170 };
171 }
172
173 private double nanosToSeconds(long nanos) {
174 return (double) nanos / NANOS_IN_A_SECOND;
175 }
176
177 private boolean hasTimeout() {
178 return timeoutNanos > 0;
179 }
180
181 private boolean hasTimeoutForced() {
182 return timeoutForcedNanos > 0;
183 }
184
185 private static long secondsToNanos(double seconds) {
186 double nanos = seconds > 0 ? seconds * NANOS_IN_A_SECOND : 0;
187 return Double.isInfinite(nanos) || nanos >= Long.MAX_VALUE ? 0 : (long) nanos;
188 }
189
190 private static long minTimeout(long timeout1, long timeout2) {
191 if (timeout1 == 0) {
192 return timeout2;
193 } else if (timeout2 == 0) {
194 return timeout1;
195 } else {
196 return Math.min(timeout1, timeout2);
197 }
198 }
199
200 private static void printShutdownHook(
201 Collection<String> executedTests,
202 Collection<String> incompleteTests,
203 Future<ShutdownResult> testsBeforeShutdown)
204 throws ExecutionException, InterruptedException {
205 if (testsBeforeShutdown != null) {
206 final ShutdownResult shutdownResult = testsBeforeShutdown.get();
207 if (shutdownResult != null) {
208 for (final Description test : shutdownResult.getTriggeredTests()) {
209 if (test != null && test.getDisplayName() != null) {
210 executedTests.add(test.getDisplayName());
211 }
212 }
213
214 for (final Description test : shutdownResult.getIncompleteTests()) {
215 if (test != null && test.getDisplayName() != null) {
216 incompleteTests.add(test.getDisplayName());
217 }
218 }
219 }
220 }
221 }
222 }