1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.booter.spi;
20
21 import javax.annotation.Nonnull;
22
23 import java.io.ByteArrayInputStream;
24 import java.io.EOFException;
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.Random;
29 import java.util.concurrent.ConcurrentLinkedQueue;
30
31 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
32 import org.apache.maven.surefire.api.booter.Command;
33 import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
34 import org.apache.maven.surefire.api.booter.Shutdown;
35 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
36 import org.apache.maven.surefire.booter.ForkedNodeArg;
37 import org.junit.After;
38 import org.junit.Before;
39 import org.junit.Rule;
40 import org.junit.Test;
41 import org.junit.rules.TemporaryFolder;
42
43 import static java.nio.channels.Channels.newChannel;
44 import static java.nio.charset.StandardCharsets.UTF_8;
45 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.BYE_ACK;
46 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.NOOP;
47 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.RUN_CLASS;
48 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SHUTDOWN;
49 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.SKIP_SINCE_NEXT_TEST;
50 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.TEST_SET_FINISHED;
51 import static org.apache.maven.surefire.api.booter.Shutdown.DEFAULT;
52 import static org.apache.maven.surefire.api.booter.Shutdown.EXIT;
53 import static org.apache.maven.surefire.api.booter.Shutdown.KILL;
54 import static org.assertj.core.api.Assertions.assertThat;
55 import static org.junit.Assert.assertEquals;
56 import static org.junit.Assert.assertNull;
57 import static org.junit.Assert.fail;
58
59
60
61
62 @SuppressWarnings("checkstyle:magicnumber")
63 public class CommandChannelDecoderTest {
64 private static final Random RND = new Random();
65
66 @Rule
67 public final TemporaryFolder tempFolder = new TemporaryFolder();
68
69 @Before
70 public void initTmpFile() {
71 File reportsDir = tempFolder.getRoot();
72 String dumpFileName = "surefire-" + RND.nextLong();
73 DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
74 }
75
76 @After
77 public void deleteTmpFiles() {
78 tempFolder.delete();
79 }
80
81 @Test
82 public void testDecoderRunClass() throws IOException {
83 assertEquals(String.class, RUN_CLASS.getDataType());
84 byte[] encoded = new StringBuilder(512)
85 .append(":maven-surefire-command:")
86 .append((char) 13)
87 .append(":run-testclass:")
88 .append((char) 5)
89 .append(":UTF-8:")
90 .append((char) 0)
91 .append((char) 0)
92 .append((char) 0)
93 .append((char) 8)
94 .append(":")
95 .append("pkg.Test")
96 .append(":")
97 .toString()
98 .getBytes(UTF_8);
99 InputStream is = new ByteArrayInputStream(encoded);
100 ForkNodeArguments args = new ForkedNodeArg(1, false);
101 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
102 Command command = decoder.decode();
103 assertThat(command.getCommandType()).isSameAs(RUN_CLASS);
104 assertThat(command.getData()).isEqualTo("pkg.Test");
105 }
106
107 @Test
108 public void testDecoderTestsetFinished() throws IOException {
109 Command command = Command.TEST_SET_FINISHED;
110 assertThat(command.getCommandType()).isSameAs(TEST_SET_FINISHED);
111 assertEquals(Void.class, TEST_SET_FINISHED.getDataType());
112 byte[] encoded = ":maven-surefire-command:\u0010:testset-finished:".getBytes();
113 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
114 ForkNodeArguments args = new ForkedNodeArg(1, false);
115 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
116 command = decoder.decode();
117 assertThat(command.getCommandType()).isSameAs(TEST_SET_FINISHED);
118 assertNull(command.getData());
119 }
120
121 @Test
122 public void testDecoderSkipSinceNextTest() throws IOException {
123 Command command = Command.SKIP_SINCE_NEXT_TEST;
124 assertThat(command.getCommandType()).isSameAs(SKIP_SINCE_NEXT_TEST);
125 assertEquals(Void.class, SKIP_SINCE_NEXT_TEST.getDataType());
126 byte[] encoded = ":maven-surefire-command:\u0014:skip-since-next-test:".getBytes();
127 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
128 ForkNodeArguments args = new ForkedNodeArg(1, false);
129 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
130 command = decoder.decode();
131 assertThat(command.getCommandType()).isSameAs(SKIP_SINCE_NEXT_TEST);
132 assertNull(command.getData());
133 }
134
135 @Test
136 public void testDecoderShutdownWithExit() throws IOException {
137 Shutdown shutdownType = EXIT;
138 assertEquals(String.class, SHUTDOWN.getDataType());
139 byte[] encoded = (":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
140 + shutdownType.getParam() + ":")
141 .getBytes();
142 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
143 ForkNodeArguments args = new ForkedNodeArg(1, false);
144 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
145 Command command = decoder.decode();
146 assertThat(command.getCommandType()).isSameAs(SHUTDOWN);
147 assertThat(command.getData()).isEqualTo(shutdownType.name());
148 }
149
150 @Test
151 public void testDecoderShutdownWithKill() throws IOException {
152 Shutdown shutdownType = KILL;
153 assertEquals(String.class, SHUTDOWN.getDataType());
154 byte[] encoded = (":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0004:"
155 + shutdownType.getParam() + ":")
156 .getBytes();
157 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
158 ForkNodeArguments args = new ForkedNodeArg(1, false);
159 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
160 Command command = decoder.decode();
161 assertThat(command.getCommandType()).isSameAs(SHUTDOWN);
162 assertThat(command.getData()).isEqualTo(shutdownType.name());
163 }
164
165 @Test
166 public void testDecoderShutdownWithDefault() throws IOException {
167 Shutdown shutdownType = DEFAULT;
168 assertEquals(String.class, SHUTDOWN.getDataType());
169 byte[] encoded = (":maven-surefire-command:\u0008:shutdown:\u0005:UTF-8:\u0000\u0000\u0000\u0007:"
170 + shutdownType.getParam() + ":")
171 .getBytes();
172 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
173 ForkNodeArguments args = new ForkedNodeArg(1, false);
174 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
175 Command command = decoder.decode();
176 assertThat(command.getCommandType()).isSameAs(SHUTDOWN);
177 assertThat(command.getData()).isEqualTo(shutdownType.name());
178 }
179
180 @Test
181 public void testDecoderNoop() throws IOException {
182 assertThat(NOOP).isSameAs(Command.NOOP.getCommandType());
183 assertEquals(Void.class, NOOP.getDataType());
184 byte[] encoded = ":maven-surefire-command:\u0004:noop:".getBytes();
185 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
186 ForkNodeArguments args = new ForkedNodeArg(1, false);
187 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
188 Command command = decoder.decode();
189 assertThat(command.getCommandType()).isSameAs(NOOP);
190 assertNull(command.getData());
191 }
192
193 @Test
194 public void shouldIgnoreDamagedStream() throws IOException {
195 assertThat(BYE_ACK).isSameAs(Command.BYE_ACK.getCommandType());
196 assertEquals(Void.class, BYE_ACK.getDataType());
197 byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
198 byte[] streamContent = ("<something>" + new String(encoded) + "<damaged>").getBytes();
199 ByteArrayInputStream is = new ByteArrayInputStream(streamContent);
200 ForkNodeArguments args = new ForkedNodeArg(1, false);
201 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
202 Command command = decoder.decode();
203 assertThat(command.getCommandType()).isSameAs(BYE_ACK);
204 assertNull(command.getData());
205 }
206
207 @Test
208 public void shouldIgnoreDamagedHeader() throws IOException {
209 assertThat(BYE_ACK).isSameAs(Command.BYE_ACK.getCommandType());
210 assertEquals(Void.class, BYE_ACK.getDataType());
211 byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
212 byte[] streamContent = (":<damaged>:" + new String(encoded)).getBytes();
213 ByteArrayInputStream is = new ByteArrayInputStream(streamContent);
214 ForkNodeArguments args = new ForkedNodeArg(1, false);
215 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
216 Command command = decoder.decode();
217 assertThat(command.getCommandType()).isSameAs(BYE_ACK);
218 assertNull(command.getData());
219 }
220
221 @Test
222 public void testDecoderByeAck() throws IOException {
223 assertThat(BYE_ACK).isSameAs(Command.BYE_ACK.getCommandType());
224 assertEquals(Void.class, BYE_ACK.getDataType());
225 byte[] encoded = ":maven-surefire-command:\u0007:bye-ack:".getBytes();
226 ByteArrayInputStream is = new ByteArrayInputStream(encoded);
227 ForkNodeArguments args = new ForkedNodeArg(1, false);
228 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
229 Command command = decoder.decode();
230 assertThat(command.getCommandType()).isSameAs(BYE_ACK);
231 assertNull(command.getData());
232 }
233
234 @Test
235 public void shouldDecodeTwoCommands() throws IOException {
236 String cmd = ":maven-surefire-command:\u0007:bye-ack:\r\n:maven-surefire-command:\u0007:bye-ack:";
237 InputStream is = new ByteArrayInputStream(cmd.getBytes());
238 ForkNodeArguments args = new ForkedNodeArg(1, false);
239 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
240
241 Command command = decoder.decode();
242 assertThat(command.getCommandType()).isEqualTo(BYE_ACK);
243 assertThat(command.getData()).isNull();
244
245 command = decoder.decode();
246 assertThat(command.getCommandType()).isEqualTo(BYE_ACK);
247 assertThat(command.getData()).isNull();
248
249 decoder.close();
250 }
251
252 @Test(expected = EOFException.class)
253 public void testIncompleteCommand() throws IOException {
254
255 ByteArrayInputStream is = new ByteArrayInputStream(":maven-surefire-command:".getBytes());
256 ForkNodeArguments args = new ForkedNodeArg(1, false);
257 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
258 decoder.decode();
259 fail();
260 }
261
262 @Test(expected = EOFException.class)
263 public void testIncompleteCommandStart() throws IOException {
264
265 ByteArrayInputStream is = new ByteArrayInputStream(new byte[] {':', '\r'});
266 ForkNodeArguments args = new ForkedNodeArg(1, false);
267 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
268 decoder.decode();
269 fail();
270 }
271
272 @Test(expected = EOFException.class)
273 public void shouldNotDecodeCorruptedCommand() throws IOException {
274 String cmd = ":maven-surefire-command:\u0007:bye-ack ::maven-surefire-command:";
275 InputStream is = new ByteArrayInputStream(cmd.getBytes());
276 ForkNodeArguments args = new ForkedNodeArg(1, false);
277 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
278
279 decoder.decode();
280 }
281
282 @Test
283 public void shouldSkipCorruptedCommand() throws IOException {
284 String cmd = ":maven-surefire-command:\0007:bye-ack\r\n::maven-surefire-command:\u0004:noop:";
285 InputStream is = new ByteArrayInputStream(cmd.getBytes());
286 ForkNodeArguments args = new ForkedNodeArg(1, false);
287 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(is), args);
288
289 Command command = decoder.decode();
290 assertThat(command.getCommandType()).isSameAs(NOOP);
291 assertNull(command.getData());
292 }
293
294 @Test
295 public void testBinaryCommandStream() throws Exception {
296 InputStream commands = getClass().getResourceAsStream("/binary-commands/75171711-encoder.bin");
297 assertThat(commands).isNotNull();
298 ConsoleLoggerMock logger = new ConsoleLoggerMock(true, true, true, true);
299 ForkNodeArguments args = new ForkNodeArgumentsMock(logger, new File(""));
300 CommandChannelDecoder decoder = new CommandChannelDecoder(newChannel(commands), args);
301
302 Command command = decoder.decode();
303 assertThat(command).isNotNull();
304 assertThat(command.getCommandType()).isEqualTo(NOOP);
305 assertThat(command.getData()).isNull();
306
307 command = decoder.decode();
308 assertThat(command).isNotNull();
309 assertThat(command.getCommandType()).isEqualTo(RUN_CLASS);
310 assertThat(command.getData()).isEqualTo("pkg.ATest");
311
312 for (int i = 0; i < 24; i++) {
313 command = decoder.decode();
314 assertThat(command).isNotNull();
315 assertThat(command.getCommandType()).isEqualTo(NOOP);
316 assertThat(command.getData()).isNull();
317 }
318 }
319
320
321
322
323 private static class ForkNodeArgumentsMock implements ForkNodeArguments {
324 private final ConcurrentLinkedQueue<String> dumpStreamText = new ConcurrentLinkedQueue<>();
325 private final ConcurrentLinkedQueue<String> logWarningAtEnd = new ConcurrentLinkedQueue<>();
326 private final ConsoleLogger logger;
327 private final File dumpStreamTextFile;
328
329 ForkNodeArgumentsMock(ConsoleLogger logger, File dumpStreamTextFile) {
330 this.logger = logger;
331 this.dumpStreamTextFile = dumpStreamTextFile;
332 }
333
334 @Nonnull
335 @Override
336 public String getSessionId() {
337 throw new UnsupportedOperationException();
338 }
339
340 @Override
341 public int getForkChannelId() {
342 return 0;
343 }
344
345 @Nonnull
346 @Override
347 public File dumpStreamText(@Nonnull String text) {
348 dumpStreamText.add(text);
349 return dumpStreamTextFile;
350 }
351
352 @Nonnull
353 @Override
354 public File dumpStreamException(@Nonnull Throwable t) {
355 throw new UnsupportedOperationException();
356 }
357
358 @Override
359 public void logWarningAtEnd(@Nonnull String text) {
360 logWarningAtEnd.add(text);
361 }
362
363 @Nonnull
364 @Override
365 public ConsoleLogger getConsoleLogger() {
366 return logger;
367 }
368
369 @Nonnull
370 @Override
371 public Object getConsoleLock() {
372 return logger;
373 }
374
375 boolean isCalled() {
376 return !dumpStreamText.isEmpty() || !logWarningAtEnd.isEmpty();
377 }
378
379 @Override
380 public File getEventStreamBinaryFile() {
381 return null;
382 }
383
384 @Override
385 public File getCommandStreamBinaryFile() {
386 return null;
387 }
388 }
389
390
391
392
393 private static class ConsoleLoggerMock implements ConsoleLogger {
394 final ConcurrentLinkedQueue<String> debug = new ConcurrentLinkedQueue<>();
395 final ConcurrentLinkedQueue<String> info = new ConcurrentLinkedQueue<>();
396 final ConcurrentLinkedQueue<String> error = new ConcurrentLinkedQueue<>();
397 final boolean isDebug;
398 final boolean isInfo;
399 final boolean isWarning;
400 final boolean isError;
401 private volatile boolean called;
402 private volatile boolean isDebugEnabledCalled;
403 private volatile boolean isInfoEnabledCalled;
404
405 ConsoleLoggerMock(boolean isDebug, boolean isInfo, boolean isWarning, boolean isError) {
406 this.isDebug = isDebug;
407 this.isInfo = isInfo;
408 this.isWarning = isWarning;
409 this.isError = isError;
410 }
411
412 @Override
413 public synchronized boolean isDebugEnabled() {
414 isDebugEnabledCalled = true;
415 called = true;
416 return isDebug;
417 }
418
419 @Override
420 public synchronized void debug(String message) {
421 debug.add(message);
422 called = true;
423 }
424
425 @Override
426 public synchronized boolean isInfoEnabled() {
427 isInfoEnabledCalled = true;
428 called = true;
429 return isInfo;
430 }
431
432 @Override
433 public synchronized void info(String message) {
434 info.add(message);
435 called = true;
436 }
437
438 @Override
439 public synchronized boolean isWarnEnabled() {
440 called = true;
441 return isWarning;
442 }
443
444 @Override
445 public synchronized void warning(String message) {
446 called = true;
447 }
448
449 @Override
450 public synchronized boolean isErrorEnabled() {
451 called = true;
452 return isError;
453 }
454
455 @Override
456 public synchronized void error(String message) {
457 error.add(message);
458 called = true;
459 }
460
461 @Override
462 public synchronized void error(String message, Throwable t) {
463 called = true;
464 }
465
466 @Override
467 public synchronized void error(Throwable t) {
468 called = true;
469 }
470
471 synchronized boolean isCalled() {
472 return called;
473 }
474 }
475 }