1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/storage/test/unit/test_statement_executeAsync.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1017 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +/* 1.9 + * This file tests the functionality of mozIStorageBaseStatement::executeAsync 1.10 + * for both mozIStorageStatement and mozIStorageAsyncStatement. 1.11 + */ 1.12 + 1.13 +const INTEGER = 1; 1.14 +const TEXT = "this is test text"; 1.15 +const REAL = 3.23; 1.16 +const BLOB = [1, 2]; 1.17 + 1.18 +/** 1.19 + * Execute the given statement asynchronously, spinning an event loop until the 1.20 + * async statement completes. 1.21 + * 1.22 + * @param aStmt 1.23 + * The statement to execute. 1.24 + * @param [aOptions={}] 1.25 + * @param [aOptions.error=false] 1.26 + * If true we should expect an error whose code we do not care about. If 1.27 + * a numeric value, that's the error code we expect and require. If we 1.28 + * are expecting an error, we expect a completion reason of REASON_ERROR. 1.29 + * Otherwise we expect no error notification and a completion reason of 1.30 + * REASON_FINISHED. 1.31 + * @param [aOptions.cancel] 1.32 + * If true we cancel the pending statement and additionally return the 1.33 + * pending statement in case you want to further manipulate it. 1.34 + * @param [aOptions.returnPending=false] 1.35 + * If true we keep the pending statement around and return it to you. We 1.36 + * normally avoid doing this to try and minimize the amount of time a 1.37 + * reference is held to the returned pending statement. 1.38 + * @param [aResults] 1.39 + * If omitted, we assume no results rows are expected. If it is a 1.40 + * number, we assume it is the number of results rows expected. If it is 1.41 + * a function, we assume it is a function that takes the 1) result row 1.42 + * number, 2) result tuple, 3) call stack for the original call to 1.43 + * execAsync as arguments. If it is a list, we currently assume it is a 1.44 + * list of functions where each function is intended to evaluate the 1.45 + * result row at that ordinal position and takes the result tuple and 1.46 + * the call stack for the original call. 1.47 + */ 1.48 +function execAsync(aStmt, aOptions, aResults) 1.49 +{ 1.50 + let caller = Components.stack.caller; 1.51 + if (aOptions == null) 1.52 + aOptions = {}; 1.53 + 1.54 + let resultsExpected; 1.55 + let resultsChecker; 1.56 + if (aResults == null) { 1.57 + resultsExpected = 0; 1.58 + } 1.59 + else if (typeof(aResults) == "number") { 1.60 + resultsExpected = aResults; 1.61 + } 1.62 + else if (typeof(aResults) == "function") { 1.63 + resultsChecker = aResults; 1.64 + } 1.65 + else { // array 1.66 + resultsExpected = aResults.length; 1.67 + resultsChecker = function(aResultNum, aTup, aCaller) { 1.68 + aResults[aResultNum](aTup, aCaller); 1.69 + }; 1.70 + } 1.71 + let resultsSeen = 0; 1.72 + 1.73 + let errorCodeExpected = false; 1.74 + let reasonExpected = Ci.mozIStorageStatementCallback.REASON_FINISHED; 1.75 + let altReasonExpected = null; 1.76 + if ("error" in aOptions) { 1.77 + errorCodeExpected = aOptions.error; 1.78 + if (errorCodeExpected) 1.79 + reasonExpected = Ci.mozIStorageStatementCallback.REASON_ERROR; 1.80 + } 1.81 + let errorCodeSeen = false; 1.82 + 1.83 + if ("cancel" in aOptions && aOptions.cancel) 1.84 + altReasonExpected = Ci.mozIStorageStatementCallback.REASON_CANCELED; 1.85 + 1.86 + let completed = false; 1.87 + 1.88 + let listener = { 1.89 + handleResult: function(aResultSet) 1.90 + { 1.91 + let row, resultsSeenThisCall = 0; 1.92 + while ((row = aResultSet.getNextRow()) != null) { 1.93 + if (resultsChecker) 1.94 + resultsChecker(resultsSeen, row, caller); 1.95 + resultsSeen++; 1.96 + resultsSeenThisCall++; 1.97 + } 1.98 + 1.99 + if (!resultsSeenThisCall) 1.100 + do_throw("handleResult invoked with 0 result rows!"); 1.101 + }, 1.102 + handleError: function(aError) 1.103 + { 1.104 + if (errorCodeSeen != false) 1.105 + do_throw("handleError called when we already had an error!"); 1.106 + errorCodeSeen = aError.result; 1.107 + }, 1.108 + handleCompletion: function(aReason) 1.109 + { 1.110 + if (completed) // paranoia check 1.111 + do_throw("Received a second handleCompletion notification!", caller); 1.112 + 1.113 + if (resultsSeen != resultsExpected) 1.114 + do_throw("Expected " + resultsExpected + " rows of results but " + 1.115 + "got " + resultsSeen + " rows!", caller); 1.116 + 1.117 + if (errorCodeExpected == true && errorCodeSeen == false) 1.118 + do_throw("Expected an error, but did not see one.", caller); 1.119 + else if (errorCodeExpected != errorCodeSeen) 1.120 + do_throw("Expected error code " + errorCodeExpected + " but got " + 1.121 + errorCodeSeen, caller); 1.122 + 1.123 + if (aReason != reasonExpected && aReason != altReasonExpected) 1.124 + do_throw("Expected reason " + reasonExpected + 1.125 + (altReasonExpected ? (" or " + altReasonExpected) : "") + 1.126 + " but got " + aReason, caller); 1.127 + 1.128 + completed = true; 1.129 + } 1.130 + }; 1.131 + 1.132 + let pending; 1.133 + // Only get a pending reference if we're supposed to do. 1.134 + // (note: This does not stop XPConnect from holding onto one currently.) 1.135 + if (("cancel" in aOptions && aOptions.cancel) || 1.136 + ("returnPending" in aOptions && aOptions.returnPending)) { 1.137 + pending = aStmt.executeAsync(listener); 1.138 + } 1.139 + else { 1.140 + aStmt.executeAsync(listener); 1.141 + } 1.142 + 1.143 + if ("cancel" in aOptions && aOptions.cancel) 1.144 + pending.cancel(); 1.145 + 1.146 + let curThread = Components.classes["@mozilla.org/thread-manager;1"] 1.147 + .getService().currentThread; 1.148 + while (!completed && !_quit) 1.149 + curThread.processNextEvent(true); 1.150 + 1.151 + return pending; 1.152 +} 1.153 + 1.154 +/** 1.155 + * Make sure that illegal SQL generates the expected runtime error and does not 1.156 + * result in any crashes. Async-only since the synchronous case generates the 1.157 + * error synchronously (and is tested elsewhere). 1.158 + */ 1.159 +function test_illegal_sql_async_deferred() 1.160 +{ 1.161 + // gibberish 1.162 + let stmt = makeTestStatement("I AM A ROBOT. DO AS I SAY."); 1.163 + execAsync(stmt, {error: Ci.mozIStorageError.ERROR}); 1.164 + stmt.finalize(); 1.165 + 1.166 + // legal SQL syntax, but with semantics issues. 1.167 + stmt = makeTestStatement("SELECT destination FROM funkytown"); 1.168 + execAsync(stmt, {error: Ci.mozIStorageError.ERROR}); 1.169 + stmt.finalize(); 1.170 + 1.171 + run_next_test(); 1.172 +} 1.173 +test_illegal_sql_async_deferred.asyncOnly = true; 1.174 + 1.175 +function test_create_table() 1.176 +{ 1.177 + // Ensure our table doesn't exist 1.178 + do_check_false(getOpenedDatabase().tableExists("test")); 1.179 + 1.180 + var stmt = makeTestStatement( 1.181 + "CREATE TABLE test (" + 1.182 + "id INTEGER, " + 1.183 + "string TEXT, " + 1.184 + "number REAL, " + 1.185 + "nuller NULL, " + 1.186 + "blober BLOB" + 1.187 + ")" 1.188 + ); 1.189 + execAsync(stmt); 1.190 + stmt.finalize(); 1.191 + 1.192 + // Check that the table has been created 1.193 + do_check_true(getOpenedDatabase().tableExists("test")); 1.194 + 1.195 + // Verify that it's created correctly (this will throw if it wasn't) 1.196 + let checkStmt = getOpenedDatabase().createStatement( 1.197 + "SELECT id, string, number, nuller, blober FROM test" 1.198 + ); 1.199 + checkStmt.finalize(); 1.200 + run_next_test(); 1.201 +} 1.202 + 1.203 +function test_add_data() 1.204 +{ 1.205 + var stmt = makeTestStatement( 1.206 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.207 + "VALUES (?, ?, ?, ?, ?)" 1.208 + ); 1.209 + stmt.bindBlobByIndex(4, BLOB, BLOB.length); 1.210 + stmt.bindByIndex(3, null); 1.211 + stmt.bindByIndex(2, REAL); 1.212 + stmt.bindByIndex(1, TEXT); 1.213 + stmt.bindByIndex(0, INTEGER); 1.214 + 1.215 + execAsync(stmt); 1.216 + stmt.finalize(); 1.217 + 1.218 + // Check that the result is in the table 1.219 + verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?", 1.220 + INTEGER, 1.221 + [TEXT, REAL, null, BLOB]); 1.222 + run_next_test(); 1.223 +} 1.224 + 1.225 +function test_get_data() 1.226 +{ 1.227 + var stmt = makeTestStatement( 1.228 + "SELECT string, number, nuller, blober, id FROM test WHERE id = ?" 1.229 + ); 1.230 + stmt.bindByIndex(0, INTEGER); 1.231 + execAsync(stmt, {}, [ 1.232 + function(tuple) 1.233 + { 1.234 + do_check_neq(null, tuple); 1.235 + 1.236 + // Check that it's what we expect 1.237 + do_check_false(tuple.getIsNull(0)); 1.238 + do_check_eq(tuple.getResultByName("string"), tuple.getResultByIndex(0)); 1.239 + do_check_eq(TEXT, tuple.getResultByName("string")); 1.240 + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_TEXT, 1.241 + tuple.getTypeOfIndex(0)); 1.242 + 1.243 + do_check_false(tuple.getIsNull(1)); 1.244 + do_check_eq(tuple.getResultByName("number"), tuple.getResultByIndex(1)); 1.245 + do_check_eq(REAL, tuple.getResultByName("number")); 1.246 + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT, 1.247 + tuple.getTypeOfIndex(1)); 1.248 + 1.249 + do_check_true(tuple.getIsNull(2)); 1.250 + do_check_eq(tuple.getResultByName("nuller"), tuple.getResultByIndex(2)); 1.251 + do_check_eq(null, tuple.getResultByName("nuller")); 1.252 + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_NULL, 1.253 + tuple.getTypeOfIndex(2)); 1.254 + 1.255 + do_check_false(tuple.getIsNull(3)); 1.256 + var blobByName = tuple.getResultByName("blober"); 1.257 + do_check_eq(BLOB.length, blobByName.length); 1.258 + var blobByIndex = tuple.getResultByIndex(3); 1.259 + do_check_eq(BLOB.length, blobByIndex.length); 1.260 + for (var i = 0; i < BLOB.length; i++) { 1.261 + do_check_eq(BLOB[i], blobByName[i]); 1.262 + do_check_eq(BLOB[i], blobByIndex[i]); 1.263 + } 1.264 + var count = { value: 0 }; 1.265 + var blob = { value: null }; 1.266 + tuple.getBlob(3, count, blob); 1.267 + do_check_eq(BLOB.length, count.value); 1.268 + for (var i = 0; i < BLOB.length; i++) 1.269 + do_check_eq(BLOB[i], blob.value[i]); 1.270 + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_BLOB, 1.271 + tuple.getTypeOfIndex(3)); 1.272 + 1.273 + do_check_false(tuple.getIsNull(4)); 1.274 + do_check_eq(tuple.getResultByName("id"), tuple.getResultByIndex(4)); 1.275 + do_check_eq(INTEGER, tuple.getResultByName("id")); 1.276 + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER, 1.277 + tuple.getTypeOfIndex(4)); 1.278 + }]); 1.279 + stmt.finalize(); 1.280 + run_next_test(); 1.281 +} 1.282 + 1.283 +function test_tuple_out_of_bounds() 1.284 +{ 1.285 + var stmt = makeTestStatement( 1.286 + "SELECT string FROM test" 1.287 + ); 1.288 + execAsync(stmt, {}, [ 1.289 + function(tuple) { 1.290 + do_check_neq(null, tuple); 1.291 + 1.292 + // Check all out of bounds - should throw 1.293 + var methods = [ 1.294 + "getTypeOfIndex", 1.295 + "getInt32", 1.296 + "getInt64", 1.297 + "getDouble", 1.298 + "getUTF8String", 1.299 + "getString", 1.300 + "getIsNull", 1.301 + ]; 1.302 + for (var i in methods) { 1.303 + try { 1.304 + tuple[methods[i]](tuple.numEntries); 1.305 + do_throw("did not throw :("); 1.306 + } 1.307 + catch (e) { 1.308 + do_check_eq(Cr.NS_ERROR_ILLEGAL_VALUE, e.result); 1.309 + } 1.310 + } 1.311 + 1.312 + // getBlob requires more args... 1.313 + try { 1.314 + var blob = { value: null }; 1.315 + var size = { value: 0 }; 1.316 + tuple.getBlob(tuple.numEntries, blob, size); 1.317 + do_throw("did not throw :("); 1.318 + } 1.319 + catch (e) { 1.320 + do_check_eq(Cr.NS_ERROR_ILLEGAL_VALUE, e.result); 1.321 + } 1.322 + }]); 1.323 + stmt.finalize(); 1.324 + run_next_test(); 1.325 +} 1.326 + 1.327 +function test_no_listener_works_on_success() 1.328 +{ 1.329 + var stmt = makeTestStatement( 1.330 + "DELETE FROM test WHERE id = ?" 1.331 + ); 1.332 + stmt.bindByIndex(0, 0); 1.333 + stmt.executeAsync(); 1.334 + stmt.finalize(); 1.335 + 1.336 + // Run the next test. 1.337 + run_next_test(); 1.338 +} 1.339 + 1.340 +function test_no_listener_works_on_results() 1.341 +{ 1.342 + var stmt = makeTestStatement( 1.343 + "SELECT ?" 1.344 + ); 1.345 + stmt.bindByIndex(0, 1); 1.346 + stmt.executeAsync(); 1.347 + stmt.finalize(); 1.348 + 1.349 + // Run the next test. 1.350 + run_next_test(); 1.351 +} 1.352 + 1.353 +function test_no_listener_works_on_error() 1.354 +{ 1.355 + // commit without a transaction will trigger an error 1.356 + var stmt = makeTestStatement( 1.357 + "COMMIT" 1.358 + ); 1.359 + stmt.executeAsync(); 1.360 + stmt.finalize(); 1.361 + 1.362 + // Run the next test. 1.363 + run_next_test(); 1.364 +} 1.365 + 1.366 +function test_partial_listener_works() 1.367 +{ 1.368 + var stmt = makeTestStatement( 1.369 + "DELETE FROM test WHERE id = ?" 1.370 + ); 1.371 + stmt.bindByIndex(0, 0); 1.372 + stmt.executeAsync({ 1.373 + handleResult: function(aResultSet) 1.374 + { 1.375 + } 1.376 + }); 1.377 + stmt.executeAsync({ 1.378 + handleError: function(aError) 1.379 + { 1.380 + } 1.381 + }); 1.382 + stmt.executeAsync({ 1.383 + handleCompletion: function(aReason) 1.384 + { 1.385 + } 1.386 + }); 1.387 + stmt.finalize(); 1.388 + 1.389 + // Run the next test. 1.390 + run_next_test(); 1.391 +} 1.392 + 1.393 +/** 1.394 + * Dubious cancellation test that depends on system loading may or may not 1.395 + * succeed in canceling things. It does at least test if calling cancel blows 1.396 + * up. test_AsyncCancellation in test_true_async.cpp is our test that canceling 1.397 + * actually works correctly. 1.398 + */ 1.399 +function test_immediate_cancellation() 1.400 +{ 1.401 + var stmt = makeTestStatement( 1.402 + "DELETE FROM test WHERE id = ?" 1.403 + ); 1.404 + stmt.bindByIndex(0, 0); 1.405 + execAsync(stmt, {cancel: true}); 1.406 + stmt.finalize(); 1.407 + run_next_test(); 1.408 +} 1.409 + 1.410 +/** 1.411 + * Test that calling cancel twice throws the second time. 1.412 + */ 1.413 +function test_double_cancellation() 1.414 +{ 1.415 + var stmt = makeTestStatement( 1.416 + "DELETE FROM test WHERE id = ?" 1.417 + ); 1.418 + stmt.bindByIndex(0, 0); 1.419 + let pendingStatement = execAsync(stmt, {cancel: true}); 1.420 + // And cancel again - expect an exception 1.421 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.422 + function() pendingStatement.cancel()); 1.423 + 1.424 + stmt.finalize(); 1.425 + run_next_test(); 1.426 +} 1.427 + 1.428 +/** 1.429 + * Verify that nothing untoward happens if we try and cancel something after it 1.430 + * has fully run to completion. 1.431 + */ 1.432 +function test_cancellation_after_execution() 1.433 +{ 1.434 + var stmt = makeTestStatement( 1.435 + "DELETE FROM test WHERE id = ?" 1.436 + ); 1.437 + stmt.bindByIndex(0, 0); 1.438 + let pendingStatement = execAsync(stmt, {returnPending: true}); 1.439 + // (the statement has fully executed at this point) 1.440 + // canceling after the statement has run to completion should not throw! 1.441 + pendingStatement.cancel(); 1.442 + 1.443 + stmt.finalize(); 1.444 + run_next_test(); 1.445 +} 1.446 + 1.447 +/** 1.448 + * Verifies that a single statement can be executed more than once. Might once 1.449 + * have been intended to also ensure that callback notifications were not 1.450 + * incorrectly interleaved, but that part was brittle (it's totally fine for 1.451 + * handleResult to get called multiple times) and not comprehensive. 1.452 + */ 1.453 +function test_double_execute() 1.454 +{ 1.455 + var stmt = makeTestStatement( 1.456 + "SELECT 1" 1.457 + ); 1.458 + execAsync(stmt, null, 1); 1.459 + execAsync(stmt, null, 1); 1.460 + stmt.finalize(); 1.461 + run_next_test(); 1.462 +} 1.463 + 1.464 +function test_finalized_statement_does_not_crash() 1.465 +{ 1.466 + var stmt = makeTestStatement( 1.467 + "SELECT * FROM TEST" 1.468 + ); 1.469 + stmt.finalize(); 1.470 + // we are concerned about a crash here; an error is fine. 1.471 + try { 1.472 + stmt.executeAsync(); 1.473 + } 1.474 + catch (ex) {} 1.475 + 1.476 + // Run the next test. 1.477 + run_next_test(); 1.478 +} 1.479 + 1.480 +/** 1.481 + * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by index. 1.482 + */ 1.483 +function test_bind_direct_binding_params_by_index() 1.484 +{ 1.485 + var stmt = makeTestStatement( 1.486 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.487 + "VALUES (?, ?, ?, ?, ?)" 1.488 + ); 1.489 + let insertId = nextUniqueId++; 1.490 + stmt.bindByIndex(0, insertId); 1.491 + stmt.bindByIndex(1, TEXT); 1.492 + stmt.bindByIndex(2, REAL); 1.493 + stmt.bindByIndex(3, null); 1.494 + stmt.bindBlobByIndex(4, BLOB, BLOB.length); 1.495 + execAsync(stmt); 1.496 + stmt.finalize(); 1.497 + verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?", 1.498 + insertId, 1.499 + [TEXT, REAL, null, BLOB]); 1.500 + run_next_test(); 1.501 +} 1.502 + 1.503 +/** 1.504 + * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by name. 1.505 + */ 1.506 +function test_bind_direct_binding_params_by_name() 1.507 +{ 1.508 + var stmt = makeTestStatement( 1.509 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.510 + "VALUES (:int, :text, :real, :null, :blob)" 1.511 + ); 1.512 + let insertId = nextUniqueId++; 1.513 + stmt.bindByName("int", insertId); 1.514 + stmt.bindByName("text", TEXT); 1.515 + stmt.bindByName("real", REAL); 1.516 + stmt.bindByName("null", null); 1.517 + stmt.bindBlobByName("blob", BLOB, BLOB.length); 1.518 + execAsync(stmt); 1.519 + stmt.finalize(); 1.520 + verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?", 1.521 + insertId, 1.522 + [TEXT, REAL, null, BLOB]); 1.523 + run_next_test(); 1.524 +} 1.525 + 1.526 +function test_bind_js_params_helper_by_index() 1.527 +{ 1.528 + var stmt = makeTestStatement( 1.529 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.530 + "VALUES (?, ?, ?, ?, NULL)" 1.531 + ); 1.532 + let insertId = nextUniqueId++; 1.533 + // we cannot bind blobs this way; no blober 1.534 + stmt.params[3] = null; 1.535 + stmt.params[2] = REAL; 1.536 + stmt.params[1] = TEXT; 1.537 + stmt.params[0] = insertId; 1.538 + execAsync(stmt); 1.539 + stmt.finalize(); 1.540 + verifyQuery("SELECT string, number, nuller FROM test WHERE id = ?", insertId, 1.541 + [TEXT, REAL, null]); 1.542 + run_next_test(); 1.543 +} 1.544 + 1.545 +function test_bind_js_params_helper_by_name() 1.546 +{ 1.547 + var stmt = makeTestStatement( 1.548 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.549 + "VALUES (:int, :text, :real, :null, NULL)" 1.550 + ); 1.551 + let insertId = nextUniqueId++; 1.552 + // we cannot bind blobs this way; no blober 1.553 + stmt.params.null = null; 1.554 + stmt.params.real = REAL; 1.555 + stmt.params.text = TEXT; 1.556 + stmt.params.int = insertId; 1.557 + execAsync(stmt); 1.558 + stmt.finalize(); 1.559 + verifyQuery("SELECT string, number, nuller FROM test WHERE id = ?", insertId, 1.560 + [TEXT, REAL, null]); 1.561 + run_next_test(); 1.562 +} 1.563 + 1.564 +function test_bind_multiple_rows_by_index() 1.565 +{ 1.566 + const AMOUNT_TO_ADD = 5; 1.567 + var stmt = makeTestStatement( 1.568 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.569 + "VALUES (?, ?, ?, ?, ?)" 1.570 + ); 1.571 + var array = stmt.newBindingParamsArray(); 1.572 + for (let i = 0; i < AMOUNT_TO_ADD; i++) { 1.573 + let bp = array.newBindingParams(); 1.574 + bp.bindByIndex(0, INTEGER); 1.575 + bp.bindByIndex(1, TEXT); 1.576 + bp.bindByIndex(2, REAL); 1.577 + bp.bindByIndex(3, null); 1.578 + bp.bindBlobByIndex(4, BLOB, BLOB.length); 1.579 + array.addParams(bp); 1.580 + do_check_eq(array.length, i + 1); 1.581 + } 1.582 + stmt.bindParameters(array); 1.583 + 1.584 + let rowCount = getTableRowCount("test"); 1.585 + execAsync(stmt); 1.586 + do_check_eq(rowCount + AMOUNT_TO_ADD, getTableRowCount("test")); 1.587 + stmt.finalize(); 1.588 + run_next_test(); 1.589 +} 1.590 + 1.591 +function test_bind_multiple_rows_by_name() 1.592 +{ 1.593 + const AMOUNT_TO_ADD = 5; 1.594 + var stmt = makeTestStatement( 1.595 + "INSERT INTO test (id, string, number, nuller, blober) " + 1.596 + "VALUES (:int, :text, :real, :null, :blob)" 1.597 + ); 1.598 + var array = stmt.newBindingParamsArray(); 1.599 + for (let i = 0; i < AMOUNT_TO_ADD; i++) { 1.600 + let bp = array.newBindingParams(); 1.601 + bp.bindByName("int", INTEGER); 1.602 + bp.bindByName("text", TEXT); 1.603 + bp.bindByName("real", REAL); 1.604 + bp.bindByName("null", null); 1.605 + bp.bindBlobByName("blob", BLOB, BLOB.length); 1.606 + array.addParams(bp); 1.607 + do_check_eq(array.length, i + 1); 1.608 + } 1.609 + stmt.bindParameters(array); 1.610 + 1.611 + let rowCount = getTableRowCount("test"); 1.612 + execAsync(stmt); 1.613 + do_check_eq(rowCount + AMOUNT_TO_ADD, getTableRowCount("test")); 1.614 + stmt.finalize(); 1.615 + run_next_test(); 1.616 +} 1.617 + 1.618 +/** 1.619 + * Verify that a mozIStorageStatement instance throws immediately when we 1.620 + * try and bind to an illegal index. 1.621 + */ 1.622 +function test_bind_out_of_bounds_sync_immediate() 1.623 +{ 1.624 + let stmt = makeTestStatement( 1.625 + "INSERT INTO test (id) " + 1.626 + "VALUES (?)" 1.627 + ); 1.628 + 1.629 + let array = stmt.newBindingParamsArray(); 1.630 + let bp = array.newBindingParams(); 1.631 + 1.632 + // Check variant binding. 1.633 + expectError(Cr.NS_ERROR_INVALID_ARG, 1.634 + function() bp.bindByIndex(1, INTEGER)); 1.635 + // Check blob binding. 1.636 + expectError(Cr.NS_ERROR_INVALID_ARG, 1.637 + function() bp.bindBlobByIndex(1, BLOB, BLOB.length)); 1.638 + 1.639 + stmt.finalize(); 1.640 + run_next_test(); 1.641 +} 1.642 +test_bind_out_of_bounds_sync_immediate.syncOnly = true; 1.643 + 1.644 +/** 1.645 + * Verify that a mozIStorageAsyncStatement reports an error asynchronously when 1.646 + * we bind to an illegal index. 1.647 + */ 1.648 +function test_bind_out_of_bounds_async_deferred() 1.649 +{ 1.650 + let stmt = makeTestStatement( 1.651 + "INSERT INTO test (id) " + 1.652 + "VALUES (?)" 1.653 + ); 1.654 + 1.655 + let array = stmt.newBindingParamsArray(); 1.656 + let bp = array.newBindingParams(); 1.657 + 1.658 + // There is no difference between variant and blob binding for async purposes. 1.659 + bp.bindByIndex(1, INTEGER); 1.660 + array.addParams(bp); 1.661 + stmt.bindParameters(array); 1.662 + execAsync(stmt, {error: Ci.mozIStorageError.RANGE}); 1.663 + 1.664 + stmt.finalize(); 1.665 + run_next_test(); 1.666 +} 1.667 +test_bind_out_of_bounds_async_deferred.asyncOnly = true; 1.668 + 1.669 +function test_bind_no_such_name_sync_immediate() 1.670 +{ 1.671 + let stmt = makeTestStatement( 1.672 + "INSERT INTO test (id) " + 1.673 + "VALUES (:foo)" 1.674 + ); 1.675 + 1.676 + let array = stmt.newBindingParamsArray(); 1.677 + let bp = array.newBindingParams(); 1.678 + 1.679 + // Check variant binding. 1.680 + expectError(Cr.NS_ERROR_INVALID_ARG, 1.681 + function() bp.bindByName("doesnotexist", INTEGER)); 1.682 + // Check blob binding. 1.683 + expectError(Cr.NS_ERROR_INVALID_ARG, 1.684 + function() bp.bindBlobByName("doesnotexist", BLOB, BLOB.length)); 1.685 + 1.686 + stmt.finalize(); 1.687 + run_next_test(); 1.688 +} 1.689 +test_bind_no_such_name_sync_immediate.syncOnly = true; 1.690 + 1.691 +function test_bind_no_such_name_async_deferred() 1.692 +{ 1.693 + let stmt = makeTestStatement( 1.694 + "INSERT INTO test (id) " + 1.695 + "VALUES (:foo)" 1.696 + ); 1.697 + 1.698 + let array = stmt.newBindingParamsArray(); 1.699 + let bp = array.newBindingParams(); 1.700 + 1.701 + bp.bindByName("doesnotexist", INTEGER); 1.702 + array.addParams(bp); 1.703 + stmt.bindParameters(array); 1.704 + execAsync(stmt, {error: Ci.mozIStorageError.RANGE}); 1.705 + 1.706 + stmt.finalize(); 1.707 + run_next_test(); 1.708 +} 1.709 +test_bind_no_such_name_async_deferred.asyncOnly = true; 1.710 + 1.711 +function test_bind_bogus_type_by_index() 1.712 +{ 1.713 + // We try to bind a JS Object here that should fail to bind. 1.714 + let stmt = makeTestStatement( 1.715 + "INSERT INTO test (blober) " + 1.716 + "VALUES (?)" 1.717 + ); 1.718 + 1.719 + let array = stmt.newBindingParamsArray(); 1.720 + let bp = array.newBindingParams(); 1.721 + // We get an error after calling executeAsync, not when we bind. 1.722 + bp.bindByIndex(0, run_test); 1.723 + array.addParams(bp); 1.724 + stmt.bindParameters(array); 1.725 + 1.726 + execAsync(stmt, {error: Ci.mozIStorageError.MISMATCH}); 1.727 + 1.728 + stmt.finalize(); 1.729 + run_next_test(); 1.730 +} 1.731 + 1.732 +function test_bind_bogus_type_by_name() 1.733 +{ 1.734 + // We try to bind a JS Object here that should fail to bind. 1.735 + let stmt = makeTestStatement( 1.736 + "INSERT INTO test (blober) " + 1.737 + "VALUES (:blob)" 1.738 + ); 1.739 + 1.740 + let array = stmt.newBindingParamsArray(); 1.741 + let bp = array.newBindingParams(); 1.742 + // We get an error after calling executeAsync, not when we bind. 1.743 + bp.bindByName("blob", run_test); 1.744 + array.addParams(bp); 1.745 + stmt.bindParameters(array); 1.746 + 1.747 + execAsync(stmt, {error: Ci.mozIStorageError.MISMATCH}); 1.748 + 1.749 + stmt.finalize(); 1.750 + run_next_test(); 1.751 +} 1.752 + 1.753 +function test_bind_params_already_locked() 1.754 +{ 1.755 + let stmt = makeTestStatement( 1.756 + "INSERT INTO test (id) " + 1.757 + "VALUES (:int)" 1.758 + ); 1.759 + 1.760 + let array = stmt.newBindingParamsArray(); 1.761 + let bp = array.newBindingParams(); 1.762 + bp.bindByName("int", INTEGER); 1.763 + array.addParams(bp); 1.764 + 1.765 + // We should get an error after we call addParams and try to bind again. 1.766 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.767 + function() bp.bindByName("int", INTEGER)); 1.768 + 1.769 + stmt.finalize(); 1.770 + run_next_test(); 1.771 +} 1.772 + 1.773 +function test_bind_params_array_already_locked() 1.774 +{ 1.775 + let stmt = makeTestStatement( 1.776 + "INSERT INTO test (id) " + 1.777 + "VALUES (:int)" 1.778 + ); 1.779 + 1.780 + let array = stmt.newBindingParamsArray(); 1.781 + let bp1 = array.newBindingParams(); 1.782 + bp1.bindByName("int", INTEGER); 1.783 + array.addParams(bp1); 1.784 + let bp2 = array.newBindingParams(); 1.785 + stmt.bindParameters(array); 1.786 + bp2.bindByName("int", INTEGER); 1.787 + 1.788 + // We should get an error after we have bound the array to the statement. 1.789 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.790 + function() array.addParams(bp2)); 1.791 + 1.792 + stmt.finalize(); 1.793 + run_next_test(); 1.794 +} 1.795 + 1.796 +function test_no_binding_params_from_locked_array() 1.797 +{ 1.798 + let stmt = makeTestStatement( 1.799 + "INSERT INTO test (id) " + 1.800 + "VALUES (:int)" 1.801 + ); 1.802 + 1.803 + let array = stmt.newBindingParamsArray(); 1.804 + let bp = array.newBindingParams(); 1.805 + bp.bindByName("int", INTEGER); 1.806 + array.addParams(bp); 1.807 + stmt.bindParameters(array); 1.808 + 1.809 + // We should not be able to get a new BindingParams object after we have bound 1.810 + // to the statement. 1.811 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.812 + function() array.newBindingParams()); 1.813 + 1.814 + stmt.finalize(); 1.815 + run_next_test(); 1.816 +} 1.817 + 1.818 +function test_not_right_owning_array() 1.819 +{ 1.820 + let stmt = makeTestStatement( 1.821 + "INSERT INTO test (id) " + 1.822 + "VALUES (:int)" 1.823 + ); 1.824 + 1.825 + let array1 = stmt.newBindingParamsArray(); 1.826 + let array2 = stmt.newBindingParamsArray(); 1.827 + let bp = array1.newBindingParams(); 1.828 + bp.bindByName("int", INTEGER); 1.829 + 1.830 + // We should not be able to add bp to array2 since it was created from array1. 1.831 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.832 + function() array2.addParams(bp)); 1.833 + 1.834 + stmt.finalize(); 1.835 + run_next_test(); 1.836 +} 1.837 + 1.838 +function test_not_right_owning_statement() 1.839 +{ 1.840 + let stmt1 = makeTestStatement( 1.841 + "INSERT INTO test (id) " + 1.842 + "VALUES (:int)" 1.843 + ); 1.844 + let stmt2 = makeTestStatement( 1.845 + "INSERT INTO test (id) " + 1.846 + "VALUES (:int)" 1.847 + ); 1.848 + 1.849 + let array1 = stmt1.newBindingParamsArray(); 1.850 + let array2 = stmt2.newBindingParamsArray(); 1.851 + let bp = array1.newBindingParams(); 1.852 + bp.bindByName("int", INTEGER); 1.853 + array1.addParams(bp); 1.854 + 1.855 + // We should not be able to bind array1 since it was created from stmt1. 1.856 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.857 + function() stmt2.bindParameters(array1)); 1.858 + 1.859 + stmt1.finalize(); 1.860 + stmt2.finalize(); 1.861 + run_next_test(); 1.862 +} 1.863 + 1.864 +function test_bind_empty_array() 1.865 +{ 1.866 + let stmt = makeTestStatement( 1.867 + "INSERT INTO test (id) " + 1.868 + "VALUES (:int)" 1.869 + ); 1.870 + 1.871 + let paramsArray = stmt.newBindingParamsArray(); 1.872 + 1.873 + // We should not be able to bind this array to the statement because it is 1.874 + // empty. 1.875 + expectError(Cr.NS_ERROR_UNEXPECTED, 1.876 + function() stmt.bindParameters(paramsArray)); 1.877 + 1.878 + stmt.finalize(); 1.879 + run_next_test(); 1.880 +} 1.881 + 1.882 +function test_multiple_results() 1.883 +{ 1.884 + let expectedResults = getTableRowCount("test"); 1.885 + // Sanity check - we should have more than one result, but let's be sure. 1.886 + do_check_true(expectedResults > 1); 1.887 + 1.888 + // Now check that we get back two rows of data from our async query. 1.889 + let stmt = makeTestStatement("SELECT * FROM test"); 1.890 + execAsync(stmt, {}, expectedResults); 1.891 + 1.892 + stmt.finalize(); 1.893 + run_next_test(); 1.894 +} 1.895 + 1.896 +//////////////////////////////////////////////////////////////////////////////// 1.897 +//// Test Runner 1.898 + 1.899 + 1.900 +const TEST_PASS_SYNC = 0; 1.901 +const TEST_PASS_ASYNC = 1; 1.902 +/** 1.903 + * We run 2 passes against the test. One where makeTestStatement generates 1.904 + * synchronous (mozIStorageStatement) statements and one where it generates 1.905 + * asynchronous (mozIStorageAsyncStatement) statements. 1.906 + * 1.907 + * Because of differences in the ability to know the number of parameters before 1.908 + * dispatching, some tests are sync/async specific. These functions are marked 1.909 + * with 'syncOnly' or 'asyncOnly' attributes and run_next_test knows what to do. 1.910 + */ 1.911 +let testPass = TEST_PASS_SYNC; 1.912 + 1.913 +/** 1.914 + * Create a statement of the type under test per testPass. 1.915 + * 1.916 + * @param aSQL 1.917 + * The SQL string from which to build a statement. 1.918 + * @return a statement of the type under test per testPass. 1.919 + */ 1.920 +function makeTestStatement(aSQL) { 1.921 + if (testPass == TEST_PASS_SYNC) 1.922 + return getOpenedDatabase().createStatement(aSQL); 1.923 + else 1.924 + return getOpenedDatabase().createAsyncStatement(aSQL); 1.925 +} 1.926 + 1.927 +var tests = 1.928 +[ 1.929 + test_illegal_sql_async_deferred, 1.930 + test_create_table, 1.931 + test_add_data, 1.932 + test_get_data, 1.933 + test_tuple_out_of_bounds, 1.934 + test_no_listener_works_on_success, 1.935 + test_no_listener_works_on_results, 1.936 + test_no_listener_works_on_error, 1.937 + test_partial_listener_works, 1.938 + test_immediate_cancellation, 1.939 + test_double_cancellation, 1.940 + test_cancellation_after_execution, 1.941 + test_double_execute, 1.942 + test_finalized_statement_does_not_crash, 1.943 + test_bind_direct_binding_params_by_index, 1.944 + test_bind_direct_binding_params_by_name, 1.945 + test_bind_js_params_helper_by_index, 1.946 + test_bind_js_params_helper_by_name, 1.947 + test_bind_multiple_rows_by_index, 1.948 + test_bind_multiple_rows_by_name, 1.949 + test_bind_out_of_bounds_sync_immediate, 1.950 + test_bind_out_of_bounds_async_deferred, 1.951 + test_bind_no_such_name_sync_immediate, 1.952 + test_bind_no_such_name_async_deferred, 1.953 + test_bind_bogus_type_by_index, 1.954 + test_bind_bogus_type_by_name, 1.955 + test_bind_params_already_locked, 1.956 + test_bind_params_array_already_locked, 1.957 + test_bind_empty_array, 1.958 + test_no_binding_params_from_locked_array, 1.959 + test_not_right_owning_array, 1.960 + test_not_right_owning_statement, 1.961 + test_multiple_results, 1.962 +]; 1.963 +let index = 0; 1.964 + 1.965 +const STARTING_UNIQUE_ID = 2; 1.966 +let nextUniqueId = STARTING_UNIQUE_ID; 1.967 + 1.968 +function run_next_test() 1.969 +{ 1.970 + function _run_next_test() { 1.971 + // use a loop so we can skip tests... 1.972 + while (index < tests.length) { 1.973 + let test = tests[index++]; 1.974 + // skip tests not appropriate to the current test pass 1.975 + if ((testPass == TEST_PASS_SYNC && ("asyncOnly" in test)) || 1.976 + (testPass == TEST_PASS_ASYNC && ("syncOnly" in test))) 1.977 + continue; 1.978 + 1.979 + // Asynchronous tests means that exceptions don't kill the test. 1.980 + try { 1.981 + print("****** Running the next test: " + test.name); 1.982 + test(); 1.983 + return; 1.984 + } 1.985 + catch (e) { 1.986 + do_throw(e); 1.987 + } 1.988 + } 1.989 + 1.990 + // if we only completed the first pass, move to the next pass 1.991 + if (testPass == TEST_PASS_SYNC) { 1.992 + print("********* Beginning mozIStorageAsyncStatement pass."); 1.993 + testPass++; 1.994 + index = 0; 1.995 + // a new pass demands a new database 1.996 + asyncCleanup(); 1.997 + nextUniqueId = STARTING_UNIQUE_ID; 1.998 + _run_next_test(); 1.999 + return; 1.1000 + } 1.1001 + 1.1002 + // we did some async stuff; we need to clean up. 1.1003 + asyncCleanup(); 1.1004 + do_test_finished(); 1.1005 + } 1.1006 + 1.1007 + // Don't actually schedule another test if we're quitting. 1.1008 + if (!_quit) { 1.1009 + // For saner stacks, we execute this code RSN. 1.1010 + do_execute_soon(_run_next_test); 1.1011 + } 1.1012 +} 1.1013 + 1.1014 +function run_test() 1.1015 +{ 1.1016 + cleanup(); 1.1017 + 1.1018 + do_test_pending(); 1.1019 + run_next_test(); 1.1020 +}