1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.api.stream;
20
21 import javax.annotation.Nonnull;
22
23 import java.io.EOFException;
24 import java.io.File;
25 import java.math.BigInteger;
26 import java.nio.Buffer;
27 import java.nio.ByteBuffer;
28 import java.nio.CharBuffer;
29 import java.nio.channels.ReadableByteChannel;
30 import java.nio.charset.CharsetDecoder;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.concurrent.TimeUnit;
34
35 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
36 import org.apache.maven.surefire.api.booter.Constants;
37 import org.apache.maven.surefire.api.booter.ForkedProcessEventType;
38 import org.apache.maven.surefire.api.event.Event;
39 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
40 import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.MalformedFrameException;
41 import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Memento;
42 import org.apache.maven.surefire.api.stream.AbstractStreamDecoder.Segment;
43 import org.junit.BeforeClass;
44 import org.junit.Test;
45
46 import static java.lang.Math.min;
47 import static java.lang.System.arraycopy;
48 import static java.nio.charset.CodingErrorAction.REPLACE;
49 import static java.nio.charset.StandardCharsets.ISO_8859_1;
50 import static java.nio.charset.StandardCharsets.US_ASCII;
51 import static java.nio.charset.StandardCharsets.UTF_8;
52 import static java.util.Collections.emptyMap;
53 import static java.util.Collections.singletonMap;
54 import static org.apache.maven.surefire.api.booter.Constants.DEFAULT_STREAM_ENCODING;
55 import static org.apache.maven.surefire.api.booter.ForkedProcessEventType.BOOTERCODE_STDOUT;
56 import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
57 import static org.assertj.core.api.Assertions.assertThat;
58 import static org.powermock.reflect.Whitebox.invokeMethod;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 @SuppressWarnings("checkstyle:magicnumber")
123 public class AbstractStreamDecoderTest {
124 private static final Map<Segment, ForkedProcessEventType> EVENTS = new HashMap<>();
125
126 private static final String PATTERN1 =
127 "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
128
129 private static final String PATTERN2 = "€ab©c";
130
131 private static final byte[] PATTERN2_BYTES =
132 new byte[] {(byte) -30, (byte) -126, (byte) -84, 'a', 'b', (byte) 0xc2, (byte) 0xa9, 'c'};
133
134 @BeforeClass
135 public static void setup() {
136 for (ForkedProcessEventType event : ForkedProcessEventType.values()) {
137 byte[] array = event.getOpcodeBinary();
138 EVENTS.put(new Segment(array, 0, array.length), event);
139 }
140 }
141
142 @Test
143 public void shouldDecodeHappyCase() throws Exception {
144 CharsetDecoder decoder = UTF_8.newDecoder().onMalformedInput(REPLACE).onUnmappableCharacter(REPLACE);
145 ByteBuffer input = ByteBuffer.allocate(1024);
146 ((Buffer) input.put(PATTERN2_BYTES)).flip();
147 int bytesToDecode = PATTERN2_BYTES.length;
148 Buffer output = CharBuffer.allocate(1024);
149 int readBytes = invokeMethod(
150 AbstractStreamDecoder.class, "decodeString", decoder, input, output, bytesToDecode, true, 0);
151
152 assertThat(readBytes).isEqualTo(bytesToDecode);
153
154 assertThat(output.flip().toString()).isEqualTo(PATTERN2);
155 }
156
157 @Test
158 public void shouldDecodeShifted() throws Exception {
159 CharsetDecoder decoder = UTF_8.newDecoder().onMalformedInput(REPLACE).onUnmappableCharacter(REPLACE);
160 ByteBuffer input = ByteBuffer.allocate(1024);
161 ((Buffer) input.put(PATTERN1.getBytes(UTF_8))
162 .put(90, (byte) 'A')
163 .put(91, (byte) 'B')
164 .put(92, (byte) 'C'))
165 .position(90);
166 Buffer output = CharBuffer.allocate(1024);
167 int readBytes = invokeMethod(AbstractStreamDecoder.class, "decodeString", decoder, input, output, 2, true, 0);
168
169 assertThat(readBytes).isEqualTo(2);
170
171 assertThat(output.flip().toString()).isEqualTo("AB");
172 }
173
174 @Test(expected = IllegalArgumentException.class)
175 public void shouldNotDecode() throws Exception {
176 CharsetDecoder decoder = UTF_8.newDecoder();
177 ByteBuffer input = ByteBuffer.allocate(100);
178 int bytesToDecode = 101;
179 CharBuffer output = CharBuffer.allocate(1000);
180 invokeMethod(AbstractStreamDecoder.class, "decodeString", decoder, input, output, bytesToDecode, true, 0);
181 }
182
183 @Test
184 public void shouldReadInt() throws Exception {
185 Channel channel = new Channel(new byte[] {0x01, 0x02, 0x03, 0x04, ':'}, 1);
186
187 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
188
189 Memento memento = thread.new Memento();
190
191 assertThat(thread.readInt(memento)).isEqualTo(new BigInteger(new byte[] {0x01, 0x02, 0x03, 0x04}).intValue());
192 }
193
194 @Test
195 public void shouldReadInteger() throws Exception {
196 Channel channel = new Channel(new byte[] {(byte) 0xff, 0x01, 0x02, 0x03, 0x04, ':'}, 1);
197
198 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
199
200 Memento memento = thread.new Memento();
201 assertThat(thread.readInteger(memento))
202 .isEqualTo(new BigInteger(new byte[] {0x01, 0x02, 0x03, 0x04}).intValue());
203 }
204
205 @Test
206 public void shouldReadNullInteger() throws Exception {
207 Channel channel = new Channel(new byte[] {(byte) 0x00, ':'}, 1);
208
209 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
210
211 Memento memento = thread.new Memento();
212 assertThat(thread.readInteger(memento)).isNull();
213 }
214
215 @Test(expected = EOFException.class)
216 public void shouldNotReadString() throws Exception {
217 Channel channel = new Channel(PATTERN1.getBytes(), PATTERN1.length());
218 channel.read(ByteBuffer.allocate(100));
219
220 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
221
222 Memento memento = thread.new Memento();
223 invokeMethod(thread, "readString", memento, 10);
224 }
225
226 @Test
227 public void shouldReadString() throws Exception {
228 Channel channel = new Channel(PATTERN1.getBytes(), PATTERN1.length());
229
230 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
231
232 Memento memento = thread.new Memento();
233 String s = invokeMethod(thread, "readString", memento, 10);
234 assertThat(s).isEqualTo("0123456789");
235 }
236
237 @Test
238 public void shouldReadStringOverflowOnNewLine() throws Exception {
239 StringBuilder s = new StringBuilder(1025);
240 for (int i = 0; i < 10; i++) {
241 s.append(PATTERN1);
242 }
243 s.append(PATTERN1, 0, 23);
244 s.append("\u00FA\n");
245
246 Channel channel = new Channel(s.toString().getBytes(UTF_8), s.length());
247
248 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
249
250 Memento memento = thread.new Memento();
251
252 assertThat((String) invokeMethod(thread, "readString", memento, 1026)).isEqualTo(s.toString());
253
254 assertThat(memento.getByteBuffer().remaining()).isEqualTo(0);
255 }
256
257 @Test
258 public void shouldReadStringOverflowOn4BytesEncodedSymbol() throws Exception {
259 StringBuilder s = new StringBuilder(1025);
260 for (int i = 0; i < 10; i++) {
261 s.append(PATTERN1);
262 }
263 s.append(PATTERN1, 0, 23);
264 s.append("\uD83D\uDE35");
265
266 Channel channel = new Channel(s.toString().getBytes(UTF_8), s.length());
267
268 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
269
270 Memento memento = thread.new Memento();
271
272 assertThat((String) invokeMethod(thread, "readString", memento, 1027)).isEqualTo(s.toString());
273
274 assertThat(memento.getByteBuffer().remaining()).isEqualTo(0);
275 }
276
277 @Test
278 public void shouldReadStringShiftedBuffer() throws Exception {
279 StringBuilder s = new StringBuilder(1100);
280 for (int i = 0; i < 11; i++) {
281 s.append(PATTERN1);
282 }
283
284 Channel channel = new Channel(s.toString().getBytes(UTF_8), s.length());
285
286 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
287
288 Memento memento = thread.new Memento();
289
290 ((Buffer) ((Buffer) memento.getByteBuffer()).limit(974)).position(974);
291 assertThat((String) invokeMethod(thread, "readString", memento, PATTERN1.length() + 3))
292 .isEqualTo(PATTERN1 + "012");
293 }
294
295 @Test
296 public void shouldReadStringShiftedInput() throws Exception {
297 StringBuilder s = new StringBuilder(1100);
298 for (int i = 0; i < 11; i++) {
299 s.append(PATTERN1);
300 }
301
302 Channel channel = new Channel(s.toString().getBytes(UTF_8), s.length());
303 channel.read(ByteBuffer.allocate(997));
304
305 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
306
307 Memento memento = thread.new Memento();
308 assertThat((String) invokeMethod(thread, "readString", memento, PATTERN1.length()))
309 .isEqualTo("789" + PATTERN1.substring(0, 97));
310 }
311
312 @Test
313 public void shouldReadMultipleStringsAndShiftedInput() throws Exception {
314 StringBuilder s = new StringBuilder(5000);
315
316 for (int i = 0; i < 50; i++) {
317 s.append(PATTERN1);
318 }
319
320 Channel channel = new Channel(s.toString().getBytes(UTF_8), s.length());
321 channel.read(ByteBuffer.allocate(1997));
322
323 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
324
325 Memento memento = thread.new Memento();
326
327 ((Buffer) memento.getByteBuffer()).limit(974).position(974);
328
329 StringBuilder expected = new StringBuilder("789");
330 for (int i = 0; i < 11; i++) {
331 expected.append(PATTERN1);
332 }
333 expected.setLength(1100);
334 assertThat((String) invokeMethod(thread, "readString", memento, 1100)).isEqualTo(expected.toString());
335 }
336
337 @Test
338 public void shouldDecode3BytesEncodedSymbol() throws Exception {
339 byte[] encodedSymbol = new byte[] {(byte) -30, (byte) -126, (byte) -84};
340 int countSymbols = 1024;
341 byte[] input = new byte[encodedSymbol.length * countSymbols];
342 for (int i = 0; i < countSymbols; i++) {
343 arraycopy(encodedSymbol, 0, input, encodedSymbol.length * i, encodedSymbol.length);
344 }
345
346 Channel channel = new Channel(input, 64 * 1024);
347 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
348 Memento memento = thread.new Memento();
349 String decodedOutput = invokeMethod(thread, "readString", memento, input.length);
350
351 assertThat(decodedOutput).isEqualTo(new String(input, 0, input.length, UTF_8));
352 }
353
354 @Test
355 public void shouldDecode100Bytes() throws Exception {
356 CharsetDecoder decoder =
357 DEFAULT_STREAM_ENCODING.newDecoder().onMalformedInput(REPLACE).onUnmappableCharacter(REPLACE);
358
359
360 ByteBuffer buffer = ByteBuffer.wrap(PATTERN1.getBytes(UTF_8));
361 CharBuffer chars = CharBuffer.allocate(100);
362
363 TimeUnit.SECONDS.sleep(2);
364 System.gc();
365 TimeUnit.SECONDS.sleep(5);
366 String s = null;
367 long l1 = System.currentTimeMillis();
368 for (int i = 0; i < 10_000_000; i++) {
369 decoder.reset().decode(buffer, chars, true);
370 s = ((Buffer) chars).flip().toString();
371 ((Buffer) buffer).clear();
372 ((Buffer) chars).clear();
373 }
374 long l2 = System.currentTimeMillis();
375 System.out.println("decoded 100 bytes within " + (l2 - l1) + " millis (10 million cycles)");
376 assertThat(s).isEqualTo(PATTERN1);
377 }
378
379 @Test
380 public void shouldReadEventType() throws Exception {
381 byte[] array = BOOTERCODE_STDOUT.getOpcodeBinary();
382 Map<Segment, ForkedProcessEventType> messageType =
383 singletonMap(new Segment(array, 0, array.length), BOOTERCODE_STDOUT);
384
385 byte[] stream = ":maven-surefire-event:\u000E:std-out-stream:".getBytes(UTF_8);
386 Channel channel = new Channel(stream, 1);
387 Mock thread = new Mock(channel, new MockForkNodeArguments(), messageType);
388
389 Memento memento = thread.new Memento();
390 memento.setCharset(UTF_8);
391
392 ForkedProcessEventType eventType = thread.readMessageType(memento);
393 assertThat(eventType).isEqualTo(BOOTERCODE_STDOUT);
394 }
395
396 @Test(expected = EOFException.class)
397 public void shouldEventTypeReachedEndOfStream() throws Exception {
398 byte[] stream = ":maven-surefire-event:\u000E:xxx".getBytes(UTF_8);
399 Channel channel = new Channel(stream, 1);
400 Mock thread = new Mock(channel, new MockForkNodeArguments(), EVENTS);
401
402 Memento memento = thread.new Memento();
403 memento.setCharset(UTF_8);
404 thread.readMessageType(memento);
405 }
406
407 @Test(expected = MalformedFrameException.class)
408 public void shouldEventTypeReachedMalformedHeader() throws Exception {
409 byte[] stream = ":xxxxx-xxxxxxxx-xxxxx:\u000E:xxx".getBytes(UTF_8);
410 Channel channel = new Channel(stream, 1);
411 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
412
413 Memento memento = thread.new Memento();
414 memento.setCharset(UTF_8);
415 thread.readMessageType(memento);
416 }
417
418 @Test
419 public void shouldReadEmptyString() throws Exception {
420 byte[] stream = "\u0000\u0000\u0000\u0000::".getBytes(UTF_8);
421 Channel channel = new Channel(stream, 1);
422 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
423
424 Memento memento = thread.new Memento();
425 memento.setCharset(UTF_8);
426
427 assertThat(thread.readString(memento)).isEmpty();
428 }
429
430 @Test
431 public void shouldReadNullString() throws Exception {
432 byte[] stream = "\u0000\u0000\u0000\u0001:\u0000:".getBytes(UTF_8);
433 Channel channel = new Channel(stream, 1);
434 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
435
436 Memento memento = thread.new Memento();
437 memento.setCharset(UTF_8);
438
439 assertThat(thread.readString(memento)).isNull();
440 }
441
442 @Test
443 public void shouldReadSingleCharString() throws Exception {
444 byte[] stream = "\u0000\u0000\u0000\u0001:A:".getBytes(UTF_8);
445 Channel channel = new Channel(stream, 1);
446 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
447
448 Memento memento = thread.new Memento();
449 memento.setCharset(UTF_8);
450
451 assertThat(thread.readString(memento)).isEqualTo("A");
452 }
453
454 @Test
455 public void shouldReadThreeCharactersString() throws Exception {
456 byte[] stream = "\u0000\u0000\u0000\u0003:ABC:".getBytes(UTF_8);
457 Channel channel = new Channel(stream, 1);
458 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
459
460 Memento memento = thread.new Memento();
461 memento.setCharset(UTF_8);
462
463 assertThat(thread.readString(memento)).isEqualTo("ABC");
464 }
465
466 @Test
467 public void shouldReadDefaultCharset() throws Exception {
468 byte[] stream = "\u0005:UTF-8:".getBytes(US_ASCII);
469 Channel channel = new Channel(stream, 1);
470 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
471
472 Memento memento = thread.new Memento();
473 memento.setCharset(UTF_8);
474
475 assertThat(thread.readCharset(memento)).isNotNull().isEqualTo(UTF_8);
476 }
477
478 @Test
479 public void shouldReadNonDefaultCharset() throws Exception {
480 byte[] stream = ((char) 10 + ":ISO_8859_1:").getBytes(US_ASCII);
481 Channel channel = new Channel(stream, 1);
482 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
483
484 Memento memento = thread.new Memento();
485 memento.setCharset(UTF_8);
486
487 assertThat(thread.readCharset(memento)).isNotNull().isEqualTo(ISO_8859_1);
488 }
489
490 @Test
491 public void shouldSetNonDefaultCharset() {
492 byte[] stream = {};
493 Channel channel = new Channel(stream, 1);
494 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
495
496 Memento memento = thread.new Memento();
497 memento.setCharset(ISO_8859_1);
498 assertThat(memento.getDecoder().charset()).isEqualTo(ISO_8859_1);
499
500 memento.setCharset(UTF_8);
501 assertThat(memento.getDecoder().charset()).isEqualTo(UTF_8);
502
503 memento.reset();
504 assertThat(memento.getDecoder()).isNotNull();
505 assertThat(memento.getDecoder().charset()).isEqualTo(UTF_8);
506 }
507
508 @Test(expected = MalformedFrameException.class)
509 public void malformedCharset() throws Exception {
510 byte[] stream = ((char) 8 + ":ISO_8859:").getBytes(US_ASCII);
511 Channel channel = new Channel(stream, 1);
512 Mock thread = new Mock(channel, new MockForkNodeArguments(), emptyMap());
513
514 Memento memento = thread.new Memento();
515 memento.setCharset(UTF_8);
516
517 thread.readCharset(memento);
518 }
519
520 private static class Channel implements ReadableByteChannel {
521 private final byte[] bytes;
522 private final int chunkSize;
523 protected int i;
524
525 Channel(byte[] bytes, int chunkSize) {
526 this.bytes = bytes;
527 this.chunkSize = chunkSize;
528 }
529
530 @Override
531 public int read(ByteBuffer dst) {
532 if (i == bytes.length) {
533 return -1;
534 } else if (dst.hasRemaining()) {
535 int length = min(min(chunkSize, bytes.length - i), dst.remaining());
536 dst.put(bytes, i, length);
537 i += length;
538 return length;
539 } else {
540 return 0;
541 }
542 }
543
544 @Override
545 public boolean isOpen() {
546 return false;
547 }
548
549 @Override
550 public void close() {}
551 }
552
553 private static class MockForkNodeArguments implements ForkNodeArguments {
554 @Nonnull
555 @Override
556 public String getSessionId() {
557 return null;
558 }
559
560 @Override
561 public int getForkChannelId() {
562 return 0;
563 }
564
565 @Nonnull
566 @Override
567 public File dumpStreamText(@Nonnull String text) {
568 return null;
569 }
570
571 @Nonnull
572 @Override
573 public File dumpStreamException(@Nonnull Throwable t) {
574 return null;
575 }
576
577 @Override
578 public void logWarningAtEnd(@Nonnull String text) {}
579
580 @Nonnull
581 @Override
582 public ConsoleLogger getConsoleLogger() {
583 return null;
584 }
585
586 @Nonnull
587 @Override
588 public Object getConsoleLock() {
589 return new Object();
590 }
591
592 @Override
593 public File getEventStreamBinaryFile() {
594 return null;
595 }
596
597 @Override
598 public File getCommandStreamBinaryFile() {
599 return null;
600 }
601 }
602
603 private static class Mock extends AbstractStreamDecoder<Event, ForkedProcessEventType, SegmentType> {
604 protected Mock(
605 @Nonnull ReadableByteChannel channel,
606 @Nonnull ForkNodeArguments arguments,
607 @Nonnull Map<Segment, ForkedProcessEventType> messageTypes) {
608 super(channel, arguments, messageTypes);
609 }
610
611 @Override
612 public Event decode(@Nonnull Memento memento) throws MalformedChannelException {
613 throw new MalformedChannelException();
614 }
615
616 @Nonnull
617 @Override
618 protected byte[] getEncodedMagicNumber() {
619 return Constants.MAGIC_NUMBER_FOR_EVENTS_BYTES;
620 }
621
622 @Nonnull
623 @Override
624 protected SegmentType[] nextSegmentType(@Nonnull ForkedProcessEventType messageType) {
625 return new SegmentType[] {END_OF_FRAME};
626 }
627
628 @Nonnull
629 @Override
630 protected Event toMessage(@Nonnull ForkedProcessEventType messageType, @Nonnull Memento memento) {
631 return null;
632 }
633
634 @Override
635 public void close() throws Exception {}
636 }
637 }