<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(); | |||
| } | |||
| } | |||
| } | |||
| } | |||