NIHVIVO-2811 Restructure the build utilities into a single directory, and compile them all at the beginning.

This commit is contained in:
j2blake 2011-09-12 19:44:32 +00:00
parent 60e92e9bf0
commit 0e4441935e
14 changed files with 113 additions and 147 deletions

View file

@ -0,0 +1,84 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.anttasks;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.PatternSet;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.apache.tools.ant.types.resources.FileResource;
/**
* A base class for our custom-made FileSet extensions.
*/
public abstract class AbstractWrappedFileSet implements ResourceCollection {
private Project p;
/** The list of FileResources that we will yield to the task. */
protected List<FileResource> files;
/** The internal FileSet */
private FileSet fileSet = new FileSet();
public void setProject(Project p) {
this.p = p;
fileSet.setProject(p);
}
public void setDir(File dir) {
fileSet.setDir(dir);
}
public PatternSet.NameEntry createInclude() {
return fileSet.createInclude();
}
public PatternSet.NameEntry createExclude() {
return fileSet.createExclude();
}
public PatternSet createPatternSet() {
return fileSet.createPatternSet();
}
@Override
public Object clone() {
throw new BuildException(this.getClass().getSimpleName()
+ " does not support cloning.");
}
@Override
public boolean isFilesystemOnly() {
return true;
}
@Override
public Iterator<? extends Resource> iterator() {
fillFileList();
return files.iterator();
}
@Override
public int size() {
fillFileList();
return files.size();
}
protected abstract void fillFileList();
protected Project getProject() {
return p;
}
protected FileSet getInternalFileSet() {
return fileSet;
}
}

View file

@ -0,0 +1,59 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.anttasks;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.resources.FileResource;
/**
* TODO
*/
public class DirDifferenceFileSet extends AbstractWrappedFileSet {
private Path blockingPath;
public Path createBlockingPath() {
if (blockingPath == null) {
blockingPath = new Path(getProject());
}
return blockingPath.createPath();
}
@Override
protected void fillFileList() {
if (files != null) {
return;
}
FileSet fs = getInternalFileSet();
@SuppressWarnings("unchecked")
Iterator<FileResource> iter = fs.iterator();
files = new ArrayList<FileResource>();
while (iter.hasNext()) {
FileResource fr = iter.next();
if (!isBlocked(fr)) {
files.add(fr);
}
}
}
/**
* Check to see whether this same file exists in any of the blocking
* directories.
*/
private boolean isBlocked(FileResource fr) {
for (String blockingDir : blockingPath.list()) {
File f = new File(blockingDir + File.separator + fr.getName());
if (f.exists()) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,123 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parse the response that we got from SVN info.
*
* Not thread-safe.
*/
public class InfoResponseParser {
private static final Pattern URL_PATTERN = Pattern.compile("URL: (\\S+)");
private static final Pattern ROOT_PATTERN = Pattern
.compile("Repository Root: (\\S+)");
private static final String TRUNK_PREFIX = "/trunk";
private static final String TAGS_PREFIX = "/tags/";
private static final String BRANCHES_PREFIX = "/branches/";
private final String infoResponse;
private String path;
public InfoResponseParser(String infoResponse) {
this.infoResponse = infoResponse;
}
public String parse() {
try {
path = figurePath();
if (isTrunkPath()) {
return "trunk";
} else if (isTagPath()) {
return "tag " + getTagName();
} else if (isBranchPath()) {
return "branch " + getBranchName();
} else {
return null;
}
} catch (Exception e) {
System.err.println(e);
return null;
}
}
private String figurePath() throws Exception {
if (infoResponse == null) {
throw new Exception("infoResponse is null.");
}
String url = getUrlFromResponse();
String root = getRootFromResponse();
if (!url.startsWith(root)) {
throw new Exception("url doesn't start with root.");
}
return url.substring(root.length());
}
private String getUrlFromResponse() throws Exception {
return findNonEmptyMatch(URL_PATTERN, 1);
}
private String getRootFromResponse() throws Exception {
return findNonEmptyMatch(ROOT_PATTERN, 1);
}
private String findNonEmptyMatch(Pattern pattern, int groupIndex)
throws Exception {
Matcher matcher = pattern.matcher(infoResponse);
if (!matcher.find()) {
throw new Exception("no match with '" + pattern + "'. Is your Subversion client out of date?");
}
String value = matcher.group(groupIndex);
if ((value == null) || (value.isEmpty())) {
throw new Exception("match with '" + pattern + "' is empty.");
}
return value;
}
private boolean isTrunkPath() {
return path.startsWith(TRUNK_PREFIX);
}
private boolean isTagPath() {
return path.startsWith(TAGS_PREFIX);
}
private String getTagName() {
return getFirstLevel(discardPrefix(path, TAGS_PREFIX));
}
private boolean isBranchPath() {
return path.startsWith(BRANCHES_PREFIX);
}
private String getBranchName() {
return getFirstLevel(discardPrefix(path, BRANCHES_PREFIX));
}
private String discardPrefix(String string, String prefix) {
if (string.length() < prefix.length()) {
return "";
} else {
return string.substring(prefix.length());
}
}
private String getFirstLevel(String string) {
int slashHere = string.indexOf('/');
if (slashHere == -1) {
return string;
} else {
return string.substring(0, slashHere);
}
}
}

View file

@ -0,0 +1,161 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A harness that runs a system-level command.
*
* No provision is made for standard input.
*
* The standard output and standard error streams are asynchronously read, so
* the sub-process will not block on full buffers. Warning: if either of these
* streams contain more data than can fit into a String, then we will have a
* problem.
*/
public class ProcessRunner {
private int returnCode;
private String stdOut = "";
private String stdErr = "";
private File workingDirectory;
private final Map<String, String> environmentAdditions = new HashMap<String, String>();
/** Set the directory that the command will run in. */
public void setWorkingDirectory(File workingDirectory) {
this.workingDirectory = workingDirectory;
}
/** Add (or replace) any environment variable. */
public void setEnvironmentVariable(String key, String value) {
this.environmentAdditions.put(key, value);
}
/**
* Run the command.
*
* @param command
* a list containing the operating system program and its
* arguments. See
* {@link java.lang.ProcessBuilder#ProcessBuilder(List)}.
*/
public void run(List<String> command) throws ProcessException {
try {
ProcessBuilder builder = new ProcessBuilder(command);
if (workingDirectory != null) {
builder.directory(workingDirectory);
}
if (!environmentAdditions.isEmpty()) {
builder.environment().putAll(this.environmentAdditions);
}
Process process = builder.start();
StreamEater outputEater = new StreamEater(process.getInputStream());
StreamEater errorEater = new StreamEater(process.getErrorStream());
this.returnCode = process.waitFor();
outputEater.join();
this.stdOut = outputEater.getContents();
errorEater.join();
this.stdErr = errorEater.getContents();
} catch (IOException e) {
throw new ProcessException("Exception when handling sub-process:",
e);
} catch (InterruptedException e) {
throw new ProcessException("Exception when handling sub-process:",
e);
}
}
public int getReturnCode() {
return returnCode;
}
public String getStdErr() {
return stdErr;
}
public String getStdOut() {
return stdOut;
}
/**
* A thread that reads an InputStream until it reaches end of file, then
* closes the stream.
*/
private static class StreamEater extends Thread {
private final InputStream stream;
private final StringWriter contents = new StringWriter();
private final byte[] buffer = new byte[4096];
public StreamEater(InputStream stream) {
this.stream = stream;
this.start();
}
@Override
public void run() {
try {
int howMany = 0;
while (true) {
howMany = stream.read(buffer);
if (howMany > 0) {
contents.write(new String(buffer, 0, howMany));
} else if (howMany == 0) {
Thread.yield();
} else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String getContents() {
return contents.toString();
}
}
/**
* Indicates a problem when dealing with a spawned sub-process.
*/
public static class ProcessException extends Exception {
public ProcessException() {
}
public ProcessException(String message) {
super(message);
}
public ProcessException(Throwable cause) {
super(cause);
}
public ProcessException(String message, Throwable cause) {
super(message, cause);
}
}
}

View file

@ -0,0 +1,230 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.revisioninfo;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import edu.cornell.mannlib.vitro.utilities.revisioninfo.ProcessRunner.ProcessException;
/**
* Get release and revision information to display on screen. Put this
* information into a single line and append it to the specified file.
*
* Ask Subversion for the information. If Subversion is available, and if this
* is a working directory, then we can build the info from the responses we get
* from "svn info" and "svnversion".
*
* If that doesn't work, read the information from the "revisionInfo" file in
* the product directory. Presumably, that file was created when the source was
* exported from Subversion.
*
* If that doesn't work either, return something like this:
* "productName ~ unknown ~ unknown"
*/
public class RevisionInfoBuilder {
/**
* Indicates a problem with the command-line arguments.
*/
private static class UsageException extends Exception {
UsageException(String message) {
super(message);
}
}
/**
* An object that holds the revision information and a message about how we
* obtained it.
*/
private static class Results {
final String message;
final String infoLine;
Results(String message, String infoLine) {
this.message = message;
this.infoLine = infoLine;
}
public String toString() {
return message + ": " + infoLine;
}
}
private static final String SVN_DIRECTORY_NAME = ".svn";
private static final String[] SVNVERSION_COMMAND = { "svnversion", "." };
private static final String[] SVN_INFO_COMMAND = { "svn", "info" };
private static final String INFO_LINE_DELIMITER = " ~ ";
private static final String REVISION_INFO_FILENAME = "revisionInfo";
private final String productName;
private final File productDirectory;
private final File resultFile;
private Results results;
public RevisionInfoBuilder(String[] args) throws UsageException {
if (args.length != 3) {
throw new UsageException(
"RevisionInfoBuilder requires 3 arguments, not "
+ args.length);
}
productName = args[0];
productDirectory = new File(args[1]);
resultFile = new File(args[2]);
if (!productDirectory.isDirectory()) {
throw new UsageException("Directory '"
+ productDirectory.getAbsolutePath() + "' does not exist.");
}
if (!resultFile.getParentFile().exists()) {
throw new UsageException("Result file '"
+ resultFile.getAbsolutePath()
+ "' does not exist, and we can't create it "
+ "because it's parent directory doesn't exist either.");
}
}
private void buildInfo() {
results = buildInfoFromSubversion();
if (results == null) {
results = buildInfoFromFile();
}
if (results == null) {
results = buildDummyInfo();
}
}
private Results buildInfoFromSubversion() {
if (!isThisASubversionWorkspace()) {
System.out.println("Not a Subversion workspace");
return null;
}
String release = assembleReleaseNameFromSubversion();
if (release == null) {
System.out.println("Couldn't get release name from Subversion");
return null;
}
String revision = obtainRevisionLevelFromSubversion();
if (revision == null) {
System.out.println("Couldn't get revision level from Subversion");
return null;
}
return new Results("Info from Subversion", buildLine(release, revision));
}
private boolean isThisASubversionWorkspace() {
File svnDirectory = new File(productDirectory, SVN_DIRECTORY_NAME);
return svnDirectory.isDirectory();
}
private String assembleReleaseNameFromSubversion() {
String infoResponse = runSubProcess(SVN_INFO_COMMAND);
return new InfoResponseParser(infoResponse).parse();
}
private String obtainRevisionLevelFromSubversion() {
String response = runSubProcess(SVNVERSION_COMMAND);
return (response == null) ? null : response.trim();
}
private String runSubProcess(String[] cmdArray) {
List<String> command = Arrays.asList(cmdArray);
try {
ProcessRunner runner = new ProcessRunner();
runner.setWorkingDirectory(productDirectory);
runner.run(command);
int rc = runner.getReturnCode();
if (rc != 0) {
throw new ProcessRunner.ProcessException("Return code from "
+ command + " was " + rc);
}
String output = runner.getStdOut();
// System.err.println(command + " response was '" + output + "'");
return output;
} catch (ProcessException e) {
return null;
}
}
private Results buildInfoFromFile() {
try {
File revisionInfoFile = new File(productDirectory,
REVISION_INFO_FILENAME);
BufferedReader reader = new BufferedReader(new FileReader(
revisionInfoFile));
String release = reader.readLine();
if (release == null) {
throw new EOFException("No release line in file.");
}
String revision = reader.readLine();
if (revision == null) {
throw new EOFException("No revision line in file.");
}
return new Results("Info from file", buildLine(release, revision));
} catch (IOException e) {
System.out.println("No information from file: " + e);
return null;
}
}
private Results buildDummyInfo() {
String line = buildLine(null, null);
return new Results("Using dummy info", line);
}
private String buildLine(String release, String revision) {
if (release == null) {
release = "unknown";
}
if (revision == null) {
revision = "unknown";
}
return productName + INFO_LINE_DELIMITER + release.trim()
+ INFO_LINE_DELIMITER + revision.trim();
}
private void writeLine() throws IOException {
Writer writer = null;
writer = new FileWriter(resultFile, true);
writer.write(results.infoLine + "\n");
writer.close();
System.out.println(results);
}
public static void main(String[] args) {
try {
RevisionInfoBuilder builder = new RevisionInfoBuilder(args);
builder.buildInfo();
builder.writeLine();
} catch (UsageException e) {
System.err.println(e);
System.err.println("usage: RevisionInfoBuilder [product_name] "
+ "[product_directory] [output_file]");
System.exit(1);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}

View file

@ -0,0 +1,346 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testing;
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.BRIEF;
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.FULL;
import static edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel.MORE;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import edu.cornell.mannlib.vitro.utilities.testing.VitroTestRunner.ReportLevel;
/**
* Listen to events as they come from the JUnit test runner. The events from the
* lifecycle methods are broken down into semantic chunks and executed. Three
* levels of output are available.
*
* On the surface, JUnit treats "failures" (failed assertions) the same as
* "errors" (unexpected exceptions). We're going to distinguish between them.
*
* @author jeb228
*/
public class VitroTestRunListener extends RunListener {
private final ReportLevel reportLevel;
private int classCount;
private int testsTotal;
private int errorsTotal;
private int failuresTotal;
private int ignoresTotal;
private long overallStartTime;
private Class<?> currentClass;
private int testsCurrentClass;
private int errorsCurrentClass;
private int failuresCurrentClass;
private int ignoresCurrentClass;
private long classStartTime;
private String currentTest;
private boolean testHadError;
private boolean testFailed;
private boolean testIgnored;
private long testStartTime;
public VitroTestRunListener(ReportLevel reportLevel) {
this.reportLevel = reportLevel;
}
/** Did any of the tests fail or have errors? */
public boolean didEverythingPass() {
return (failuresTotal == 0) && (errorsTotal == 0);
}
// -------------------------------------------------------------------------
// Life-cycle methods that will be called by the test runner.
// -------------------------------------------------------------------------
@Override
public void testRunStarted(Description description) throws Exception {
openTestRun();
reportTestRunStart();
}
@Override
public void testStarted(Description description) throws Exception {
if (currentClass != description.getTestClass()) {
if (currentClass != null) {
closeCurrentClass();
reportCurrentClass();
}
openCurrentClass(description);
reportCurrentClassStart();
}
openCurrentTest(description);
}
@Override
public void testAssumptionFailure(Failure failure) {
if (isError(failure)) {
testHadError = true;
reportError(failure);
} else {
testFailed = true;
reportFailure(failure);
}
}
@Override
public void testFailure(Failure failure) throws Exception {
if (isError(failure)) {
testHadError = true;
reportError(failure);
} else {
testFailed = true;
reportFailure(failure);
}
}
@Override
public void testFinished(Description description) throws Exception {
closeCurrentTest();
reportCurrentTest();
}
@Override
public void testIgnored(Description description) throws Exception {
testStarted(description);
testIgnored = true;
testFinished(description);
}
@Override
public void testRunFinished(Result result) throws Exception {
if (currentClass != null) {
closeCurrentClass();
reportCurrentClass();
}
closeTestRun();
reportTestRun();
System.out.println();
}
// -------------------------------------------------------------------------
// Handling the logical events.
// -------------------------------------------------------------------------
private void openTestRun() {
overallStartTime = System.currentTimeMillis();
}
private void closeTestRun() {
// Nothing to close.
}
private void reportTestRunStart() {
if (reportLevel == FULL) {
System.out
.println("Starting test run at " + time(overallStartTime));
System.out.println();
}
if (reportLevel == MORE) {
System.out
.println("Starting test run at " + time(overallStartTime));
System.out.println();
System.out.println("Tests Pass Error Fail Ignore Seconds");
}
}
private void reportTestRun() {
int successes = testsTotal - errorsTotal - failuresTotal - ignoresTotal;
if (reportLevel != BRIEF) {
System.out.println();
}
System.out.format(
"Tests Pass Error Fail Ignore Seconds TOTAL (%d classes)\n",
classCount);
System.out.format(" %4d %4d %4d %4d %4d %6s\n", testsTotal,
successes, errorsTotal, failuresTotal, ignoresTotal,
elapsed(overallStartTime));
if (reportLevel != BRIEF) {
System.out.println("Ending test run at "
+ time(System.currentTimeMillis()));
}
}
private void openCurrentClass(Description description) {
currentClass = description.getTestClass();
classStartTime = System.currentTimeMillis();
testsCurrentClass = 0;
errorsCurrentClass = 0;
failuresCurrentClass = 0;
ignoresCurrentClass = 0;
}
private void closeCurrentClass() {
classCount++;
testsTotal += testsCurrentClass;
errorsTotal += errorsCurrentClass;
failuresTotal += failuresCurrentClass;
ignoresTotal += ignoresCurrentClass;
}
private void reportCurrentClassStart() {
if (reportLevel == FULL) {
System.out.format("Tests Pass Error Fail Ignore Seconds %s\n",
currentClass.getName());
}
}
private void reportCurrentClass() {
int successes = testsCurrentClass - errorsCurrentClass
- failuresCurrentClass - ignoresCurrentClass;
if (reportLevel == MORE) {
System.out.format(" %4d %4d %4d %4d %4d %6s %s\n",
testsCurrentClass, successes, errorsCurrentClass,
failuresCurrentClass, ignoresCurrentClass,
elapsed(classStartTime), currentClass.getSimpleName());
}
if (reportLevel == FULL) {
System.out.println("-----------------------------------");
System.out.format(" %4d %4d %4d %4d %4d %6s\n",
testsCurrentClass, successes, errorsCurrentClass,
failuresCurrentClass, ignoresCurrentClass,
elapsed(classStartTime));
System.out.println();
}
}
private void openCurrentTest(Description description) {
currentTest = description.getMethodName();
testHadError = false;
testFailed = false;
testIgnored = false;
testStartTime = System.currentTimeMillis();
}
private void closeCurrentTest() {
if (testHadError) {
errorsCurrentClass++;
}
if (testFailed) {
failuresCurrentClass++;
}
if (testIgnored) {
ignoresCurrentClass++;
}
testsCurrentClass++;
}
private boolean isError(Failure failure) {
Throwable throwable = failure.getException();
return (throwable != null) && !(throwable instanceof AssertionError);
}
private void reportError(Failure error) {
Description description = error.getDescription();
String methodName = description.getMethodName();
String className = description.getTestClass().getName();
String message = error.getMessage();
System.out.format("EXCEPTION: test %s() in %s: %s\n",
methodName, className, message);
System.out.println(formatStackTrace(error.getException()));
}
private void reportFailure(Failure failure) {
Description description = failure.getDescription();
String methodName = description.getMethodName();
String className = description.getTestClass().getName();
String message = failure.getMessage();
System.out.format("TEST FAILED: test %s() in %s: %s\n", methodName,
className, message);
}
private void reportCurrentTest() {
if (reportLevel == FULL) {
char passFlag = (testIgnored | testFailed | testHadError) ? ' '
: '1';
char errorFlag = testHadError ? '1' : ' ';
char failFlag = testFailed ? '1' : ' ';
char ignoreFlag = testIgnored ? '1' : ' ';
System.out.format(
" %c %c %c %c %6s %s()\n",
passFlag, errorFlag, failFlag, ignoreFlag,
elapsed(testStartTime), currentTest);
}
}
// -------------------------------------------------------------------------
// Formatting methods.
// -------------------------------------------------------------------------
private final SimpleDateFormat formatter = new SimpleDateFormat(
"HH:mm:ss 'on' MMM dd, yyyy");
private String time(long time) {
return formatter.format(new Date(time));
}
/** Show elapsed time in 6 columns. */
private String elapsed(long start) {
long interval = System.currentTimeMillis() - start;
return String.format("%6.2f", ((float) interval) / 1000.0);
}
/**
* Trim the stack trace: don't show the line saying "23 more", and don't
* show the lines about org.junit or java.lang.reflect or sun.reflect.
*
* Once we hit some "client code", we won't trim any futher lines even if
* they belong to org.junit, or the others.
*
* If we have nested exceptions, the process repeats for each "Caused by"
* section.
*/
private String formatStackTrace(Throwable throwable) {
StringWriter w = new StringWriter();
throwable.printStackTrace(new PrintWriter(w));
String[] lineArray = w.toString().split("\\n");
List<String> lines = new ArrayList<String>(Arrays.asList(lineArray));
boolean removing = true;
for (int i = lines.size() - 1; i > 0; i--) {
String line = lines.get(i);
if (removing) {
if (line.matches("\\s*[\\.\\s\\d]+more\\s*")
|| line.contains("at "
+ VitroTestRunner.class.getName())
|| line.contains("at org.junit.")
|| line.contains("at java.lang.reflect.")
|| line.contains("at sun.reflect.")) {
lines.remove(line);
} else {
removing = false;
}
} else {
if (line.contains("Caused by: ")) {
removing = true;
}
}
}
StringBuilder result = new StringBuilder();
for (String line : lines) {
result.append(line).append('\n');
}
return result.toString().trim();
}
}

View file

@ -0,0 +1,153 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.testing;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.runner.JUnitCore;
/**
* A Java application that will run the Vitro unit tests. It searches for unit
* tests in the supplied source directory (any file whose name is *Test.Java).
* It runs the tests with a variety of reporting detail, depending on the level
* selected. If the level selector is absent or unrecognized, the medium level
* is used.
*
* @author jeb228
*/
public class VitroTestRunner {
public enum ReportLevel {
/** Report only the one-line summary. */
BRIEF,
/** Report times and statistics for each test class. */
MORE,
/** Report times and statistics for each test method. */
FULL
}
private final List<Class<?>> classes;
private final VitroTestRunListener listener;
/**
* Locate the test classes. Initialize the test listener.
*/
public VitroTestRunner(File sourceRootDir, ReportLevel reportLevel) {
List<String> classNames = getListOfTestClassNames(sourceRootDir);
this.classes = getTestClasses(classNames);
this.listener = new VitroTestRunListener(reportLevel);
}
/**
* Start a recursive search through the source directory.
*/
private List<String> getListOfTestClassNames(File sourceRootDir) {
SortedSet<String> names = new TreeSet<String>();
searchForTestClasses(names, "", sourceRootDir);
return new ArrayList<String>(names);
}
/**
* Recursively search the directory for files in the form "*Test.java".
* Ignore any files or directories whose names start with a "."
*/
private void searchForTestClasses(SortedSet<String> names, String prefix,
File directory) {
for (File file : directory.listFiles()) {
String filename = file.getName();
if (filename.startsWith(".")) {
// ignore .svn, etc.
} else if (file.isDirectory()) {
searchForTestClasses(names, prefix + filename + ".", file);
} else if (filename.endsWith("Test.java")) {
String classname = filename.substring(0, filename.length() - 5);
names.add(prefix + classname);
}
}
}
/**
* Instantiate a class for each test class name.
*/
private List<Class<?>> getTestClasses(List<String> classNames) {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (String classname : classNames) {
try {
classes.add(Class.forName(classname));
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Can't load test class: "
+ classname, e);
}
}
return classes;
}
/**
* We've located all of the test clases. Now run them.
*/
private void run() {
JUnitCore junitCore = new JUnitCore();
junitCore.addListener(this.listener);
junitCore.run(this.classes.toArray(new Class<?>[0]));
}
/**
* Did any of the tests fail?
*/
private boolean didEverythingPass() {
return this.listener.didEverythingPass();
}
/**
* <p>
* You must provide a path to the source directory of the test classes.
* </p>
* <p>
* You may also provide a reporting level of "BRIEF", "MORE", or "FULL". If
* no level is provided, or if it is not recognized, "BRIEF" is used.
* </p>
*/
public static void main(String[] args) {
if ((args.length < 1) || (args.length > 2)) {
usage("Wrong number of arguments: expecting 1 or 2, but found "
+ args.length + ".");
}
File sourceRootDir = new File(args[0]);
if (!sourceRootDir.exists()) {
usage(sourceRootDir + " does not exist.");
}
if (!sourceRootDir.isDirectory()) {
usage(sourceRootDir + " is not a directory.");
}
ReportLevel reportLevel = ReportLevel.MORE;
if (args.length == 2) {
for (ReportLevel level : ReportLevel.values()) {
if (level.toString().equalsIgnoreCase(args[1])) {
reportLevel = level;
}
}
}
VitroTestRunner runner = new VitroTestRunner(sourceRootDir, reportLevel);
runner.run();
if (!runner.didEverythingPass()) {
System.exit(1);
}
}
/**
* Tell them how it should have been done.
*/
private static void usage(String message) {
System.out.println(message);
System.out.println("usage: " + VitroTestRunner.class.getSimpleName()
+ " sourceRootDirectory [ BRIEF | MORE | FULL ]");
System.exit(1);
}
}