View Javadoc
1   package org.apache.maven.surefire.testset;
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.shared.utils.StringUtils;
23  import org.apache.maven.shared.utils.io.MatchPatterns;
24  import org.apache.maven.shared.utils.io.SelectorUtils;
25  
26  import java.io.File;
27  
28  /**
29   * Single pattern test filter resolved from multi pattern filter -Dtest=MyTest#test,AnotherTest#otherTest.
30   */
31  public final class ResolvedTest
32  {
33      /**
34       * Type of patterns in ResolvedTest constructor.
35       */
36      public static enum Type
37      {
38          CLASS, METHOD
39      }
40  
41      private static final String CLASS_FILE_EXTENSION = ".class";
42  
43      private static final String WILDCARD_CLASS_FILE_EXTENSION = ".class";
44  
45      private static final String JAVA_FILE_EXTENSION = ".java";
46  
47      private static final String WILDCARD_PATH_PREFIX = "**/";
48  
49      private static final String WILDCARD_FILENAME_POSTFIX = ".*";
50  
51      private final String classPattern;
52  
53      private final String methodPattern;
54  
55      private final boolean isRegexTestClassPattern;
56  
57      private final boolean isRegexTestMethodPattern;
58  
59      private final String description;
60  
61      /**
62       * '*' means zero or more characters<br>
63       * '?' means one and only one character
64       * The pattern %regex[] prefix and suffix does not appear. The regex <code>pattern</code> is always
65       * unwrapped by the caller.
66       *
67       * @param classPattern     test class file pattern
68       * @param methodPattern    test method
69       * @param isRegex          {@code true} if regex
70       */
71      public ResolvedTest( String classPattern, String methodPattern, boolean isRegex )
72      {
73          classPattern = tryBlank( classPattern );
74          methodPattern = tryBlank( methodPattern );
75          description = description( classPattern, methodPattern, isRegex );
76  
77          if ( isRegex && classPattern != null )
78          {
79              classPattern = wrapRegex( classPattern );
80          }
81  
82          if ( isRegex && methodPattern != null )
83          {
84              methodPattern = wrapRegex( methodPattern );
85          }
86  
87          this.classPattern = reformatClassPattern( classPattern, isRegex );
88          this.methodPattern = methodPattern;
89          isRegexTestClassPattern = isRegex;
90          isRegexTestMethodPattern = isRegex;
91      }
92  
93      /**
94       * The regex <code>pattern</code> is always unwrapped.
95       */
96      public ResolvedTest( Type type, String pattern, boolean isRegex )
97      {
98          pattern = tryBlank( pattern );
99          final boolean isClass = type == Type.CLASS;
100         description = description( isClass ? pattern : null, !isClass ? pattern : null, isRegex );
101         if ( isRegex && pattern != null )
102         {
103             pattern = wrapRegex( pattern );
104         }
105         classPattern = isClass ? reformatClassPattern( pattern, isRegex ) : null;
106         methodPattern = !isClass ? pattern : null;
107         isRegexTestClassPattern = isRegex && isClass;
108         isRegexTestMethodPattern = isRegex && !isClass;
109     }
110 
111     /**
112      * Test class file pattern, e.g. org&#47;**&#47;Cat*.class<br/>, or null if not any
113      * and {@link #hasTestClassPattern()} returns false.
114      * Other examples: org&#47;animals&#47;Cat*, org&#47;animals&#47;Ca?.class, %regex[Cat.class|Dog.*]<br/>
115      * <br/>
116      * '*' means zero or more characters<br>
117      * '?' means one and only one character
118      */
119     public String getTestClassPattern()
120     {
121         return classPattern;
122     }
123 
124     public boolean hasTestClassPattern()
125     {
126         return classPattern != null;
127     }
128 
129     /**
130      * Test method, e.g. "realTestMethod".<br/>, or null if not any and {@link #hasTestMethodPattern()} returns false.
131      * Other examples: test* or testSomethin? or %regex[testOne|testTwo] or %ant[testOne|testTwo]<br/>
132      * <br/>
133      * '*' means zero or more characters<br>
134      * '?' means one and only one character
135      */
136     public String getTestMethodPattern()
137     {
138         return methodPattern;
139     }
140 
141     public boolean hasTestMethodPattern()
142     {
143         return methodPattern != null;
144     }
145 
146     public boolean isRegexTestClassPattern()
147     {
148         return isRegexTestClassPattern;
149     }
150 
151     public boolean isRegexTestMethodPattern()
152     {
153         return isRegexTestMethodPattern;
154     }
155 
156     public boolean isEmpty()
157     {
158         return classPattern == null && methodPattern == null;
159     }
160 
161     public boolean matchAsInclusive( String testClassFile, String methodName )
162     {
163         testClassFile = tryBlank( testClassFile );
164         methodName = tryBlank( methodName );
165 
166         return isEmpty() || alwaysInclusiveQuietly( testClassFile ) || match( testClassFile, methodName );
167     }
168 
169     public boolean matchAsExclusive( String testClassFile, String methodName )
170     {
171         testClassFile = tryBlank( testClassFile );
172         methodName = tryBlank( methodName );
173 
174         return !isEmpty() && canMatchExclusive( testClassFile, methodName ) && match( testClassFile, methodName );
175     }
176 
177     @Override
178     public boolean equals( Object o )
179     {
180         if ( this == o )
181         {
182             return true;
183         }
184         if ( o == null || getClass() != o.getClass() )
185         {
186             return false;
187         }
188 
189         ResolvedTest that = (ResolvedTest) o;
190 
191         return ( classPattern == null ? that.classPattern == null : classPattern.equals( that.classPattern ) )
192             && ( methodPattern == null ? that.methodPattern == null : methodPattern.equals( that.methodPattern ) );
193     }
194 
195     @Override
196     public int hashCode()
197     {
198         int result = classPattern != null ? classPattern.hashCode() : 0;
199         result = 31 * result + ( methodPattern != null ? methodPattern.hashCode() : 0 );
200         return result;
201     }
202 
203     @Override
204     public String toString()
205     {
206         return isEmpty() ? null : description;
207     }
208 
209     private static String description( String clazz, String method, boolean isRegex )
210     {
211         String description;
212         if ( clazz == null && method == null )
213         {
214             description = null;
215         }
216         else if ( clazz == null )
217         {
218             description = "#" + method;
219         }
220         else if ( method == null )
221         {
222             description = clazz;
223         }
224         else
225         {
226             description = clazz + "#" + method;
227         }
228         return isRegex && description != null ? wrapRegex( description ) : description;
229     }
230 
231     private boolean canMatchExclusive( String testClassFile, String methodName )
232     {
233         return testClassFile == null && methodName != null && classPattern == null && methodPattern != null
234             || testClassFile != null && methodName == null && classPattern != null && methodPattern == null
235             || testClassFile != null && methodName != null && ( classPattern != null || methodPattern != null );
236     }
237 
238     /**
239      * Prevents {@link #match(String, String)} from throwing NPE in situations when inclusive returns true.
240      */
241     private boolean alwaysInclusiveQuietly( String testClassFile )
242     {
243         return testClassFile == null && classPattern != null;
244     }
245 
246     private boolean match( String testClassFile, String methodName )
247     {
248         return ( classPattern == null || matchTestClassFile( testClassFile ) )
249             && ( methodPattern == null || methodName == null || matchMethodName( methodName ) );
250     }
251 
252     private boolean matchTestClassFile( String testClassFile )
253     {
254         return isRegexTestClassPattern ? matchClassRegexPatter( testClassFile ) : matchClassPatter( testClassFile );
255     }
256 
257     private boolean matchMethodName( String methodName )
258     {
259         return SelectorUtils.matchPath( methodPattern, methodName );
260     }
261 
262     private boolean matchClassPatter( String testClassFile )
263     {
264         //@todo We have to use File.separator only because the MatchPatterns is using it internally - cannot override.
265         String classPattern = this.classPattern;
266         if ( File.separatorChar != '/' )
267         {
268             testClassFile = testClassFile.replace( '/', File.separatorChar );
269             classPattern = classPattern.replace( '/', File.separatorChar );
270         }
271 
272         if ( classPattern.endsWith( WILDCARD_FILENAME_POSTFIX ) || classPattern.endsWith( CLASS_FILE_EXTENSION ) )
273         {
274             return MatchPatterns.from( classPattern ).matches( testClassFile, true );
275         }
276         else
277         {
278             String[] classPatterns = { classPattern + CLASS_FILE_EXTENSION, classPattern };
279             return MatchPatterns.from( classPatterns ).matches( testClassFile, true );
280         }
281     }
282 
283     private boolean matchClassRegexPatter( String testClassFile )
284     {
285         if ( File.separatorChar != '/' )
286         {
287             testClassFile = testClassFile.replace( '/', File.separatorChar );
288         }
289         return MatchPatterns.from( classPattern ).matches( testClassFile, true );
290     }
291 
292     private static String tryBlank( String s )
293     {
294         if ( s == null )
295         {
296             return null;
297         }
298         else
299         {
300             s = s.trim();
301             return StringUtils.isEmpty( s ) ? null : s;
302         }
303     }
304 
305     private static String reformatClassPattern( String s, boolean isRegex )
306     {
307         if ( s != null && !isRegex )
308         {
309             s = convertToPath( s );
310             s = fromFullyQualifiedClass( s );
311             if ( s != null && !s.startsWith( WILDCARD_PATH_PREFIX ) )
312             {
313                 s = WILDCARD_PATH_PREFIX + s;
314             }
315         }
316         return s;
317     }
318 
319     private static String convertToPath( String className )
320     {
321         if ( StringUtils.isBlank( className ) )
322         {
323             return null;
324         }
325         else
326         {
327             if ( className.endsWith( JAVA_FILE_EXTENSION ) )
328             {
329                 className = className.substring( 0, className.length() - JAVA_FILE_EXTENSION.length() );
330                 className += CLASS_FILE_EXTENSION;
331             }
332             return className;
333         }
334     }
335 
336     static String wrapRegex( String unwrapped )
337     {
338         return SelectorUtils.REGEX_HANDLER_PREFIX + unwrapped + SelectorUtils.PATTERN_HANDLER_SUFFIX;
339     }
340 
341     static String fromFullyQualifiedClass( String cls )
342     {
343         if ( cls.endsWith( CLASS_FILE_EXTENSION ) )
344         {
345             cls = cls.substring( 0, cls.length() - CLASS_FILE_EXTENSION.length() );
346             return cls.replace( '.', '/' ) + CLASS_FILE_EXTENSION;
347         }
348         else if ( !cls.contains( "/" ) )
349         {
350             if ( cls.endsWith( WILDCARD_CLASS_FILE_EXTENSION ) )
351             {
352                 String origin = cls;
353                 cls = cls.substring( 0, cls.length() - WILDCARD_CLASS_FILE_EXTENSION.length() );
354                 return cls.contains( "." ) ? cls.replace( '.', '/' ) + WILDCARD_CLASS_FILE_EXTENSION : origin;
355             }
356             else
357             {
358                 return cls.replace( '.', '/' );
359             }
360         }
361         else
362         {
363             return cls;
364         }
365     }
366 }