diff --git a/test/test-case.vala b/test/test-case.vala index 6292c498..901be037 100644 --- a/test/test-case.vala +++ b/test/test-case.vala @@ -200,7 +200,108 @@ public void delete_file(File parent) throws GLib.Error { } -public abstract class TestCase : Object { +/** + * Allows non-async code to wait for async calls to be completed. + * + * To use instances of this class, call an async function or method + * using the `begin()` form, passing {@link async_complete} as + * completion argument (that is, the last argument): + * + * {{{ + * var waiter = new AsyncResultWaiter(); + * my_async_call.begin("foo", waiter.async_completion); + * }}} + * + * Then, when you want to ensure the call is complete, pass the result + * of calling {@link async_result} to its `end()` form: + * + * {{{ + * my_async_call.end(waiter.async_result()); + * }}} + * + * This will block until the async call has completed. + * + * Note that {@link TestCase} exposes the same interface, so it is + * usually easier to just call those when testing a single async call, + * or multiple, non-interleaved async calls. + * + * This class is implemented as a FIFO queue of {@link + * GLib.AsyncResult} instances, and thus can be used for waiting for + * multiple calls. Note however the ordering depends on the order in + * which the async calls being invoked are executed and are + * completed. Thus if testing multiple interleaved async calls, you + * should probably use an instance of this class per call. + */ +public class AsyncResultWaiter : GLib.Object { + + + /** The main loop that is executed when waiting for async results. */ + public GLib.MainContext main_loop { get; construct set; } + + private GLib.AsyncQueue results = + new GLib.AsyncQueue(); + + + /** + * Constructs a new waiter. + * + * @param main_loop a main loop context to execute when waiting + * for an async result + */ + public AsyncResultWaiter(GLib.MainContext main_loop) { + Object(main_loop: main_loop); + } + + /** + * The last argument of an async call to be tested. + * + * Records the given {@link GLib.AsyncResult}, adding it to the + * internal FIFO queue. This method should be called as the + * completion of an async call to be tested. + * + * To use it, pass as the last argument to the `begin()` form of + * the async call: + * + * {{{ + * var waiter = new AsyncResultWaiter(); + * my_async_call.begin("foo", waiter.async_completion); + * }}} + */ + public void async_completion(GLib.Object? object, + GLib.AsyncResult result) { + this.results.push(result); + // Notify the loop so that if async_result() has already been + // called, that method won't block. + this.main_loop.wakeup(); + } + + /** + * Waits for async calls to complete, returning the most recent one. + * + * This returns the first {@link GLib.AsyncResult} from the + * internal FIFO queue that has been provided by {@link + * async_completion}. If none are available, it will pump the main + * loop, blocking until one becomes available. + * + * To use it, pass its return value as the argument to the `end()` + * call: + * + * {{{ + * my_async_call.end(waiter.async_result()); + * }}} + */ + public GLib.AsyncResult async_result() { + GLib.AsyncResult? result = this.results.try_pop(); + while (result == null) { + this.main_loop.iteration(true); + result = this.results.try_pop(); + } + return result; + } + +} + +public abstract class TestCase : GLib.Object { /** GLib.File URI for resources in test/data. */ @@ -221,12 +322,13 @@ public abstract class TestCase : Object { private GLib.TestSuite suite; private Adaptor[] adaptors = new Adaptor[0]; - private AsyncQueue async_results = new AsyncQueue(); + private AsyncResultWaiter async_waiter; public delegate void TestMethod() throws Error; protected TestCase(string name) { this.suite = new GLib.TestSuite(name); + this.async_waiter = new AsyncResultWaiter(this.main_loop); } public void add_test(string name, owned TestMethod test) { @@ -253,28 +355,32 @@ public abstract class TestCase : Object { return this.suite; } + /** + * Calls the same method on the test case's default async waiter. + * + * @see AsyncResultWaiter.async_completion + */ protected void async_complete(AsyncResult result) { - this.async_results.push(result); - // notify the loop so that if async_result() has already been - // called, that method won't block - this.main_loop.wakeup(); + this.async_waiter.async_completion(null, result); } + /** + * Calls the same method on the test case's default async waiter. + * + * @see AsyncResultWaiter.async_completion + */ protected void async_complete_full(GLib.Object? object, AsyncResult result) { - this.async_results.push(result); - // notify the loop so that if async_result() has already been - // called, that method won't block - this.main_loop.wakeup(); + this.async_waiter.async_completion(object, result); } + /** + * Calls the same method on the test case's default async waiter. + * + * @see AsyncResultWaiter.async_result + */ protected AsyncResult async_result() { - AsyncResult? result = null; - while (result == null) { - this.main_loop.iteration(true); - result = this.async_results.try_pop(); - } - return result; + return this.async_waiter.async_result(); } /**