/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.mozilla.gecko.sync.Logger;
/**
* Implements waiting for asynchronous test events.
*
* Call WaitHelper.getTestWaiter() to get the unique instance.
*
* Call performWait(runnable) to execute runnable synchronously.
* runnable *must* call performNotify() on all exit paths to signal to
* the TestWaiter that the runnable has completed.
*
* @author rnewman
* @author nalexander
*/
public class WaitHelper {
public static final String LOG_TAG = "WaitHelper";
public static class Result {
public Throwable error;
public Result() {
error = null;
}
public Result(Throwable error) {
this.error = error;
}
}
public static abstract class WaitHelperError extends Error {
private static final long serialVersionUID = 7074690961681883619L;
}
/**
* Immutable.
*
* @author rnewman
*/
public static class TimeoutError extends WaitHelperError {
private static final long serialVersionUID = 8591672555848651736L;
public final int waitTimeInMillis;
public TimeoutError(int waitTimeInMillis) {
this.waitTimeInMillis = waitTimeInMillis;
}
}
public static class MultipleNotificationsError extends WaitHelperError {
private static final long serialVersionUID = -9072736521571635495L;
}
public static class InterruptedError extends WaitHelperError {
private static final long serialVersionUID = 8383948170038639308L;
}
public static class InnerError extends WaitHelperError {
private static final long serialVersionUID = 3008502618576773778L;
public Throwable innerError;
public InnerError(Throwable e) {
innerError = e;
if (e != null) {
// Eclipse prints the stack trace of the cause.
this.initCause(e);
}
}
}
public BlockingQueue<Result> queue = new ArrayBlockingQueue<Result>(1);
/**
* How long performWait should wait for, in milliseconds, with the
* convention that a negative value means "wait forever".
*/
public static int defaultWaitTimeoutInMillis = -1;
public void performWait(Runnable action) throws WaitHelperError {
this.performWait(defaultWaitTimeoutInMillis, action);
}
public void performWait(int waitTimeoutInMillis, Runnable action) throws WaitHelperError {
Logger.debug(LOG_TAG, "performWait called.");
Result result = null;
try {
if (action != null) {
try {
action.run();
Logger.debug(LOG_TAG, "Action done.");
} catch (Exception ex) {
Logger.debug(LOG_TAG, "Performing action threw: " + ex.getMessage());
throw new InnerError(ex);
}
}
if (waitTimeoutInMillis < 0) {
result = queue.take();
} else {
result = queue.poll(waitTimeoutInMillis, TimeUnit.MILLISECONDS);
}
Logger.debug(LOG_TAG, "Got result from queue: " + result);
} catch (InterruptedException e) {
// We were interrupted.
Logger.debug(LOG_TAG, "performNotify interrupted with InterruptedException " + e);
final InterruptedError interruptedError = new InterruptedError();
interruptedError.initCause(e);
throw interruptedError;
}
if (result == null) {
// We timed out.
throw new TimeoutError(waitTimeoutInMillis);
} else if (result.error != null) {
Logger.debug(LOG_TAG, "Notified with error: " + result.error.getMessage());
// Rethrow any assertion with which we were notified.
InnerError innerError = new InnerError(result.error);
throw innerError;
}
// Success!
}
public void performNotify(final Throwable e) {
if (e != null) {
Logger.debug(LOG_TAG, "performNotify called with Throwable: " + e.getMessage());
} else {
Logger.debug(LOG_TAG, "performNotify called.");
}
if (!queue.offer(new Result(e))) {
// This could happen if performNotify is called multiple times (which is an error).
throw new MultipleNotificationsError();
}
}
public void performNotify() {
this.performNotify(null);
}
public static Runnable onThreadRunnable(final Runnable r) {
return new Runnable() {
@Override
public void run() {
new Thread(r).start();
}
};
}
private static WaitHelper singleWaiter = new WaitHelper();
public static WaitHelper getTestWaiter() {
return singleWaiter;
}
public static void resetTestWaiter() {
singleWaiter = new WaitHelper();
}
public boolean isIdle() {
return queue.isEmpty();
}
}