MiGenTester.java :  » Math » migen » uk » ac » lkl » migen » Java Open Source

Java Open Source » Math » migen 
migen » uk » ac » lkl » migen » MiGenTester.java
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;
  }
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.