View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.surefire.booterclient;
20  
21  import javax.annotation.Nonnull;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.Closeable;
26  import java.io.File;
27  import java.io.PrintStream;
28  import java.nio.channels.ReadableByteChannel;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.BlockingQueue;
34  import java.util.concurrent.ConcurrentLinkedQueue;
35  import java.util.concurrent.LinkedBlockingQueue;
36  import java.util.concurrent.TimeUnit;
37  
38  import junit.framework.TestCase;
39  import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream;
40  import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
41  import org.apache.maven.plugin.surefire.extensions.EventConsumerThread;
42  import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
43  import org.apache.maven.surefire.api.booter.ForkingRunListener;
44  import org.apache.maven.surefire.api.event.Event;
45  import org.apache.maven.surefire.api.fork.ForkNodeArguments;
46  import org.apache.maven.surefire.api.report.CategorizedReportEntry;
47  import org.apache.maven.surefire.api.report.LegacyPojoStackTraceWriter;
48  import org.apache.maven.surefire.api.report.ReportEntry;
49  import org.apache.maven.surefire.api.report.ReporterException;
50  import org.apache.maven.surefire.api.report.RunListener;
51  import org.apache.maven.surefire.api.report.SimpleReportEntry;
52  import org.apache.maven.surefire.api.report.StackTraceWriter;
53  import org.apache.maven.surefire.api.report.TestOutputReceiver;
54  import org.apache.maven.surefire.api.report.TestOutputReportEntry;
55  import org.apache.maven.surefire.api.report.TestReportListener;
56  import org.apache.maven.surefire.api.report.TestSetReportEntry;
57  import org.apache.maven.surefire.api.util.internal.WritableBufferedByteChannel;
58  import org.apache.maven.surefire.booter.spi.EventChannelEncoder;
59  import org.apache.maven.surefire.extensions.EventHandler;
60  import org.apache.maven.surefire.extensions.util.CountdownCloseable;
61  
62  import static org.apache.maven.surefire.api.report.RunMode.NORMAL_RUN;
63  import static org.apache.maven.surefire.api.report.TestOutputReportEntry.stdOut;
64  import static org.apache.maven.surefire.api.util.internal.Channels.newBufferedChannel;
65  import static org.apache.maven.surefire.api.util.internal.Channels.newChannel;
66  import static org.assertj.core.api.Assertions.assertThat;
67  import static org.mockito.Mockito.mock;
68  import static org.mockito.Mockito.verifyZeroInteractions;
69  
70  /**
71   * @author Kristian Rosenvold
72   */
73  @SuppressWarnings("checkstyle:magicnumber")
74  public class ForkingRunListenerTest extends TestCase {
75      private final ByteArrayOutputStream content, anotherContent;
76      private final PrintStream printStream, anotherPrintStream;
77  
78      public ForkingRunListenerTest() {
79          content = new ByteArrayOutputStream();
80          printStream = new PrintStream(content);
81  
82          anotherContent = new ByteArrayOutputStream();
83          anotherPrintStream = new PrintStream(anotherContent);
84      }
85  
86      private void reset() {
87          printStream.flush();
88          content.reset();
89      }
90  
91      public void testSetStarting() throws Exception {
92          final StandardTestRun standardTestRun = new StandardTestRun();
93          TestSetReportEntry expected = createDefaultReportEntry();
94          standardTestRun.run().testSetStarting(expected);
95          standardTestRun.assertExpected(MockReporter.SET_STARTING, expected);
96      }
97  
98      public void testSetCompleted() throws Exception {
99          final StandardTestRun standardTestRun = new StandardTestRun();
100         TestSetReportEntry expected = createDefaultReportEntry();
101         standardTestRun.run().testSetCompleted(expected);
102         standardTestRun.assertExpected(MockReporter.SET_COMPLETED, expected);
103     }
104 
105     public void testStarting() throws Exception {
106         final StandardTestRun standardTestRun = new StandardTestRun();
107         ReportEntry expected = createDefaultReportEntry();
108         standardTestRun.run().testStarting(expected);
109         standardTestRun.assertExpected(MockReporter.TEST_STARTING, expected);
110     }
111 
112     public void testSucceeded() throws Exception {
113         final StandardTestRun standardTestRun = new StandardTestRun();
114         ReportEntry expected = createDefaultReportEntry();
115         standardTestRun.run().testSucceeded(expected);
116         standardTestRun.assertExpected(MockReporter.TEST_SUCCEEDED, expected);
117     }
118 
119     public void testFailed() throws Exception {
120         final StandardTestRun standardTestRun = new StandardTestRun();
121         ReportEntry expected = createReportEntryWithStackTrace();
122         standardTestRun.run().testFailed(expected);
123         standardTestRun.assertExpected(MockReporter.TEST_FAILED, expected);
124     }
125 
126     public void testFailedWithCommaInMessage() throws Exception {
127         final StandardTestRun standardTestRun = new StandardTestRun();
128         ReportEntry expected = createReportEntryWithSpecialMessage("We, the people");
129         standardTestRun.run().testFailed(expected);
130         standardTestRun.assertExpected(MockReporter.TEST_FAILED, expected);
131     }
132 
133     public void testFailedWithUnicodeEscapeInMessage() throws Exception {
134         final StandardTestRun standardTestRun = new StandardTestRun();
135         ReportEntry expected = createReportEntryWithSpecialMessage("We, \\u0177 people");
136         standardTestRun.run().testFailed(expected);
137         standardTestRun.assertExpected(MockReporter.TEST_FAILED, expected);
138     }
139 
140     public void testFailure() throws Exception {
141         final StandardTestRun standardTestRun = new StandardTestRun();
142         ReportEntry expected = createDefaultReportEntry();
143         standardTestRun.run().testError(expected);
144         standardTestRun.assertExpected(MockReporter.TEST_ERROR, expected);
145     }
146 
147     public void testSkipped() throws Exception {
148         final StandardTestRun standardTestRun = new StandardTestRun();
149         ReportEntry expected = createDefaultReportEntry();
150         standardTestRun.run().testSkipped(expected);
151         standardTestRun.assertExpected(MockReporter.TEST_SKIPPED, expected);
152     }
153 
154     public void testAssumptionFailure() throws Exception {
155         final StandardTestRun standardTestRun = new StandardTestRun();
156         ReportEntry expected = createDefaultReportEntry();
157         standardTestRun.run().testAssumptionFailure(expected);
158         standardTestRun.assertExpected(MockReporter.TEST_ASSUMPTION_FAIL, expected);
159     }
160 
161     public void testConsole() throws Exception {
162         final StandardTestRun standardTestRun = new StandardTestRun();
163         ConsoleLogger directConsoleReporter = standardTestRun.run();
164         directConsoleReporter.info("HeyYou");
165         standardTestRun.assertExpected(MockReporter.CONSOLE_INFO, "HeyYou");
166     }
167 
168     public void testConsoleOutput() throws Exception {
169         final StandardTestRun standardTestRun = new StandardTestRun();
170         TestOutputReceiver<TestOutputReportEntry> directConsoleReporter = standardTestRun.run();
171         directConsoleReporter.writeTestOutput(new TestOutputReportEntry(stdOut("HeyYou"), NORMAL_RUN, 1L));
172         standardTestRun.assertExpected(MockReporter.STDOUT, "HeyYou");
173     }
174 
175     public void testSystemProperties() throws Exception {
176         StandardTestRun standardTestRun = new StandardTestRun();
177         standardTestRun.run();
178 
179         reset();
180         createForkingRunListener();
181 
182         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
183         ForkClient forkStreamClient = new ForkClient(providerReporterFactory, new MockNotifiableTestStream(), 1);
184 
185         byte[] cmd = (":maven-surefire-event:\u0008:sys-prop:" + (char) 10 + ":normal-run:"
186                         + "\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001:"
187                         + "\u0005:UTF-8:"
188                         + "\u0000\u0000\u0000\u0002:k1:\u0000\u0000\u0000\u0002:v1:")
189                 .getBytes();
190         for (Event e : streamToEvent(cmd)) {
191             forkStreamClient.handleEvent(e);
192         }
193         cmd = ("\n:maven-surefire-event:\u0008:sys-prop:" + (char) 10 + ":normal-run:"
194                         + "\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001:"
195                         + "\u0005:UTF-8:"
196                         + "\u0000\u0000\u0000\u0002:k2:\u0000\u0000\u0000\u0002:v2:")
197                 .getBytes();
198         for (Event e : streamToEvent(cmd)) {
199             forkStreamClient.handleEvent(e);
200         }
201 
202         assertThat(forkStreamClient.getTestVmSystemProperties()).hasSize(2);
203 
204         assertThat(forkStreamClient.getTestVmSystemProperties()).containsEntry("k1", "v1");
205 
206         assertThat(forkStreamClient.getTestVmSystemProperties()).containsEntry("k2", "v2");
207     }
208 
209     public void testMultipleEntries() throws Exception {
210         StandardTestRun standardTestRun = new StandardTestRun();
211         standardTestRun.run();
212 
213         reset();
214         RunListener forkingReporter = createForkingRunListener();
215 
216         TestSetReportEntry reportEntry = createDefaultReportEntry();
217         forkingReporter.testSetStarting(reportEntry);
218         forkingReporter.testStarting(reportEntry);
219         forkingReporter.testSucceeded(reportEntry);
220         forkingReporter.testSetCompleted(reportEntry);
221 
222         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
223         ForkClient forkStreamClient = new ForkClient(providerReporterFactory, new MockNotifiableTestStream(), 1);
224 
225         for (Event e : streamToEvent(content.toByteArray())) {
226             forkStreamClient.handleEvent(e);
227         }
228 
229         final MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
230         final List<String> events = reporter.getEvents();
231         assertEquals(MockReporter.SET_STARTING, events.get(0));
232         assertEquals(MockReporter.TEST_STARTING, events.get(1));
233         assertEquals(MockReporter.TEST_SUCCEEDED, events.get(2));
234         assertEquals(MockReporter.SET_COMPLETED, events.get(3));
235     }
236 
237     public void test2DifferentChannels() throws Exception {
238         reset();
239         ReportEntry expected = createDefaultReportEntry();
240         SimpleReportEntry secondExpected = createAnotherDefaultReportEntry();
241 
242         new ForkingRunListener(new EventChannelEncoder(newBufferedChannel(printStream)), false).testStarting(expected);
243 
244         new ForkingRunListener(new EventChannelEncoder(newBufferedChannel(anotherPrintStream)), false)
245                 .testSkipped(secondExpected);
246 
247         TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
248         NotifiableTestStream notifiableTestStream = new MockNotifiableTestStream();
249 
250         ForkClient forkStreamClient = new ForkClient(providerReporterFactory, notifiableTestStream, 1);
251         for (Event e : streamToEvent(content.toByteArray())) {
252             forkStreamClient.handleEvent(e);
253         }
254 
255         MockReporter reporter = (MockReporter) forkStreamClient.getReporter();
256         assertThat(reporter.getFirstEvent()).isEqualTo(MockReporter.TEST_STARTING);
257         assertThat(reporter.getFirstData()).isEqualTo(expected);
258         assertThat(reporter.getEvents()).hasSize(1);
259 
260         forkStreamClient = new ForkClient(providerReporterFactory, notifiableTestStream, 2);
261         for (Event e : streamToEvent(anotherContent.toByteArray())) {
262             forkStreamClient.handleEvent(e);
263         }
264         MockReporter reporter2 = (MockReporter) forkStreamClient.getReporter();
265         assertThat(reporter2.getFirstEvent()).isEqualTo(MockReporter.TEST_SKIPPED);
266         assertThat(reporter2.getFirstData()).isEqualTo(secondExpected);
267         assertThat(reporter2.getEvents()).hasSize(1);
268     }
269 
270     private static List<Event> streamToEvent(byte[] stream) throws Exception {
271         List<Event> events = new ArrayList<>();
272         EH handler = new EH();
273         CountdownCloseable countdown = new CountdownCloseable(mock(Closeable.class), 1);
274         ConsoleLogger logger = mock(ConsoleLogger.class);
275         ForkNodeArgumentsMock arguments = new ForkNodeArgumentsMock(logger, new File(""));
276         ReadableByteChannel channel = newChannel(new ByteArrayInputStream(stream));
277         try (EventConsumerThread t = new EventConsumerThread("t", channel, handler, countdown, arguments)) {
278             t.start();
279             countdown.awaitClosed();
280             verifyZeroInteractions(logger);
281             assertThat(arguments.isCalled()).isFalse();
282             for (int i = 0, size = handler.countEventsInCache(); i < size; i++) {
283                 events.add(handler.pullEvent());
284             }
285             assertEquals(0, handler.countEventsInCache());
286             return events;
287         }
288     }
289 
290     /**
291      * Threadsafe impl. Mockito and Powermock are not thread-safe.
292      */
293     private static class ForkNodeArgumentsMock implements ForkNodeArguments {
294         private final ConcurrentLinkedQueue<String> dumpStreamText = new ConcurrentLinkedQueue<>();
295         private final ConcurrentLinkedQueue<String> logWarningAtEnd = new ConcurrentLinkedQueue<>();
296         private final ConsoleLogger logger;
297         private final File dumpStreamTextFile;
298 
299         ForkNodeArgumentsMock(ConsoleLogger logger, File dumpStreamTextFile) {
300             this.logger = logger;
301             this.dumpStreamTextFile = dumpStreamTextFile;
302         }
303 
304         @Nonnull
305         @Override
306         public String getSessionId() {
307             throw new UnsupportedOperationException();
308         }
309 
310         @Override
311         public int getForkChannelId() {
312             return 0;
313         }
314 
315         @Nonnull
316         @Override
317         public File dumpStreamText(@Nonnull String text) {
318             dumpStreamText.add(text);
319             return dumpStreamTextFile;
320         }
321 
322         @Nonnull
323         @Override
324         public File dumpStreamException(@Nonnull Throwable t) {
325             throw new UnsupportedOperationException();
326         }
327 
328         @Override
329         public void logWarningAtEnd(@Nonnull String text) {
330             logWarningAtEnd.add(text);
331         }
332 
333         @Nonnull
334         @Override
335         public ConsoleLogger getConsoleLogger() {
336             return logger;
337         }
338 
339         @Nonnull
340         @Override
341         public Object getConsoleLock() {
342             return logger;
343         }
344 
345         boolean isCalled() {
346             return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
347         }
348 
349         @Override
350         public File getEventStreamBinaryFile() {
351             return null;
352         }
353 
354         @Override
355         public File getCommandStreamBinaryFile() {
356             return null;
357         }
358     }
359 
360     private static class EH implements EventHandler<Event> {
361         private final BlockingQueue<Event> cache = new LinkedBlockingQueue<>();
362 
363         Event pullEvent() throws InterruptedException {
364             return cache.poll(1, TimeUnit.MINUTES);
365         }
366 
367         int countEventsInCache() {
368             return cache.size();
369         }
370 
371         @Override
372         public void handleEvent(@Nonnull Event event) {
373             cache.add(event);
374         }
375     }
376 
377     // Todo: Test weird characters
378 
379     private SimpleReportEntry createDefaultReportEntry(Map<String, String> sysProps) {
380         return new SimpleReportEntry(NORMAL_RUN, 1L, "com.abc.TestClass", null, "testMethod", null, null, 22, sysProps);
381     }
382 
383     private SimpleReportEntry createDefaultReportEntry() {
384         return createDefaultReportEntry(Collections.<String, String>emptyMap());
385     }
386 
387     private SimpleReportEntry createAnotherDefaultReportEntry() {
388         return new SimpleReportEntry(NORMAL_RUN, 0L, "com.abc.AnotherTestClass", null, "testAnotherMethod", null, 42);
389     }
390 
391     private SimpleReportEntry createReportEntryWithStackTrace() {
392         try {
393             throw new RuntimeException();
394         } catch (RuntimeException e) {
395             StackTraceWriter stackTraceWriter =
396                     new LegacyPojoStackTraceWriter("org.apache.tests.TestClass", "testMethod11", e);
397             return new CategorizedReportEntry(
398                     NORMAL_RUN, 0L, "com.abc.TestClass", "testMethod", "aGroup", stackTraceWriter, 77);
399         }
400     }
401 
402     private SimpleReportEntry createReportEntryWithSpecialMessage(String message) {
403         try {
404             throw new RuntimeException(message);
405         } catch (RuntimeException e) {
406             StackTraceWriter stackTraceWriter =
407                     new LegacyPojoStackTraceWriter("org.apache.tests.TestClass", "testMethod11", e);
408             return new CategorizedReportEntry(
409                     NORMAL_RUN, 1L, "com.abc.TestClass", "testMethod", "aGroup", stackTraceWriter, 77);
410         }
411     }
412 
413     private TestReportListener<TestOutputReportEntry> createForkingRunListener() {
414         WritableBufferedByteChannel channel = (WritableBufferedByteChannel) newChannel(printStream);
415         return new ForkingRunListener(new EventChannelEncoder(channel), false);
416     }
417 
418     private class StandardTestRun {
419         private MockReporter reporter;
420 
421         public TestReportListener<TestOutputReportEntry> run() throws ReporterException {
422             reset();
423             return createForkingRunListener();
424         }
425 
426         public void clientReceiveContent() throws Exception {
427             TestSetMockReporterFactory providerReporterFactory = new TestSetMockReporterFactory();
428             ForkClient handler = new ForkClient(providerReporterFactory, new MockNotifiableTestStream(), 1);
429             for (Event e : streamToEvent(content.toByteArray())) {
430                 handler.handleEvent(e);
431             }
432             reporter = (MockReporter) handler.getReporter();
433         }
434 
435         public String getFirstEvent() {
436             return reporter.getEvents().get(0);
437         }
438 
439         public ReportEntry getFirstData() {
440             return (ReportEntry) reporter.getData().get(0);
441         }
442 
443         private void assertExpected(String actionCode, ReportEntry expected) throws Exception {
444             clientReceiveContent();
445             assertEquals(actionCode, getFirstEvent());
446             final ReportEntry firstData = getFirstData();
447             assertEquals(expected.getSourceName(), firstData.getSourceName());
448             assertEquals(expected.getName(), firstData.getName());
449             //noinspection deprecation
450             assertEquals(expected.getElapsed(), firstData.getElapsed());
451             assertEquals(expected.getGroup(), firstData.getGroup());
452             if (expected.getStackTraceWriter() != null) {
453                 //noinspection ThrowableResultOfMethodCallIgnored
454                 assertEquals(
455                         expected.getStackTraceWriter().getThrowable().getLocalizedMessage(),
456                         firstData.getStackTraceWriter().getThrowable().getLocalizedMessage());
457                 assertEquals(
458                         expected.getStackTraceWriter().writeTraceToString(),
459                         firstData.getStackTraceWriter().writeTraceToString());
460             }
461         }
462 
463         private void assertExpected(String actionCode, String expected) throws Exception {
464             clientReceiveContent();
465             assertEquals(actionCode, getFirstEvent());
466             final String firstData = (String) reporter.getData().get(0);
467             assertEquals(expected, firstData);
468         }
469     }
470 }