1 package org.apache.maven.surefire.booter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.PrintStream;
23 import java.nio.charset.Charset;
24 import java.util.Enumeration;
25 import java.util.Properties;
26
27 import org.apache.maven.surefire.report.ConsoleLogger;
28 import org.apache.maven.surefire.report.ConsoleOutputReceiver;
29 import org.apache.maven.surefire.report.ReportEntry;
30 import org.apache.maven.surefire.report.RunListener;
31 import org.apache.maven.surefire.report.SafeThrowable;
32 import org.apache.maven.surefire.report.SimpleReportEntry;
33 import org.apache.maven.surefire.report.StackTraceWriter;
34 import org.apache.maven.surefire.util.internal.StringUtils;
35
36 import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class ForkingRunListener
56 implements RunListener, ConsoleLogger, ConsoleOutputReceiver
57 {
58 public static final byte BOOTERCODE_TESTSET_STARTING = (byte) '1';
59
60 public static final byte BOOTERCODE_TESTSET_COMPLETED = (byte) '2';
61
62 public static final byte BOOTERCODE_STDOUT = (byte) '3';
63
64 public static final byte BOOTERCODE_STDERR = (byte) '4';
65
66 public static final byte BOOTERCODE_TEST_STARTING = (byte) '5';
67
68 public static final byte BOOTERCODE_TEST_SUCCEEDED = (byte) '6';
69
70 public static final byte BOOTERCODE_TEST_ERROR = (byte) '7';
71
72 public static final byte BOOTERCODE_TEST_FAILED = (byte) '8';
73
74 public static final byte BOOTERCODE_TEST_SKIPPED = (byte) '9';
75
76 public static final byte BOOTERCODE_TEST_ASSUMPTIONFAILURE = (byte) 'G';
77
78 public static final byte BOOTERCODE_CONSOLE = (byte) 'H';
79
80 public static final byte BOOTERCODE_SYSPROPS = (byte) 'I';
81
82 public static final byte BOOTERCODE_NEXT_TEST = (byte) 'N';
83
84 public static final byte BOOTERCODE_STOP_ON_NEXT_TEST = (byte) 'S';
85
86 public static final byte BOOTERCODE_ERROR = (byte) 'X';
87
88 public static final byte BOOTERCODE_BYE = (byte) 'Z';
89
90 private final PrintStream target;
91
92 private final int testSetChannelId;
93
94 private final boolean trimStackTraces;
95
96 private final byte[] stdOutHeader;
97
98 private final byte[] stdErrHeader;
99
100 public ForkingRunListener( PrintStream target, int testSetChannelId, boolean trimStackTraces )
101 {
102 this.target = target;
103 this.testSetChannelId = testSetChannelId;
104 this.trimStackTraces = trimStackTraces;
105 stdOutHeader = createHeader( BOOTERCODE_STDOUT, testSetChannelId );
106 stdErrHeader = createHeader( BOOTERCODE_STDERR, testSetChannelId );
107 sendProps();
108 }
109
110 public void testSetStarting( ReportEntry report )
111 {
112 encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_STARTING, report, testSetChannelId ) );
113 }
114
115 public void testSetCompleted( ReportEntry report )
116 {
117 encodeAndWriteToTarget( toString( BOOTERCODE_TESTSET_COMPLETED, report, testSetChannelId ) );
118 }
119
120 public void testStarting( ReportEntry report )
121 {
122 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_STARTING, report, testSetChannelId ) );
123 }
124
125 public void testSucceeded( ReportEntry report )
126 {
127 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SUCCEEDED, report, testSetChannelId ) );
128 }
129
130 public void testAssumptionFailure( ReportEntry report )
131 {
132 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ASSUMPTIONFAILURE, report, testSetChannelId ) );
133 }
134
135 public void testError( ReportEntry report )
136 {
137 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_ERROR, report, testSetChannelId ) );
138 }
139
140 public void testFailed( ReportEntry report )
141 {
142 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_FAILED, report, testSetChannelId ) );
143 }
144
145 public void testSkipped( ReportEntry report )
146 {
147 encodeAndWriteToTarget( toString( BOOTERCODE_TEST_SKIPPED, report, testSetChannelId ) );
148 }
149
150 public void testExecutionSkippedByUser()
151 {
152 encodeAndWriteToTarget( toString( BOOTERCODE_STOP_ON_NEXT_TEST, new SimpleReportEntry(), testSetChannelId ) );
153 }
154
155 void sendProps()
156 {
157 Properties systemProperties = System.getProperties();
158
159 if ( systemProperties != null )
160 {
161 for ( Enumeration<?> propertyKeys = systemProperties.propertyNames(); propertyKeys.hasMoreElements(); )
162 {
163 String key = (String) propertyKeys.nextElement();
164 String value = systemProperties.getProperty( key );
165 encodeAndWriteToTarget( toPropertyString( key, value == null ? "null" : value ) );
166 }
167 }
168 }
169
170 public void writeTestOutput( byte[] buf, int off, int len, boolean stdout )
171 {
172 byte[] header = stdout ? stdOutHeader : stdErrHeader;
173 byte[] content =
174 new byte[buf.length * 3 + 1];
175 int i = StringUtils.escapeBytesToPrintable( content, 0, buf, off, len );
176 content[i++] = (byte) '\n';
177
178 synchronized ( target )
179 {
180 target.write( header, 0, header.length );
181 target.write( content, 0, i );
182 }
183 }
184
185 public static byte[] createHeader( byte booterCode, int testSetChannel )
186 {
187 return encodeStringForForkCommunication( String.valueOf( (char) booterCode )
188 + ','
189 + Integer.toString( testSetChannel, 16 )
190 + ',' + Charset.defaultCharset().name()
191 + ',' );
192 }
193
194 public void info( String message )
195 {
196 if ( message != null )
197 {
198 StringBuilder sb = new StringBuilder( 7 + message.length() * 5 );
199 append( sb, BOOTERCODE_CONSOLE ); comma( sb );
200 append( sb, Integer.toHexString( testSetChannelId ) ); comma( sb );
201 StringUtils.escapeToPrintable( sb, message );
202
203 sb.append( '\n' );
204 encodeAndWriteToTarget( sb.toString() );
205 }
206 }
207
208 private void encodeAndWriteToTarget( String string )
209 {
210 byte[] encodeBytes = encodeStringForForkCommunication( string );
211 synchronized ( target )
212 {
213 target.write( encodeBytes, 0, encodeBytes.length );
214 }
215 }
216
217 private String toPropertyString( String key, String value )
218 {
219 StringBuilder stringBuilder = new StringBuilder();
220
221 append( stringBuilder, BOOTERCODE_SYSPROPS ); comma( stringBuilder );
222 append( stringBuilder, Integer.toHexString( testSetChannelId ) ); comma( stringBuilder );
223
224 StringUtils.escapeToPrintable( stringBuilder, key );
225 comma( stringBuilder );
226 StringUtils.escapeToPrintable( stringBuilder, value );
227 stringBuilder.append( "\n" );
228 return stringBuilder.toString();
229 }
230
231 private String toString( byte operationCode, ReportEntry reportEntry, int testSetChannelId )
232 {
233 StringBuilder stringBuilder = new StringBuilder();
234 append( stringBuilder, operationCode ); comma( stringBuilder );
235 append( stringBuilder, Integer.toHexString( testSetChannelId ) ); comma( stringBuilder );
236
237 nullableEncoding( stringBuilder, reportEntry.getSourceName() );
238 comma( stringBuilder );
239 nullableEncoding( stringBuilder, reportEntry.getName() );
240 comma( stringBuilder );
241 nullableEncoding( stringBuilder, reportEntry.getGroup() );
242 comma( stringBuilder );
243 nullableEncoding( stringBuilder, reportEntry.getMessage() );
244 comma( stringBuilder );
245 nullableEncoding( stringBuilder, reportEntry.getElapsed() );
246 encode( stringBuilder, reportEntry.getStackTraceWriter() );
247 stringBuilder.append( "\n" );
248 return stringBuilder.toString();
249 }
250
251 private static void comma( StringBuilder stringBuilder )
252 {
253 stringBuilder.append( "," );
254 }
255
256 private ForkingRunListener append( StringBuilder stringBuilder, String message )
257 {
258 stringBuilder.append( encode( message ) );
259 return this;
260 }
261
262 private ForkingRunListener append( StringBuilder stringBuilder, byte b )
263 {
264 stringBuilder.append( (char) b );
265 return this;
266 }
267
268 private void nullableEncoding( StringBuilder stringBuilder, Integer source )
269 {
270 if ( source == null )
271 {
272 stringBuilder.append( "null" );
273 }
274 else
275 {
276 stringBuilder.append( source.toString() );
277 }
278 }
279
280 private String encode( String source )
281 {
282 return source;
283 }
284
285
286 private static void nullableEncoding( StringBuilder stringBuilder, String source )
287 {
288 if ( source == null || source.length() == 0 )
289 {
290 stringBuilder.append( "null" );
291 }
292 else
293 {
294 StringUtils.escapeToPrintable( stringBuilder, source );
295 }
296 }
297
298 private void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter )
299 {
300 encode( stringBuilder, stackTraceWriter, trimStackTraces );
301 }
302
303 public static void encode( StringBuilder stringBuilder, StackTraceWriter stackTraceWriter, boolean trimStackTraces )
304 {
305 if ( stackTraceWriter != null )
306 {
307 comma( stringBuilder );
308
309 final SafeThrowable throwable = stackTraceWriter.getThrowable();
310 if ( throwable != null )
311 {
312 String message = throwable.getLocalizedMessage();
313 nullableEncoding( stringBuilder, message );
314 }
315 comma( stringBuilder );
316 nullableEncoding( stringBuilder, stackTraceWriter.smartTrimmedStackTrace() );
317 comma( stringBuilder );
318 nullableEncoding( stringBuilder, trimStackTraces
319 ? stackTraceWriter.writeTrimmedTraceToString()
320 : stackTraceWriter.writeTraceToString() );
321 }
322 }
323 }