package play.test; import java.io.File; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.ListIterator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.Assert; import org.junit.runner.Description; import org.junit.runner.JUnitCore; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunListener; import play.Logger; import play.Play; import play.vfs.VirtualFile; /** * Run application tests */ public class TestEngine { private final static class ClassNameComparator implements Comparator<Class> { public int compare(Class aClass, Class bClass) { return aClass.getName().compareTo(bClass.getName()); } } private final static ClassNameComparator classNameComparator = new ClassNameComparator(); public static ExecutorService functionalTestsExecutor = Executors.newSingleThreadExecutor(); public static List<Class> allUnitTests() { List<Class> classes = Play.classloader.getAssignableClasses(Assert.class); for (ListIterator<Class> it = classes.listIterator(); it.hasNext();) { Class c = it.next(); if (Modifier.isAbstract(c.getModifiers())) { it.remove(); } else { if (FunctionalTest.class.isAssignableFrom(c)) { it.remove(); } } } Collections.sort(classes, classNameComparator); return classes; } public static List<Class> allFunctionalTests() { List<Class> classes = Play.classloader.getAssignableClasses(FunctionalTest.class); for (ListIterator<Class> it = classes.listIterator(); it.hasNext();) { if (Modifier.isAbstract(it.next().getModifiers())) { it.remove(); } } Collections.sort(classes, classNameComparator); return classes; } public static List<String> seleniumTests(String testPath, List<String> results) { File testDir = Play.getFile(testPath); if (testDir.exists()) { scanForSeleniumTests(testDir, results); } return results; } public static List<String> allSeleniumTests() { List<String> results = new ArrayList<String>(); seleniumTests("test", results); for (VirtualFile root : Play.roots) { seleniumTests(root.relativePath() + "/test", results); } Collections.sort(results); return results; } private static void scanForSeleniumTests(File dir, List<String> tests) { for (File f : dir.listFiles()) { if (f.isDirectory()) { scanForSeleniumTests(f, tests); } else if (f.getName().endsWith(".test.html")) { String test = f.getName(); while (!f.getParentFile().getName().equals("test")) { test = f.getParentFile().getName() + "/" + test; f = f.getParentFile(); } tests.add(test); } } } @SuppressWarnings("unchecked") public static TestResults run(final String name) { final TestResults testResults = new TestResults(); try { // Load test class final Class testClass = Play.classloader.loadClass(name); TestResults pluginTestResults = Play.pluginCollection.runTest(testClass); if (pluginTestResults != null) { return pluginTestResults; } JUnitCore junit = new JUnitCore(); junit.addListener(new Listener(testClass.getName(), testResults)); junit.run(testClass); } catch (ClassNotFoundException e) { Logger.error(e, "Test not found %s", name); } return testResults; } // ~~~~~~ Run listener static class Listener extends RunListener { TestResults results; TestResult current; String className; public Listener(String className, TestResults results) { this.results = results; this.className = className; } @Override public void testStarted(Description description) throws Exception { current = new TestResult(); current.name = description.getDisplayName().substring(0, description.getDisplayName().indexOf("(")); current.time = System.currentTimeMillis(); } @Override public void testFailure(Failure failure) throws Exception { if (current == null) { // The test probably failed before it could start, ie in @BeforeClass current = new TestResult(); results.add(current); // must add it here since testFinished() never was called. current.name = "Before any test started, maybe in @BeforeClass?"; current.time = System.currentTimeMillis(); } if (failure.getException() instanceof AssertionError) { current.error = "Failure, " + failure.getMessage(); } else { current.error = "A " + failure.getException().getClass().getName() + " has been caught, " + failure.getMessage(); current.trace = failure.getTrace(); } for (StackTraceElement stackTraceElement : failure.getException().getStackTrace()) { if (stackTraceElement.getClassName().equals(className)) { current.sourceInfos = "In " + Play.classes.getApplicationClass(className).javaFile.relativePath() + ", line " + stackTraceElement.getLineNumber(); current.sourceCode = Play.classes.getApplicationClass(className).javaSource.split("\n")[stackTraceElement.getLineNumber() - 1]; current.sourceFile = Play.classes.getApplicationClass(className).javaFile.relativePath(); current.sourceLine = stackTraceElement.getLineNumber(); } } current.passed = false; results.passed = false; } @Override public void testFinished(Description description) throws Exception { current.time = System.currentTimeMillis() - current.time; results.add(current); } } public static class TestResults { public List<TestResult> results = new ArrayList<TestResult>(); public boolean passed = true; public int success = 0; public int errors = 0; public int failures = 0; public long time = 0; public void add(TestResult result) { time = result.time + time; this.results.add(result); if (result.passed) { success++; } else { if (result.error.startsWith("Failure")) { failures++; } else { errors++; } } } } public static class TestResult { public String name; public String error; public boolean passed = true; public long time; public String trace; public String sourceInfos; public String sourceCode; public String sourceFile; public int sourceLine; } }