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