1 package org.apache.maven.surefire.extensions;
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.plugin.surefire.booterclient.MockReporter;
23 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream;
24 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder;
25 import org.apache.maven.plugin.surefire.extensions.SurefireForkNodeFactory;
26 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
27 import org.apache.maven.surefire.api.event.ControlByeEvent;
28 import org.apache.maven.surefire.api.event.Event;
29 import org.apache.maven.surefire.extensions.util.CountdownCloseable;
30 import org.junit.Test;
31
32 import javax.annotation.Nonnull;
33 import java.io.Closeable;
34 import java.io.File;
35 import java.io.IOException;
36 import java.net.InetAddress;
37 import java.net.Socket;
38 import java.net.URI;
39 import java.nio.ByteBuffer;
40 import java.nio.channels.ReadableByteChannel;
41 import java.util.Queue;
42 import java.util.concurrent.ConcurrentLinkedQueue;
43 import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.atomic.AtomicBoolean;
45
46 import static java.nio.charset.StandardCharsets.US_ASCII;
47 import static java.util.concurrent.TimeUnit.MILLISECONDS;
48 import static org.fest.assertions.Assertions.assertThat;
49 import static org.mockito.ArgumentMatchers.any;
50 import static org.mockito.Mockito.mock;
51 import static org.mockito.Mockito.when;
52
53
54
55
56 public class ForkChannelTest
57 {
58 private static final long TESTCASE_TIMEOUT = 30_000L;
59
60 private final AtomicBoolean hasError = new AtomicBoolean();
61
62 @Test( timeout = TESTCASE_TIMEOUT )
63 public void shouldRequestReplyMessagesViaTCP() throws Exception
64 {
65 ForkNodeArguments forkNodeArguments = new ForkNodeArguments()
66 {
67 @Override
68 public int getForkChannelId()
69 {
70 return 1;
71 }
72
73 @Override
74 @Nonnull
75 public File dumpStreamText( @Nonnull String text )
76 {
77 return new File( "" );
78 }
79
80 @Override
81 public void logWarningAtEnd( @Nonnull String text )
82 {
83 }
84
85 @Override
86 @Nonnull
87 public ConsoleLogger getConsoleLogger()
88 {
89 return new MockReporter();
90 }
91 };
92
93 ForkNodeFactory factory = new SurefireForkNodeFactory();
94 try ( ForkChannel channel = factory.createForkChannel( forkNodeArguments ) )
95 {
96 assertThat( channel.getArguments().getForkChannelId() )
97 .isEqualTo( 1 );
98
99 assertThat( channel.getCountdownCloseablePermits() )
100 .isEqualTo( 3 );
101
102 String localHost = InetAddress.getLocalHost().getHostAddress();
103 assertThat( channel.getForkNodeConnectionString() )
104 .startsWith( "tcp://" + localHost + ":" )
105 .isNotEqualTo( "tcp://" + localHost + ":" );
106
107 URI uri = new URI( channel.getForkNodeConnectionString() );
108
109 assertThat( uri.getPort() )
110 .isPositive();
111
112 final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder();
113 TestLessInputStream commandReader = builder.build();
114 final CountDownLatch isCloseableCalled = new CountDownLatch( 1 );
115 Closeable closeable = new Closeable()
116 {
117 @Override
118 public void close()
119 {
120 isCloseableCalled.countDown();
121 }
122 };
123 CountdownCloseable cc = new CountdownCloseable( closeable, 2 );
124 Consumer consumer = new Consumer();
125
126 Client client = new Client( uri.getPort() );
127 client.start();
128
129 channel.connectToClient();
130 channel.bindCommandReader( commandReader, null ).start();
131 ReadableByteChannel stdOut = mock( ReadableByteChannel.class );
132 when( stdOut.read( any( ByteBuffer.class ) ) ).thenReturn( -1 );
133 channel.bindEventHandler( consumer, cc, stdOut ).start();
134
135 commandReader.noop();
136
137 client.join( TESTCASE_TIMEOUT );
138
139 assertThat( hasError.get() )
140 .isFalse();
141
142 assertThat( isCloseableCalled.await( TESTCASE_TIMEOUT, MILLISECONDS ) )
143 .isTrue();
144
145 assertThat( consumer.lines )
146 .hasSize( 1 );
147
148 assertThat( consumer.lines.element() )
149 .isInstanceOf( ControlByeEvent.class );
150 }
151 }
152
153 private static class Consumer implements EventHandler<Event>
154 {
155 final Queue<Event> lines = new ConcurrentLinkedQueue<>();
156
157 @Override
158 public void handleEvent( @Nonnull Event s )
159 {
160 lines.add( s );
161 }
162 }
163
164 private final class Client extends Thread
165 {
166 private final int port;
167
168 private Client( int port )
169 {
170 this.port = port;
171 }
172
173 @Override
174 public void run()
175 {
176 try ( Socket socket = new Socket( InetAddress.getLocalHost().getHostAddress(), port ) )
177 {
178 byte[] data = new byte[128];
179 int readLength = socket.getInputStream().read( data );
180 String token = new String( data, 0, readLength, US_ASCII );
181 assertThat( token ).isEqualTo( ":maven-surefire-command:noop:" );
182 socket.getOutputStream().write( ":maven-surefire-event:bye:".getBytes( US_ASCII ) );
183 }
184 catch ( IOException e )
185 {
186 hasError.set( true );
187 e.printStackTrace();
188 throw new IllegalStateException( e );
189 }
190 catch ( RuntimeException e )
191 {
192 hasError.set( true );
193 e.printStackTrace();
194 throw e;
195 }
196 }
197 }
198 }