1 package org.apache.maven.plugin.surefire;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.maven.execution.MavenExecutionRequest;
23 import org.apache.maven.execution.MavenSession;
24 import org.apache.maven.plugin.MojoExecutionException;
25 import org.apache.maven.plugin.MojoFailureException;
26 import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
27 import org.apache.maven.surefire.api.cli.CommandLineOption;
28 import org.apache.maven.surefire.api.suite.RunResult;
29 import org.apache.maven.surefire.api.testset.TestSetFailedException;
30 import org.apache.maven.surefire.booter.SurefireBooterForkException;
31
32 import javax.annotation.Nonnull;
33 import java.io.File;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Deque;
37 import java.util.LinkedList;
38 import java.util.List;
39
40 import static java.util.Collections.unmodifiableList;
41 import static org.apache.maven.surefire.api.util.internal.DumpFileUtils.newFormattedDateFileName;
42 import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_WINDOWS;
43 import static org.apache.maven.surefire.api.booter.DumpErrorSingleton.DUMPSTREAM_FILE_EXT;
44 import static org.apache.maven.surefire.api.booter.DumpErrorSingleton.DUMP_FILE_EXT;
45 import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
46 import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_ERROR;
47 import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_INFO;
48 import static org.apache.maven.surefire.api.cli.CommandLineOption.LOGGING_LEVEL_WARN;
49 import static org.apache.maven.surefire.api.cli.CommandLineOption.SHOW_ERRORS;
50
51
52
53
54 public final class SurefireHelper
55 {
56 private static final String DUMP_FILE_DATE = newFormattedDateFileName();
57
58 public static final String DUMP_FILE_PREFIX = DUMP_FILE_DATE + "-jvmRun";
59
60 public static final String DUMP_FILENAME_FORMATTER = DUMP_FILE_PREFIX + "%d" + DUMP_FILE_EXT;
61
62 public static final String DUMPSTREAM_FILENAME_FORMATTER = DUMP_FILE_PREFIX + "%d" + DUMPSTREAM_FILE_EXT;
63
64 public static final String DUMPSTREAM_FILENAME = DUMP_FILE_DATE + DUMPSTREAM_FILE_EXT;
65
66 public static final String DUMP_FILENAME = DUMP_FILE_DATE + DUMP_FILE_EXT;
67
68 public static final String EVENTS_BINARY_DUMP_FILENAME_FORMATTER = DUMP_FILE_DATE + "-jvmRun%d-events.bin";
69
70
71
72
73
74
75
76
77
78
79
80
81 private static final int MAX_PATH_LENGTH_WINDOWS = 247;
82
83 private static final String[] DUMP_FILES_PRINT =
84 {
85 "[date]" + DUMP_FILE_EXT,
86 "[date]-jvmRun[N]" + DUMP_FILE_EXT,
87 "[date]" + DUMPSTREAM_FILE_EXT,
88 "[date]-jvmRun[N]" + DUMPSTREAM_FILE_EXT
89 };
90
91
92
93
94
95
96 private static final String THREAD_NUMBER_PLACEHOLDER = "${surefire.threadNumber}";
97
98
99
100
101
102 private static final String FORK_NUMBER_PLACEHOLDER = "${surefire.forkNumber}";
103
104
105
106
107 private SurefireHelper()
108 {
109 throw new IllegalAccessError( "Utility class" );
110 }
111
112 @Nonnull
113 public static String replaceThreadNumberPlaceholders( @Nonnull String argLine, int threadNumber )
114 {
115 String threadNumberAsString = String.valueOf( threadNumber );
116 return argLine.replace( THREAD_NUMBER_PLACEHOLDER, threadNumberAsString )
117 .replace( FORK_NUMBER_PLACEHOLDER, threadNumberAsString );
118 }
119
120 public static File replaceForkThreadsInPath( File path, int replacement )
121 {
122 Deque<String> dirs = new LinkedList<>();
123 File root = path;
124 while ( !root.exists() )
125 {
126 dirs.addFirst( replaceThreadNumberPlaceholders( root.getName(), replacement ) );
127 root = root.getParentFile();
128 }
129 File replacedPath = root;
130 for ( String dir : dirs )
131 {
132 replacedPath = new File( replacedPath, dir );
133 }
134 return replacedPath;
135 }
136
137 public static String[] getDumpFilesToPrint()
138 {
139 return DUMP_FILES_PRINT.clone();
140 }
141
142 public static void reportExecution( SurefireReportParameters reportParameters, RunResult result,
143 PluginConsoleLogger log, Exception firstForkException )
144 throws MojoFailureException, MojoExecutionException
145 {
146 boolean isError = firstForkException != null || result.isTimeout() || !result.isErrorFree();
147 boolean isTooFlaky = isTooFlaky( result, reportParameters );
148 if ( !isError && !isTooFlaky )
149 {
150 if ( result.getCompletedCount() == 0 && failIfNoTests( reportParameters ) )
151 {
152 throw new MojoFailureException( "No tests were executed! "
153 + "(Set -DfailIfNoTests=false to ignore this error.)" );
154 }
155 return;
156 }
157
158 if ( reportParameters.isTestFailureIgnore() )
159 {
160 String errorMessage = createErrorMessage( reportParameters, result, firstForkException );
161
162 if ( firstForkException instanceof SurefireBooterForkException )
163 {
164 throw new MojoExecutionException( errorMessage, firstForkException );
165 }
166
167 log.error( errorMessage );
168 }
169 else
170 {
171 throwException( reportParameters, result, firstForkException );
172 }
173 }
174
175 public static List<CommandLineOption> commandLineOptions( MavenSession session, PluginConsoleLogger log )
176 {
177 List<CommandLineOption> cli = new ArrayList<>();
178 if ( log.isErrorEnabled() )
179 {
180 cli.add( LOGGING_LEVEL_ERROR );
181 }
182
183 if ( log.isWarnEnabled() )
184 {
185 cli.add( LOGGING_LEVEL_WARN );
186 }
187
188 if ( log.isInfoEnabled() )
189 {
190 cli.add( LOGGING_LEVEL_INFO );
191 }
192
193 if ( log.isDebugEnabled() )
194 {
195 cli.add( LOGGING_LEVEL_DEBUG );
196 }
197
198 MavenExecutionRequest request = session.getRequest();
199
200 if ( request.isShowErrors() )
201 {
202 cli.add( SHOW_ERRORS );
203 }
204
205 String failureBehavior = request.getReactorFailureBehavior();
206 if ( failureBehavior != null )
207 {
208 try
209 {
210 cli.add( CommandLineOption.valueOf( failureBehavior ) );
211 }
212 catch ( IllegalArgumentException e )
213 {
214
215 }
216 }
217
218 return unmodifiableList( cli );
219 }
220
221 public static void logDebugOrCliShowErrors( String s, PluginConsoleLogger log, Collection<CommandLineOption> cli )
222 {
223 if ( cli.contains( LOGGING_LEVEL_DEBUG ) )
224 {
225 log.debug( s );
226 }
227 else if ( cli.contains( SHOW_ERRORS ) )
228 {
229 if ( log.isDebugEnabled() )
230 {
231 log.debug( s );
232 }
233 else
234 {
235 log.info( s );
236 }
237 }
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 public static String escapeToPlatformPath( String path )
254 {
255 if ( IS_OS_WINDOWS && path.length() > MAX_PATH_LENGTH_WINDOWS )
256 {
257 path = path.startsWith( "\\\\" ) ? "\\\\?\\UNC\\" + path.substring( 2 ) : "\\\\?\\" + path;
258 }
259 return path;
260 }
261
262 private static boolean failIfNoTests( SurefireReportParameters reportParameters )
263 {
264 return reportParameters.getFailIfNoTests();
265 }
266
267 private static boolean isFatal( Exception firstForkException )
268 {
269 return firstForkException != null && !( firstForkException instanceof TestSetFailedException );
270 }
271
272 private static void throwException( SurefireReportParameters reportParameters, RunResult result,
273 Exception firstForkException )
274 throws MojoFailureException, MojoExecutionException
275 {
276 if ( isFatal( firstForkException ) || result.isInternalError() )
277 {
278 throw new MojoExecutionException( createErrorMessage( reportParameters, result, firstForkException ),
279 firstForkException );
280 }
281 else
282 {
283 throw new MojoFailureException( createErrorMessage( reportParameters, result, firstForkException ),
284 firstForkException );
285 }
286 }
287
288 private static String createErrorMessage( SurefireReportParameters reportParameters, RunResult result,
289 Exception firstForkException )
290 {
291 StringBuilder msg = new StringBuilder( 512 );
292
293 if ( result.isTimeout() )
294 {
295 msg.append( "There was a timeout in the fork" );
296 }
297 else
298 {
299 if ( result.getFailures() > 0 )
300 {
301 msg.append( "There are test failures." );
302 }
303 if ( isTooFlaky( result, reportParameters ) )
304 {
305 if ( result.getFailures() > 0 )
306 {
307 msg.append( "\n" );
308 }
309 msg.append( "There" )
310 .append( result.getFlakes() == 1 ? " is " : " are " )
311 .append( result.getFlakes() )
312 .append( result.getFlakes() == 1 ? " flake " : " flakes " )
313 .append( "and failOnFlakeCount is set to " )
314 .append( reportParameters.getFailOnFlakeCount() )
315 .append( "." );
316 }
317 msg.append( "\n\nPlease refer to " )
318 .append( reportParameters.getReportsDirectory() )
319 .append( " for the individual test results." )
320 .append( '\n' )
321 .append( "Please refer to dump files (if any exist) " )
322 .append( DUMP_FILES_PRINT[0] )
323 .append( ", " )
324 .append( DUMP_FILES_PRINT[1] )
325 .append( " and " )
326 .append( DUMP_FILES_PRINT[2] )
327 .append( "." );
328 }
329
330 if ( firstForkException != null && firstForkException.getLocalizedMessage() != null )
331 {
332 msg.append( '\n' )
333 .append( firstForkException.getLocalizedMessage() );
334 }
335
336 if ( result.isFailure() )
337 {
338 msg.append( '\n' )
339 .append( result.getFailure() );
340 }
341
342 return msg.toString();
343 }
344
345 private static boolean isTooFlaky( RunResult result, SurefireReportParameters reportParameters )
346 {
347 int failOnFlakeCount = reportParameters.getFailOnFlakeCount();
348 return failOnFlakeCount > 0 && result.getFlakes() >= failOnFlakeCount;
349 }
350
351 }