<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" | * @ant.task category="control" | ||||
*/ | */ | ||||
public class WaitFor extends ConditionBase { | 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; | private String timeoutProperty; | ||||
/** | /** | ||||
@@ -75,14 +79,26 @@ public class WaitFor extends ConditionBase { | |||||
super("waitfor"); | 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. | * Set the maximum length of time to wait. | ||||
* @param time a <code>long</code> value | * @param time a <code>long</code> value | ||||
*/ | */ | ||||
public void setMaxWait(long time) { | public void setMaxWait(long time) { | ||||
maxWaitMillis = time; | |||||
maxWait = time; | |||||
} | } | ||||
/** | /** | ||||
* Set the max wait time unit | * Set the max wait time unit | ||||
* @param unit an enumerated <code>Unit</code> value | * @param unit an enumerated <code>Unit</code> value | ||||
@@ -91,12 +107,14 @@ public class WaitFor extends ConditionBase { | |||||
maxWaitMultiplier = unit.getMultiplier(); | maxWaitMultiplier = unit.getMultiplier(); | ||||
} | } | ||||
/** | /** | ||||
* Set the time between each check | * Set the time between each check | ||||
* @param time a <code>long</code> value | * @param time a <code>long</code> value | ||||
*/ | */ | ||||
public void setCheckEvery(long time) { | public void setCheckEvery(long time) { | ||||
checkEveryMillis = time; | |||||
checkEvery = time; | |||||
} | } | ||||
/** | /** | ||||
@@ -131,32 +149,42 @@ public class WaitFor extends ConditionBase { | |||||
+ getTaskName()); | + getTaskName()); | ||||
} | } | ||||
Condition c = (Condition) getConditions().nextElement(); | Condition c = (Condition) getConditions().nextElement(); | ||||
long savedMaxWaitMillis = maxWaitMillis; | |||||
long savedCheckEveryMillis = checkEveryMillis; | |||||
try { | 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(); | |||||
} | |||||
} | |||||
} | |||||
} |