<funtest> for functional testing <blockfor> is waitfor that throws a BuildTimeoutException when it times out git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@589767 13f79535-47bb-0310-9956-ffa450edef68master
@@ -52,20 +52,24 @@ import org.apache.tools.ant.types.EnumeratedAttribute; | |||
* @ant.task category="control" | |||
*/ | |||
public class WaitFor extends ConditionBase { | |||
private static final long ONE_SECOND = 1000L; | |||
private static final long ONE_MINUTE = ONE_SECOND * 60L; | |||
private static final long ONE_HOUR = ONE_MINUTE * 60L; | |||
private static final long ONE_DAY = ONE_HOUR * 24L; | |||
private static final long ONE_WEEK = ONE_DAY * 7L; | |||
private static final long DEFAULT_MAX_WAIT_MILLIS = ONE_MINUTE * 3L; | |||
private static final long DEFAULT_CHECK_MILLIS = 500L; | |||
/** default max wait time */ | |||
private long maxWaitMillis = DEFAULT_MAX_WAIT_MILLIS; | |||
private long maxWaitMultiplier = 1L; | |||
private long checkEveryMillis = DEFAULT_CHECK_MILLIS; | |||
private long checkEveryMultiplier = 1L; | |||
public static final long ONE_MILLISECOND = 1L; | |||
public static final long ONE_SECOND = 1000L; | |||
public static final long ONE_MINUTE = ONE_SECOND * 60L; | |||
public static final long ONE_HOUR = ONE_MINUTE * 60L; | |||
public static final long ONE_DAY = ONE_HOUR * 24L; | |||
public static final long ONE_WEEK = ONE_DAY * 7L; | |||
public static final long DEFAULT_MAX_WAIT_MILLIS = ONE_MINUTE * 3L; | |||
public static final long DEFAULT_CHECK_MILLIS = 500L; | |||
/** default max wait time in the current unit*/ | |||
private long maxWait = DEFAULT_MAX_WAIT_MILLIS; | |||
private long maxWaitMultiplier = ONE_MILLISECOND; | |||
/** | |||
* check time in the current unit | |||
*/ | |||
private long checkEvery = DEFAULT_CHECK_MILLIS; | |||
private long checkEveryMultiplier = ONE_MILLISECOND; | |||
private String timeoutProperty; | |||
/** | |||
@@ -75,14 +79,26 @@ public class WaitFor extends ConditionBase { | |||
super("waitfor"); | |||
} | |||
/** | |||
* Constructor that takes the name of the task in the task name. | |||
* | |||
* @param taskName the name of the task. | |||
* @since Ant 1.8 | |||
*/ | |||
public WaitFor(String taskName) { | |||
super(taskName); | |||
} | |||
/** | |||
* Set the maximum length of time to wait. | |||
* @param time a <code>long</code> value | |||
*/ | |||
public void setMaxWait(long time) { | |||
maxWaitMillis = time; | |||
maxWait = time; | |||
} | |||
/** | |||
* Set the max wait time unit | |||
* @param unit an enumerated <code>Unit</code> value | |||
@@ -91,12 +107,14 @@ public class WaitFor extends ConditionBase { | |||
maxWaitMultiplier = unit.getMultiplier(); | |||
} | |||
/** | |||
* Set the time between each check | |||
* @param time a <code>long</code> value | |||
*/ | |||
public void setCheckEvery(long time) { | |||
checkEveryMillis = time; | |||
checkEvery = time; | |||
} | |||
/** | |||
@@ -131,32 +149,42 @@ public class WaitFor extends ConditionBase { | |||
+ getTaskName()); | |||
} | |||
Condition c = (Condition) getConditions().nextElement(); | |||
long savedMaxWaitMillis = maxWaitMillis; | |||
long savedCheckEveryMillis = checkEveryMillis; | |||
try { | |||
try { | |||
maxWaitMillis *= maxWaitMultiplier; | |||
checkEveryMillis *= checkEveryMultiplier; | |||
long start = System.currentTimeMillis(); | |||
long end = start + maxWaitMillis; | |||
while (System.currentTimeMillis() < end) { | |||
if (c.eval()) { | |||
processSuccess(); | |||
return; | |||
} | |||
Thread.sleep(checkEveryMillis); | |||
long maxWaitMillis = calculateMaxWaitMillis(); | |||
long checkEveryMillis = calculateCheckEveryMillis(); | |||
long start = System.currentTimeMillis(); | |||
long end = start + maxWaitMillis; | |||
while (System.currentTimeMillis() < end) { | |||
if (c.eval()) { | |||
processSuccess(); | |||
return; | |||
} | |||
} catch (InterruptedException e) { | |||
log("Task " + getTaskName() | |||
+ " interrupted, treating as timed out."); | |||
Thread.sleep(checkEveryMillis); | |||
} | |||
processTimeout(); | |||
} finally { | |||
maxWaitMillis = savedMaxWaitMillis; | |||
checkEveryMillis = savedCheckEveryMillis; | |||
} catch (InterruptedException e) { | |||
log("Task " + getTaskName() | |||
+ " interrupted, treating as timed out."); | |||
} | |||
processTimeout(); | |||
} | |||
/** | |||
* Get the check wait time, in milliseconds. | |||
* @since Ant 1.8 | |||
* @return how long to wait between checks | |||
*/ | |||
public long calculateCheckEveryMillis() { | |||
return checkEvery * checkEveryMultiplier; | |||
} | |||
/** | |||
* Get the maxiumum wait time, in milliseconds. | |||
* @since Ant 1.8 | |||
* @return how long to wait before timing out | |||
*/ | |||
public long calculateMaxWaitMillis() { | |||
return maxWait * maxWaitMultiplier; | |||
} | |||
/** | |||
@@ -0,0 +1,73 @@ | |||
/* | |||
* Copyright 2007 The Apache Software Foundation | |||
* | |||
* Licensed 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.tools.ant.taskdefs.optional.testing; | |||
import org.apache.tools.ant.taskdefs.WaitFor; | |||
/** | |||
* | |||
* Created 29-Oct-2007 12:28:28 | |||
* @since Ant 1.8 | |||
*/ | |||
public class BlockFor extends WaitFor { | |||
/** | |||
* Text to include in a message | |||
*/ | |||
private String text; | |||
/** | |||
* Constructor that takes the name of the task in the task name. | |||
* | |||
*/ | |||
public BlockFor() { | |||
super("blockfor"); | |||
text=getTaskName()+" timed out"; | |||
} | |||
/** | |||
* Constructor that takes the name of the task in the task name. | |||
* | |||
* @param taskName the name of the task. | |||
*/ | |||
public BlockFor(String taskName) { | |||
super(taskName); | |||
} | |||
/** | |||
* If the wait fails, a BuildException is thrown. All the superclasses actions are called first. | |||
* @throws BuildTimeoutException on timeout, using the text in {@link #text} | |||
* | |||
*/ | |||
protected void processTimeout() throws BuildTimeoutException { | |||
super.processTimeout(); | |||
throw new BuildTimeoutException(text,getLocation()); | |||
} | |||
/** | |||
* Set the error text; all properties are expanded in the message. | |||
* | |||
* @param message the text to use in a failure message | |||
*/ | |||
public void addText(String message) { | |||
text = getProject().replaceProperties(message); | |||
} | |||
} |
@@ -0,0 +1,111 @@ | |||
/* | |||
* Copyright 2007 The Apache Software Foundation | |||
* | |||
* Licensed 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.tools.ant.taskdefs.optional.testing; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Location; | |||
/** | |||
* | |||
* This exception is used to indicate timeouts. | |||
* @since Ant1.8 | |||
* | |||
*/ | |||
public class BuildTimeoutException extends BuildException { | |||
/** | |||
* Constructs a build exception with no descriptive information. | |||
*/ | |||
public BuildTimeoutException() { | |||
} | |||
/** | |||
* Constructs an exception with the given descriptive message. | |||
* | |||
* @param message A description of or information about the exception. | |||
* Should not be <code>null</code>. | |||
*/ | |||
public BuildTimeoutException(String message) { | |||
super(message); | |||
} | |||
/** | |||
* Constructs an exception with the given message and exception as | |||
* a root cause. | |||
* | |||
* @param message A description of or information about the exception. | |||
* Should not be <code>null</code> unless a cause is specified. | |||
* @param cause The exception that might have caused this one. | |||
* May be <code>null</code>. | |||
*/ | |||
public BuildTimeoutException(String message, Throwable cause) { | |||
super(message, cause); | |||
} | |||
/** | |||
* Constructs an exception with the given message and exception as | |||
* a root cause and a location in a file. | |||
* | |||
* @param msg A description of or information about the exception. | |||
* Should not be <code>null</code> unless a cause is specified. | |||
* @param cause The exception that might have caused this one. | |||
* May be <code>null</code>. | |||
* @param location The location in the project file where the error | |||
* occurred. Must not be <code>null</code>. | |||
*/ | |||
public BuildTimeoutException(String msg, Throwable cause, Location location) { | |||
super(msg, cause, location); | |||
} | |||
/** | |||
* Constructs an exception with the given exception as a root cause. | |||
* | |||
* @param cause The exception that might have caused this one. | |||
* Should not be <code>null</code>. | |||
*/ | |||
public BuildTimeoutException(Throwable cause) { | |||
super(cause); | |||
} | |||
/** | |||
* Constructs an exception with the given descriptive message and a | |||
* location in a file. | |||
* | |||
* @param message A description of or information about the exception. | |||
* Should not be <code>null</code>. | |||
* @param location The location in the project file where the error | |||
* occurred. Must not be <code>null</code>. | |||
*/ | |||
public BuildTimeoutException(String message, Location location) { | |||
super(message, location); | |||
} | |||
/** | |||
* Constructs an exception with the given exception as | |||
* a root cause and a location in a file. | |||
* | |||
* @param cause The exception that might have caused this one. | |||
* Should not be <code>null</code>. | |||
* @param location The location in the project file where the error | |||
* occurred. Must not be <code>null</code>. | |||
*/ | |||
public BuildTimeoutException(Throwable cause, Location location) { | |||
super(cause, location); | |||
} | |||
} |
@@ -0,0 +1,457 @@ | |||
/* | |||
* Copyright 2007 The Apache Software Foundation | |||
* | |||
* Licensed 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.tools.ant.taskdefs.optional.testing; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.TaskAdapter; | |||
import org.apache.tools.ant.util.WorkerAnt; | |||
import org.apache.tools.ant.taskdefs.condition.Condition; | |||
import org.apache.tools.ant.taskdefs.Parallel; | |||
import org.apache.tools.ant.taskdefs.Sequential; | |||
import org.apache.tools.ant.taskdefs.WaitFor; | |||
/** | |||
* Task to provide functional testing under Ant, with a fairly complex worflow of: | |||
* | |||
* <ul> | |||
* <li>Conditional execution</li> | |||
* <li>Application to start</li> | |||
* <li>A probe to "waitfor" before running tests</li> | |||
* <li>A tests sequence</li> | |||
* <li>A reporting sequence that runs after the tests have finished</li> | |||
* <li>A "teardown" clause that runs after the rest.</li> | |||
* <li>Automated termination of the program it executes, if a timeout is not met</li> | |||
* <li>Checking of a failure property and automatic raising of a fault (with the text in failureText) | |||
* if test shutdown and reporting succeeded</li> | |||
* </ul> | |||
* | |||
* The task is designed to be framework neutral; it will work with JUnit, TestNG and other test frameworks That can be | |||
* executed from Ant. It bears a resemblance to the FunctionalTest task from SmartFrog, as the attribute names were | |||
* chosen to make migration easier. However, this task benefits from the ability to tweak Ant's internals, and so | |||
* simplify the workflow, and from the experience of using the SmartFrog task. No code has been shared. | |||
* | |||
* @since Ant 1.8 | |||
*/ | |||
public class Funtest extends Task { | |||
/** | |||
* A condition that must be true before the tests are run. This makes it easier to define complex tests that only | |||
* run if certain conditions are met, such as OS or network state. | |||
*/ | |||
private Condition condition; | |||
/** | |||
* Used internally to set the workflow up | |||
*/ | |||
private Parallel timedTests; | |||
/** | |||
* Setup runs if the condition is met. Once setup is complete, teardown will be run when the task finishes | |||
*/ | |||
private Sequential setup; | |||
/** | |||
* The application to run | |||
*/ | |||
private Sequential application; | |||
/** | |||
* A block that halts the tests until met. | |||
*/ | |||
private BlockFor block; | |||
/** | |||
* Tests to run | |||
*/ | |||
private Sequential tests; | |||
/** | |||
* Reporting only runs if the tests were executed. If the block stopped them, reporting is skipped. | |||
*/ | |||
private Sequential reporting; | |||
/** | |||
* Any teardown operations. | |||
*/ | |||
private Sequential teardown; | |||
/** | |||
* time for the tests to time out | |||
*/ | |||
private long timeout; | |||
private long timeoutUnitMultiplier= WaitFor.ONE_MILLISECOND; | |||
/** | |||
* time for the execution to time out. | |||
*/ | |||
private long shutdownTime = 10*WaitFor.ONE_SECOND; | |||
private long shutdownUnitMultiplier = WaitFor.ONE_MILLISECOND; | |||
/** | |||
* Name of a property to look for | |||
*/ | |||
private String failureProperty; | |||
/** | |||
* Message to send when tests failed | |||
*/ | |||
private String failureMessage="Tests failed"; | |||
/** | |||
* Flag to set to true if you don't care about any shutdown errors. | |||
* <p/> | |||
* In that situation, errors raised during teardown are logged but not | |||
* turned into BuildFault events. Similar to catching and ignoring | |||
* <code>finally {}</code> clauses in Java/ | |||
*/ | |||
private boolean failOnTeardownErrors=true; | |||
/** | |||
* What was thrown in the test run (including reporting) | |||
*/ | |||
private BuildException testException; | |||
/** | |||
* What got thrown during teardown | |||
*/ | |||
private BuildException teardownException; | |||
/** | |||
* Did the application throw an exception | |||
*/ | |||
private BuildException applicationException; | |||
/** | |||
* Did the task throw an exception | |||
*/ | |||
private BuildException taskException; | |||
/** {@value} */ | |||
public static final String WARN_OVERRIDING = "Overriding previous definition of "; | |||
/** {@value} */ | |||
public static final String APPLICATION_FORCIBLY_SHUT_DOWN = "Application forcibly shut down"; | |||
/** {@value} */ | |||
public static final String SHUTDOWN_INTERRUPTED = "Shutdown interrupted"; | |||
public static final String SKIPPING_TESTS = "Condition failed -skipping tests"; | |||
/** | |||
* Log if the definition is overriding something | |||
* | |||
* @param name what is being defined | |||
* @param definition what should be null if you don't want a warning | |||
*/ | |||
private void logOverride(String name, Object definition) { | |||
if (definition != null) { | |||
log(WARN_OVERRIDING + '<' + name + '>', Project.MSG_WARN); | |||
} | |||
} | |||
public void addCondition(Condition newCondition) { | |||
logOverride("condition", condition); | |||
condition = newCondition; | |||
} | |||
public void addApplication(Sequential sequence) { | |||
logOverride("application", application); | |||
application = sequence; | |||
} | |||
public void addSetup(Sequential sequence) { | |||
logOverride("setup", setup); | |||
setup = sequence; | |||
} | |||
public void addBlock(BlockFor sequence) { | |||
logOverride("block", block); | |||
block = sequence; | |||
} | |||
public void addTests(Sequential sequence) { | |||
logOverride("tests", tests); | |||
tests = sequence; | |||
} | |||
public void addReporting(Sequential sequence) { | |||
logOverride("reporting", reporting); | |||
reporting = sequence; | |||
} | |||
public void addTeardown(Sequential sequence) { | |||
logOverride("teardown", teardown); | |||
teardown = sequence; | |||
} | |||
public void setFailOnTeardownErrors(boolean failOnTeardownErrors) { | |||
this.failOnTeardownErrors = failOnTeardownErrors; | |||
} | |||
public void setFailureMessage(String failureMessage) { | |||
this.failureMessage = failureMessage; | |||
} | |||
public void setFailureProperty(String failureProperty) { | |||
this.failureProperty = failureProperty; | |||
} | |||
public void setShutdownTime(long shutdownTime) { | |||
this.shutdownTime = shutdownTime; | |||
} | |||
public void setTimeout(long timeout) { | |||
this.timeout = timeout; | |||
} | |||
public void setTimeoutUnit(WaitFor.Unit unit) { | |||
timeoutUnitMultiplier=unit.getMultiplier(); | |||
} | |||
public void setShutdownUnit(WaitFor.Unit unit) { | |||
shutdownUnitMultiplier = unit.getMultiplier(); | |||
} | |||
public BuildException getApplicationException() { | |||
return applicationException; | |||
} | |||
public BuildException getTeardownException() { | |||
return teardownException; | |||
} | |||
public BuildException getTestException() { | |||
return testException; | |||
} | |||
public BuildException getTaskException() { | |||
return taskException; | |||
} | |||
/** | |||
* Bind and initialise a task | |||
* @param task task to bind | |||
*/ | |||
private void bind(Task task) { | |||
task.bindToOwner(this); | |||
task.init(); | |||
} | |||
/** | |||
* Create a newly bound parallel instance | |||
* @param parallelTimeout timeout | |||
* @return a bound and initialised parallel instance. | |||
*/ | |||
private Parallel newParallel(long parallelTimeout) { | |||
Parallel par=new Parallel(); | |||
bind(par); | |||
par.setFailOnAny(true); | |||
par.setTimeout(parallelTimeout); | |||
return par; | |||
} | |||
/** | |||
* Create a newly bound parallel instance with one child | |||
* @param parallelTimeout timeout | |||
* @return a bound and initialised parallel instance. | |||
*/ | |||
private Parallel newParallel(long parallelTimeout,Task child) { | |||
Parallel par = newParallel(parallelTimeout); | |||
par.addTask(child); | |||
return par; | |||
} | |||
/** | |||
* Run the functional test sequence. | |||
* <p/> | |||
* This is a fairly complex workflow -what is going on is that we try to clean up | |||
* no matter how the run ended, and to retain the innermost exception that got thrown | |||
* during cleanup. That is, if teardown fails after the tests themselves failed, it is the | |||
* test failing that is more important. | |||
* @throws BuildException if something was caught during the run or teardown. | |||
*/ | |||
public void execute() throws BuildException { | |||
//before anything else, check the condition | |||
//and bail out if it is defined but not true | |||
if (condition != null && !condition.eval()) { | |||
//we are skipping the test | |||
log(SKIPPING_TESTS); | |||
return; | |||
} | |||
long timeoutMillis = timeout * timeoutUnitMultiplier; | |||
//set up the application to run in a separate thread | |||
Parallel applicationRun = newParallel(timeoutMillis); | |||
//with a worker which we can use to manage it | |||
WorkerAnt worker = new WorkerAnt(applicationRun, null); | |||
if (application != null) { | |||
applicationRun.addTask(application); | |||
} | |||
//The test run consists of the block followed by the tests. | |||
long testRunTimeout = 0; | |||
Sequential testRun = new Sequential(); | |||
bind(testRun); | |||
if (block != null) { | |||
//waitfor is not a task, it needs to be adapted | |||
testRun.addTask(new TaskAdapter(block)); | |||
//add the block time to the total test run timeout | |||
testRunTimeout = block.calculateMaxWaitMillis(); | |||
} | |||
//add the tests and more delay | |||
if (tests != null) { | |||
testRun.addTask(tests); | |||
testRunTimeout += timeoutMillis; | |||
} | |||
//add the reporting and more delay | |||
if (reporting != null) { | |||
testRun.addTask(reporting); | |||
testRunTimeout += timeoutMillis; | |||
} | |||
//wrap this in a parallel purely to set up timeouts for the | |||
//test run | |||
timedTests = newParallel(testRunTimeout, testRun); | |||
try { | |||
//run any setup task | |||
if (setup != null) { | |||
Parallel setupRun = newParallel(timeoutMillis, setup); | |||
setupRun.execute(); | |||
} | |||
//start the worker thread and leave it running | |||
worker.start(); | |||
//start the probe+test sequence | |||
timedTests.execute(); | |||
} catch (BuildException e) { | |||
//Record the exception and continue | |||
testException = e; | |||
} finally { | |||
//teardown always runs; its faults are filed away | |||
if (teardown != null) { | |||
try { | |||
Parallel teardownRun = newParallel(timeoutMillis, teardown); | |||
teardownRun.execute(); | |||
} catch (BuildException e) { | |||
teardownException = e; | |||
} | |||
} | |||
} | |||
//we get here whether or not the tests/teardown have thrown a BuildException. | |||
//do a forced shutdown of the running application, before processing the faults | |||
try { | |||
//wait for the worker to have finished | |||
long shutdownTimeMillis = shutdownTime * shutdownUnitMultiplier; | |||
worker.waitUntilFinished(shutdownTimeMillis); | |||
if (worker.isAlive()) { | |||
//then, if it is still running, interrupt it a second time. | |||
log(APPLICATION_FORCIBLY_SHUT_DOWN, Project.MSG_WARN); | |||
worker.interrupt(); | |||
worker.waitUntilFinished(shutdownTimeMillis); | |||
} | |||
} catch (InterruptedException e) { | |||
//success, something interrupted the shutdown. There may be a leaked | |||
//worker; | |||
log(SHUTDOWN_INTERRUPTED, e, Project.MSG_VERBOSE); | |||
} | |||
applicationException = worker.getBuildException(); | |||
/**Now faults are analysed | |||
the priority is | |||
-testexceptions, except those indicating a build timeout when the application itself | |||
failed. | |||
(because often it is the application fault that is more interesting than the probe | |||
failure, which is usually triggered by the application not starting | |||
-application exceptions (above test timeout exceptions) | |||
-teardown exceptions -except when they are being ignored | |||
-any | |||
*/ | |||
processExceptions(); | |||
} | |||
/** | |||
* Now faults are analysed. | |||
* <p> The priority is | |||
* <ol> | |||
* <li>testexceptions, except those indicating a build timeout when the application itself | |||
failed.<br> | |||
(because often it is the application fault that is more interesting than the probe | |||
failure, which is usually triggered by the application not starting | |||
</li><li> | |||
Application exceptions (above test timeout exceptions) | |||
</li><li> | |||
Teardown exceptions -except when they are being ignored | |||
</li><li> | |||
Test failures as indicated by the failure property | |||
</li></ol> | |||
*/ | |||
protected void processExceptions() { | |||
taskException = testException; | |||
//look for an application fault | |||
if (applicationException != null) { | |||
if (taskException == null || taskException instanceof BuildTimeoutException) { | |||
taskException = applicationException; | |||
} else { | |||
log("Application Exception:" + applicationException.toString(), | |||
applicationException, | |||
Project.MSG_WARN); | |||
} | |||
} | |||
//now look for teardown faults, which may be ignored | |||
if (teardownException != null) { | |||
if (taskException == null && failOnTeardownErrors) { | |||
taskException = teardownException; | |||
} else { | |||
//don't let the cleanup exception get in the way of any other failure | |||
log("teardown exception" + teardownException.toString(), | |||
teardownException, | |||
Project.MSG_WARN); | |||
} | |||
} | |||
//now, analyse the tests | |||
if (failureProperty != null | |||
&& getProject().getProperty(failureProperty) != null) { | |||
//we've failed | |||
log(failureMessage); | |||
if(taskException == null) { | |||
taskException = new BuildException(failureMessage); | |||
} | |||
} | |||
//at this point taskException is null or not. | |||
//if not, throw the exception | |||
if (taskException != null) { | |||
throw taskException; | |||
} | |||
} | |||
} |
@@ -0,0 +1,170 @@ | |||
/* | |||
* Copyright 2007 The Apache Software Foundation | |||
* | |||
* Licensed 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.tools.ant.util; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.BuildException; | |||
/** | |||
* A worker ant executes a single task in a background thread. | |||
* After the run, any exception thrown is turned into a buildexception, which can be | |||
* rethrown, the finished attribute is set, then notifyAll() is called, | |||
* so that anyone waiting on the same notify object gets woken up. | |||
* </p> | |||
* This class is effectively a superset of | |||
* {@link org.apache.tools.ant.taskdefs.Parallel.TaskRunnable} | |||
* | |||
* @since Ant 1.8 | |||
*/ | |||
public class WorkerAnt extends Thread { | |||
private Task task; | |||
private Object notify; | |||
private volatile boolean finished=false; | |||
private volatile BuildException buildException; | |||
private volatile Throwable exception; | |||
/** | |||
* Error message if invoked with no task | |||
*/ | |||
public static final String ERROR_NO_TASK = "No task defined"; | |||
/** | |||
* Create the worker. | |||
* <p/> | |||
* This does not start the thread, merely configures it. | |||
* @param task the task | |||
* @param notify what to notify | |||
*/ | |||
public WorkerAnt(Task task, Object notify) { | |||
this.task = task; | |||
this.notify = notify; | |||
} | |||
/** | |||
* Create the worker, using the worker as the notification point. | |||
* <p/> | |||
* This does not start the thread, merely configures it. | |||
* @param task the task | |||
*/ | |||
public WorkerAnt(Task task) { | |||
this(task,null); | |||
notify = this; | |||
} | |||
/** | |||
* Get any build exception. | |||
* This would seem to be oversynchronised, but know that Java pre-1.5 can reorder volatile access. | |||
* The synchronized attribute is to force an ordering. | |||
* | |||
* @return the exception or null | |||
*/ | |||
public synchronized BuildException getBuildException() { | |||
return buildException; | |||
} | |||
/** | |||
* Get whatever was thrown, which may or may not be a buildException. | |||
* Assertion: getException() instanceof BuildException <=> getBuildException()==getException() | |||
* @return | |||
*/ | |||
public synchronized Throwable getException() { | |||
return exception; | |||
} | |||
/** | |||
* Get the task | |||
* @return the task | |||
*/ | |||
public Task getTask() { | |||
return task; | |||
} | |||
/** | |||
* Query the task/thread for being finished. | |||
* This would seem to be oversynchronised, but know that Java pre-1.5 can reorder volatile access. | |||
* The synchronized attribute is to force an ordering. | |||
* @return true if the task is finished. | |||
*/ | |||
public synchronized boolean isFinished() { | |||
return finished; | |||
} | |||
/** | |||
* Block on the notify object and so wait until the thread is finished. | |||
* @param timeout timeout in milliseconds | |||
* @throws InterruptedException if the execution was interrupted | |||
*/ | |||
public void waitUntilFinished(long timeout) throws InterruptedException { | |||
synchronized(notify) { | |||
if(finished) { | |||
return; | |||
} | |||
notify.wait(timeout); | |||
} | |||
} | |||
/** | |||
* Raise an exception if one was caught | |||
* | |||
* @throws BuildException if one has been picked up | |||
*/ | |||
public void rethrowAnyBuildException() { | |||
BuildException ex = getBuildException(); | |||
if (ex != null) { | |||
throw ex; | |||
} | |||
} | |||
/** | |||
* Handle a caught exception, by recording it and possibly wrapping it | |||
* in a BuildException for later rethrowing. | |||
* @param thrown what was caught earlier | |||
*/ | |||
private synchronized void caught(Throwable thrown) { | |||
exception = thrown; | |||
buildException = (thrown instanceof BuildException)? | |||
(BuildException)thrown | |||
:new BuildException(thrown); | |||
} | |||
/** | |||
* Run the task, which is skipped if null. | |||
* When invoked again, the task is re-run. | |||
*/ | |||
public void run() { | |||
try { | |||
if (task != null) { | |||
task.execute(); | |||
} | |||
} catch (Throwable thrown) { | |||
caught(thrown); | |||
} finally { | |||
synchronized (notify) { | |||
finished=true; | |||
//reset the task. | |||
//wake up our owner, if it is waiting | |||
notify.notifyAll(); | |||
} | |||
} | |||
} | |||
} |