448 lines
14 KiB
Java
448 lines
14 KiB
Java
/*
|
|
* 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.io.File;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import org.apache.tools.ant.BuildEvent;
|
|
import org.apache.tools.ant.BuildException;
|
|
import org.apache.tools.ant.BuildListener;
|
|
import org.apache.tools.ant.MagicNames;
|
|
import org.apache.tools.ant.Project;
|
|
import org.apache.tools.ant.ProjectHelper;
|
|
import org.apache.tools.ant.Task;
|
|
import org.apache.tools.ant.types.PropertySet;
|
|
import org.apache.tools.ant.types.ResourceCollection;
|
|
import org.apache.tools.ant.types.resources.FileResource;
|
|
import org.apache.tools.ant.types.resources.Union;
|
|
|
|
/**
|
|
* Run every target whose name starts with "test" in a set of build files.
|
|
*
|
|
* <p>Run the "setUp" target before each of them if present, same for
|
|
* "tearDown" after each "test*" target (targets named just "test" are
|
|
* ignored). If a target throws an AssertionFailedException, the test
|
|
* has failed; any other exception is considered an error (although
|
|
* BuildException will be scanned recursively for nested
|
|
* AssertionFailedExceptions).</p>
|
|
*/
|
|
public class AntUnit extends Task {
|
|
|
|
/**
|
|
* Message to print if an error or failure occured.
|
|
*/
|
|
public static final String ERROR_TESTS_FAILED = "Tests failed with ";
|
|
|
|
/**
|
|
* Message if no tests have been specified.
|
|
*/
|
|
public static final String ERROR_NO_TESTS =
|
|
"You must specify build files to test.";
|
|
|
|
/**
|
|
* Message if non-File resources have been specified.
|
|
*/
|
|
public static final String ERROR_NON_FILES =
|
|
"Only file system resources are supported.";
|
|
|
|
/**
|
|
* The build files to process.
|
|
*/
|
|
private Union buildFiles;
|
|
|
|
|
|
private AntUnitExecutionPlatform myExecutionPlatform = new AntUnitExecutionPlatform() {
|
|
|
|
public Project createProjectForFile(File f) {
|
|
return AntUnit.this.createProjectForFile(f);
|
|
}
|
|
|
|
public void fireEndTest(String targetName) {
|
|
AntUnit.this.fireEndTest(targetName);
|
|
}
|
|
|
|
public void fireError(String targetName, Throwable t) {
|
|
AntUnit.this.fireError(targetName, t);
|
|
}
|
|
|
|
public void fireFail(String targetName, AssertionFailedException ae) {
|
|
AntUnit.this.fireFail(targetName, ae);
|
|
}
|
|
|
|
public void fireStartTest(String targetName) {
|
|
AntUnit.this.fireStartTest(targetName);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The object responsible for the execution of the unit test.
|
|
* scriptRunner is invoked to executes the targets and keep the
|
|
* reference to the project.
|
|
*/
|
|
private AntUnitScriptRunner scriptRunner = new AntUnitScriptRunner(myExecutionPlatform);
|
|
|
|
/**
|
|
* listeners.
|
|
*/
|
|
private ArrayList listeners = new ArrayList();
|
|
|
|
/**
|
|
* propertysets.
|
|
*/
|
|
private ArrayList propertySets = new ArrayList();
|
|
|
|
/**
|
|
* has a failure occured?
|
|
*/
|
|
private int failures = 0;
|
|
|
|
/**
|
|
* has an error occured?
|
|
*/
|
|
private int errors = 0;
|
|
|
|
/**
|
|
* stop testing if an error or failure occurs?
|
|
*/
|
|
private boolean failOnError = true;
|
|
|
|
/**
|
|
* Name of a property to set in case of an error.
|
|
*/
|
|
private String errorProperty = null;
|
|
|
|
/**
|
|
* Add build files to run as tests.
|
|
* @param rc the ResourceCollection to add.
|
|
*/
|
|
public void add(ResourceCollection rc) {
|
|
if (buildFiles == null) {
|
|
buildFiles = new Union();
|
|
}
|
|
buildFiles.add(rc);
|
|
}
|
|
|
|
/**
|
|
* Add a test listener.
|
|
* @param al the AntUnitListener to add.
|
|
*/
|
|
public void add(AntUnitListener al) {
|
|
listeners.add(al);
|
|
al.setParentTask(this);
|
|
}
|
|
|
|
/**
|
|
* Add a PropertySet.
|
|
* @param ps the PropertySet to add.
|
|
*/
|
|
public void addPropertySet(PropertySet ps) {
|
|
propertySets.add(ps);
|
|
}
|
|
|
|
/**
|
|
* Set the name of a property to set if an error or failure occurs.
|
|
* @param s the name of the error property.
|
|
*/
|
|
public void setErrorProperty(String s) {
|
|
errorProperty = s;
|
|
}
|
|
|
|
/**
|
|
* Set whether to stop testing if an error or failure occurs?
|
|
* @param failOnError default <code>true</code>
|
|
*/
|
|
public void setFailOnError(boolean failOnError) {
|
|
this.failOnError = failOnError;
|
|
}
|
|
|
|
/**
|
|
* Execute the tests.
|
|
*/
|
|
public void execute() {
|
|
if (buildFiles == null) {
|
|
throw new BuildException(ERROR_NO_TESTS);
|
|
}
|
|
doResourceCollection(buildFiles);
|
|
if (failures > 0 || errors > 0) {
|
|
if (errorProperty != null) {
|
|
getProject().setNewProperty(errorProperty, "true");
|
|
}
|
|
if (failOnError) {
|
|
throw new BuildException(ERROR_TESTS_FAILED
|
|
+ failures + " failure"
|
|
+ (failures != 1 ? "s" : "")
|
|
+ " and "
|
|
+ errors + " error"
|
|
+ (errors != 1 ? "s" : ""));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes a ResourceCollection.
|
|
*/
|
|
private void doResourceCollection(ResourceCollection rc) {
|
|
//should relax this restriction if/when Ant core allows non-files
|
|
if (!rc.isFilesystemOnly()) {
|
|
throw new BuildException(ERROR_NON_FILES);
|
|
}
|
|
|
|
Iterator i = rc.iterator();
|
|
while (i.hasNext()) {
|
|
FileResource r = (FileResource) i.next();
|
|
if (r.isExists()) {
|
|
doFile(r.getFile());
|
|
} else {
|
|
log("Skipping " + r + " since it doesn't exist",
|
|
Project.MSG_VERBOSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes a single build file.
|
|
*/
|
|
private void doFile(File f) {
|
|
log("Running tests in build file " + f, Project.MSG_DEBUG);
|
|
scriptRunner.activate(f);
|
|
List testTargets = scriptRunner.scanFile();
|
|
|
|
// start test
|
|
Throwable caught = null;
|
|
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;
|
|
} finally {
|
|
scriptRunner.endSuite(caught);
|
|
scriptRunner.deactivate();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Redirect output to new project instance.
|
|
* @param outputToHandle the output to handle.
|
|
*/
|
|
public void handleOutput(String outputToHandle) {
|
|
if (scriptRunner.isActive()) {
|
|
scriptRunner.getCurrentProject().demuxOutput(outputToHandle, false);
|
|
} else {
|
|
super.handleOutput(outputToHandle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirect input to new project instance.
|
|
* @param buffer the buffer containing the input.
|
|
* @param offset the offset into <code>buffer</code>.
|
|
* @param length the length of the data.
|
|
*/
|
|
public int handleInput(byte[] buffer, int offset, int length)
|
|
throws IOException {
|
|
if (scriptRunner.isActive()) {
|
|
return scriptRunner.getCurrentProject().demuxInput(buffer, offset, length);
|
|
}
|
|
return super.handleInput(buffer, offset, length);
|
|
}
|
|
|
|
/**
|
|
* Redirect flush to new project instance.
|
|
* @param toFlush the output String to flush.
|
|
*/
|
|
public void handleFlush(String toFlush) {
|
|
if (scriptRunner.isActive()) {
|
|
scriptRunner.getCurrentProject().demuxFlush(toFlush, false);
|
|
} else {
|
|
super.handleFlush(toFlush);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirect error output to new project instance.
|
|
* @param errorOutputToHandle the error output to handle.
|
|
*/
|
|
public void handleErrorOutput(String errorOutputToHandle) {
|
|
if (scriptRunner.isActive()) {
|
|
scriptRunner.getCurrentProject().demuxOutput(errorOutputToHandle, true);
|
|
} else {
|
|
super.handleErrorOutput(errorOutputToHandle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirect error flush to new project instance.
|
|
* @param errorOutputToFlush the error output to flush.
|
|
*/
|
|
public void handleErrorFlush(String errorOutputToFlush) {
|
|
if (scriptRunner.isActive()) {
|
|
scriptRunner.getCurrentProject().demuxFlush(errorOutputToFlush, true);
|
|
} else {
|
|
super.handleErrorFlush(errorOutputToFlush);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new project instance and configures it.
|
|
* @param f the File for which to create a Project.
|
|
*/
|
|
private Project createProjectForFile(File f) {
|
|
Project p = new Project();
|
|
p.setDefaultInputStream(getProject().getDefaultInputStream());
|
|
p.initProperties();
|
|
p.setInputHandler(getProject().getInputHandler());
|
|
getProject().initSubProject(p);
|
|
for (Iterator outer = propertySets.iterator(); outer.hasNext(); ) {
|
|
PropertySet set = (PropertySet) outer.next();
|
|
Map props = set.getProperties();
|
|
for (Iterator keys = props.keySet().iterator();
|
|
keys.hasNext(); ) {
|
|
String key = keys.next().toString();
|
|
if (MagicNames.PROJECT_BASEDIR.equals(key)
|
|
|| MagicNames.ANT_FILE.equals(key)) {
|
|
continue;
|
|
}
|
|
Object value = props.get(key);
|
|
if (value != null && value instanceof String
|
|
&& p.getProperty(key) == null) {
|
|
p.setNewProperty(key, (String) value);
|
|
}
|
|
}
|
|
}
|
|
p.setUserProperty(MagicNames.ANT_FILE, f.getAbsolutePath());
|
|
attachListeners(f, p);
|
|
|
|
// read build file
|
|
ProjectHelper.configureProject(p, f);
|
|
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Wraps all registered test listeners in BuildListeners and
|
|
* attaches them to the new project instance.
|
|
* @param buildFile a build file.
|
|
* @param p the Project to attach to.
|
|
*/
|
|
private void attachListeners(File buildFile, Project p) {
|
|
Iterator it = listeners.iterator();
|
|
while (it.hasNext()) {
|
|
AntUnitListener al = (AntUnitListener) it.next();
|
|
p.addBuildListener(new BuildToAntUnitListener(buildFile
|
|
.getAbsolutePath(),
|
|
al));
|
|
al.setCurrentTestProject(p);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* invokes start on all registered test listeners.
|
|
* @param targetName the name of the target.
|
|
*/
|
|
private void fireStartTest(String targetName) {
|
|
Iterator it = listeners.iterator();
|
|
while (it.hasNext()) {
|
|
AntUnitListener al = (AntUnitListener) it.next();
|
|
al.startTest(targetName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* invokes addFailure on all registered test listeners.
|
|
* @param targetName the name of the failed target.
|
|
* @param ae the associated AssertionFailedException.
|
|
*/
|
|
private void fireFail(String targetName, AssertionFailedException ae) {
|
|
failures++;
|
|
Iterator it = listeners.iterator();
|
|
while (it.hasNext()) {
|
|
AntUnitListener al = (AntUnitListener) it.next();
|
|
al.addFailure(targetName, ae);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* invokes addError on all registered test listeners.
|
|
* @param targetName the name of the failed target.
|
|
* @param t the associated Throwable.
|
|
*/
|
|
private void fireError(String targetName, Throwable t) {
|
|
errors++;
|
|
Iterator it = listeners.iterator();
|
|
while (it.hasNext()) {
|
|
AntUnitListener al = (AntUnitListener) it.next();
|
|
al.addError(targetName, t);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* invokes endTest on all registered test listeners.
|
|
* @param targetName the name of the current target.
|
|
*/
|
|
private void fireEndTest(String targetName) {
|
|
Iterator it = listeners.iterator();
|
|
while (it.hasNext()) {
|
|
AntUnitListener al = (AntUnitListener) it.next();
|
|
al.endTest(targetName);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adapts AntUnitListener to BuildListener.
|
|
*/
|
|
private class BuildToAntUnitListener implements BuildListener {
|
|
private String buildFile;
|
|
private AntUnitListener a;
|
|
|
|
BuildToAntUnitListener(String buildFile, AntUnitListener a) {
|
|
this.buildFile = buildFile;
|
|
this.a = a;
|
|
}
|
|
|
|
public void buildStarted(BuildEvent event) {
|
|
a.startTestSuite(event.getProject(), buildFile);
|
|
}
|
|
public void buildFinished(BuildEvent event) {
|
|
a.endTestSuite(event.getProject(), buildFile);
|
|
}
|
|
public void targetStarted(BuildEvent event) {
|
|
}
|
|
public void targetFinished(BuildEvent event) {
|
|
}
|
|
public void taskStarted(BuildEvent event) {}
|
|
public void taskFinished(BuildEvent event) {}
|
|
public void messageLogged(BuildEvent event) {}
|
|
}
|
|
|
|
}
|