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