diff options
Diffstat (limited to 'storage/test/unit/test_statement_executeAsync.js')
-rw-r--r-- | storage/test/unit/test_statement_executeAsync.js | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/storage/test/unit/test_statement_executeAsync.js b/storage/test/unit/test_statement_executeAsync.js new file mode 100644 index 0000000000..dab15121a5 --- /dev/null +++ b/storage/test/unit/test_statement_executeAsync.js @@ -0,0 +1,1045 @@ +/* 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(); +} |