/* 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.