1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugin.surefire.booterclient.output;
20
21 import javax.annotation.Nonnull;
22
23 import java.io.File;
24 import java.util.Map;
25 import java.util.Queue;
26 import java.util.Set;
27 import java.util.TreeSet;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentLinkedQueue;
30 import java.util.concurrent.atomic.AtomicLong;
31
32 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
33 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
34 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
35 import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
36 import org.apache.maven.surefire.api.event.Event;
37 import org.apache.maven.surefire.api.report.ReportEntry;
38 import org.apache.maven.surefire.api.report.RunListener;
39 import org.apache.maven.surefire.api.report.RunMode;
40 import org.apache.maven.surefire.api.report.StackTraceWriter;
41 import org.apache.maven.surefire.api.report.TestOutputReceiver;
42 import org.apache.maven.surefire.api.report.TestOutputReportEntry;
43 import org.apache.maven.surefire.api.report.TestReportListener;
44 import org.apache.maven.surefire.api.report.TestSetReportEntry;
45 import org.apache.maven.surefire.extensions.EventHandler;
46
47 import static java.lang.System.currentTimeMillis;
48 import static java.util.Collections.unmodifiableMap;
49 import static org.apache.maven.surefire.api.booter.Shutdown.KILL;
50 import static org.apache.maven.surefire.api.report.CategorizedReportEntry.reportEntry;
51
52
53
54
55
56
57
58
59 public final class ForkClient implements EventHandler<Event> {
60 private static final long START_TIME_ZERO = 0L;
61 private static final long START_TIME_NEGATIVE_TIMEOUT = -1L;
62
63 private final DefaultReporterFactory defaultReporterFactory;
64
65 private final Map<String, String> testVmSystemProperties = new ConcurrentHashMap<>();
66
67 private final NotifiableTestStream notifiableTestStream;
68
69 private final Queue<String> testsInProgress = new ConcurrentLinkedQueue<>();
70
71
72
73
74
75 private final AtomicLong testSetStartedAt = new AtomicLong(START_TIME_ZERO);
76
77 private final ForkedProcessEventNotifier notifier = new ForkedProcessEventNotifier();
78
79 private final int forkNumber;
80
81 private volatile TestReportListener<TestOutputReportEntry> testSetReporter;
82
83
84
85
86 private volatile boolean saidGoodBye;
87
88 private volatile StackTraceWriter errorInFork;
89
90 public ForkClient(
91 DefaultReporterFactory defaultReporterFactory, NotifiableTestStream notifiableTestStream, int forkNumber) {
92 this.defaultReporterFactory = defaultReporterFactory;
93 this.notifiableTestStream = notifiableTestStream;
94 this.forkNumber = forkNumber;
95 notifier.setTestSetStartingListener(new TestSetStartingListener());
96 notifier.setTestSetCompletedListener(new TestSetCompletedListener());
97 notifier.setTestStartingListener(new TestStartingListener());
98 notifier.setTestSucceededListener(new TestSucceededListener());
99 notifier.setTestFailedListener(new TestFailedListener());
100 notifier.setTestSkippedListener(new TestSkippedListener());
101 notifier.setTestErrorListener(new TestErrorListener());
102 notifier.setTestAssumptionFailureListener(new TestAssumptionFailureListener());
103 notifier.setSystemPropertiesListener(new SystemPropertiesListener());
104 notifier.setStdOutListener(new StdOutListener());
105 notifier.setStdErrListener(new StdErrListener());
106 notifier.setConsoleInfoListener(new ConsoleListener());
107 notifier.setAcquireNextTestListener(new AcquireNextTestListener());
108 notifier.setConsoleErrorListener(new ErrorListener());
109 notifier.setByeListener(new ByeListener());
110 notifier.setConsoleDebugListener(new DebugListener());
111 notifier.setConsoleWarningListener(new WarningListener());
112 notifier.setExitErrorEventListener(new ExitErrorEventListener());
113 }
114
115 public void setStopOnNextTestListener(ForkedProcessEventListener listener) {
116 notifier.setStopOnNextTestListener(listener);
117 }
118
119 private final class TestSetStartingListener implements ForkedProcessReportEventListener<TestSetReportEntry> {
120 @Override
121 public void handle(TestSetReportEntry reportEntry) {
122 getTestSetReporter().testSetStarting(reportEntry);
123 setCurrentStartTime();
124 }
125 }
126
127 private final class TestSetCompletedListener implements ForkedProcessReportEventListener<TestSetReportEntry> {
128 @Override
129 public void handle(TestSetReportEntry reportEntry) {
130 testsInProgress.clear();
131 TestSetReportEntry entry = reportEntry(
132 reportEntry.getRunMode(),
133 reportEntry.getTestRunId(),
134 reportEntry.getSourceName(),
135 reportEntry.getSourceText(),
136 reportEntry.getName(),
137 reportEntry.getNameText(),
138 reportEntry.getGroup(),
139 reportEntry.getStackTraceWriter(),
140 reportEntry.getElapsed(),
141 reportEntry.getMessage(),
142 getTestVmSystemProperties());
143 getTestSetReporter().testSetCompleted(entry);
144 }
145 }
146
147 private final class TestStartingListener implements ForkedProcessReportEventListener<ReportEntry> {
148 @Override
149 public void handle(ReportEntry reportEntry) {
150 testsInProgress.offer(reportEntry.getSourceName());
151 getTestSetReporter().testStarting(reportEntry);
152 }
153 }
154
155 private final class TestSucceededListener implements ForkedProcessReportEventListener<ReportEntry> {
156 @Override
157 public void handle(ReportEntry reportEntry) {
158 testsInProgress.remove(reportEntry.getSourceName());
159 getTestSetReporter().testSucceeded(reportEntry);
160 }
161 }
162
163 private final class TestFailedListener implements ForkedProcessReportEventListener<ReportEntry> {
164 @Override
165 public void handle(ReportEntry reportEntry) {
166 testsInProgress.remove(reportEntry.getSourceName());
167 getTestSetReporter().testFailed(reportEntry);
168 }
169 }
170
171 private final class TestSkippedListener implements ForkedProcessReportEventListener<ReportEntry> {
172 @Override
173 public void handle(ReportEntry reportEntry) {
174 testsInProgress.remove(reportEntry.getSourceName());
175 getTestSetReporter().testSkipped(reportEntry);
176 }
177 }
178
179 private final class TestErrorListener implements ForkedProcessReportEventListener<ReportEntry> {
180 @Override
181 public void handle(ReportEntry reportEntry) {
182 testsInProgress.remove(reportEntry.getSourceName());
183 getTestSetReporter().testError(reportEntry);
184 }
185 }
186
187 private final class TestAssumptionFailureListener implements ForkedProcessReportEventListener<ReportEntry> {
188 @Override
189 public void handle(ReportEntry reportEntry) {
190 testsInProgress.remove(reportEntry.getSourceName());
191 getTestSetReporter().testAssumptionFailure(reportEntry);
192 }
193 }
194
195 private final class SystemPropertiesListener implements ForkedProcessPropertyEventListener {
196 @Override
197 public void handle(String key, String value, RunMode runMode, Long testRunId) {
198 testVmSystemProperties.put(key, value);
199 }
200 }
201
202 private final class StdOutListener implements ForkedProcessStandardOutErrEventListener {
203 @Override
204 public void handle(String output, boolean newLine, RunMode runMode, Long testRunId) {
205 writeTestOutput(output, true, newLine, runMode, testRunId);
206 }
207 }
208
209 private final class StdErrListener implements ForkedProcessStandardOutErrEventListener {
210 @Override
211 public void handle(String output, boolean newLine, RunMode runMode, Long testRunId) {
212 writeTestOutput(output, false, newLine, runMode, testRunId);
213 }
214 }
215
216 private final class ConsoleListener implements ForkedProcessStringEventListener {
217 @Override
218 public void handle(String msg) {
219 getOrCreateConsoleLogger().info(msg);
220 }
221 }
222
223 private final class AcquireNextTestListener implements ForkedProcessEventListener {
224 @Override
225 public void handle() {
226 notifiableTestStream.provideNewTest();
227 }
228 }
229
230 private class ErrorListener implements ForkedProcessStackTraceEventListener {
231 @Override
232 public void handle(@Nonnull StackTraceWriter stackTrace) {
233 String msg = stackTrace.getThrowable().getMessage();
234 if (errorInFork == null) {
235 errorInFork = stackTrace.writeTraceToString() != null ? stackTrace : null;
236 if (msg != null) {
237 getOrCreateConsoleLogger().error(msg);
238 }
239 }
240 dumpToLoFile(msg);
241 }
242 }
243
244 private final class ByeListener implements ForkedProcessEventListener {
245 @Override
246 public void handle() {
247 saidGoodBye = true;
248 notifiableTestStream.acknowledgeByeEventReceived();
249 }
250 }
251
252 private final class DebugListener implements ForkedProcessStringEventListener {
253 @Override
254 public void handle(String msg) {
255 getOrCreateConsoleLogger().debug(msg);
256 }
257 }
258
259 private final class WarningListener implements ForkedProcessStringEventListener {
260 @Override
261 public void handle(String msg) {
262 getOrCreateConsoleLogger().warning(msg);
263 }
264 }
265
266 private final class ExitErrorEventListener implements ForkedProcessExitErrorListener {
267 @Override
268 public void handle(StackTraceWriter stackTrace) {
269 getOrCreateConsoleLogger().error("The surefire booter JVM" + forkNumber + " was interrupted and exits.");
270 }
271 }
272
273 public void kill() {
274 if (!saidGoodBye) {
275 notifiableTestStream.shutdown(KILL);
276 }
277 }
278
279
280
281
282
283
284
285
286 public void tryToTimeout(long currentTimeMillis, int forkedProcessTimeoutInSeconds) {
287 if (forkedProcessTimeoutInSeconds > 0) {
288 final long forkedProcessTimeoutInMillis = 1000L * forkedProcessTimeoutInSeconds;
289 final long startedAt = testSetStartedAt.get();
290 if (startedAt > START_TIME_ZERO && currentTimeMillis - startedAt >= forkedProcessTimeoutInMillis) {
291 testSetStartedAt.set(START_TIME_NEGATIVE_TIMEOUT);
292 notifiableTestStream.shutdown(KILL);
293 }
294 }
295 }
296
297 public DefaultReporterFactory getDefaultReporterFactory() {
298 return defaultReporterFactory;
299 }
300
301 @Override
302 public void handleEvent(@Nonnull Event event) {
303 notifier.notifyEvent(event);
304 }
305
306 private void setCurrentStartTime() {
307 if (testSetStartedAt.get() == START_TIME_ZERO)
308 {
309
310
311 testSetStartedAt.compareAndSet(START_TIME_ZERO, currentTimeMillis());
312 }
313 }
314
315 public boolean hadTimeout() {
316 return testSetStartedAt.get() == START_TIME_NEGATIVE_TIMEOUT;
317 }
318
319
320
321
322 private TestReportListener<TestOutputReportEntry> getTestSetReporter() {
323 if (testSetReporter == null) {
324 synchronized (this) {
325 if (testSetReporter == null) {
326 testSetReporter = defaultReporterFactory.createTestReportListener();
327 }
328 }
329 }
330 return testSetReporter;
331 }
332
333 void dumpToLoFile(String msg) {
334 File reportsDir = defaultReporterFactory.getReportsDirectory();
335 InPluginProcessDumpSingleton util = InPluginProcessDumpSingleton.getSingleton();
336 util.dumpStreamText(msg, reportsDir, forkNumber);
337 }
338
339 private void writeTestOutput(String output, boolean isStdout, boolean newLine, RunMode runMode, Long testRunId) {
340 getConsoleOutputReceiver()
341 .writeTestOutput(new TestOutputReportEntry(output, isStdout, newLine, runMode, testRunId));
342 }
343
344 public Map<String, String> getTestVmSystemProperties() {
345 return unmodifiableMap(testVmSystemProperties);
346 }
347
348
349
350
351
352
353
354 public RunListener getReporter() {
355 return getTestSetReporter();
356 }
357
358 public TestOutputReceiver<TestOutputReportEntry> getConsoleOutputReceiver() {
359 return getTestSetReporter();
360 }
361
362 private ConsoleLogger getOrCreateConsoleLogger() {
363 return getTestSetReporter();
364 }
365
366 public void close(boolean hadTimeout) {
367
368 }
369
370 public boolean isSaidGoodBye() {
371 return saidGoodBye;
372 }
373
374 public StackTraceWriter getErrorInFork() {
375 return errorInFork;
376 }
377
378 public boolean isErrorInFork() {
379 return errorInFork != null;
380 }
381
382 public Set<String> testsInProgress() {
383 return new TreeSet<>(testsInProgress);
384 }
385
386 public boolean hasTestsInProgress() {
387 return !testsInProgress.isEmpty();
388 }
389 }