summaryrefslogtreecommitdiffstats
path: root/storage/test/unit/test_connection_asyncClose.js
diff options
context:
space:
mode:
Diffstat (limited to 'storage/test/unit/test_connection_asyncClose.js')
-rw-r--r--storage/test/unit/test_connection_asyncClose.js128
1 files changed, 128 insertions, 0 deletions
diff --git a/storage/test/unit/test_connection_asyncClose.js b/storage/test/unit/test_connection_asyncClose.js
new file mode 100644
index 0000000000..2d89087c5c
--- /dev/null
+++ b/storage/test/unit/test_connection_asyncClose.js
@@ -0,0 +1,128 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Thorough branch coverage for asyncClose.
+ *
+ * Coverage of asyncClose by connection state at time of AsyncClose invocation:
+ * - (asyncThread && mDBConn) => AsyncCloseConnection used, actually closes
+ * - test_asyncClose_does_not_complete_before_statements
+ * - test_double_asyncClose_throws
+ * - test_asyncClose_does_not_throw_without_callback
+ * - (asyncThread && !mDBConn) => AsyncCloseConnection used, although no close
+ * is required. Note that this is only possible in the event that
+ * openAsyncDatabase was used and we failed to open the database.
+ * Additionally, the async connection will never be exposed to the caller and
+ * AsyncInitDatabase will be the one to (automatically) call AsyncClose.
+ * - test_asyncClose_failed_open
+ * - (!asyncThread && mDBConn) => Close() invoked, actually closes
+ * - test_asyncClose_on_sync_db
+ * - (!asyncThread && !mDBConn) => Close() invoked, no close needed, errors.
+ * This happens if the database has already been closed.
+ * - test_double_asyncClose_throws
+ */
+
+/**
+ * Sanity check that our close indeed happens after asynchronously executed
+ * statements scheduled during the same turn of the event loop. Note that we
+ * just care that the statement says it completed without error, we're not
+ * worried that the close will happen and then the statement will magically
+ * complete.
+ */
+add_task(async function test_asyncClose_does_not_complete_before_statements() {
+ let db = Services.storage.openDatabase(getTestDB());
+ let stmt = db.createStatement("SELECT * FROM sqlite_master");
+ // Issue the executeAsync but don't yield for it...
+ let asyncStatementPromise = executeAsync(stmt);
+ stmt.finalize();
+
+ // Issue the close. (And now the order of yielding doesn't matter.)
+ // Branch coverage: (asyncThread && mDBConn)
+ await asyncClose(db);
+ equal(
+ await asyncStatementPromise,
+ Ci.mozIStorageStatementCallback.REASON_FINISHED
+ );
+});
+
+/**
+ * Open an async database (ensures the async thread is created) and then invoke
+ * AsyncClose() twice without yielding control flow. The first will initiate
+ * the actual async close after calling setClosedState which synchronously
+ * impacts what the second call will observe. The second call will then see the
+ * async thread is not available and fall back to invoking Close() which will
+ * notice the mDBConn is already gone.
+ */
+if (!AppConstants.DEBUG) {
+ add_task(async function test_double_asyncClose_throws() {
+ let db = await openAsyncDatabase(getTestDB());
+
+ // (Don't yield control flow yet, save the promise for after we make the
+ // second call.)
+ // Branch coverage: (asyncThread && mDBConn)
+ let realClosePromise = await asyncClose(db);
+ try {
+ // Branch coverage: (!asyncThread && !mDBConn)
+ db.asyncClose();
+ ok(false, "should have thrown");
+ } catch (e) {
+ equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ await realClosePromise;
+ });
+}
+
+/**
+ * Create a sync db connection and never take it asynchronous and then call
+ * asyncClose on it. This will bring the async thread to life to perform the
+ * shutdown to avoid blocking the main thread, although we won't be able to
+ * tell the difference between this happening and the method secretly shunting
+ * to close().
+ */
+add_task(async function test_asyncClose_on_sync_db() {
+ let db = Services.storage.openDatabase(getTestDB());
+
+ // Branch coverage: (!asyncThread && mDBConn)
+ await asyncClose(db);
+ ok(true, "closed sync connection asynchronously");
+});
+
+/**
+ * Fail to asynchronously open a DB in order to get an async thread existing
+ * without having an open database and asyncClose invoked. As per the file
+ * doc-block, note that asyncClose will automatically be invoked by the
+ * AsyncInitDatabase when it fails to open the database. We will never be
+ * provided with a reference to the connection and so cannot call AsyncClose on
+ * it ourselves.
+ */
+add_task(async function test_asyncClose_failed_open() {
+ // This will fail and the promise will be rejected.
+ let openPromise = openAsyncDatabase(getFakeDB());
+ await openPromise.then(
+ () => {
+ ok(false, "we should have failed to open the db; this test is broken!");
+ },
+ () => {
+ ok(true, "correctly failed to open db; bg asyncClose should happen");
+ }
+ );
+ // (NB: we are unable to observe the thread shutdown, but since we never open
+ // a database, this test is not going to interfere with other tests so much.)
+});
+
+// THE TEST BELOW WANTS TO BE THE LAST TEST WE RUN. DO NOT MAKE IT SAD.
+/**
+ * Verify that asyncClose without a callback does not explode. Without a
+ * callback the shutdown is not actually observable, so we run this test last
+ * in order to avoid weird overlaps.
+ */
+add_task(async function test_asyncClose_does_not_throw_without_callback() {
+ let db = await openAsyncDatabase(getTestDB());
+ // Branch coverage: (asyncThread && mDBConn)
+ db.asyncClose();
+ ok(true, "if we shutdown cleanly and do not crash, then we succeeded");
+});
+// OBEY SHOUTING UPPER-CASE COMMENTS.
+// ADD TESTS ABOVE THE FORMER TEST, NOT BELOW IT.