View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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   * The performance of "get( Integer )" is 13.5 nano seconds on i5/2.6GHz:
62   * <pre>
63   *     {@code
64   *     TreeMap<Integer, ForkedProcessEventType> map = new TreeMap<>();
65   *     map.get( hash );
66   *     }
67   * </pre>
68   *
69   * <br> The performance of getting event type by Segment is 33.7 nano seconds:
70   * <pre>
71   *     {@code
72   *     Map<Segment, ForkedProcessEventType> map = new HashMap<>();
73   *     byte[] array = ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 );
74   *     map.get( new Segment( array, 0, array.length ) );
75   *     }
76   * </pre>
77   *
78   * <br> The performance of decoder:
79   * <pre>
80   *     {@code
81   *     CharsetDecoder decoder = STREAM_ENCODING.newDecoder()
82   *             .onMalformedInput( REPLACE )
83   *             .onUnmappableCharacter( REPLACE );
84   *     ByteBuffer buffer = ByteBuffer.wrap( ForkedProcessEventType.BOOTERCODE_STDOUT.getOpcode().getBytes( UTF_8 ) );
85   *     CharBuffer chars = CharBuffer.allocate( 100 );
86   *     decoder.reset().decode( buffer, chars, true );
87   *
88   *     String s = chars.flip().toString(); // 37 nanos = CharsetDecoder + toString
89   *
90   *     buffer.clear();
91   *     chars.clear();
92   *
93   *     ForkedProcessEventType.byOpcode( s ); // 65 nanos = CharsetDecoder + toString + byOpcode
94   *     }
95   * </pre>
96   *
97   * <br> The performance of decoding 100 bytes via CharacterDecoder - 71 nano seconds:
98   * <pre>
99   *     {@code
100  *     decoder.reset()
101  *         .decode( buffer, chars, true ); // CharsetDecoder 71 nanos
102  *     chars.flip().toString(); // CharsetDecoder + toString = 91 nanos
103  *     }
104  * </pre>
105  *
106  * <br> The performance of a pure string creation (instead of decoder) - 31.5 nano seconds:
107  * <pre>
108  *     {@code
109  *     byte[] b = {};
110  *     new String( b, UTF_8 );
111  *     }
112  * </pre>
113  *
114  * <br> The performance of CharsetDecoder with empty ByteBuffer:
115  * <pre>
116  *     {@code
117  *     CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
118  *     CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
119  *     }
120  * </pre>
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"); // 2-bytes encoded character + LF
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"); // 4-bytes encoded character
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         // whatever position will be compacted to 0
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         // whatever position will be compacted to 0
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         // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) makes 11.5 nanos
359         // empty stream: CharsetDecoder + ByteBuffer.allocate( 0 ) + toString() makes 16.1 nanos
360         ByteBuffer buffer = ByteBuffer.wrap(PATTERN1.getBytes(UTF_8));
361         CharBuffer chars = CharBuffer.allocate(100);
362         // uncomment this section for a proper measurement of the exec time
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); // CharsetDecoder 71 nanos
370             s = ((Buffer) chars).flip().toString(); // CharsetDecoder + toString = 91 nanos
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 }