[geary/mjog/async-unit-test-improvements: 3/6] test/test-case.vala: Add AsyncResultWaiter class



commit 768f6afc7827aadef427e2b16aa8531c8d14b9d7
Author: Michael Gratton <mike vee net>
Date:   Fri Apr 10 12:27:20 2020 +1000

    test/test-case.vala: Add AsyncResultWaiter class
    
    Add new class to allow testing interleaving of multiple async calls.
    Re-implement TestCase's support for this using an internal instance
    of the new class.

 test/test-case.vala | 138 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 122 insertions(+), 16 deletions(-)
---
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<GLib.AsyncResult> results =
+        new GLib.AsyncQueue<GLib.AsyncResult>();
+
+
+    /**
+     * 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<AsyncResult> async_results = new AsyncQueue<AsyncResult>();
+    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();
     }
 
     /**


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]