1 package org.apache.maven.surefire.booter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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 }