package uk.ac.lkl.migen;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
/*
*
*
*
*
*
*
* DO NOT USE THIS CLASS
*
* IT IS DEPRECATED
*
* USE BatchTester INSTEAD.
*
*
*
*
*
*
*
*
*/
/*
* A Tester that looks recursively for all tests in the
* codebase hierarchy, and then runs them.
*
* Implementation note: this relies on test classes being called *Test*.
*
* @author sergut
*/
/*
* Implementation note:
* An alternative approach to just look for "*Test*java" files might be
* might be to get the classes, do some reflection to find @Test methods,
* and then run them, but I am not sure whether it would work.
*/
@Deprecated
public class MiGenTester implements Runnable {
/**
* The initial directory in which to look for JUnits tests
*/
private File initialDir;
/**
* The JUnit tests' results in a string
*/
private String resultsAsString;
/**
* String to distinguish where classes are
*/
private static final String INITIAL_PREFIX = "uk.ac.lkl.";
/**
* Additional information, if any
*/
String additional = "";
/**
* Filters out files that are not JUnit test files.
*
* @author sergut
*/
private class TestClassFilenameFilter implements FilenameFilter {
/*
* Modify this at compile time to make a reduced run of tests.
*
* Possible values are names of specific packages, e.g. common, system, cdst, expresser, etc
*/
private String packageFilter = null;
@Override
public boolean accept(File dir, String name) {
if (!name.startsWith("Test") && !name.endsWith("Test.class"))
return false;
if (!name.contains(".class"))
return false;
if (name.contains("$"))
return false;
if (packageFilter != null && !dir.getAbsolutePath().contains(packageFilter))
return false;
if (dir.getAbsolutePath().contains("server"))
return false; // Hack while developing. FIXME: fix server tests and remove this
return true;
}
}
/**
* Filters out directories that cannot be read or are hidden (like .svn).
*
* @author sergut
*/
private class DirectoryFilter implements FileFilter {
@Override
public boolean accept(File directory) {
if (!directory.isDirectory())
return false;
if (!directory.canRead())
return false;
if (directory.isHidden())
return false;
return true;
}
}
/**
* A new MiGenTester.
*
* @param initialDirectoryName initial directory in which to look for tests
*/
public MiGenTester(String initialDirectoryName) {
this.initialDir = new File(initialDirectoryName);
if (!initialDir.isDirectory() || !initialDir.canRead()) {
throw new IllegalArgumentException("'" + initialDirectoryName + " is not a valid directory.");
}
this.resultsAsString = "";
}
/**
* Finds all tests in the given dir.
*
* @param thisDir the given dir
*
* @return a list of tests
*/
private Set<Class<?>> findTestClassesInDir(File thisDir) {
if (!thisDir.isDirectory())
return new HashSet<Class<?>>();
Set<Class<?>> result = new HashSet<Class<?>>();
// 1. Look recursively in directories
File[] directories = thisDir.listFiles(new DirectoryFilter());
for (File directory : directories) {
Set<Class<?>> testClassesInDescendants = findTestClassesInDir(directory);
for (Class<?> testClass : testClassesInDescendants) {
result.add(testClass);
}
}
// 2. Look for files "*Test*class" in current directory, with some caveats (bellow)
File[] testFiles = thisDir.listFiles(new TestClassFilenameFilter());
for (File testFile : testFiles) {
String testFilename = testFile.getPath();
try {
String testClassName = toClassName(testFilename);
Class<?> testClass = Class.forName(testClassName);
// Abstract classes and interfaces are not valid
if (Modifier.isAbstract(testClass.getModifiers()) && !testClass.isInterface())
throw new IllegalArgumentException("Cannot instantiate abstract class '"+testClassName+"', so it is not valid for JUnit tests.");
else if (testClass.isInterface())
continue;
else {
// Finally, check whether there is at least one @Test in this test class
Method[] methods = testClass.getMethods();
for (int i = 0; i < methods.length; i++) {
if (methodIsTestMethod(methods[i])) {
result.add(testClass);
break;
}
}
}
} catch (ClassNotFoundException e) {
resultsAsString += "ERROR: Class " + testFilename + " could not be instantiated.";
} catch (IllegalArgumentException e) {
resultsAsString += "ERROR: File " + testFilename + " does not contain a valid class.";
}
}
return result;
}
/**
* Returns true if the method is annotated as test (i.e. org.unit.Test),
* false otherwise.
*
* @param method the method
*
* @return true if the method is annotated as test, false otherwise.
*/
private boolean methodIsTestMethod(Method method) {
Annotation[] annotations = method.getAnnotations();
for (int j = 0; j < annotations.length; j++) {
String annotationName = annotations[j].annotationType().getCanonicalName();
if ("org.junit.Test".equals(annotationName))
return true;
}
return false;
}
/**
* Converts the given class filename into a class name, ready for instantiation (i.e. Class.forName(name)).
*
* E.g.: uk/ac/lkl/migen/system/test/TestMyClass.class -> uk.ac.lkl.migen.system.test.TestMyClass
*
* @param filename a class file name
*
* @return the given filename into a class name
*/
private String toClassName(String filename) {
String result = filename.replaceAll(File.separator, ".");
int startIdx = result.indexOf(INITIAL_PREFIX);
int endIdx = result.length() - ".class".length();
if (startIdx < 0 || endIdx < 0 || endIdx < startIdx) {
throw new IllegalArgumentException("Invalid class file name.");
}
result = result.substring(startIdx, endIdx);
return result;
}
/**
* Executes all tests, and stores the results in the general result,
* i.e. a string that can be retrieved with getResultsAsString().
*/
public void run() {
int totalCount = 0;
int totalFailureCount = 0;
long totalTimeCount = 0;
Set<Class<?>> testClassesList = findTestClassesInDir(initialDir);
int size = testClassesList.size();
if (size == 0) {
reportAbsenceOfTests();
return;
}
JUnitCore jUnitCore = new JUnitCore();
List<PassedTest> passedTestList = new ArrayList<PassedTest>();
List<FailedTest> failedTestList = new ArrayList<FailedTest>();
for (Class<?> cl : testClassesList) {
Result result = jUnitCore.run(cl);
String testClassName = cl.getCanonicalName();
int failureCount = result.getFailureCount();
int testCount = result.getRunCount();
if (failureCount == 0) {
passedTestList.add(new PassedTest(testClassName, testCount));
} else {
failedTestList.add(new FailedTest(testClassName, testCount, failureCount, result.getFailures()));
for (Failure f : result.getFailures()) {
additional += "Description: " + f.getDescription() + "\n";
additional += "Message: " + f.getMessage() + "\n";
additional += "Trace: " + f.getTrace() + "\n";
}
}
totalCount += testCount;
totalFailureCount += failureCount;
totalTimeCount += result.getRunTime();
}
if (totalFailureCount == 0) {
reportSuccess();
} else {
reportFailure(passedTestList, failedTestList);
}
resultsAsString += "\nTotal number of tests failed: " + totalFailureCount + " (out of " + totalCount + ").";
resultsAsString += "\nTime: " + totalTimeCount/1000 + "s\n";
if (true) // might make this configurable - TODO
System.out.println("\n\n\n" + additional);
}
private void reportSuccess() {
resultsAsString += "\n\n\nAll tests run successfully. Congratulations!\n";
}
/**
* Builds a general report on the failed tests, adds it to the result.
*
* @param passedTestList the list of passed tests
* @param failedTestList the list of failed tests
*/
private void reportFailure(List<PassedTest> passedTestList, List<FailedTest> failedTestList) {
int passedTestCount = passedTestList.size();
int failedTestCount = failedTestList.size();
resultsAsString += "\nResults for " + initialDir + ":\n";
resultsAsString += "- Total test classes: " + (passedTestCount + failedTestCount) + "\n";
resultsAsString += "- Test classes that pass all tests: " + passedTestCount + "\n";
resultsAsString += "- Test classes that fail one or more tests: " + failedTestCount + "\n";
for (FailedTest failedTest : failedTestList) {
resultsAsString += failedTest;
}
}
/**
* Adds information about a list of failed tests to the general result.
*
* NOTE: Not clear if needed, because this is lost among the verbosity of the MiGen code.
*
* @param failures the list of failed tests
*/
@SuppressWarnings("unused")
private void reportOnTestFailure(List<Failure> failures) {
for (Failure failure : failures) {
resultsAsString += "Header: " + failure.getTestHeader() + "\n";
resultsAsString += "Message: " + failure.getMessage() + "\n";
resultsAsString += "Stack trace: " + failure.getTrace() + "\n";
}
}
private void reportAbsenceOfTests() {
resultsAsString += "There are no tests to run.\n";
}
private String getResultAsString() {
return resultsAsString;
}
private static void usage() {
System.out.println("USAGE: MiGenTester [<directory>]");
}
public static void main(String args[]) {
try {
new JUnitCore(); // If JUnit4 is not in CLASSPATH, abort immediately (i.e. throw NoClassDefFoundError)
MiGenTester tester = null;
switch (args.length) {
case 0:
String defaultDirName = "./bin/";
File dir = new File(defaultDirName);
if (dir.exists() && dir.isDirectory())
tester = new MiGenTester(defaultDirName);
else
throw new IllegalStateException("Could not find " + defaultDirName + " directory.");
break;
case 1:
tester = new MiGenTester(args[0]);
break;
default:
usage();
System.exit(0);
}
tester.run();
System.out.println(tester.getResultAsString());
System.exit(0);
} catch (NoClassDefFoundError e) {
System.out.println("ERROR: Please add JUnit4 to your class path and try again.");
System.exit(-1);
} catch (IllegalStateException e) {
e.printStackTrace();
usage();
System.exit(-1);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Unexpected error. Quitting...");
System.exit(2);
}
}
class PassedTest {
/**
* The name of the class with the tests
*/
private String className;
/**
* Number of tests in the class
*/
private int totalTestCount;
public PassedTest(String className, int totalTestCount) {
this.className = className;
this.totalTestCount = totalTestCount;
}
@Override
public String toString() {
return className + "(" + totalTestCount + " OK)";
}
public int getTotalTestCount() {
return totalTestCount;
}
public String getClassName() {
return className;
}
}
class FailedTest {
/**
* The name of the class with the tests
*/
private String className;
/**
* Number of tests in the class
*/
private int totalTestCount;
/**
* Number of tests in the class that failed
*/
private int failedTestCount;
/**
* Names of the tests in the class that failed
*/
private List<String> failedTestNameList;
public FailedTest(String className, int totalTestCount, int failedTestCount, List<Failure> failures) {
this.className = className;
this.totalTestCount = totalTestCount;
this.failedTestCount = failedTestCount;
this.failedTestNameList = new ArrayList<String>();
for (Failure failure : failures) {
String failedTestName = failure.getTestHeader().split("\\(")[0];
failedTestNameList.add(failedTestName);
}
}
@Override
public String toString() {
String result = " * " + className + "(" + failedTestCount + "/" + totalTestCount + ")";
for (String name : failedTestNameList)
result += "\n - " + name;
result += "\n";
return result;
}
public String getClassName() {
return className;
}
public int getTotalTestCount() {
return totalTestCount;
}
public int getFailedTestCount() {
return failedTestCount;
}
}
}
|