aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java')
-rw-r--r--framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java577
1 files changed, 577 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java
new file mode 100644
index 00000000..2eb357a3
--- /dev/null
+++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java
@@ -0,0 +1,577 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.Project;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.TaskAdapter;
+import org.apache.tools.ant.taskdefs.Parallel;
+import org.apache.tools.ant.taskdefs.Sequential;
+import org.apache.tools.ant.taskdefs.WaitFor;
+import org.apache.tools.ant.taskdefs.condition.Condition;
+import org.apache.tools.ant.taskdefs.condition.ConditionBase;
+import org.apache.tools.ant.util.WorkerAnt;
+
+/**
+ * Task to provide functional testing under Ant, with a fairly complex workflow 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 NestedCondition 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";
+ /** {@value} */
+ public static final String SKIPPING_TESTS
+ = "Condition failed -skipping tests";
+ /** Application exception : {@value} */
+ public static final String APPLICATION_EXCEPTION = "Application Exception";
+ /** Teardown exception : {@value} */
+ public static final String TEARDOWN_EXCEPTION = "Teardown Exception";
+
+ /**
+ * 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_INFO);
+ }
+ }
+
+ /**
+ * Add a condition element.
+ * @return <code>ConditionBase</code>.
+ * @since Ant 1.6.2
+ */
+ public ConditionBase createCondition() {
+ logOverride("condition", condition);
+ condition = new NestedCondition();
+ return condition;
+ }
+
+ /**
+ * Add an application.
+ * @param sequence the application to add.
+ */
+ public void addApplication(Sequential sequence) {
+ logOverride("application", application);
+ application = sequence;
+ }
+
+ /**
+ * Add a setup sequence.
+ * @param sequence the setup sequence to add.
+ */
+ public void addSetup(Sequential sequence) {
+ logOverride("setup", setup);
+ setup = sequence;
+ }
+
+ /**
+ * Add a block.
+ * @param sequence the block for to add.
+ */
+ public void addBlock(BlockFor sequence) {
+ logOverride("block", block);
+ block = sequence;
+ }
+
+ /**
+ * add tests.
+ * @param sequence a sequence to add.
+ */
+ public void addTests(Sequential sequence) {
+ logOverride("tests", tests);
+ tests = sequence;
+ }
+
+ /**
+ * set reporting sequence of tasks.
+ * @param sequence a reporting sequence to use.
+ */
+ public void addReporting(Sequential sequence) {
+ logOverride("reporting", reporting);
+ reporting = sequence;
+ }
+
+ /**
+ * set teardown sequence of tasks.
+ * @param sequence a teardown sequence to use.
+ */
+ public void addTeardown(Sequential sequence) {
+ logOverride("teardown", teardown);
+ teardown = sequence;
+ }
+
+ /**
+ * Set the failOnTeardownErrors attribute.
+ * @param failOnTeardownErrors the value to use.
+ */
+ public void setFailOnTeardownErrors(boolean failOnTeardownErrors) {
+ this.failOnTeardownErrors = failOnTeardownErrors;
+ }
+
+ /**
+ * Set the failureMessage attribute.
+ * @param failureMessage the value to use.
+ */
+ public void setFailureMessage(String failureMessage) {
+ this.failureMessage = failureMessage;
+ }
+
+ /**
+ * Set the failureProperty attribute.
+ * @param failureProperty the value to use.
+ */
+ public void setFailureProperty(String failureProperty) {
+ this.failureProperty = failureProperty;
+ }
+
+ /**
+ * Set the shutdownTime attribute.
+ * @param shutdownTime the value to use.
+ */
+ public void setShutdownTime(long shutdownTime) {
+ this.shutdownTime = shutdownTime;
+ }
+
+ /**
+ * Set the timeout attribute.
+ * @param timeout the value to use.
+ */
+ public void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ /**
+ * Set the timeoutunit attribute.
+ * @param unit the value to use.
+ */
+ public void setTimeoutUnit(WaitFor.Unit unit) {
+ timeoutUnitMultiplier = unit.getMultiplier();
+ }
+
+ /**
+ * Set the shutdownunit attribute.
+ * @param unit the value to use.
+ */
+ public void setShutdownUnit(WaitFor.Unit unit) {
+ shutdownUnitMultiplier = unit.getMultiplier();
+ }
+
+
+ /**
+ * Get the application exception.
+ * @return the application exception.
+ */
+ public BuildException getApplicationException() {
+ return applicationException;
+ }
+
+ /**
+ * Get the teardown exception.
+ * @return the teardown exception.
+ */
+ public BuildException getTeardownException() {
+ return teardownException;
+ }
+
+ /**
+ * Get the test exception.
+ * @return the test exception.
+ */
+ public BuildException getTestException() {
+ return testException;
+ }
+
+ /**
+ * Get the task exception.
+ * @return the task exception.
+ */
+ 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
+ * @param child task
+ * @return a bound and initialised parallel instance.
+ */
+ private Parallel newParallel(long parallelTimeout, Task child) {
+ Parallel par = newParallel(parallelTimeout);
+ par.addTask(child);
+ return par;
+ }
+
+ /**
+ * Add any task validation needed to ensure internal code quality
+ * @param task task
+ * @param role role of the task
+ */
+ private void validateTask(Task task, String role) {
+ if (task!=null && task.getProject() == null) {
+ throw new BuildException(role + " task is not bound to the project" + task);
+ }
+ }
+
+ /**
+ * 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 {
+
+ //validation
+ validateTask(setup, "setup");
+ validateTask(application, "application");
+ validateTask(tests, "tests");
+ validateTask(reporting, "reporting");
+ validateTask(teardown, "teardown");
+
+ //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
+ TaskAdapter ta = new TaskAdapter(block);
+ ta.bindToOwner(this);
+ validateTask(ta, "block");
+ testRun.addTask(ta);
+ //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
+
+ 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 {
+ ignoringThrowable(APPLICATION_EXCEPTION, applicationException);
+ }
+ }
+
+ //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
+ ignoringThrowable(TEARDOWN_EXCEPTION, teardownException);
+ }
+ }
+
+ //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;
+ }
+ }
+
+ /**
+ * log that we are ignoring something rather than rethrowing it.
+ * @param type name of exception
+ * @param thrown what was thrown
+ */
+ protected void ignoringThrowable(String type, Throwable thrown) {
+ log(type + ": " + thrown.toString(),
+ thrown,
+ Project.MSG_WARN);
+ }
+
+ private static class NestedCondition extends ConditionBase implements Condition {
+ public boolean eval() {
+ if (countConditions() != 1) {
+ throw new BuildException(
+ "A single nested condition is required.");
+ }
+ return ((Condition) (getConditions().nextElement())).eval();
+ }
+ }
+}