/* 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/. */ /* * This file tests the functionality of mozIStorageBaseStatement::executeAsync * for both mozIStorageStatement and mozIStorageAsyncStatement. */ // This file uses the internal _quit from testing/xpcshell/head.js */ /* global _quit */ const INTEGER = 1; const TEXT = "this is test text"; const REAL = 3.23; const BLOB = [1, 2]; /** * Execute the given statement asynchronously, spinning an event loop until the * async statement completes. * * @param aStmt * The statement to execute. * @param [aOptions={}] * @param [aOptions.error=false] * If true we should expect an error whose code we do not care about. If * a numeric value, that's the error code we expect and require. If we * are expecting an error, we expect a completion reason of REASON_ERROR. * Otherwise we expect no error notification and a completion reason of * REASON_FINISHED. * @param [aOptions.cancel] * If true we cancel the pending statement and additionally return the * pending statement in case you want to further manipulate it. * @param [aOptions.returnPending=false] * If true we keep the pending statement around and return it to you. We * normally avoid doing this to try and minimize the amount of time a * reference is held to the returned pending statement. * @param [aResults] * If omitted, we assume no results rows are expected. If it is a * number, we assume it is the number of results rows expected. If it is * a function, we assume it is a function that takes the 1) result row * number, 2) result tuple, 3) call stack for the original call to * execAsync as arguments. If it is a list, we currently assume it is a * list of functions where each function is intended to evaluate the * result row at that ordinal position and takes the result tuple and * the call stack for the original call. */ function execAsync(aStmt, aOptions, aResults) { let caller = Components.stack.caller; if (aOptions == null) { aOptions = {}; } let resultsExpected; let resultsChecker; if (aResults == null) { resultsExpected = 0; } else if (typeof aResults == "number") { resultsExpected = aResults; } else if (typeof aResults == "function") { resultsChecker = aResults; } else { // array resultsExpected = aResults.length; resultsChecker = function (aResultNum, aTup, aCaller) { aResults[aResultNum](aTup, aCaller); }; } let resultsSeen = 0; let errorCodeExpected = false; let reasonExpected = Ci.mozIStorageStatementCallback.REASON_FINISHED; let altReasonExpected = null; if ("error" in aOptions) { errorCodeExpected = aOptions.error; if (errorCodeExpected) { reasonExpected = Ci.mozIStorageStatementCallback.REASON_ERROR; } } let errorCodeSeen = false; if ("cancel" in aOptions && aOptions.cancel) { altReasonExpected = Ci.mozIStorageStatementCallback.REASON_CANCELED; } let completed = false; let listener = { handleResult(aResultSet) { let row, resultsSeenThisCall = 0; while ((row = aResultSet.getNextRow()) != null) { if (resultsChecker) { resultsChecker(resultsSeen, row, caller); } resultsSeen++; resultsSeenThisCall++; } if (!resultsSeenThisCall) { do_throw("handleResult invoked with 0 result rows!"); } }, handleError(aError) { if (errorCodeSeen) { do_throw("handleError called when we already had an error!"); } errorCodeSeen = aError.result; }, handleCompletion(aReason) { if (completed) { // paranoia check do_throw("Received a second handleCompletion notification!", caller); } if (resultsSeen != resultsExpected) { do_throw( "Expected " + resultsExpected + " rows of results but " + "got " + resultsSeen + " rows!", caller ); } if (errorCodeExpected && !errorCodeSeen) { do_throw("Expected an error, but did not see one.", caller); } else if (errorCodeExpected != errorCodeSeen) { do_throw( "Expected error code " + errorCodeExpected + " but got " + errorCodeSeen, caller ); } if (aReason != reasonExpected && aReason != altReasonExpected) { do_throw( "Expected reason " + reasonExpected + (altReasonExpected ? " or " + altReasonExpected : "") + " but got " + aReason, caller ); } completed = true; }, }; let pending; // Only get a pending reference if we're supposed to do. // (note: This does not stop XPConnect from holding onto one currently.) if ( ("cancel" in aOptions && aOptions.cancel) || ("returnPending" in aOptions && aOptions.returnPending) ) { pending = aStmt.executeAsync(listener); } else { aStmt.executeAsync(listener); } if ("cancel" in aOptions && aOptions.cancel) { pending.cancel(); } Services.tm.spinEventLoopUntil( "Test(test_statement_executeAsync.js:execAsync)", () => completed || _quit ); return pending; } /** * Make sure that illegal SQL generates the expected runtime error and does not * result in any crashes. Async-only since the synchronous case generates the * error synchronously (and is tested elsewhere). */ function test_illegal_sql_async_deferred() { let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM); // gibberish let stmt = makeTestStatement("I AM A ROBOT. DO AS I SAY."); execAsync(stmt, { error: Ci.mozIStorageError.ERROR }); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.failure, 1 ); histogram.clear(); // legal SQL syntax, but with semantics issues. stmt = makeTestStatement("SELECT destination FROM funkytown"); execAsync(stmt, { error: Ci.mozIStorageError.ERROR }); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.failure, 1 ); histogram.clear(); run_next_test(); } test_illegal_sql_async_deferred.asyncOnly = true; function test_create_table() { // Ensure our table doesn't exist Assert.ok(!getOpenedDatabase().tableExists("test")); let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM); var stmt = makeTestStatement( "CREATE TABLE test (" + "id INTEGER, " + "string TEXT, " + "number REAL, " + "nuller NULL, " + "blober BLOB" + ")" ); execAsync(stmt); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.success, 1 ); histogram.clear(); // Check that the table has been created Assert.ok(getOpenedDatabase().tableExists("test")); histogram.clear(); // Verify that it's created correctly (this will throw if it wasn't) let checkStmt = getOpenedDatabase().createStatement( "SELECT id, string, number, nuller, blober FROM test" ); checkStmt.finalize(); // Nothing has executed so the histogram should be empty. Assert.ok(!histogram.snapshot().values); run_next_test(); } function test_add_data() { let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM); var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (?, ?, ?, ?, ?)" ); stmt.bindBlobByIndex(4, BLOB, BLOB.length); stmt.bindByIndex(3, null); stmt.bindByIndex(2, REAL); stmt.bindByIndex(1, TEXT); stmt.bindByIndex(0, INTEGER); execAsync(stmt); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.success, 1 ); histogram.clear(); // Check that the result is in the table verifyQuery( "SELECT string, number, nuller, blober FROM test WHERE id = ?", INTEGER, [TEXT, REAL, null, BLOB] ); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.success, 1 ); histogram.clear(); run_next_test(); } function test_get_data() { let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM); var stmt = makeTestStatement( "SELECT string, number, nuller, blober, id FROM test WHERE id = ?" ); stmt.bindByIndex(0, INTEGER); execAsync(stmt, {}, [ function (tuple) { Assert.notEqual(null, tuple); // Check that it's what we expect Assert.ok(!tuple.getIsNull(0)); Assert.equal(tuple.getResultByName("string"), tuple.getResultByIndex(0)); Assert.equal(TEXT, tuple.getResultByName("string")); Assert.equal( Ci.mozIStorageValueArray.VALUE_TYPE_TEXT, tuple.getTypeOfIndex(0) ); Assert.ok(!tuple.getIsNull(1)); Assert.equal(tuple.getResultByName("number"), tuple.getResultByIndex(1)); Assert.equal(REAL, tuple.getResultByName("number")); Assert.equal( Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT, tuple.getTypeOfIndex(1) ); Assert.ok(tuple.getIsNull(2)); Assert.equal(tuple.getResultByName("nuller"), tuple.getResultByIndex(2)); Assert.equal(null, tuple.getResultByName("nuller")); Assert.equal( Ci.mozIStorageValueArray.VALUE_TYPE_NULL, tuple.getTypeOfIndex(2) ); Assert.ok(!tuple.getIsNull(3)); var blobByName = tuple.getResultByName("blober"); Assert.equal(BLOB.length, blobByName.length); var blobByIndex = tuple.getResultByIndex(3); Assert.equal(BLOB.length, blobByIndex.length); for (let i = 0; i < BLOB.length; i++) { Assert.equal(BLOB[i], blobByName[i]); Assert.equal(BLOB[i], blobByIndex[i]); } var count = { value: 0 }; var blob = { value: null }; tuple.getBlob(3, count, blob); Assert.equal(BLOB.length, count.value); for (let i = 0; i < BLOB.length; i++) { Assert.equal(BLOB[i], blob.value[i]); } Assert.equal( Ci.mozIStorageValueArray.VALUE_TYPE_BLOB, tuple.getTypeOfIndex(3) ); Assert.ok(!tuple.getIsNull(4)); Assert.equal(tuple.getResultByName("id"), tuple.getResultByIndex(4)); Assert.equal(INTEGER, tuple.getResultByName("id")); Assert.equal( Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER, tuple.getTypeOfIndex(4) ); }, ]); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.success, 1 ); histogram.clear(); run_next_test(); } function test_tuple_out_of_bounds() { let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM); var stmt = makeTestStatement("SELECT string FROM test"); execAsync(stmt, {}, [ function (tuple) { Assert.notEqual(null, tuple); // Check all out of bounds - should throw var methods = [ "getTypeOfIndex", "getInt32", "getInt64", "getDouble", "getUTF8String", "getString", "getIsNull", ]; for (var i in methods) { try { tuple[methods[i]](tuple.numEntries); do_throw("did not throw :("); } catch (e) { Assert.equal(Cr.NS_ERROR_ILLEGAL_VALUE, e.result); } } // getBlob requires more args... try { var blob = { value: null }; var size = { value: 0 }; tuple.getBlob(tuple.numEntries, blob, size); do_throw("did not throw :("); } catch (e) { Assert.equal(Cr.NS_ERROR_ILLEGAL_VALUE, e.result); } }, ]); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.success, 1 ); histogram.clear(); run_next_test(); } function test_no_listener_works_on_success() { var stmt = makeTestStatement("DELETE FROM test WHERE id = ?"); stmt.bindByIndex(0, 0); stmt.executeAsync(); stmt.finalize(); // Run the next test. run_next_test(); } function test_no_listener_works_on_results() { var stmt = makeTestStatement("SELECT ?"); stmt.bindByIndex(0, 1); stmt.executeAsync(); stmt.finalize(); // Run the next test. run_next_test(); } function test_no_listener_works_on_error() { // commit without a transaction will trigger an error var stmt = makeTestStatement("COMMIT"); stmt.executeAsync(); stmt.finalize(); // Run the next test. run_next_test(); } function test_partial_listener_works() { var stmt = makeTestStatement("DELETE FROM test WHERE id = ?"); stmt.bindByIndex(0, 0); stmt.executeAsync({ handleResult(aResultSet) {}, }); stmt.executeAsync({ handleError(aError) {}, }); stmt.executeAsync({ handleCompletion(aReason) {}, }); stmt.finalize(); // Run the next test. run_next_test(); } /** * Dubious cancellation test that depends on system loading may or may not * succeed in canceling things. It does at least test if calling cancel blows * up. test_AsyncCancellation in test_true_async.cpp is our test that canceling * actually works correctly. */ function test_immediate_cancellation() { var stmt = makeTestStatement("DELETE FROM test WHERE id = ?"); stmt.bindByIndex(0, 0); execAsync(stmt, { cancel: true }); stmt.finalize(); run_next_test(); } /** * Test that calling cancel twice throws the second time. */ function test_double_cancellation() { var stmt = makeTestStatement("DELETE FROM test WHERE id = ?"); stmt.bindByIndex(0, 0); let pendingStatement = execAsync(stmt, { cancel: true }); // And cancel again - expect an exception expectError(Cr.NS_ERROR_UNEXPECTED, () => pendingStatement.cancel()); stmt.finalize(); run_next_test(); } /** * Verify that nothing untoward happens if we try and cancel something after it * has fully run to completion. */ function test_cancellation_after_execution() { var stmt = makeTestStatement("DELETE FROM test WHERE id = ?"); stmt.bindByIndex(0, 0); let pendingStatement = execAsync(stmt, { returnPending: true }); // (the statement has fully executed at this point) // canceling after the statement has run to completion should not throw! pendingStatement.cancel(); stmt.finalize(); run_next_test(); } /** * Verifies that a single statement can be executed more than once. Might once * have been intended to also ensure that callback notifications were not * incorrectly interleaved, but that part was brittle (it's totally fine for * handleResult to get called multiple times) and not comprehensive. */ function test_double_execute() { let histogram = TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM); var stmt = makeTestStatement("SELECT 1"); execAsync(stmt, null, 1); execAsync(stmt, null, 1); stmt.finalize(); TelemetryTestUtils.assertKeyedHistogramValue( histogram, TEST_DB_NAME, TELEMETRY_VALUES.success, 2 ); histogram.clear(); run_next_test(); } function test_finalized_statement_does_not_crash() { var stmt = makeTestStatement("SELECT * FROM TEST"); stmt.finalize(); // we are concerned about a crash here; an error is fine. try { stmt.executeAsync(); } catch (ex) { // Do nothing. } // Run the next test. run_next_test(); } /** * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by index. */ function test_bind_direct_binding_params_by_index() { var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (?, ?, ?, ?, ?)" ); let insertId = nextUniqueId++; stmt.bindByIndex(0, insertId); stmt.bindByIndex(1, TEXT); stmt.bindByIndex(2, REAL); stmt.bindByIndex(3, null); stmt.bindBlobByIndex(4, BLOB, BLOB.length); execAsync(stmt); stmt.finalize(); verifyQuery( "SELECT string, number, nuller, blober FROM test WHERE id = ?", insertId, [TEXT, REAL, null, BLOB] ); run_next_test(); } /** * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by name. */ function test_bind_direct_binding_params_by_name() { var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (:int, :text, :real, :null, :blob)" ); let insertId = nextUniqueId++; stmt.bindByName("int", insertId); stmt.bindByName("text", TEXT); stmt.bindByName("real", REAL); stmt.bindByName("null", null); stmt.bindBlobByName("blob", BLOB); execAsync(stmt); stmt.finalize(); verifyQuery( "SELECT string, number, nuller, blober FROM test WHERE id = ?", insertId, [TEXT, REAL, null, BLOB] ); run_next_test(); } function test_bind_js_params_helper_by_index() { var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (?, ?, ?, ?, NULL)" ); let insertId = nextUniqueId++; // we cannot bind blobs this way; no blober stmt.params[3] = null; stmt.params[2] = REAL; stmt.params[1] = TEXT; stmt.params[0] = insertId; execAsync(stmt); stmt.finalize(); verifyQuery( "SELECT string, number, nuller FROM test WHERE id = ?", insertId, [TEXT, REAL, null] ); run_next_test(); } function test_bind_js_params_helper_by_name() { var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (:int, :text, :real, :null, NULL)" ); let insertId = nextUniqueId++; // we cannot bind blobs this way; no blober stmt.params.null = null; stmt.params.real = REAL; stmt.params.text = TEXT; stmt.params.int = insertId; execAsync(stmt); stmt.finalize(); verifyQuery( "SELECT string, number, nuller FROM test WHERE id = ?", insertId, [TEXT, REAL, null] ); run_next_test(); } function test_bind_multiple_rows_by_index() { const AMOUNT_TO_ADD = 5; var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (?, ?, ?, ?, ?)" ); var array = stmt.newBindingParamsArray(); for (let i = 0; i < AMOUNT_TO_ADD; i++) { let bp = array.newBindingParams(); bp.bindByIndex(0, INTEGER); bp.bindByIndex(1, TEXT); bp.bindByIndex(2, REAL); bp.bindByIndex(3, null); bp.bindBlobByIndex(4, BLOB, BLOB.length); array.addParams(bp); Assert.equal(array.length, i + 1); } stmt.bindParameters(array); let rowCount = getTableRowCount("test"); execAsync(stmt); Assert.equal(rowCount + AMOUNT_TO_ADD, getTableRowCount("test")); stmt.finalize(); run_next_test(); } function test_bind_multiple_rows_by_name() { const AMOUNT_TO_ADD = 5; var stmt = makeTestStatement( "INSERT INTO test (id, string, number, nuller, blober) " + "VALUES (:int, :text, :real, :null, :blob)" ); var array = stmt.newBindingParamsArray(); for (let i = 0; i < AMOUNT_TO_ADD; i++) { let bp = array.newBindingParams(); bp.bindByName("int", INTEGER); bp.bindByName("text", TEXT); bp.bindByName("real", REAL); bp.bindByName("null", null); bp.bindBlobByName("blob", BLOB); array.addParams(bp); Assert.equal(array.length, i + 1); } stmt.bindParameters(array); let rowCount = getTableRowCount("test"); execAsync(stmt); Assert.equal(rowCount + AMOUNT_TO_ADD, getTableRowCount("test")); stmt.finalize(); run_next_test(); } /** * Verify that a mozIStorageStatement instance throws immediately when we * try and bind to an illegal index. */ function test_bind_out_of_bounds_sync_immediate() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (?)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); // Check variant binding. expectError(Cr.NS_ERROR_INVALID_ARG, () => bp.bindByIndex(1, INTEGER)); // Check blob binding. expectError(Cr.NS_ERROR_INVALID_ARG, () => bp.bindBlobByIndex(1, BLOB, BLOB.length) ); stmt.finalize(); run_next_test(); } test_bind_out_of_bounds_sync_immediate.syncOnly = true; /** * Verify that a mozIStorageAsyncStatement reports an error asynchronously when * we bind to an illegal index. */ function test_bind_out_of_bounds_async_deferred() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (?)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); // There is no difference between variant and blob binding for async purposes. bp.bindByIndex(1, INTEGER); array.addParams(bp); stmt.bindParameters(array); execAsync(stmt, { error: Ci.mozIStorageError.RANGE }); stmt.finalize(); run_next_test(); } test_bind_out_of_bounds_async_deferred.asyncOnly = true; function test_bind_no_such_name_sync_immediate() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:foo)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); // Check variant binding. expectError(Cr.NS_ERROR_INVALID_ARG, () => bp.bindByName("doesnotexist", INTEGER) ); // Check blob binding. expectError(Cr.NS_ERROR_INVALID_ARG, () => bp.bindBlobByName("doesnotexist", BLOB) ); stmt.finalize(); run_next_test(); } test_bind_no_such_name_sync_immediate.syncOnly = true; function test_bind_no_such_name_async_deferred() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:foo)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); bp.bindByName("doesnotexist", INTEGER); array.addParams(bp); stmt.bindParameters(array); execAsync(stmt, { error: Ci.mozIStorageError.RANGE }); stmt.finalize(); run_next_test(); } test_bind_no_such_name_async_deferred.asyncOnly = true; function test_bind_bogus_type_by_index() { // We try to bind a JS Object here that should fail to bind. let stmt = makeTestStatement("INSERT INTO test (blober) VALUES (?)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); Assert.throws(() => bp.bindByIndex(0, run_test), /NS_ERROR_UNEXPECTED/); stmt.finalize(); run_next_test(); } function test_bind_bogus_type_by_name() { // We try to bind a JS Object here that should fail to bind. let stmt = makeTestStatement("INSERT INTO test (blober) VALUES (:blob)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); Assert.throws(() => bp.bindByName("blob", run_test), /NS_ERROR_UNEXPECTED/); stmt.finalize(); run_next_test(); } function test_bind_params_already_locked() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); bp.bindByName("int", INTEGER); array.addParams(bp); // We should get an error after we call addParams and try to bind again. expectError(Cr.NS_ERROR_UNEXPECTED, () => bp.bindByName("int", INTEGER)); stmt.finalize(); run_next_test(); } function test_bind_params_array_already_locked() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let array = stmt.newBindingParamsArray(); let bp1 = array.newBindingParams(); bp1.bindByName("int", INTEGER); array.addParams(bp1); let bp2 = array.newBindingParams(); stmt.bindParameters(array); bp2.bindByName("int", INTEGER); // We should get an error after we have bound the array to the statement. expectError(Cr.NS_ERROR_UNEXPECTED, () => array.addParams(bp2)); stmt.finalize(); run_next_test(); } function test_no_binding_params_from_locked_array() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let array = stmt.newBindingParamsArray(); let bp = array.newBindingParams(); bp.bindByName("int", INTEGER); array.addParams(bp); stmt.bindParameters(array); // We should not be able to get a new BindingParams object after we have bound // to the statement. expectError(Cr.NS_ERROR_UNEXPECTED, () => array.newBindingParams()); stmt.finalize(); run_next_test(); } function test_not_right_owning_array() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let array1 = stmt.newBindingParamsArray(); let array2 = stmt.newBindingParamsArray(); let bp = array1.newBindingParams(); bp.bindByName("int", INTEGER); // We should not be able to add bp to array2 since it was created from array1. expectError(Cr.NS_ERROR_UNEXPECTED, () => array2.addParams(bp)); stmt.finalize(); run_next_test(); } function test_not_right_owning_statement() { let stmt1 = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let stmt2 = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let array1 = stmt1.newBindingParamsArray(); stmt2.newBindingParamsArray(); let bp = array1.newBindingParams(); bp.bindByName("int", INTEGER); array1.addParams(bp); // We should not be able to bind array1 since it was created from stmt1. expectError(Cr.NS_ERROR_UNEXPECTED, () => stmt2.bindParameters(array1)); stmt1.finalize(); stmt2.finalize(); run_next_test(); } function test_bind_empty_array() { let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)"); let paramsArray = stmt.newBindingParamsArray(); // We should not be able to bind this array to the statement because it is // empty. expectError(Cr.NS_ERROR_UNEXPECTED, () => stmt.bindParameters(paramsArray)); stmt.finalize(); run_next_test(); } function test_multiple_results() { let expectedResults = getTableRowCount("test"); // Sanity check - we should have more than one result, but let's be sure. Assert.ok(expectedResults > 1); // Now check that we get back two rows of data from our async query. let stmt = makeTestStatement("SELECT * FROM test"); execAsync(stmt, {}, expectedResults); stmt.finalize(); run_next_test(); } // Test Runner const TEST_PASS_SYNC = 0; const TEST_PASS_ASYNC = 1; /** * We run 2 passes against the test. One where makeTestStatement generates * synchronous (mozIStorageStatement) statements and one where it generates * asynchronous (mozIStorageAsyncStatement) statements. * * Because of differences in the ability to know the number of parameters before * dispatching, some tests are sync/async specific. These functions are marked * with 'syncOnly' or 'asyncOnly' attributes and run_next_test knows what to do. */ var testPass = TEST_PASS_SYNC; /** * Create a statement of the type under test per testPass. * * @param aSQL * The SQL string from which to build a statement. * @return a statement of the type under test per testPass. */ function makeTestStatement(aSQL) { if (testPass == TEST_PASS_SYNC) { return getOpenedDatabase().createStatement(aSQL); } return getOpenedDatabase().createAsyncStatement(aSQL); } var tests = [ test_illegal_sql_async_deferred, test_create_table, test_add_data, test_get_data, test_tuple_out_of_bounds, test_no_listener_works_on_success, test_no_listener_works_on_results, test_no_listener_works_on_error, test_partial_listener_works, test_immediate_cancellation, test_double_cancellation, test_cancellation_after_execution, test_double_execute, test_finalized_statement_does_not_crash, test_bind_direct_binding_params_by_index, test_bind_direct_binding_params_by_name, test_bind_js_params_helper_by_index, test_bind_js_params_helper_by_name, test_bind_multiple_rows_by_index, test_bind_multiple_rows_by_name, test_bind_out_of_bounds_sync_immediate, test_bind_out_of_bounds_async_deferred, test_bind_no_such_name_sync_immediate, test_bind_no_such_name_async_deferred, test_bind_bogus_type_by_index, test_bind_bogus_type_by_name, test_bind_params_already_locked, test_bind_params_array_already_locked, test_bind_empty_array, test_no_binding_params_from_locked_array, test_not_right_owning_array, test_not_right_owning_statement, test_multiple_results, ]; var index = 0; const STARTING_UNIQUE_ID = 2; var nextUniqueId = STARTING_UNIQUE_ID; function run_next_test() { function _run_next_test() { // use a loop so we can skip tests... while (index < tests.length) { let test = tests[index++]; // skip tests not appropriate to the current test pass if ( (testPass == TEST_PASS_SYNC && "asyncOnly" in test) || (testPass == TEST_PASS_ASYNC && "syncOnly" in test) ) { continue; } // Asynchronous tests means that exceptions don't kill the test. try { print("****** Running the next test: " + test.name); test(); return; } catch (e) { do_throw(e); } } // if we only completed the first pass, move to the next pass if (testPass == TEST_PASS_SYNC) { print("********* Beginning mozIStorageAsyncStatement pass."); testPass++; index = 0; // a new pass demands a new database asyncCleanup(); nextUniqueId = STARTING_UNIQUE_ID; _run_next_test(); return; } // we did some async stuff; we need to clean up. asyncCleanup(); do_test_finished(); } // Don't actually schedule another test if we're quitting. if (!_quit) { // For saner stacks, we execute this code RSN. executeSoon(_run_next_test); } } function run_test() { // Thunderbird doesn't have one or more of the probes used in this test. // Ensure the data is collected anyway. Services.prefs.setBoolPref( "toolkit.telemetry.testing.overrideProductsCheck", true ); cleanup(); do_test_pending(); run_next_test(); }