Refactoring : - Move active/inactive state managment from AntUnitScriptRunner to AntUnit - Split AntUnitExecutionPlatform into a ProjectFactory (required to parse the script) and AntUnitExceutionNotifier (required to run the script) - Move the invocation of the sequence suiteSetUp, test targets suiteTearDown from AntUnit to AntUnitScriptRunner

git-svn-id: https://svn.apache.org/repos/asf/ant/antlibs/antunit/trunk@740452 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Scokart Gilles 2009-02-03 21:13:59 +00:00
parent cf53bf4bf5
commit 0b6bc826c1
4 changed files with 129 additions and 163 deletions

View File

@ -74,11 +74,7 @@ public class AntUnit extends Task {
private Union buildFiles;
private AntUnitExecutionPlatform myExecutionPlatform = new AntUnitExecutionPlatform() {
public Project createProjectForFile(File f) {
return AntUnit.this.createProjectForFile(f);
}
private AntUnitExecutionNotifier notifier = new AntUnitExecutionNotifier() {
public void fireEndTest(String targetName) {
AntUnit.this.fireEndTest(targetName);
@ -100,9 +96,10 @@ public class AntUnit extends Task {
/**
* The object responsible for the execution of the unit test.
* scriptRunner is invoked to executes the targets and keep the
* reference to the project.
* reference to the project. scriptRunner is defined only when the
* antunit script is running.
*/
private AntUnitScriptRunner scriptRunner = new AntUnitScriptRunner(myExecutionPlatform);
private AntUnitScriptRunner scriptRunner;
/**
* listeners.
@ -225,27 +222,19 @@ public class AntUnit extends Task {
/**
* Processes a single build file.
*/
private void doFile(File f) {
private void doFile(final File f) {
log("Running tests in build file " + f, Project.MSG_DEBUG);
scriptRunner.activate(f);
List testTargets = scriptRunner.scanFile();
// start test
Throwable caught = null;
ProjectFactory prjFactory = new ProjectFactory() {
public Project createProject() {
return createProjectForFile(f);
}
};
try {
if (!scriptRunner.startSuite()) {
return;
}
Iterator iter = testTargets.iterator();
while (iter.hasNext()) {
String name = (String) iter.next();
scriptRunner.runTarget(name);
}
} catch (Throwable e) {
caught = e;
scriptRunner = new AntUnitScriptRunner(prjFactory);
List testTargets = scriptRunner.getTestTartgets();
scriptRunner.runSuite(testTargets, notifier);
} finally {
scriptRunner.endSuite(caught);
scriptRunner.deactivate();
scriptRunner=null;
}
}
@ -255,7 +244,7 @@ public class AntUnit extends Task {
* @param outputToHandle the output to handle.
*/
public void handleOutput(String outputToHandle) {
if (scriptRunner.isActive()) {
if (scriptRunner!=null) {
scriptRunner.getCurrentProject().demuxOutput(outputToHandle, false);
} else {
super.handleOutput(outputToHandle);
@ -270,7 +259,7 @@ public class AntUnit extends Task {
*/
public int handleInput(byte[] buffer, int offset, int length)
throws IOException {
if (scriptRunner.isActive()) {
if (scriptRunner!=null) {
return scriptRunner.getCurrentProject().demuxInput(buffer, offset, length);
}
return super.handleInput(buffer, offset, length);
@ -281,7 +270,7 @@ public class AntUnit extends Task {
* @param toFlush the output String to flush.
*/
public void handleFlush(String toFlush) {
if (scriptRunner.isActive()) {
if (scriptRunner!=null) {
scriptRunner.getCurrentProject().demuxFlush(toFlush, false);
} else {
super.handleFlush(toFlush);
@ -293,7 +282,7 @@ public class AntUnit extends Task {
* @param errorOutputToHandle the error output to handle.
*/
public void handleErrorOutput(String errorOutputToHandle) {
if (scriptRunner.isActive()) {
if (scriptRunner!=null) {
scriptRunner.getCurrentProject().demuxOutput(errorOutputToHandle, true);
} else {
super.handleErrorOutput(errorOutputToHandle);
@ -305,7 +294,7 @@ public class AntUnit extends Task {
* @param errorOutputToFlush the error output to flush.
*/
public void handleErrorFlush(String errorOutputToFlush) {
if (scriptRunner.isActive()) {
if (scriptRunner!=null) {
scriptRunner.getCurrentProject().demuxFlush(errorOutputToFlush, true);
} else {
super.handleErrorFlush(errorOutputToFlush);

View File

@ -25,16 +25,10 @@ import java.io.File;
import org.apache.tools.ant.Project;
/**
* Provides methods that allow the AntUnitScriptRunner to interact
* with the environment in which it executes.
* Provides methods that allow the AntUnitScriptRunner to notify the test progress.
* @since 1.2
*/
public interface AntUnitExecutionPlatform {
/**
* Creates a new project instance and configures it.
* @param f the File for which to create a Project.
*/
public Project createProjectForFile(File f);
public interface AntUnitExecutionNotifier {
/**
* invokes start on all registered test listeners.

View File

@ -1,6 +1,5 @@
package org.apache.ant.antunit;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@ -11,17 +10,13 @@ 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.
* Run antunit tests suites. This AntUnitScriptRunner is responsible for the
* management of the ant project and the correct invocation the target (taking
* into account properly the [case]setUp and [case]tearDown targets).
* The user can however provide the order of the test targets and or can filter
* the list of test targets to execute.
* The user must also provide its ProjectFactory and an AntUnitExecutionNotifier.
* @since 1.2
*/
public class AntUnitScriptRunner {
@ -51,23 +46,10 @@ public class AntUnitScriptRunner {
private static final String SUITETEARDOWN = "suiteTearDown";
/**
* Object used to interact with the environment (for example an ant task or a junit runner)
* Object used to create projects in order to support test isolation.
*/
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;
private final ProjectFactory prjFactory;
/**
* 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
@ -77,22 +59,27 @@ public class AntUnitScriptRunner {
/**
* Does that script have a setUp target (defined when scanning the script)
*/
private boolean hasSetUp;
private final boolean hasSetUp;
/**
* Does that script have a tearDown target (defined when scanning the script)
*/
private boolean hasTearDown;
private final boolean hasTearDown;
/**
* Does that script has a suiteSetUp target.
*/
private boolean hasSuiteSetUp;
private final boolean hasSuiteSetUp;
/**
* Does that script has a suite tearDown target that should be executed.
*/
private boolean hasSuiteTearDown;
private final boolean hasSuiteTearDown;
/**
* List of target names
*/
private final List testTargets;
/**
* The project currently used.
@ -108,54 +95,35 @@ public class AntUnitScriptRunner {
/**
* Create a new AntScriptRunner on the given environment.
* @param env The environment used to create project and where the test progress will be
* notified.
* @param prjFactory A factory for the ant project that will contains the antunit test to execute.
* The factory might be invoked multiple time in order to provide test isolation.
*/
public AntUnitScriptRunner(AntUnitExecutionPlatform env) {
if (env == null) {
throw new AssertionError();
public AntUnitScriptRunner(ProjectFactory prjFactory) {
this.prjFactory = prjFactory;
Project newProject = getCurrentProject();
Map targets = newProject.getTargets();
hasSetUp = targets.containsKey(SETUP);
hasTearDown = targets.containsKey(TEARDOWN);
hasSuiteSetUp = targets.containsKey(SUITESETUP);
hasSuiteTearDown = targets.containsKey(SUITETEARDOWN);
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);
}
}
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();
}
public final Project getCurrentProject() {
//Method is final because it is called from the constructor
if (project == null) {
project = env.createProjectForFile(scriptFile);
project = prjFactory.createProject();
projectIsDirty = false;
}
return project;
@ -163,14 +131,10 @@ public class AntUnitScriptRunner {
/**
* 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);
project = prjFactory.createProject();
}
//we already set isDirty to true in order to make sure we didn't reuse
//this project next time getCleanProject is called.
@ -179,60 +143,34 @@ public class AntUnitScriptRunner {
}
/**
* Provides the list of test targets of the active antunit script.
* @pre isActive()
* @return List<String> List of test target names
* @return List<String> List of test targets of the script file
*/
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;
public List getTestTartgets() {
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.
* A failure is reported to the notifier and by returning false.
* 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();
}
private boolean startSuite(AntUnitExecutionNotifier notifier) {
getCurrentProject().fireBuildStarted();
if (hasSuiteSetUp) {
try {
Project newProject = getCleanProject();
newProject.executeTarget(SUITESETUP);
} catch (BuildException e) {
env.fireStartTest(SUITESETUP);
fireFailOrError(SUITESETUP, e);
notifier.fireStartTest(SUITESETUP);
fireFailOrError(SUITESETUP, e, notifier);
return false;
}
}
@ -242,10 +180,12 @@ public class AntUnitScriptRunner {
/**
* Run the specific test target, possibly between the setUp and tearDown targets if
* it exists. Exception or failures are reported to the execution environment.
* it exists. Exception or failures are reported to the notifier.
* @param name name of the test target to execute.
* @param notifier will receive execution notifications.
* @pre startSuite has been invoked successfully
*/
public void runTarget(String name) {
private void runTarget(String name, AntUnitExecutionNotifier notifier) {
if (!isSuiteStarted) {
throw new AssertionError();
}
@ -258,23 +198,23 @@ public class AntUnitScriptRunner {
// create and register a logcapturer on the newProject
LogCapturer lc = new LogCapturer(newProject);
try {
env.fireStartTest(name);
notifier.fireStartTest(name);
newProject.executeTargets(v);
} catch (BuildException e) {
fireFailOrError(name, e);
fireFailOrError(name, e, notifier);
} 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);
notifier.fireEndTest(name);
// clean up
if (hasTearDown) {
try {
newProject.executeTarget(TEARDOWN);
} catch (final BuildException e) {
fireFailOrError(name, e);
fireFailOrError(name, e, notifier);
}
}
}
@ -282,20 +222,18 @@ public class AntUnitScriptRunner {
/**
* 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.
* @param caught Any internal exception triggered (and caught) by the caller indicating that
* the execution could not be invoked as expected.
* @param notifier will receive execution notifications.
*/
public void endSuite(Throwable caught) {
if (!isScanned) {
throw new AssertionError();
}
private void endSuite(Throwable caught, AntUnitExecutionNotifier notifier) {
if (hasSuiteTearDown) {
try {
Project newProject = getCleanProject();
newProject.executeTarget(SUITETEARDOWN);
} catch (BuildException e) {
env.fireStartTest(SUITETEARDOWN);
fireFailOrError(SUITETEARDOWN, e);
notifier.fireStartTest(SUITETEARDOWN);
fireFailOrError(SUITETEARDOWN, e, notifier);
}
}
getCurrentProject().fireBuildFinished(caught);
@ -307,20 +245,45 @@ public class AntUnitScriptRunner {
* 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) {
private void fireFailOrError(String targetName, BuildException e,
AntUnitExecutionNotifier notifier) {
boolean failed = false;
Throwable t = e;
while (t != null && t instanceof BuildException) {
if (t instanceof AssertionFailedException) {
failed = true;
env.fireFail(targetName, (AssertionFailedException) t);
notifier.fireFail(targetName, (AssertionFailedException) t);
break;
}
t = ((BuildException) t).getCause();
}
if (!failed) {
env.fireError(targetName, e);
notifier.fireError(targetName, e);
}
}
/**
* Executes the suite.
* @param suiteTargets An ordered list of test targets. It must be a sublist of getTestTargets
* @param notifier
*/
public void runSuite(List suiteTargets, AntUnitExecutionNotifier notifier) {
Throwable caught = null;
try {
if (!startSuite(notifier)) {
return;
}
Iterator iter = suiteTargets.iterator();
while (iter.hasNext()) {
String name = (String) iter.next();
runTarget(name, notifier);
}
} catch (Throwable e) {
caught = e;
} finally {
endSuite(caught, notifier);
}
}

View File

@ -0,0 +1,20 @@
package org.apache.ant.antunit;
import org.apache.tools.ant.Project;
/**
* Provides project instances for AntUnit execution.<br/>
* The aproach to creates a project depends on the context. When invoked from an
* ant project, some elements might be intialized from the parent project. When
* executed in a junit runner, a brand new project must be initialized.<br/>
* The AntScriptRunner will usually creates multiple project in order to provide test isolation.
* @since 1.2
*/
public interface ProjectFactory {
/**
* Creates a new project instance and configures it according to the execution context.
*/
public Project createProject();
}