blob: cf207a35460f0283e9175fa94aa98ce0a5ecab24 [file] [log] [blame]
package org.apache.ant.antunit;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
/**
* Run antunit tests. The lifecycle of this object is :
* <ol>
* <li> activate(file) : Indicates the runner that the given file should be used.</li>
* <li> scanFile() : Provides you the list of targets.</li>
* <li> startSuite() : Start the suite</li>
* <li> runTarget(targetName) : Executed one or more time</li>
* <li> endSuite() : End the suite</li>
* <li> deactivate() Indicates to the runner that the test is finished and all
* resources can be freed </li>
* </ol>
* Every step is mandatory.
*/
public class AntUnitScriptRunner {
/**
* name of the magic setUp target.
*/
private static final String SETUP = "setUp";
/**
* prefix that identifies test targets.
*/
private static final String TEST = "test";
/**
* name of the magic tearDown target.
*/
private static final String TEARDOWN = "tearDown";
/**
* name of the magic suiteSetUp target.
*/
private static final String SUITESETUP = "suiteSetUp";
/**
* name of the magic suiteTearDown target.
*/
private static final String SUITETEARDOWN = "suiteTearDown";
/**
* Object used to interact with the environment (for example an ant task or a junit runner)
*/
private final AntUnitExecutionPlatform env;
/**
* Ant script file currently under testing. The file is set at activation, and used
* during all execution every time we want have to create a new project.</br>
* It is only defined when the project isActive()
*/
private File scriptFile;
/**
* Indicates if the active project is already scanned and if the value the fields
* hasSuiteSetUp, hasSetUp, hasTearDown, hasSuiteTearDown are defined.
*/
private boolean isScanned;
/**
* Indicates if the startSuite method has been invoked. Use to fail fast if the
* the caller forget to call the startSuite method
*/
private boolean isSuiteStarted;
/**
* Does that script have a setUp target (defined when scanning the script)
*/
private boolean hasSetUp;
/**
* Does that script have a tearDown target (defined when scanning the script)
*/
private boolean hasTearDown;
/**
* Does that script has a suiteSetUp target.
*/
private boolean hasSuiteSetUp;
/**
* Does that script has a suite tearDown target that should be executed.
*/
private boolean hasSuiteTearDown;
/**
* The project currently used.
*/
private Project project = null;
/**
* Indicates if a target has already be executed using this project.
* Value is undefined when project is null.
*/
private boolean projectIsDirty;
/**
* Create a new AntScriptRunner on the given environment.
* @param env The environment used to create project and where the test progress will be
* notified.
*/
public AntUnitScriptRunner(AntUnitExecutionPlatform env) {
if (env == null) {
throw new AssertionError();
}
this.env = env;
}
/**
* Set the ant script to use.
* @post isActive()
*/
public void activate(File f) {
scriptFile = f;
project = null;
isScanned = false;
isSuiteStarted = false;
}
/**
* Declare that the current ant script doesn't need to be used anymore.
* @post !isActive()
*/
public void deactivate() {
scriptFile = null;
project = null;
}
/**
* Indicates if there is a project currently under test.
*/
public boolean isActive() {
return scriptFile != null;
}
/**
* Get the project currently in use. The caller is not allowed to invoke a target or
* do anything that would break the isolation of the test targets.
* @pre isActif()
*/
public Project getCurrentProject() {
if (!isActive()) {
throw new AssertionError();
}
if (project == null) {
project = env.createProjectForFile(scriptFile);
projectIsDirty = false;
}
return project;
}
/**
* Get a project that has not yet been used in order to execute a target on it.
* @pre isActive()
*/
private Project getCleanProject() {
if (!isActive()) {
throw new AssertionError();
}
if (project == null || projectIsDirty) {
project = env.createProjectForFile(scriptFile);
}
//we already set isDirty to true in order to make sure we didn't reuse
//this project next time getCleanProject is called.
projectIsDirty = true;
return project;
}
/**
* Provides the list of test targets of the active antunit script.
* @pre isActive()
* @return List<String> List of test target names
*/
public List scanFile() {
if (!isActive()) {
throw new AssertionError();
}
Project newProject = getCurrentProject();
Map targets = newProject.getTargets();
hasSetUp = targets.containsKey(SETUP);
hasTearDown = targets.containsKey(TEARDOWN);
hasSuiteSetUp = targets.containsKey(SUITESETUP);
hasSuiteTearDown = targets.containsKey(SUITETEARDOWN);
List testTargets = new LinkedList();
Iterator it = targets.keySet().iterator();
while (it.hasNext()) {
String name = (String) it.next();
if (name.startsWith(TEST) && !name.equals(TEST)) {
testTargets.add(name);
}
}
isScanned = true;
return testTargets;
}
/**
* Provides the name of the active script.
* @pre isAvtive()
*/
public String getName() {
if (!isActive()) {
throw new AssertionError();
}
return getCurrentProject().getName();
}
/**
* Executes the suiteSetUp target if presents and report any execution error.
* Note that if the method return false, you are not allowed to run targets.
* @return false in case of execution failure. true in case of success.
*/
public boolean startSuite() {
if (!isScanned) {
throw new AssertionError();
}
getCurrentProject().fireBuildStarted();
if (hasSuiteSetUp) {
try {
Project newProject = getCleanProject();
newProject.executeTarget(SUITESETUP);
} catch (BuildException e) {
env.fireStartTest(SUITESETUP);
fireFailOrError(SUITESETUP, e);
return false;
}
}
isSuiteStarted = true; //set to true only if suiteSetUp executed properly.
return true;
}
/**
* Run the specific test target, possibly between the setUp and tearDown targets if
* it exists. Exception or failures are reported to the execution environment.
* @param name name of the test target to execute.
*/
public void runTarget(String name) {
if (!isSuiteStarted) {
throw new AssertionError();
}
Project newProject = getCleanProject();
Vector v = new Vector();
if (hasSetUp) {
v.add(SETUP);
}
v.add(name);
// create and register a logcapturer on the newProject
LogCapturer lc = new LogCapturer(newProject);
try {
env.fireStartTest(name);
newProject.executeTargets(v);
} catch (BuildException e) {
fireFailOrError(name, e);
} finally {
// fire endTest here instead of the endTarget
// event, otherwise an error would be
// registered after the endTest event -
// endTarget is called before this method's catch block
// is reached.
env.fireEndTest(name);
// clean up
if (hasTearDown) {
try {
newProject.executeTarget(TEARDOWN);
} catch (final BuildException e) {
fireFailOrError(name, e);
}
}
}
}
/**
* Executes the suiteTearDown target if presents and report any execution error.
* @param caught Any internal exception triggered (and catched) by the caller indicating that
* the this runner could not be invoked as expected.
*/
public void endSuite(Throwable caught) {
if (!isScanned) {
throw new AssertionError();
}
if (hasSuiteTearDown) {
try {
Project newProject = getCleanProject();
newProject.executeTarget(SUITETEARDOWN);
} catch (BuildException e) {
env.fireStartTest(SUITETEARDOWN);
fireFailOrError(SUITETEARDOWN, e);
}
}
getCurrentProject().fireBuildFinished(caught);
isSuiteStarted = false;
}
/**
* Try to see whether the BuildException e is an AssertionFailedException
* or is caused by an AssertionFailedException. If so, fire a failure for
* given targetName. Otherwise fire an error.
*/
private void fireFailOrError(String targetName, BuildException e) {
boolean failed = false;
Throwable t = e;
while (t != null && t instanceof BuildException) {
if (t instanceof AssertionFailedException) {
failed = true;
env.fireFail(targetName, (AssertionFailedException) t);
break;
}
t = ((BuildException) t).getCause();
}
if (!failed) {
env.fireError(targetName, e);
}
}
}