View Javadoc
1   package org.apache.maven.plugin.surefire;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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.cli.CommandLineOption;
28  import org.apache.maven.surefire.suite.RunResult;
29  import org.apache.maven.surefire.testset.TestSetFailedException;
30  import org.apache.maven.surefire.util.internal.DumpFileUtils;
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.commons.lang3.SystemUtils.IS_OS_WINDOWS;
42  import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMPSTREAM_FILE_EXT;
43  import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMP_FILE_EXT;
44  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
45  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_ERROR;
46  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_INFO;
47  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_WARN;
48  import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
49  
50  /**
51   * Helper class for surefire plugins
52   */
53  public final class SurefireHelper
54  {
55      private static final String DUMP_FILE_DATE = DumpFileUtils.newFormattedDateFileName();
56  
57      public static final String DUMP_FILE_PREFIX = DUMP_FILE_DATE + "-jvmRun";
58  
59      public static final String DUMP_FILENAME_FORMATTER = DUMP_FILE_PREFIX + "%d" + DUMP_FILE_EXT;
60  
61      public static final String DUMPSTREAM_FILENAME_FORMATTER = DUMP_FILE_PREFIX + "%d" + DUMPSTREAM_FILE_EXT;
62  
63      public static final String DUMPSTREAM_FILENAME = DUMP_FILE_DATE + DUMPSTREAM_FILE_EXT;
64  
65      public static final String DUMP_FILENAME = DUMP_FILE_DATE + DUMP_FILE_EXT;
66  
67      /**
68       * The maximum path that does not require long path prefix on Windows.<br>
69       * See {@code sun/nio/fs/WindowsPath} in
70       * <a href=
71       * "http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/7534523b4174/src/windows/classes/sun/nio/fs/WindowsPath.java#l46">
72       * OpenJDK</a>
73       * and <a href="https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath">MSDN article</a>.
74       * <br>
75       * The maximum path is 260 minus 1 (NUL) but for directories it is 260
76       * minus 12 minus 1 (to allow for the creation of a 8.3 file in the directory).
77       */
78      private static final int MAX_PATH_LENGTH_WINDOWS = 247;
79  
80      private static final String[] DUMP_FILES_PRINT =
81              {
82                      "[date]" + DUMP_FILE_EXT,
83                      "[date]-jvmRun[N]" + DUMP_FILE_EXT,
84                      "[date]" + DUMPSTREAM_FILE_EXT,
85                      "[date]-jvmRun[N]" + DUMPSTREAM_FILE_EXT
86              };
87  
88      /**
89       * The placeholder that is replaced by the executing thread's running number. The thread number
90       * range starts with 1
91       * Deprecated.
92       */
93      private static final String THREAD_NUMBER_PLACEHOLDER = "${surefire.threadNumber}";
94  
95      /**
96       * The placeholder that is replaced by the executing fork's running number. The fork number
97       * range starts with 1
98       */
99      private static final String FORK_NUMBER_PLACEHOLDER = "${surefire.forkNumber}";
100 
101     /**
102      * Do not instantiate.
103      */
104     private SurefireHelper()
105     {
106         throw new IllegalAccessError( "Utility class" );
107     }
108 
109     @Nonnull
110     public static String replaceThreadNumberPlaceholders( @Nonnull String argLine, int threadNumber )
111     {
112         String threadNumberAsString = String.valueOf( threadNumber );
113         return argLine.replace( THREAD_NUMBER_PLACEHOLDER, threadNumberAsString )
114                 .replace( FORK_NUMBER_PLACEHOLDER, threadNumberAsString );
115     }
116 
117     public static File replaceForkThreadsInPath( File path, int replacement )
118     {
119         Deque<String> dirs = new LinkedList<>();
120         File root = path;
121         while ( !root.exists() )
122         {
123             dirs.addFirst( replaceThreadNumberPlaceholders( root.getName(), replacement ) );
124             root = root.getParentFile();
125         }
126         File replacedPath = root;
127         for ( String dir : dirs )
128         {
129             replacedPath = new File( replacedPath, dir );
130         }
131         return replacedPath;
132     }
133 
134     public static String[] getDumpFilesToPrint()
135     {
136         return DUMP_FILES_PRINT.clone();
137     }
138 
139     public static void reportExecution( SurefireReportParameters reportParameters, RunResult result,
140                                         PluginConsoleLogger log, Exception firstForkException )
141         throws MojoFailureException, MojoExecutionException
142     {
143         if ( firstForkException == null && !result.isTimeout() && result.isErrorFree() )
144         {
145             if ( result.getCompletedCount() == 0 && failIfNoTests( reportParameters ) )
146             {
147                 throw new MojoFailureException( "No tests were executed!  "
148                                                         + "(Set -DfailIfNoTests=false to ignore this error.)" );
149             }
150             return;
151         }
152 
153         if ( reportParameters.isTestFailureIgnore() )
154         {
155             log.error( createErrorMessage( reportParameters, result, firstForkException ) );
156         }
157         else
158         {
159             throwException( reportParameters, result, firstForkException );
160         }
161     }
162 
163     public static List<CommandLineOption> commandLineOptions( MavenSession session, PluginConsoleLogger log )
164     {
165         List<CommandLineOption> cli = new ArrayList<>();
166         if ( log.isErrorEnabled() )
167         {
168             cli.add( LOGGING_LEVEL_ERROR );
169         }
170 
171         if ( log.isWarnEnabled() )
172         {
173             cli.add( LOGGING_LEVEL_WARN );
174         }
175 
176         if ( log.isInfoEnabled() )
177         {
178             cli.add( LOGGING_LEVEL_INFO );
179         }
180 
181         if ( log.isDebugEnabled() )
182         {
183             cli.add( LOGGING_LEVEL_DEBUG );
184         }
185 
186         MavenExecutionRequest request = session.getRequest();
187 
188         if ( request.isShowErrors() )
189         {
190             cli.add( SHOW_ERRORS );
191         }
192 
193         String failureBehavior = request.getReactorFailureBehavior();
194         if ( failureBehavior != null )
195         {
196             try
197             {
198                 cli.add( CommandLineOption.valueOf( failureBehavior ) );
199             }
200             catch ( IllegalArgumentException e )
201             {
202                 // CommandLineOption does not have specified enum as string. See getRequest() method in Maven Session.
203             }
204         }
205 
206         return unmodifiableList( cli );
207     }
208 
209     public static void logDebugOrCliShowErrors( String s, PluginConsoleLogger log, Collection<CommandLineOption> cli )
210     {
211         if ( cli.contains( LOGGING_LEVEL_DEBUG ) )
212         {
213             log.debug( s );
214         }
215         else if ( cli.contains( SHOW_ERRORS ) )
216         {
217             if ( log.isDebugEnabled() )
218             {
219                 log.debug( s );
220             }
221             else
222             {
223                 log.info( s );
224             }
225         }
226     }
227 
228     /**
229      * Escape file path for Windows when the path is too long; otherwise returns {@code path}.
230      * <br>
231      * See <a href=
232      * "http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/7534523b4174/src/windows/classes/sun/nio/fs/WindowsPath.java#l46">
233      * sun/nio/fs/WindowsPath</a> for "long path" value explanation (=247), and
234      * <a href="https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath">MSDN article</a>
235      * for detailed escaping strategy explanation: in short, {@code \\?\} prefix for path with drive letter
236      * or {@code \\?\UNC\} for UNC path.
237      *
238      * @param path    source path
239      * @return escaped to platform path
240      */
241     public static String escapeToPlatformPath( String path )
242     {
243         if ( IS_OS_WINDOWS && path.length() > MAX_PATH_LENGTH_WINDOWS )
244         {
245             path = path.startsWith( "\\\\" ) ? "\\\\?\\UNC\\" + path.substring( 2 ) : "\\\\?\\" + path;
246         }
247         return path;
248     }
249 
250     private static boolean failIfNoTests( SurefireReportParameters reportParameters )
251     {
252         return reportParameters.getFailIfNoTests() != null && reportParameters.getFailIfNoTests();
253     }
254 
255     private static boolean isFatal( Exception firstForkException )
256     {
257         return firstForkException != null && !( firstForkException instanceof TestSetFailedException );
258     }
259 
260     private static void throwException( SurefireReportParameters reportParameters, RunResult result,
261                                            Exception firstForkException )
262             throws MojoFailureException, MojoExecutionException
263     {
264         if ( isFatal( firstForkException ) || result.isInternalError()  )
265         {
266             throw new MojoExecutionException( createErrorMessage( reportParameters, result, firstForkException ),
267                                                     firstForkException );
268         }
269         else
270         {
271             throw new MojoFailureException( createErrorMessage( reportParameters, result, firstForkException ),
272                                                   firstForkException );
273         }
274     }
275 
276     private static String createErrorMessage( SurefireReportParameters reportParameters, RunResult result,
277                                               Exception firstForkException )
278     {
279         StringBuilder msg = new StringBuilder( 512 );
280 
281         if ( result.isTimeout() )
282         {
283             msg.append( "There was a timeout or other error in the fork" );
284         }
285         else
286         {
287             msg.append( "There are test failures.\n\nPlease refer to " )
288                     .append( reportParameters.getReportsDirectory() )
289                     .append( " for the individual test results." )
290                     .append( '\n' )
291                     .append( "Please refer to dump files (if any exist) " )
292                     .append( DUMP_FILES_PRINT[0] )
293                     .append( ", " )
294                     .append( DUMP_FILES_PRINT[1] )
295                     .append( " and " )
296                     .append( DUMP_FILES_PRINT[2] )
297                     .append( "." );
298         }
299 
300         if ( firstForkException != null && firstForkException.getLocalizedMessage() != null )
301         {
302             msg.append( '\n' )
303                     .append( firstForkException.getLocalizedMessage() );
304         }
305 
306         if ( result.isFailure() )
307         {
308             msg.append( '\n' )
309                     .append( result.getFailure() );
310         }
311 
312         return msg.toString();
313     }
314 
315 }