storage/test/unit/test_statement_executeAsync.js

changeset 0
6474c204b198
     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 +}

mercurial