package com.sap.furcas.test.util; import java.io.File; import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import org.junit.Test; import org.junit.runners.Suite; import org.junit.runners.model.InitializationError; /** * Test runner class extending org.junit.runners.Suite. In addition to the * SuiteClasses annotation of Suite, a new annotation called SuitePackages was * added. Both annotations are mandatory, but can be empty. * * @author C5126871 * */ public class ExtSuite extends Suite { /** * This test runner will (recursively) gather all classes with at least one @Test * method from each of the listed packages. It is assumed, that classes are * compiled to the 'bin' folder and that the classes are deployed in folders * (not jar). * * Note: As the class loader of the annotated suite class is used, any * visible class is added, disregarding for example it's source folder. * * @author C5126871 * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface SuitePackages { public String[] value(); } public ExtSuite(Class<?> klass) throws Exception { super(klass, getAllClasses(klass)); } private static Class<?>[] getAllClasses(Class<?> klass) throws InitializationError { HashSet<Class<?>> result = new HashSet<Class<?>>(); result.addAll(getClassesInPackages(klass.getClassLoader(), getAnnotatedPackages(klass))); result.addAll(Arrays.asList(getAnnotatedClasses(klass))); ArrayList<Class<?>> sortedResult = new ArrayList<Class<?>>(result); Collections.sort(sortedResult, new Comparator<Class<?>>() { @Override public int compare(Class<?> a, Class<?> b) { return a.getName().compareTo(b.getName()); } }); return sortedResult.toArray(new Class<?>[0]); } private static List<Class<?>> getClassesInPackages(ClassLoader classLoader, String[] pckes) { ArrayList<Class<?>> result = new ArrayList<Class<?>>(); for (String pck : pckes) { try { result.addAll(getClassesInPackage(classLoader, pck)); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return result; } private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError { SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class); if (annotation == null) { throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName())); } return annotation.value(); } private static String[] getAnnotatedPackages(Class<?> klass) throws InitializationError { SuitePackages annotation = klass.getAnnotation(SuitePackages.class); if (annotation == null) { throw new InitializationError(String.format("class '%s' must have a SuitePackages annotation", klass.getName())); } return annotation.value(); } /** * Scans all classes accessible from the context class loader which belong * to the given package and subpackages. * * @param packageName * The base package * @return The classes * @throws ClassNotFoundException * @throws IOException */ private static List<Class<?>> getClassesInPackage(ClassLoader classLoader, String packageName) throws ClassNotFoundException, IOException { assert classLoader != null; String path = packageName.replace('.', '/'); Enumeration<URL> resources = classLoader.getResources(path); List<File> dirs = new ArrayList<File>(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); String fullPath = ""; if (resource.getProtocol().equals("bundleresource")) { // relative platform path fullPath = "bin" + resource.getPath(); } else if (resource.getProtocol().equals("file")) { // absolute platform path fullPath = resource.getPath(); } else { System.out.println("ExtSuite is missing support for resource of protocol " + resource.getProtocol() + " (" + resource.getPath() + ")"); continue; } // ignore folder starting with '.' (i.e '.svn') if (fullPath.endsWith("/") || !(fullPath.charAt(fullPath.lastIndexOf("/") + 1) == '.')) { dirs.add(new File(fullPath.replace("%20", " "))); } } ArrayList<Class<?>> classes = new ArrayList<Class<?>>(); for (File directory : dirs) { classes.addAll(findClasses(directory, packageName)); } return classes; } /** * Recursive method used to find all classes in a given directory and * subdirs. * * @param directory * The base directory * @param packageName * The package name for classes found inside the base directory * @return The classes * @throws ClassNotFoundException */ private static List<Class<?>> findClasses(File directory, String packageName) throws ClassNotFoundException { List<Class<?>> classes = new ArrayList<Class<?>>(); if (!directory.exists()) { return classes; } File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) { assert !file.getName().contains("."); classes.addAll(findClasses(file, packageName + "." + file.getName())); } else if (file.getName().endsWith(".class")) { Class<?> candidate = Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)); // find at least one @Test method for (Method method : candidate.getMethods()) { if (method.getAnnotation(Test.class) != null) { classes.add(candidate); break; } } } } return classes; } }