diff options
Diffstat (limited to 'storage/test/unit/test_connection_asyncClose.js')
-rw-r--r-- | storage/test/unit/test_connection_asyncClose.js | 128 |
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. |