<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-ffa450edef68remotes/1776816827838153613/tmp_25f451bd36ab3145e487fcb2cd5c62c571e5b602
| @@ -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(); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||