View Javadoc
1   package org.apache.maven.surefire.booter.stream;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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             // logger file was deleted
221             // System.out is already used by the stream in this decoder
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 }