blob: a5b92a8da208e125bb8c4a1d24460870332e0f0f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.ant.antunit;
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 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 {
/**
* 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 create projects in order to support test isolation.
*/
private final ProjectFactory prjFactory;
/**
* 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 final boolean hasSetUp;
/**
* Does that script have a tearDown target (defined when scanning the script)
*/
private final boolean hasTearDown;
/**
* Does that script has a suiteSetUp target.
*/
private final boolean hasSuiteSetUp;
/**
* Does that script has a suite tearDown target that should be executed.
*/
private final boolean hasSuiteTearDown;
/**
* List of target names
*/
private final List testTargets;
/**
* 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 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.
* @throws BuildException The project can not be parsed
*/
public AntUnitScriptRunner(ProjectFactory prjFactory) throws BuildException {
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);
}
}
}
/**
* 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.
* @throws BuildException The project can not be parsed
* @return the current project
*/
public final Project getCurrentProject() throws BuildException {
//Method is final because it is called from the constructor
if (project == null) {
project = prjFactory.createProject();
projectIsDirty = false;
}
return project;
}
/**
* Get a project that has not yet been used in order to execute a target on it.
*/
private Project getCleanProject() {
if (project == null || projectIsDirty) {
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.
projectIsDirty = true;
return project;
}
/**
* @return List<String> List of test targets of the script file
*/
public List getTestTartgets() {
return testTargets;
}
/**
* Provides the name of the active script.
* @return name of the project
*/
public String getName() {
return getCurrentProject().getName();
}
/**
* Executes the suiteSetUp target if presents and report any execution error.
* <p>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.</p>
* @return false in case of execution failure. true in case of success.
*/
private boolean startSuite(AntUnitExecutionNotifier notifier) {
getCurrentProject().fireBuildStarted();
if (hasSuiteSetUp) {
try {
Project newProject = getCleanProject();
newProject.executeTarget(SUITESETUP);
} catch (BuildException e) {
notifier.fireStartTest(SUITESETUP);
fireFailOrError(SUITESETUP, e, notifier);
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 notifier.
* @param name name of the test target to execute.
* @param notifier will receive execution notifications.
* @pre startSuite has been invoked successfully
*/
private void runTarget(String name, AntUnitExecutionNotifier notifier) {
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 {
notifier.fireStartTest(name);
newProject.executeTargets(v);
} catch (BuildException 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.
notifier.fireEndTest(name);
// clean up
if (hasTearDown) {
try {
newProject.executeTarget(TEARDOWN);
} catch (final BuildException e) {
fireFailOrError(name, e, notifier);
}
}
}
}
/**
* Executes the suiteTearDown target if presents and report any execution error.
* @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.
*/
private void endSuite(Throwable caught, AntUnitExecutionNotifier notifier) {
if (hasSuiteTearDown) {
try {
Project newProject = getCleanProject();
newProject.executeTarget(SUITETEARDOWN);
} catch (BuildException e) {
notifier.fireStartTest(SUITETEARDOWN);
fireFailOrError(SUITETEARDOWN, e, notifier);
}
}
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,
AntUnitExecutionNotifier notifier) {
boolean failed = false;
Throwable t = e;
while (t != null && t instanceof BuildException) {
if (t instanceof AssertionFailedException) {
failed = true;
notifier.fireFail(targetName, (AssertionFailedException) t);
break;
}
t = ((BuildException) t).getCause();
}
if (!failed) {
notifier.fireError(targetName, e);
}
}
/**
* Executes the suite.
* @param suiteTargets An ordered list of test targets. It must be a sublist of getTestTargets
* @param notifier is notified on test progress
*/
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);
}
}
}