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