Return-Path:
The priority is
+ * long
value
*/
public void setMaxWait(long time) {
- maxWaitMillis = time;
+ maxWait = time;
}
+
/**
* Set the max wait time unit
* @param unit an enumerated Unit
value
@@ -91,12 +107,14 @@
maxWaitMultiplier = unit.getMultiplier();
}
+
+
/**
* Set the time between each check
* @param time a long
value
*/
public void setCheckEvery(long time) {
- checkEveryMillis = time;
+ checkEvery = time;
}
/**
@@ -131,32 +149,42 @@
+ 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;
}
/**
Added: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BlockFor.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BlockFor.java?rev=589767&view=auto
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BlockFor.java (added)
+++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BlockFor.java Mon Oct 29 10:44:21 2007
@@ -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);
+ }
+
+
+}
Added: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BuildTimeoutException.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BuildTimeoutException.java?rev=589767&view=auto
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BuildTimeoutException.java (added)
+++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/BuildTimeoutException.java Mon Oct 29 10:44:21 2007
@@ -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 null
.
+ */
+ 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 null
unless a cause is specified.
+ * @param cause The exception that might have caused this one.
+ * May be null
.
+ */
+ 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 null
unless a cause is specified.
+ * @param cause The exception that might have caused this one.
+ * May be null
.
+ * @param location The location in the project file where the error
+ * occurred. Must not be null
.
+ */
+ 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 null
.
+ */
+ 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 null
.
+ * @param location The location in the project file where the error
+ * occurred. Must not be null
.
+ */
+ 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 null
.
+ * @param location The location in the project file where the error
+ * occurred. Must not be null
.
+ */
+ public BuildTimeoutException(Throwable cause, Location location) {
+ super(cause, location);
+ }
+}
Added: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java?rev=589767&view=auto
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java (added)
+++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java Mon Oct 29 10:44:21 2007
@@ -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:
+ *
+ *
+ *
+ *
+ * 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.
+ *
+ * In that situation, errors raised during teardown are logged but not
+ * turned into BuildFault events. Similar to catching and ignoring
+ * finally {}
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.
+ *
+ * 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.
+ *
+ *
+
+ */
+ 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;
+ }
+ }
+}
Added: ant/core/trunk/src/main/org/apache/tools/ant/util/WorkerAnt.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/util/WorkerAnt.java?rev=589767&view=auto
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/util/WorkerAnt.java (added)
+++ ant/core/trunk/src/main/org/apache/tools/ant/util/WorkerAnt.java Mon Oct 29 10:44:21 2007
@@ -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.
+ *
+ (because often it is the application fault that is more interesting than the probe
+ failure, which is usually triggered by the application not starting
+