View Javadoc
1   package org.apache.maven.surefire.booter;
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 org.apache.maven.surefire.api.booter.MasterProcessChannelDecoder;
23  import org.apache.maven.surefire.api.booter.MasterProcessChannelEncoder;
24  import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelDecoder;
25  import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelEncoder;
26  import org.apache.maven.surefire.booter.spi.LegacyMasterProcessChannelProcessorFactory;
27  import org.apache.maven.surefire.booter.spi.SurefireMasterProcessChannelProcessorFactory;
28  import org.apache.maven.surefire.api.report.StackTraceWriter;
29  import org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils;
30  import org.apache.maven.surefire.spi.MasterProcessChannelProcessorFactory;
31  import org.junit.Rule;
32  import org.junit.Test;
33  import org.junit.function.ThrowingRunnable;
34  import org.junit.rules.ErrorCollector;
35  import org.junit.runner.RunWith;
36  import org.mockito.ArgumentCaptor;
37  import org.mockito.Captor;
38  import org.mockito.Mock;
39  import org.mockito.invocation.InvocationOnMock;
40  import org.mockito.stubbing.Answer;
41  import org.powermock.core.classloader.annotations.PowerMockIgnore;
42  import org.powermock.core.classloader.annotations.PrepareForTest;
43  import org.powermock.modules.junit4.PowerMockRunner;
44  
45  import java.io.IOException;
46  import java.net.InetSocketAddress;
47  import java.net.MalformedURLException;
48  import java.nio.channels.ServerSocketChannel;
49  
50  import static java.net.StandardSocketOptions.SO_KEEPALIVE;
51  import static java.net.StandardSocketOptions.SO_REUSEADDR;
52  import static java.net.StandardSocketOptions.TCP_NODELAY;
53  import static org.fest.assertions.Assertions.assertThat;
54  import static org.fest.assertions.Fail.fail;
55  import static org.mockito.ArgumentMatchers.any;
56  import static org.mockito.ArgumentMatchers.anyString;
57  import static org.mockito.ArgumentMatchers.same;
58  import static org.mockito.Mockito.mock;
59  import static org.mockito.Mockito.times;
60  import static org.mockito.Mockito.verify;
61  import static org.mockito.Mockito.verifyZeroInteractions;
62  import static org.powermock.api.mockito.PowerMockito.doAnswer;
63  import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
64  import static org.powermock.api.mockito.PowerMockito.doNothing;
65  import static org.powermock.api.mockito.PowerMockito.doThrow;
66  import static org.powermock.api.mockito.PowerMockito.mockStatic;
67  import static org.powermock.api.mockito.PowerMockito.verifyNoMoreInteractions;
68  import static org.powermock.api.mockito.PowerMockito.verifyPrivate;
69  import static org.powermock.api.mockito.PowerMockito.when;
70  import static org.powermock.reflect.Whitebox.invokeMethod;
71  import static org.powermock.reflect.Whitebox.setInternalState;
72  
73  /**
74   * PowerMock tests for {@link ForkedBooter}.
75   */
76  @RunWith( PowerMockRunner.class )
77  @PrepareForTest( {
78                       PpidChecker.class,
79                       ForkedBooter.class,
80                       LegacyMasterProcessChannelEncoder.class,
81                       ShutdownHookUtils.class
82  } )
83  @PowerMockIgnore( { "org.jacoco.agent.rt.*", "com.vladium.emma.rt.*" } )
84  public class ForkedBooterMockTest
85  {
86      @Rule
87      public final ErrorCollector errorCollector = new ErrorCollector();
88  
89      @Mock
90      private PpidChecker pluginProcessChecker;
91  
92      @Mock
93      private ForkedBooter booter;
94  
95      @Mock
96      private MasterProcessChannelProcessorFactory channelProcessorFactory;
97  
98      @Mock
99      private LegacyMasterProcessChannelEncoder eventChannel;
100 
101     @Captor
102     private ArgumentCaptor<StackTraceWriter> capturedStackTraceWriter;
103 
104     @Captor
105     private ArgumentCaptor<Boolean> capturedBoolean;
106 
107     @Captor
108     private ArgumentCaptor<String[]> capturedArgs;
109 
110     @Captor
111     private ArgumentCaptor<ForkedBooter> capturedBooter;
112 
113     @Test
114     public void shouldCheckNewPingMechanism() throws Exception
115     {
116         boolean canUse = invokeMethod( ForkedBooter.class, "canUseNewPingMechanism", (PpidChecker) null );
117         assertThat( canUse ).isFalse();
118 
119         when( pluginProcessChecker.canUse() ).thenReturn( false );
120         canUse = invokeMethod( ForkedBooter.class, "canUseNewPingMechanism", pluginProcessChecker );
121         assertThat( canUse ).isFalse();
122 
123         when( pluginProcessChecker.canUse() ).thenReturn( true );
124         canUse = invokeMethod( ForkedBooter.class, "canUseNewPingMechanism", pluginProcessChecker );
125         assertThat( canUse ).isTrue();
126     }
127 
128     @Test
129     public void testMain() throws Exception
130     {
131         mockStatic( ForkedBooter.class );
132 
133         doCallRealMethod()
134                 .when( ForkedBooter.class, "run", capturedBooter.capture(), capturedArgs.capture() );
135 
136         String[] args = new String[]{ "/", "dump", "surefire.properties", "surefire-effective.properties" };
137         invokeMethod( ForkedBooter.class, "run", booter, args );
138 
139         assertThat( capturedBooter.getAllValues() )
140                 .hasSize( 1 )
141                 .contains( booter );
142 
143         assertThat( capturedArgs.getAllValues() )
144                 .hasSize( 1 );
145         assertThat( capturedArgs.getAllValues().get( 0 )[0] )
146                 .isEqualTo( "/" );
147         assertThat( capturedArgs.getAllValues().get( 0 )[1] )
148                 .isEqualTo( "dump" );
149         assertThat( capturedArgs.getAllValues().get( 0 )[2] )
150                 .isEqualTo( "surefire.properties" );
151         assertThat( capturedArgs.getAllValues().get( 0 )[3] )
152                 .isEqualTo( "surefire-effective.properties" );
153 
154         verifyPrivate( booter, times( 1 ) )
155                 .invoke( "setupBooter", same( args[0] ), same( args[1] ), same( args[2] ), same( args[3] ) );
156 
157         verifyPrivate( booter, times( 1 ) )
158                 .invoke( "execute" );
159 
160         verifyNoMoreInteractions( booter );
161     }
162 
163     @Test
164     public void testMainWithError() throws Exception
165     {
166         mockStatic( ForkedBooter.class );
167 
168         doCallRealMethod()
169                 .when( ForkedBooter.class, "run", any( ForkedBooter.class ), any( String[].class ) );
170 
171         doThrow( new RuntimeException( "dummy exception" ) )
172                 .when( booter, "execute" );
173 
174         doNothing()
175                 .when( booter, "setupBooter",
176                         any( String.class ), any( String.class ), any( String.class ), any( String.class ) );
177 
178         setInternalState( booter, "eventChannel", eventChannel );
179 
180         String[] args = new String[]{ "/", "dump", "surefire.properties", "surefire-effective.properties" };
181         invokeMethod( ForkedBooter.class, "run", booter, args );
182 
183         verifyPrivate( booter, times( 1 ) )
184                 .invoke( "setupBooter", same( args[0] ), same( args[1] ), same( args[2] ), same( args[3] ) );
185 
186         verifyPrivate( booter, times( 1 ) )
187                 .invoke( "execute" );
188 
189         verify( eventChannel, times( 1 ) )
190                 .consoleErrorLog( capturedStackTraceWriter.capture(), capturedBoolean.capture() );
191         assertThat( capturedStackTraceWriter.getValue() )
192                 .isNotNull();
193         assertThat( capturedStackTraceWriter.getValue().smartTrimmedStackTrace() )
194                 .isEqualTo( "test subsystem#no method RuntimeException dummy exception" );
195         assertThat( capturedStackTraceWriter.getValue().getThrowable().getTarget() )
196                 .isNotNull()
197                 .isInstanceOf( RuntimeException.class );
198         assertThat( capturedStackTraceWriter.getValue().getThrowable().getTarget().getMessage() )
199                 .isEqualTo( "dummy exception" );
200 
201         verifyPrivate( booter, times( 1 ) )
202                 .invoke( "cancelPingScheduler" );
203 
204         verifyPrivate( booter, times( 1 ) )
205                 .invoke( "exit1" );
206 
207         verifyNoMoreInteractions( booter );
208     }
209 
210     @Test
211     public void shouldNotCloseChannelProcessorFactory() throws Exception
212     {
213         setInternalState( booter, "channelProcessorFactory", (MasterProcessChannelProcessorFactory) null );
214 
215         doCallRealMethod()
216             .when( booter, "closeForkChannel" );
217 
218         invokeMethod( booter, "closeForkChannel" );
219 
220         verifyZeroInteractions( channelProcessorFactory );
221     }
222 
223     @Test
224     public void shouldCloseChannelProcessorFactory() throws Exception
225     {
226         setInternalState( booter, "channelProcessorFactory", channelProcessorFactory );
227 
228         doCallRealMethod()
229             .when( booter, "closeForkChannel" );
230 
231         invokeMethod( booter, "closeForkChannel" );
232 
233         verify( channelProcessorFactory, times( 1 ) )
234             .close();
235         verifyNoMoreInteractions( channelProcessorFactory );
236     }
237 
238     @Test
239     public void shouldFailOnCloseChannelProcessorFactory() throws Exception
240     {
241         setInternalState( booter, "channelProcessorFactory", channelProcessorFactory );
242 
243         doThrow( new IOException() )
244             .when( channelProcessorFactory )
245             .close();
246 
247         doCallRealMethod()
248             .when( booter, "closeForkChannel" );
249 
250         invokeMethod( booter, "closeForkChannel" );
251 
252         verify( channelProcessorFactory, times( 1 ) )
253             .close();
254         verifyNoMoreInteractions( channelProcessorFactory );
255     }
256 
257     @Test
258     public void shouldLookupLegacyDecoderFactory() throws Exception
259     {
260         mockStatic( ForkedBooter.class );
261 
262         doCallRealMethod()
263             .when( ForkedBooter.class, "lookupDecoderFactory", anyString() );
264 
265         try ( final MasterProcessChannelProcessorFactory factory =
266                   invokeMethod( ForkedBooter.class, "lookupDecoderFactory", "pipe://3" ) )
267         {
268             assertThat( factory ).isInstanceOf( LegacyMasterProcessChannelProcessorFactory.class );
269 
270             assertThat( factory.canUse( "pipe://3" ) ).isTrue();
271 
272             assertThat( factory.canUse( "-- whatever --" ) ).isFalse();
273 
274             errorCollector.checkThrows( MalformedURLException.class, new ThrowingRunnable()
275             {
276                 @Override
277                 public void run() throws Throwable
278                 {
279                     factory.connect( "tcp://localhost:123" );
280                     fail();
281                 }
282             } );
283 
284             factory.connect( "pipe://3" );
285 
286             MasterProcessChannelDecoder decoder = factory.createDecoder();
287             assertThat( decoder ).isInstanceOf( LegacyMasterProcessChannelDecoder.class );
288             MasterProcessChannelEncoder encoder = factory.createEncoder();
289             assertThat( encoder ).isInstanceOf( LegacyMasterProcessChannelEncoder.class );
290         }
291     }
292 
293     @Test
294     public void shouldLookupSurefireDecoderFactory() throws Exception
295     {
296         mockStatic( ForkedBooter.class );
297 
298         doCallRealMethod()
299             .when( ForkedBooter.class, "lookupDecoderFactory", anyString() );
300 
301         try ( ServerSocketChannel server = ServerSocketChannel.open() )
302         {
303             if ( server.supportedOptions().contains( SO_REUSEADDR ) )
304             {
305                 server.setOption( SO_REUSEADDR, true );
306             }
307 
308             if ( server.supportedOptions().contains( TCP_NODELAY ) )
309             {
310                 server.setOption( TCP_NODELAY, true );
311             }
312 
313             if ( server.supportedOptions().contains( SO_KEEPALIVE ) )
314             {
315                 server.setOption( SO_KEEPALIVE, true );
316             }
317 
318             server.bind( new InetSocketAddress( 0 ) );
319             int serverPort = ( (InetSocketAddress) server.getLocalAddress() ).getPort();
320 
321             try ( MasterProcessChannelProcessorFactory factory =
322                      invokeMethod( ForkedBooter.class, "lookupDecoderFactory", "tcp://127.0.0.1:" + serverPort ) )
323             {
324                 assertThat( factory )
325                     .isInstanceOf( SurefireMasterProcessChannelProcessorFactory.class );
326 
327                 assertThat( factory.canUse( "tcp://127.0.0.1:" + serverPort ) )
328                     .isTrue();
329 
330                 assertThat( factory.canUse( "-- whatever --" ) )
331                     .isFalse();
332 
333                 errorCollector.checkThrows( MalformedURLException.class, new ThrowingRunnable()
334                 {
335                     @Override
336                     public void run() throws Throwable
337                     {
338                         factory.connect( "pipe://1" );
339                         fail();
340                     }
341                 } );
342 
343                 errorCollector.checkThrows( IOException.class, new ThrowingRunnable()
344                 {
345                     @Override
346                     public void run() throws Throwable
347                     {
348                         factory.connect( "tcp://localhost:123\u0000\u0000\u0000" );
349                         fail();
350                     }
351                 } );
352 
353                 factory.connect( "tcp://127.0.0.1:" + serverPort );
354 
355                 MasterProcessChannelDecoder decoder = factory.createDecoder();
356                 assertThat( decoder )
357                     .isInstanceOf( LegacyMasterProcessChannelDecoder.class );
358                 MasterProcessChannelEncoder encoder = factory.createEncoder();
359                 assertThat( encoder )
360                     .isInstanceOf( LegacyMasterProcessChannelEncoder.class );
361             }
362         }
363     }
364 
365     @Test
366     public void testFlushEventChannelOnExit() throws Exception
367     {
368         mockStatic( ShutdownHookUtils.class );
369 
370         final MasterProcessChannelEncoder eventChannel = mock( MasterProcessChannelEncoder.class );
371         ForkedBooter booter = new ForkedBooter();
372         setInternalState( booter, "eventChannel", eventChannel );
373 
374         doAnswer( new Answer<Object>()
375         {
376             @Override
377             public Object answer( InvocationOnMock invocation )
378             {
379                 Thread t = invocation.getArgument( 0 );
380                 assertThat( t.isDaemon() ).isTrue();
381                 t.run();
382                 verify( eventChannel, times( 1 ) ).onJvmExit();
383                 return null;
384             }
385         } ).when( ShutdownHookUtils.class, "addShutDownHook", any( Thread.class ) );
386         invokeMethod( booter, "flushEventChannelOnExit" );
387     }
388 }