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