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
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
189
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 }