Browse Source

new test tasks, not declared yet -so they dont exist-

<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-ffa450edef68
master
Steve Loughran 18 years ago
parent
commit
a4bf996061
5 changed files with 877 additions and 38 deletions
  1. +66
    -38
      src/main/org/apache/tools/ant/taskdefs/WaitFor.java
  2. +73
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/testing/BlockFor.java
  3. +111
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/testing/BuildTimeoutException.java
  4. +457
    -0
      src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java
  5. +170
    -0
      src/main/org/apache/tools/ant/util/WorkerAnt.java

+ 66
- 38
src/main/org/apache/tools/ant/taskdefs/WaitFor.java View File

@@ -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;
}

/**


+ 73
- 0
src/main/org/apache/tools/ant/taskdefs/optional/testing/BlockFor.java View File

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


}

+ 111
- 0
src/main/org/apache/tools/ant/taskdefs/optional/testing/BuildTimeoutException.java View File

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

+ 457
- 0
src/main/org/apache/tools/ant/taskdefs/optional/testing/Funtest.java View File

@@ -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;
}
}
}

+ 170
- 0
src/main/org/apache/tools/ant/util/WorkerAnt.java View File

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

Loading…
Cancel
Save