
com.googlecode.junittoolbox.WildcardPatternSuite Maven / Gradle / Ivy
package com.googlecode.junittoolbox;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.AbstractFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.junit.experimental.categories.Categories;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.regex.Pattern;
import static org.junit.experimental.categories.Categories.*;
/**
* A replacement for the JUnit runners {@link Suite} and {@link Categories},
* which allows you to specify the children classes of your test suite class
* using a wildcard pattern.
* Example:
* @RunWith(WildcardPatternSuite.class)
* @SuiteClasses("**/*Test.class")
* public class AllTests {}
*
* You can also specify multiple patterns as well as exclude patterns:
* @RunWith(WildcardPatternSuite.class)
* @SuiteClasses({"**/*Test.class", "!gui/**"})
* public class AllButGuiTests {}
*
* Because it is also a replacement for the {@link Categories} runner,
* you can use the {@link IncludeCategory @IncludeCategory} and
* {@link ExcludeCategory @ExcludeCategory} annotations too:
* @RunWith(WildcardPatternSuite.class)
* @SuiteClasses("**/*Test.class")
* @IncludeCategory(SlowTests.class)
* public class OnlySlowTests {}
*
*/
public class WildcardPatternSuite extends Suite {
private static Class>[] getSuiteClasses(Class> klass) throws InitializationError {
final org.junit.runners.Suite.SuiteClasses annotation1 = klass.getAnnotation(org.junit.runners.Suite.SuiteClasses.class);
final com.googlecode.junittoolbox.SuiteClasses annotation2 = klass.getAnnotation(com.googlecode.junittoolbox.SuiteClasses.class);
if (annotation1 == null && annotation2 == null) {
throw new InitializationError("class " + klass.getName() + " must have a SuiteClasses annotation");
}
final Class>[] suiteClasses1 = (annotation1 == null ? null : annotation1.value());
final Class>[] suiteClasses2 = (annotation2 == null ? null : findSuiteClasses(klass, annotation2.value()));
return union(suiteClasses1, suiteClasses2);
}
private static Class>[] findSuiteClasses(Class> klass, String... wildcardPatterns) throws InitializationError {
final File baseDir = getBaseDir(klass);
try {
final String basePath = baseDir.getCanonicalPath().replace('\\', '/');
final List includePatterns = new ArrayList();
final List excludePatterns = new ArrayList();
for (String wildcardPattern : wildcardPatterns) {
if (wildcardPattern == null) {
throw new InitializationError("wildcard pattern for the SuiteClasses annotation must not be null");
}
boolean exclude = wildcardPattern.startsWith("!");
if (exclude) {
wildcardPattern = wildcardPattern.substring(1);
}
if (wildcardPattern.startsWith("/")) {
throw new InitializationError("wildcard pattern for the SuiteClasses annotation must not start with a '/' character");
}
if (!exclude && !wildcardPattern.endsWith(".class")) {
throw new InitializationError("wildcard pattern for the SuiteClasses annotation must end with \".class\"");
}
Pattern regex = convertWildcardPatternToRegex("/" + wildcardPattern);
(exclude ? excludePatterns : includePatterns).add(regex);
}
final IOFileFilter fileFilter = new AbstractFileFilter() {
@Override
public boolean accept(File file) {
try {
// Never accept directories, hidden files, and inner classes ...
if (file.isDirectory() || file.isHidden() || file.getName().contains("$")) {
return false;
}
final String canonicalPath = file.getCanonicalPath().replace('\\', '/');
if (canonicalPath.startsWith(basePath)) {
final String path = canonicalPath.substring(basePath.length());
for (Pattern excludePattern : excludePatterns) {
if (excludePattern.matcher(path).matches()) {
return false;
}
}
for (Pattern includePattern : includePatterns) {
if (includePattern.matcher(path).matches()) {
return true;
}
}
return false;
} else {
return false;
}
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
};
final Collection classFiles = FileUtils.listFiles(baseDir, fileFilter, TrueFileFilter.INSTANCE);
if (classFiles.isEmpty()) {
throw new InitializationError("did not find any *.class file using the specified wildcard patterns " + Arrays.toString(wildcardPatterns) + " in directory " + basePath);
}
final String classNamePrefix = (klass.getPackage() == null ? "" : klass.getPackage().getName() + ".");
final Class>[] result = new Class>[classFiles.size()];
int i = 0;
final ClassLoader classLoader = klass.getClassLoader();
for (File file : classFiles) {
final String canonicalPath = file.getCanonicalPath().replace('\\', '/');
assert canonicalPath.startsWith(basePath) && canonicalPath.endsWith(".class");
final String path = canonicalPath.substring(basePath.length() + 1);
final String className = classNamePrefix + path.substring(0, path.length() - ".class".length()).replace('/', '.');
result[i++] = classLoader.loadClass(className);
}
return result;
} catch (Exception e) {
throw new InitializationError("failed to scan " + baseDir + " using wildcard patterns " + Arrays.toString(wildcardPatterns) + " -- " + e);
}
}
private static File getBaseDir(Class> klass) throws InitializationError {
final URL klassUrl = klass.getResource(klass.getSimpleName() + ".class");
try {
return new File(klassUrl.toURI()).getParentFile();
} catch (URISyntaxException e) {
throw new InitializationError("failed to determine directory of " + klass.getSimpleName() + ".class file: " + e.getMessage());
}
}
private static Pattern convertWildcardPatternToRegex(String wildCardPattern) throws InitializationError {
String s = wildCardPattern;
while (s.contains("***")) {
s = s.replace("***", "**");
}
String suffix;
if (s.endsWith("/**")) {
s = s.substring(0, s.length() - 3);
suffix = "(.*)";
} else {
suffix ="";
}
s = s.replace(".", "[.]");
s = s.replace("/**/", "/::/");
s = s.replace("*", "([^/]*)");
s = s.replace("/::/", "((/.*/)|(/))");
s = s.replace("?", ".");
if (s.contains("**")) {
throw new InitializationError("Invalid wildcard pattern \"" + wildCardPattern + "\"");
}
return Pattern.compile(s + suffix);
}
private static Class>[] union(Class>[] suiteClasses1, Class>[] suiteClasses2) {
if (suiteClasses1 == null) {
return suiteClasses2;
} else if (suiteClasses2 == null) {
return suiteClasses1;
} else {
final HashSet> temp = new HashSet>();
temp.addAll(Arrays.asList(suiteClasses1));
temp.addAll(Arrays.asList(suiteClasses2));
final Class>[] result = new Class>[temp.size()];
temp.toArray(result);
return result;
}
}
public WildcardPatternSuite(Class> klass, RunnerBuilder builder) throws InitializationError {
super(builder, klass, getSuiteClasses(klass));
IncludeCategory includeCategoryAnnotation= klass.getAnnotation(IncludeCategory.class);
Class> includedCategory = (includeCategoryAnnotation == null ? null : includeCategoryAnnotation.value());
ExcludeCategory excludeCategoryAnnotation= klass.getAnnotation(ExcludeCategory.class);
Class> excludedCategory = (excludeCategoryAnnotation == null ? null : excludeCategoryAnnotation.value());
if (includedCategory != null || excludedCategory != null) {
try {
filter(new CategoryFilter(includedCategory, excludedCategory));
} catch (NoTestsRemainException e) {
throw new InitializationError(e);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy