1 package org.apache.maven.surefire.booter.stream;
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.Command;
23 import org.apache.maven.surefire.api.booter.MasterProcessCommand;
24 import org.apache.maven.surefire.api.booter.Shutdown;
25 import org.apache.maven.surefire.api.fork.ForkNodeArguments;
26 import org.apache.maven.surefire.api.stream.AbstractStreamDecoder;
27 import org.apache.maven.surefire.api.stream.MalformedChannelException;
28 import org.apache.maven.surefire.api.stream.SegmentType;
29
30 import javax.annotation.Nonnull;
31 import java.io.BufferedOutputStream;
32 import java.io.File;
33 import java.io.FileNotFoundException;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.OutputStream;
37 import java.nio.channels.ReadableByteChannel;
38 import java.util.concurrent.FutureTask;
39
40 import static org.apache.maven.surefire.api.booter.Command.BYE_ACK;
41 import static org.apache.maven.surefire.api.booter.Command.NOOP;
42 import static org.apache.maven.surefire.api.booter.Command.SKIP_SINCE_NEXT_TEST;
43 import static org.apache.maven.surefire.api.booter.Command.TEST_SET_FINISHED;
44 import static org.apache.maven.surefire.api.booter.Command.toRunClass;
45 import static org.apache.maven.surefire.api.booter.Command.toShutdown;
46 import static org.apache.maven.surefire.api.booter.Constants.MAGIC_NUMBER_FOR_COMMANDS_BYTES;
47 import static org.apache.maven.surefire.api.booter.MasterProcessCommand.COMMAND_TYPES;
48 import static org.apache.maven.surefire.api.stream.SegmentType.DATA_STRING;
49 import static org.apache.maven.surefire.api.stream.SegmentType.END_OF_FRAME;
50 import static org.apache.maven.surefire.api.stream.SegmentType.STRING_ENCODING;
51 import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
52
53
54
55
56 public class CommandDecoder extends AbstractStreamDecoder<Command, MasterProcessCommand, SegmentType>
57 {
58 private static final int DEBUG_SINK_BUFFER_SIZE = 64 * 1024;
59 private static final int NO_POSITION = -1;
60
61 private static final SegmentType[] COMMAND_WITHOUT_DATA = new SegmentType[] {
62 END_OF_FRAME
63 };
64
65 private static final SegmentType[] COMMAND_WITH_ONE_STRING = new SegmentType[] {
66 STRING_ENCODING,
67 DATA_STRING,
68 END_OF_FRAME
69 };
70
71 private final ForkNodeArguments arguments;
72 private final OutputStream debugSink;
73
74 public CommandDecoder( @Nonnull ReadableByteChannel channel,
75 @Nonnull ForkNodeArguments arguments )
76 {
77 super( channel, arguments, COMMAND_TYPES );
78 this.arguments = arguments;
79 debugSink = newDebugSink();
80 }
81
82 @Override
83 public Command decode( @Nonnull Memento memento ) throws IOException, MalformedChannelException
84 {
85 try
86 {
87 MasterProcessCommand commandType = readMessageType( memento );
88 if ( commandType == null )
89 {
90 throw new MalformedFrameException( memento.getLine().getPositionByteBuffer(),
91 memento.getByteBuffer().position() );
92 }
93
94 for ( SegmentType segmentType : nextSegmentType( commandType ) )
95 {
96 switch ( segmentType )
97 {
98 case STRING_ENCODING:
99 memento.setCharset( readCharset( memento ) );
100 break;
101 case DATA_STRING:
102 memento.getData().add( readString( memento ) );
103 break;
104 case DATA_INTEGER:
105 memento.getData().add( readInteger( memento ) );
106 break;
107 case END_OF_FRAME:
108 memento.getLine().setPositionByteBuffer( memento.getByteBuffer().position() );
109 memento.getLine().clear();
110 return toMessage( commandType, memento );
111 default:
112 memento.getLine().setPositionByteBuffer( NO_POSITION );
113 arguments.dumpStreamText( "Unknown enum ("
114 + SegmentType.class.getSimpleName()
115 + ") "
116 + segmentType );
117 }
118 }
119 }
120 catch ( MalformedFrameException e )
121 {
122 if ( e.hasValidPositions() )
123 {
124 int length = e.readTo() - e.readFrom();
125 memento.getLine().write( memento.getByteBuffer(), e.readFrom(), length );
126 }
127 return null;
128 }
129 catch ( RuntimeException e )
130 {
131 getArguments().dumpStreamException( e );
132 return null;
133 }
134 catch ( IOException e )
135 {
136 if ( !( e.getCause() instanceof InterruptedException ) )
137 {
138 printRemainingStream( memento );
139 }
140 throw e;
141 }
142 finally
143 {
144 memento.reset();
145 }
146
147 throw new MalformedChannelException();
148 }
149
150 @Nonnull
151 @Override
152 protected final byte[] getEncodedMagicNumber()
153 {
154 return MAGIC_NUMBER_FOR_COMMANDS_BYTES;
155 }
156
157 @Nonnull
158 @Override
159 protected SegmentType[] nextSegmentType( @Nonnull MasterProcessCommand commandType )
160 {
161 switch ( commandType )
162 {
163 case NOOP:
164 case BYE_ACK:
165 case SKIP_SINCE_NEXT_TEST:
166 case TEST_SET_FINISHED:
167 return COMMAND_WITHOUT_DATA;
168 case RUN_CLASS:
169 case SHUTDOWN:
170 return COMMAND_WITH_ONE_STRING;
171 default:
172 throw new IllegalArgumentException( "Unknown enum " + commandType );
173 }
174 }
175
176 @Nonnull
177 @Override
178 protected Command toMessage( @Nonnull MasterProcessCommand commandType, @Nonnull Memento memento )
179 throws MalformedFrameException
180 {
181 switch ( commandType )
182 {
183 case NOOP:
184 checkArguments( memento, 0 );
185 return NOOP;
186 case BYE_ACK:
187 checkArguments( memento, 0 );
188 return BYE_ACK;
189 case SKIP_SINCE_NEXT_TEST:
190 checkArguments( memento, 0 );
191 return SKIP_SINCE_NEXT_TEST;
192 case TEST_SET_FINISHED:
193 checkArguments( memento, 0 );
194 return TEST_SET_FINISHED;
195 case RUN_CLASS:
196 checkArguments( memento, 1 );
197 return toRunClass( (String) memento.getData().get( 0 ) );
198 case SHUTDOWN:
199 checkArguments( memento, 1 );
200 return toShutdown( Shutdown.parameterOf( (String) memento.getData().get( 0 ) ) );
201 default:
202 throw new IllegalArgumentException( "Missing a branch for the event type " + commandType );
203 }
204 }
205
206 @Override
207 protected void debugStream( byte[] array, int position, int remaining )
208 {
209 if ( debugSink == null )
210 {
211 return;
212 }
213
214 try
215 {
216 debugSink.write( array, position, remaining );
217 }
218 catch ( IOException e )
219 {
220
221
222 }
223 }
224
225 private OutputStream newDebugSink()
226 {
227 final File sink = arguments.getCommandStreamBinaryFile();
228 if ( sink == null )
229 {
230 return null;
231 }
232
233 try
234 {
235 OutputStream fos = new FileOutputStream( sink, true );
236 final OutputStream os = new BufferedOutputStream( fos, DEBUG_SINK_BUFFER_SIZE );
237 addShutDownHook( new Thread( new FutureTask<>( () ->
238 {
239 os.close();
240 return null;
241 } ) ) );
242 return os;
243 }
244 catch ( FileNotFoundException e )
245 {
246 return null;
247 }
248 }
249
250 @Override
251 public void close() throws IOException
252 {
253 if ( debugSink != null )
254 {
255 debugSink.close();
256 }
257 }
258 }