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