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 java.lang.reflect.InvocationTargetException;
33  import java.lang.reflect.Method;
34  import java.util.ArrayList;
35  import java.util.Collection;
36  import java.util.List;
37  
38  import static java.util.Collections.unmodifiableList;
39  import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
40  import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMPSTREAM_FILE_EXT;
41  import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMP_FILE_EXT;
42  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG;
43  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_ERROR;
44  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_INFO;
45  import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_WARN;
46  import static org.apache.maven.surefire.cli.CommandLineOption.SHOW_ERRORS;
47  
48  /**
49   * Helper class for surefire plugins
50   */
51  public final class SurefireHelper
52  {
53      private static final String DUMP_FILE_DATE = DumpFileUtils.newFormattedDateFileName();
54  
55      public static final String DUMP_FILE_PREFIX = DUMP_FILE_DATE + "-jvmRun";
56  
57      public static final String DUMPSTREAM_FILENAME_FORMATTER = DUMP_FILE_PREFIX + "%d" + DUMPSTREAM_FILE_EXT;
58  
59      /**
60       * The maximum path that does not require long path prefix on Windows.<br>
61       * See {@code sun/nio/fs/WindowsPath} in
62       * <a href=
63       * "http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/7534523b4174/src/windows/classes/sun/nio/fs/WindowsPath.java#l46">
64       * OpenJDK</a>
65       * and <a href="https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath">MSDN article</a>.
66       * <br>
67       * The maximum path is 260 minus 1 (NUL) but for directories it is 260
68       * minus 12 minus 1 (to allow for the creation of a 8.3 file in the directory).
69       */
70      private static final int MAX_PATH_LENGTH_WINDOWS = 247;
71  
72      private static final String[] DUMP_FILES_PRINT =
73              {
74                      "[date]-jvmRun[N]" + DUMP_FILE_EXT,
75                      "[date]" + DUMPSTREAM_FILE_EXT,
76                      "[date]-jvmRun[N]" + DUMPSTREAM_FILE_EXT
77              };
78  
79      /**
80       * Do not instantiate.
81       */
82      private SurefireHelper()
83      {
84          throw new IllegalAccessError( "Utility class" );
85      }
86  
87      public static String[] getDumpFilesToPrint()
88      {
89          return DUMP_FILES_PRINT.clone();
90      }
91  
92      public static void reportExecution( SurefireReportParameters reportParameters, RunResult result,
93                                          PluginConsoleLogger log, Exception firstForkException )
94          throws MojoFailureException, MojoExecutionException
95      {
96          if ( firstForkException == null && !result.isTimeout() && result.isErrorFree() )
97          {
98              if ( result.getCompletedCount() == 0 && failIfNoTests( reportParameters ) )
99              {
100                 throw new MojoFailureException( "No tests were executed!  "
101                                                         + "(Set -DfailIfNoTests=false to ignore this error.)" );
102             }
103             return;
104         }
105 
106         if ( reportParameters.isTestFailureIgnore() )
107         {
108             log.error( createErrorMessage( reportParameters, result, firstForkException ) );
109         }
110         else
111         {
112             throwException( reportParameters, result, firstForkException );
113         }
114     }
115 
116     public static List<CommandLineOption> commandLineOptions( MavenSession session, PluginConsoleLogger log )
117     {
118         List<CommandLineOption> cli = new ArrayList<CommandLineOption>();
119         if ( log.isErrorEnabled() )
120         {
121             cli.add( LOGGING_LEVEL_ERROR );
122         }
123 
124         if ( log.isWarnEnabled() )
125         {
126             cli.add( LOGGING_LEVEL_WARN );
127         }
128 
129         if ( log.isInfoEnabled() )
130         {
131             cli.add( LOGGING_LEVEL_INFO );
132         }
133 
134         if ( log.isDebugEnabled() )
135         {
136             cli.add( LOGGING_LEVEL_DEBUG );
137         }
138 
139         try
140         {
141             Method getRequestMethod = session.getClass().getMethod( "getRequest" );
142             MavenExecutionRequest request = (MavenExecutionRequest) getRequestMethod.invoke( session );
143 
144             if ( request.isShowErrors() )
145             {
146                 cli.add( SHOW_ERRORS );
147             }
148 
149             String f = getFailureBehavior( request );
150             if ( f != null )
151             {
152                 // compatible with enums Maven 3.0
153                 cli.add( CommandLineOption.valueOf( f.startsWith( "REACTOR_" ) ? f : "REACTOR_" + f ) );
154             }
155         }
156         catch ( Exception e )
157         {
158             // don't need to log the exception that Maven 2 does not have getRequest() method in Maven Session
159         }
160         return unmodifiableList( cli );
161     }
162 
163     public static void logDebugOrCliShowErrors( String s, PluginConsoleLogger log, Collection<CommandLineOption> cli )
164     {
165         if ( cli.contains( LOGGING_LEVEL_DEBUG ) )
166         {
167             log.debug( s );
168         }
169         else if ( cli.contains( SHOW_ERRORS ) )
170         {
171             if ( log.isDebugEnabled() )
172             {
173                 log.debug( s );
174             }
175             else
176             {
177                 log.info( s );
178             }
179         }
180     }
181 
182     /**
183      * Escape file path for Windows when the path is too long; otherwise returns {@code path}.
184      * <br>
185      * See <a href=
186      * "http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/7534523b4174/src/windows/classes/sun/nio/fs/WindowsPath.java#l46">
187      * sun/nio/fs/WindowsPath</a> for "long path" value explanation (=247), and
188      * <a href="https://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath">MSDN article</a>
189      * for detailed escaping strategy explanation: in short, {@code \\?\} prefix for path with drive letter
190      * or {@code \\?\UNC\} for UNC path.
191      *
192      * @param path    source path
193      * @return escaped to platform path
194      */
195     public static String escapeToPlatformPath( String path )
196     {
197         if ( IS_OS_WINDOWS && path.length() > MAX_PATH_LENGTH_WINDOWS )
198         {
199             path = path.startsWith( "\\\\" ) ? "\\\\?\\UNC\\" + path.substring( 2 ) : "\\\\?\\" + path;
200         }
201         return path;
202     }
203 
204     private static String getFailureBehavior( MavenExecutionRequest request )
205         throws NoSuchMethodException, InvocationTargetException, IllegalAccessException
206     {
207         try
208         {
209             return request.getFailureBehavior();
210         }
211         catch ( NoSuchMethodError e )
212         {
213             return (String) request.getClass()
214                 .getMethod( "getReactorFailureBehavior" )
215                 .invoke( request );
216         }
217     }
218 
219     private static boolean failIfNoTests( SurefireReportParameters reportParameters )
220     {
221         return reportParameters.getFailIfNoTests() != null && reportParameters.getFailIfNoTests();
222     }
223 
224     private static boolean isFatal( Exception firstForkException )
225     {
226         return firstForkException != null && !( firstForkException instanceof TestSetFailedException );
227     }
228 
229     private static void throwException( SurefireReportParameters reportParameters, RunResult result,
230                                            Exception firstForkException )
231             throws MojoFailureException, MojoExecutionException
232     {
233         if ( isFatal( firstForkException ) || result.isInternalError()  )
234         {
235             throw new MojoExecutionException( createErrorMessage( reportParameters, result, firstForkException ),
236                                                     firstForkException );
237         }
238         else
239         {
240             throw new MojoFailureException( createErrorMessage( reportParameters, result, firstForkException ),
241                                                   firstForkException );
242         }
243     }
244 
245     private static String createErrorMessage( SurefireReportParameters reportParameters, RunResult result,
246                                               Exception firstForkException )
247     {
248         StringBuilder msg = new StringBuilder( 512 );
249 
250         if ( result.isTimeout() )
251         {
252             msg.append( "There was a timeout or other error in the fork" );
253         }
254         else
255         {
256             msg.append( "There are test failures.\n\nPlease refer to " )
257                     .append( reportParameters.getReportsDirectory() )
258                     .append( " for the individual test results." )
259                     .append( '\n' )
260                     .append( "Please refer to dump files (if any exist) " )
261                     .append( DUMP_FILES_PRINT[0] )
262                     .append( ", " )
263                     .append( DUMP_FILES_PRINT[1] )
264                     .append( " and " )
265                     .append( DUMP_FILES_PRINT[2] )
266                     .append( "." );
267         }
268 
269         if ( firstForkException != null && firstForkException.getLocalizedMessage() != null )
270         {
271             msg.append( '\n' )
272                     .append( firstForkException.getLocalizedMessage() );
273         }
274 
275         if ( result.isFailure() )
276         {
277             msg.append( '\n' )
278                     .append( result.getFailure() );
279         }
280 
281         return msg.toString();
282     }
283 
284 }