package ise.antelope.tasks;
import java.io.*;
import java.util.*;
import org.apache.tools.ant.*;
import ise.library.ascii.MessageBox;
/**
* Modeled after the TestCase provided by jUnit, this class is an Ant task that
* looks through the build file set in the setFile
method, calls a
* 'setUp' target in that build file (if it exists), then all targets whose
* names start with 'test', and last calls a target named 'tearDown' (if it
* exists). Both 'setUp' and 'tearDown' are optional targets in the build file.
*
* * Ant stores targets in a hashtable, so there is no guaranteed order in which * the 'test*' classes will be called. If order is important, use the 'depends' * attribue of a target to enforce order, and do not name dependent targets with * a name starting with 'test'.
*
* Test targets may also be imported with the import task. Imported files should
* not have a "suite" task in the implicit target as such a task would rerun all
* test targets in all files.
*
* @author Dale Anson
* @version $Revision: 1.3 $
*/
public class TestCase extends Task implements TestStatisticAccumulator {
private boolean enabled = true;
private boolean assertEnabled = true;
private Target setUp = null;
private Target tearDown = null;
private Vector testTargets = new Vector();
private Vector failures = new Vector();
private boolean failOnError = false;
private boolean showSummary = true;
private boolean showOutput = true;
private File testFile = null;
private String test_name = "";
private int tests_passed = 0;
private int tests_failed = 0;
private int tests_warning = 0;
/** task initilization */
public void init() {
super.init();
setTaskName("testcase");
}
/**
* @return the count of test targets.
*/
public int getTestCaseCount() {
return testTargets.size();
}
/**
* @return the number of tests targets actually executed.
*/
public int getRanCount() {
return tests_passed + tests_warning + tests_failed;
}
/**
* @return the number of tests that failed.
*/
public int getFailedCount() {
return tests_failed;
}
public int getWarningCount() {
return tests_warning;
}
/**
* @return the number of tests that passed.
*/
public int getPassedCount() {
return tests_passed;
}
/**
* @return an Enumeration of the failures. Individual elements are Strings
* containing the name of the failed target and the reason why it
* failed.
*/
public Enumeration getFailures() {
return failures.elements();
}
/**
* @param b if true, show the output of the tests as they run. Optional,
* default is true, do show output.
*/
public void setShowoutput(boolean b) {
showOutput = b;
}
/**
* @param b if true, show the summary output (number of tests, passed,
* failed) after the completion of all tests. Optional, default is
* true, do show summary.
*/
public void setShowsummary(boolean b) {
showSummary = b;
}
/**
* @param f the file containing the tests to execute. Required. The file
* itself is a standard Ant build file, but will be treated differently
* than if Ant itself ran it. If there is a target named "setUp", that
* target will be executed first, then all targets with names starting
* with "test" (not in any particular order), then if there is a target
* named "tearDown", that target will be executed last. All other
* targets are ignored.
*/
public void setFile(File f) {
testFile = f;
}
/**
* Should Asserts be enabled? Many (most?) tests will use the Assert task,
* which requires a property to be set to actually enable the asserts. By
* default, Asserts are enabled for testcases.
*
* @param b if false, do not enable asserts. Note that this sets an Ant
* property, and due to property immutability, this attribute may have
* no effect if it has been set already. Generally, asserts are enabled
* or disabled for an entire build.
*/
public void setAssertsenabled(boolean b) {
assertEnabled = b;
}
/**
* Should the build fail if the test fails? By default, a failed test does
* not cause the build to fail, so all tests may have the opportunity to
* run.
*
* @param b set to true to cause the build to fail if the test fails
*/
public void setFailonerror(boolean b) {
failOnError = b;
}
/**
* @return the name of the test as set by setName
. If the
* name has not be explicitly set, then the test name is the project
* name if there is one, otherwise, the filename of the test file.
*/
public String getName() {
return test_name;
}
/**
* Set the name for this testcase.
*
* @param n the name for the testcase
*/
public void setName(String n) {
test_name = n;
}
/**
* @param b if true, execute the test. This is handy for enabling or
* disabling groups of tests in a 'suite' by setting a single property.
* Optional, default is true, the test should run.
*/
public void setEnabled(boolean b) {
enabled = b;
}
/** Run tests. */
public void execute() {
/*
Hashtable parent_targets = getProject().getTargets();
for (Iterator it = parent_targets.keySet().iterator(); it.hasNext(); ) {
log(it.next());
}
*/
if (!enabled)
return;
// check the test file
if (testFile == null)
throw ProjectHelper.addLocationToBuildException(new BuildException("missing file for testcase"), getLocation());
if (!testFile.exists())
throw ProjectHelper.addLocationToBuildException(new BuildException("file not found for testcase: " + testFile), getLocation());
// make sure asserts are enabled
String ae = assertEnabled ? "true" : "false";
if (assertEnabled)
getProject().setProperty("ant.enable.asserts", ae);
// create a new project, similar to the "ant" task, but not nearly as involved.
// All properties will be copied from the parent project to the subproject, and
// any properties set during execution of the subproject will be copied back to
// the parent project.
Project myProject = new Project();
initializeProject(myProject);
try {
ProjectHelper.configureProject(myProject, testFile);
}
catch (BuildException be) {
throw ProjectHelper.addLocationToBuildException(be, getLocation());
}
// set the test name, use a name that was set as an attribute first,
// otherwise, use the project name, and if all else fails, use the name
// of the test file
if (test_name == null || test_name.equals(""))
test_name = myProject.getName();
if (test_name == null || test_name.equals(""))
test_name = testFile.getName();
if (showOutput) {
// output start of test
log(MessageBox.box("Starting test: " + test_name));
}
// get the setUp, tearDown, and test targets
Hashtable targets = myProject.getTargets();
Enumeration en = targets.keys();
while (en.hasMoreElements()) {
String target = (String) en.nextElement();
if (target.equals("setUp"))
setUp = (Target) targets.get(target);
else if (target.equals("tearDown"))
tearDown = (Target) targets.get(target);
else if (target.startsWith("test"))
testTargets.addElement(targets.get(target));
// also check for imported targets. Imported targets are named like
// projectname.targetname, so check for whatever follows the last dot.
// This is somewhat naive, as there is no restriction using dots in
// target names, so a target named "build.testimonials" would be treated
// as a test target.
else if (target.lastIndexOf(".") > 0 && target.substring(target.lastIndexOf(".") + 1).startsWith("test")) {
testTargets.addElement(targets.get(target));
}
}
// run the setUp target
if (setUp != null)
setUp.execute();
// run the test targets
StringBuffer messages = new StringBuffer();
en = testTargets.elements();
while (en.hasMoreElements()) {
Target target = (Target) en.nextElement();
try {
myProject.executeTarget(target.getName());
if (showOutput)
log(target.getName() + " passed.");
++tests_passed;
}
catch (Exception e) {
String error = "ERROR: ";
if (e instanceof AssertException) {
int level = ((AssertException)e).getLevel();
if (level == AssertException.ERROR)
++ tests_failed;
else if (level == AssertException.WARN) {
++ tests_warning;
error = "WARNING: ";
}
else {
// info or debug level
if (showOutput)
log(target.getName() + ": " + e.getMessage());
continue;
}
}
else
++tests_failed;
if (showOutput)
log(error + target.getName() + " failed: " + e.getMessage());
failures.addElement(error + test_name + ": " + target.getName() + " failed: " + e.getMessage());
if (failOnError)
throw new BuildException(e.getMessage());
}
}
// run the tearDown target
if (tearDown != null)
tearDown.execute();
// copy any new properties to parent project
addAlmostAll(getProject(), myProject.getProperties());
/*
Hashtable props = myProject.getProperties();
for (en = props.keys(); en.hasMoreElements(); ) {
String key = (String)en.nextElement();
String value = (String)props.get(key);
if (value != null)
getProject().setNewProperty(key, value);
}
*/
// print any error messages
if (showSummary) {
log(getSummary());
}
}
/**
* Gets the summary attribute of the TestCase object
*
* @return The summary value
*/
public String getSummary() {
String title = (test_name == null ? "Test" : test_name) + " Results";
StringBuffer msg = new StringBuffer();
String ls = System.getProperty("line.separator");
// log the failures
if (failures.size() > 0) {
String error_title = "Errors";
StringBuffer error_msg = new StringBuffer();
Enumeration en = failures.elements();
while (en.hasMoreElements()) {
error_msg.append((String) en.nextElement()).append(ls);
}
int box_width = MessageBox.getMaxWidth();
MessageBox.setMaxWidth(box_width - 8);
msg.append(MessageBox.box(error_title, error_msg));
MessageBox.setMaxWidth(box_width);
msg.append(ls);
}
// log the test count info
msg.append("Ran: ").append(getRanCount()).append(" out of ").append(getTestCaseCount()).append(" tests.").append(ls);
msg.append("Passed: ").append(getPassedCount()).append(ls);
msg.append("Warning: ").append(getWarningCount()).append(ls);
msg.append("Failed: ").append(getFailedCount()).append(ls);
return MessageBox.box(title, msg);
}
/**
* Attaches the build listeners of the current project to the new project,
* configures a possible logfile, transfers task and data-type definitions,
* transfers properties (either all or just the ones specified as user
* properties to the current project, depending on inheritall), transfers
* the input handler.
*
* @param newProject
*/
private void initializeProject(Project newProject) {
newProject.setBaseDir(getProject().getBaseDir());
newProject.setInputHandler(getProject().getInputHandler());
Iterator iter = getProject().getBuildListeners().iterator();
while (iter.hasNext()) {
newProject.addBuildListener((BuildListener) iter.next());
}
getProject().initSubProject(newProject);
// copy properties
/// are these necessary? Does addAlmostAll cover these properties?
getProject().copyInheritedProperties(newProject);
getProject().copyUserProperties(newProject);
// set all properties from calling project
addAlmostAll(newProject, getProject().getProperties());
}
/**
* Copies all properties from the given table to the given project -
* omitting those that have already been set in the project as well as
* properties named basedir or ant.file.
*
* @param props properties to copy to the project
* @param project The feature to be added to the AlmostAll attribute
*/
private void addAlmostAll(Project project, Hashtable props) {
Enumeration e = props.keys();
while (e.hasMoreElements()) {
String key = e.nextElement().toString();
if ("basedir".equals(key) || "ant.file".equals(key)) {
// basedir and ant.file get special treatment in execute()
continue;
}
String value = props.get(key).toString();
// don't re-set user properties, avoid the warning message
if (project.getProperty(key) == null) {
// no user property
project.setNewProperty(key, value);
}
}
}
}