tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_statement_executeAsync.js (28061B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /*
      6 * This file tests the functionality of mozIStorageBaseStatement::executeAsync
      7 * for both mozIStorageStatement and mozIStorageAsyncStatement.
      8 */
      9 
     10 // This file uses the internal _quit from testing/xpcshell/head.js */
     11 /* global _quit */
     12 
     13 const INTEGER = 1;
     14 const TEXT = "this is test text";
     15 const REAL = 3.23;
     16 const BLOB = [1, 2];
     17 
     18 /**
     19 * Execute the given statement asynchronously, spinning an event loop until the
     20 * async statement completes.
     21 *
     22 * @param aStmt
     23 *        The statement to execute.
     24 * @param [aOptions={}]
     25 * @param [aOptions.error=false]
     26 *        If true we should expect an error whose code we do not care about.  If
     27 *        a numeric value, that's the error code we expect and require.  If we
     28 *        are expecting an error, we expect a completion reason of REASON_ERROR.
     29 *        Otherwise we expect no error notification and a completion reason of
     30 *        REASON_FINISHED.
     31 * @param [aOptions.cancel]
     32 *        If true we cancel the pending statement and additionally return the
     33 *        pending statement in case you want to further manipulate it.
     34 * @param [aOptions.returnPending=false]
     35 *        If true we keep the pending statement around and return it to you.  We
     36 *        normally avoid doing this to try and minimize the amount of time a
     37 *        reference is held to the returned pending statement.
     38 * @param [aResults]
     39 *        If omitted, we assume no results rows are expected.  If it is a
     40 *        number, we assume it is the number of results rows expected.  If it is
     41 *        a function, we assume it is a function that takes the 1) result row
     42 *        number, 2) result tuple, 3) call stack for the original call to
     43 *        execAsync as arguments.  If it is a list, we currently assume it is a
     44 *        list of functions where each function is intended to evaluate the
     45 *        result row at that ordinal position and takes the result tuple and
     46 *        the call stack for the original call.
     47 */
     48 function execAsync(aStmt, aOptions, aResults) {
     49  let caller = Components.stack.caller;
     50  if (aOptions == null) {
     51    aOptions = {};
     52  }
     53 
     54  let resultsExpected;
     55  let resultsChecker;
     56  if (aResults == null) {
     57    resultsExpected = 0;
     58  } else if (typeof aResults == "number") {
     59    resultsExpected = aResults;
     60  } else if (typeof aResults == "function") {
     61    resultsChecker = aResults;
     62  } else {
     63    // array
     64    resultsExpected = aResults.length;
     65    resultsChecker = function (aResultNum, aTup, aCaller) {
     66      aResults[aResultNum](aTup, aCaller);
     67    };
     68  }
     69  let resultsSeen = 0;
     70 
     71  let errorCodeExpected = false;
     72  let reasonExpected = Ci.mozIStorageStatementCallback.REASON_FINISHED;
     73  let altReasonExpected = null;
     74  if ("error" in aOptions) {
     75    errorCodeExpected = aOptions.error;
     76    if (errorCodeExpected) {
     77      reasonExpected = Ci.mozIStorageStatementCallback.REASON_ERROR;
     78    }
     79  }
     80  let errorCodeSeen = false;
     81 
     82  if ("cancel" in aOptions && aOptions.cancel) {
     83    altReasonExpected = Ci.mozIStorageStatementCallback.REASON_CANCELED;
     84  }
     85 
     86  let completed = false;
     87 
     88  let listener = {
     89    handleResult(aResultSet) {
     90      let row,
     91        resultsSeenThisCall = 0;
     92      while ((row = aResultSet.getNextRow()) != null) {
     93        if (resultsChecker) {
     94          resultsChecker(resultsSeen, row, caller);
     95        }
     96        resultsSeen++;
     97        resultsSeenThisCall++;
     98      }
     99 
    100      if (!resultsSeenThisCall) {
    101        do_throw("handleResult invoked with 0 result rows!");
    102      }
    103    },
    104    handleError(aError) {
    105      if (errorCodeSeen) {
    106        do_throw("handleError called when we already had an error!");
    107      }
    108      errorCodeSeen = aError.result;
    109    },
    110    handleCompletion(aReason) {
    111      if (completed) {
    112        // paranoia check
    113        do_throw("Received a second handleCompletion notification!", caller);
    114      }
    115 
    116      if (resultsSeen != resultsExpected) {
    117        do_throw(
    118          "Expected " +
    119            resultsExpected +
    120            " rows of results but " +
    121            "got " +
    122            resultsSeen +
    123            " rows!",
    124          caller
    125        );
    126      }
    127 
    128      if (errorCodeExpected && !errorCodeSeen) {
    129        do_throw("Expected an error, but did not see one.", caller);
    130      } else if (errorCodeExpected != errorCodeSeen) {
    131        do_throw(
    132          "Expected error code " +
    133            errorCodeExpected +
    134            " but got " +
    135            errorCodeSeen,
    136          caller
    137        );
    138      }
    139 
    140      if (aReason != reasonExpected && aReason != altReasonExpected) {
    141        do_throw(
    142          "Expected reason " +
    143            reasonExpected +
    144            (altReasonExpected ? " or " + altReasonExpected : "") +
    145            " but got " +
    146            aReason,
    147          caller
    148        );
    149      }
    150 
    151      completed = true;
    152    },
    153  };
    154 
    155  let pending;
    156  // Only get a pending reference if we're supposed to do.
    157  // (note: This does not stop XPConnect from holding onto one currently.)
    158  if (
    159    ("cancel" in aOptions && aOptions.cancel) ||
    160    ("returnPending" in aOptions && aOptions.returnPending)
    161  ) {
    162    pending = aStmt.executeAsync(listener);
    163  } else {
    164    aStmt.executeAsync(listener);
    165  }
    166 
    167  if ("cancel" in aOptions && aOptions.cancel) {
    168    pending.cancel();
    169  }
    170 
    171  Services.tm.spinEventLoopUntil(
    172    "Test(test_statement_executeAsync.js:execAsync)",
    173    () => completed || _quit
    174  );
    175 
    176  return pending;
    177 }
    178 
    179 /**
    180 * Make sure that illegal SQL generates the expected runtime error and does not
    181 * result in any crashes.  Async-only since the synchronous case generates the
    182 * error synchronously (and is tested elsewhere).
    183 */
    184 function test_illegal_sql_async_deferred() {
    185  // gibberish
    186  let stmt = makeTestStatement("I AM A ROBOT. DO AS I SAY.");
    187  execAsync(stmt, { error: Ci.mozIStorageError.ERROR });
    188  stmt.finalize();
    189 
    190  Assert.equal(
    191    Glean.sqliteStore.query.get(TEST_DB_NAME, "failure").testGetValue(),
    192    1
    193  );
    194 
    195  // legal SQL syntax, but with semantics issues.
    196  stmt = makeTestStatement("SELECT destination FROM funkytown");
    197  execAsync(stmt, { error: Ci.mozIStorageError.ERROR });
    198  stmt.finalize();
    199 
    200  Assert.equal(
    201    Glean.sqliteStore.query.get(TEST_DB_NAME, "failure").testGetValue(),
    202    2
    203  );
    204 
    205  run_next_test();
    206 }
    207 test_illegal_sql_async_deferred.asyncOnly = true;
    208 
    209 function test_create_table() {
    210  // Ensure our table doesn't exist
    211  Assert.ok(!getOpenedDatabase().tableExists("test"));
    212 
    213  var stmt = makeTestStatement(
    214    "CREATE TABLE test (" +
    215      "id INTEGER, " +
    216      "string TEXT, " +
    217      "number REAL, " +
    218      "nuller NULL, " +
    219      "blober BLOB" +
    220      ")"
    221  );
    222  execAsync(stmt);
    223  stmt.finalize();
    224 
    225  // Check that the table has been created
    226  Assert.ok(getOpenedDatabase().tableExists("test"));
    227 
    228  // Verify that it's created correctly (this will throw if it wasn't)
    229  let checkStmt = getOpenedDatabase().createStatement(
    230    "SELECT id, string, number, nuller, blober FROM test"
    231  );
    232  checkStmt.finalize();
    233 
    234  run_next_test();
    235 }
    236 
    237 function test_add_data() {
    238  var stmt = makeTestStatement(
    239    "INSERT INTO test (id, string, number, nuller, blober) " +
    240      "VALUES (?, ?, ?, ?, ?)"
    241  );
    242  stmt.bindBlobByIndex(4, BLOB, BLOB.length);
    243  stmt.bindByIndex(3, null);
    244  stmt.bindByIndex(2, REAL);
    245  stmt.bindByIndex(1, TEXT);
    246  stmt.bindByIndex(0, INTEGER);
    247 
    248  execAsync(stmt);
    249  stmt.finalize();
    250 
    251  // Check that the result is in the table
    252  verifyQuery(
    253    "SELECT string, number, nuller, blober FROM test WHERE id = ?",
    254    INTEGER,
    255    [TEXT, REAL, null, BLOB]
    256  );
    257 
    258  run_next_test();
    259 }
    260 
    261 function test_get_data() {
    262  var stmt = makeTestStatement(
    263    "SELECT string, number, nuller, blober, id FROM test WHERE id = ?"
    264  );
    265  stmt.bindByIndex(0, INTEGER);
    266  execAsync(stmt, {}, [
    267    function (tuple) {
    268      Assert.notEqual(null, tuple);
    269 
    270      // Check that it's what we expect
    271      Assert.ok(!tuple.getIsNull(0));
    272      Assert.equal(tuple.getResultByName("string"), tuple.getResultByIndex(0));
    273      Assert.equal(TEXT, tuple.getResultByName("string"));
    274      Assert.equal(
    275        Ci.mozIStorageValueArray.VALUE_TYPE_TEXT,
    276        tuple.getTypeOfIndex(0)
    277      );
    278 
    279      Assert.ok(!tuple.getIsNull(1));
    280      Assert.equal(tuple.getResultByName("number"), tuple.getResultByIndex(1));
    281      Assert.equal(REAL, tuple.getResultByName("number"));
    282      Assert.equal(
    283        Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT,
    284        tuple.getTypeOfIndex(1)
    285      );
    286 
    287      Assert.ok(tuple.getIsNull(2));
    288      Assert.equal(tuple.getResultByName("nuller"), tuple.getResultByIndex(2));
    289      Assert.equal(null, tuple.getResultByName("nuller"));
    290      Assert.equal(
    291        Ci.mozIStorageValueArray.VALUE_TYPE_NULL,
    292        tuple.getTypeOfIndex(2)
    293      );
    294 
    295      Assert.ok(!tuple.getIsNull(3));
    296      var blobByName = tuple.getResultByName("blober");
    297      Assert.equal(BLOB.length, blobByName.length);
    298      var blobByIndex = tuple.getResultByIndex(3);
    299      Assert.equal(BLOB.length, blobByIndex.length);
    300      for (let i = 0; i < BLOB.length; i++) {
    301        Assert.equal(BLOB[i], blobByName[i]);
    302        Assert.equal(BLOB[i], blobByIndex[i]);
    303      }
    304      var count = { value: 0 };
    305      var blob = { value: null };
    306      tuple.getBlob(3, count, blob);
    307      Assert.equal(BLOB.length, count.value);
    308      for (let i = 0; i < BLOB.length; i++) {
    309        Assert.equal(BLOB[i], blob.value[i]);
    310      }
    311      Assert.equal(
    312        Ci.mozIStorageValueArray.VALUE_TYPE_BLOB,
    313        tuple.getTypeOfIndex(3)
    314      );
    315 
    316      Assert.ok(!tuple.getIsNull(4));
    317      Assert.equal(tuple.getResultByName("id"), tuple.getResultByIndex(4));
    318      Assert.equal(INTEGER, tuple.getResultByName("id"));
    319      Assert.equal(
    320        Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER,
    321        tuple.getTypeOfIndex(4)
    322      );
    323    },
    324  ]);
    325  stmt.finalize();
    326 
    327  run_next_test();
    328 }
    329 
    330 function test_tuple_out_of_bounds() {
    331  var stmt = makeTestStatement("SELECT string FROM test");
    332  execAsync(stmt, {}, [
    333    function (tuple) {
    334      Assert.notEqual(null, tuple);
    335 
    336      // Check all out of bounds - should throw
    337      var methods = [
    338        "getTypeOfIndex",
    339        "getInt32",
    340        "getInt64",
    341        "getDouble",
    342        "getUTF8String",
    343        "getString",
    344        "getIsNull",
    345      ];
    346      for (var i in methods) {
    347        try {
    348          tuple[methods[i]](tuple.numEntries);
    349          do_throw("did not throw :(");
    350        } catch (e) {
    351          Assert.equal(Cr.NS_ERROR_ILLEGAL_VALUE, e.result);
    352        }
    353      }
    354 
    355      // getBlob requires more args...
    356      try {
    357        var blob = { value: null };
    358        var size = { value: 0 };
    359        tuple.getBlob(tuple.numEntries, blob, size);
    360        do_throw("did not throw :(");
    361      } catch (e) {
    362        Assert.equal(Cr.NS_ERROR_ILLEGAL_VALUE, e.result);
    363      }
    364    },
    365  ]);
    366  stmt.finalize();
    367 
    368  run_next_test();
    369 }
    370 
    371 function test_no_listener_works_on_success() {
    372  var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
    373  stmt.bindByIndex(0, 0);
    374  stmt.executeAsync();
    375  stmt.finalize();
    376 
    377  // Run the next test.
    378  run_next_test();
    379 }
    380 
    381 function test_no_listener_works_on_results() {
    382  var stmt = makeTestStatement("SELECT ?");
    383  stmt.bindByIndex(0, 1);
    384  stmt.executeAsync();
    385  stmt.finalize();
    386 
    387  // Run the next test.
    388  run_next_test();
    389 }
    390 
    391 function test_no_listener_works_on_error() {
    392  // commit without a transaction will trigger an error
    393  var stmt = makeTestStatement("COMMIT");
    394  stmt.executeAsync();
    395  stmt.finalize();
    396 
    397  // Run the next test.
    398  run_next_test();
    399 }
    400 
    401 function test_partial_listener_works() {
    402  var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
    403  stmt.bindByIndex(0, 0);
    404  stmt.executeAsync({
    405    handleResult() {},
    406  });
    407  stmt.executeAsync({
    408    handleError() {},
    409  });
    410  stmt.executeAsync({
    411    handleCompletion() {},
    412  });
    413  stmt.finalize();
    414 
    415  // Run the next test.
    416  run_next_test();
    417 }
    418 
    419 /**
    420 * Dubious cancellation test that depends on system loading may or may not
    421 * succeed in canceling things.  It does at least test if calling cancel blows
    422 * up.  test_AsyncCancellation in test_true_async.cpp is our test that canceling
    423 * actually works correctly.
    424 */
    425 function test_immediate_cancellation() {
    426  var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
    427  stmt.bindByIndex(0, 0);
    428  execAsync(stmt, { cancel: true });
    429  stmt.finalize();
    430  run_next_test();
    431 }
    432 
    433 /**
    434 * Test that calling cancel twice throws the second time.
    435 */
    436 function test_double_cancellation() {
    437  var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
    438  stmt.bindByIndex(0, 0);
    439  let pendingStatement = execAsync(stmt, { cancel: true });
    440  // And cancel again - expect an exception
    441  expectError(Cr.NS_ERROR_UNEXPECTED, () => pendingStatement.cancel());
    442 
    443  stmt.finalize();
    444  run_next_test();
    445 }
    446 
    447 /**
    448 * Verify that nothing untoward happens if we try and cancel something after it
    449 * has fully run to completion.
    450 */
    451 function test_cancellation_after_execution() {
    452  var stmt = makeTestStatement("DELETE FROM test WHERE id = ?");
    453  stmt.bindByIndex(0, 0);
    454  let pendingStatement = execAsync(stmt, { returnPending: true });
    455  // (the statement has fully executed at this point)
    456  // canceling after the statement has run to completion should not throw!
    457  pendingStatement.cancel();
    458 
    459  stmt.finalize();
    460  run_next_test();
    461 }
    462 
    463 function test_finalized_statement_does_not_crash() {
    464  var stmt = makeTestStatement("SELECT * FROM TEST");
    465  stmt.finalize();
    466  // we are concerned about a crash here; an error is fine.
    467  try {
    468    stmt.executeAsync();
    469  } catch (ex) {
    470    // Do nothing.
    471  }
    472 
    473  // Run the next test.
    474  run_next_test();
    475 }
    476 
    477 /**
    478 * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by index.
    479 */
    480 function test_bind_direct_binding_params_by_index() {
    481  var stmt = makeTestStatement(
    482    "INSERT INTO test (id, string, number, nuller, blober) " +
    483      "VALUES (?, ?, ?, ?, ?)"
    484  );
    485  let insertId = nextUniqueId++;
    486  stmt.bindByIndex(0, insertId);
    487  stmt.bindByIndex(1, TEXT);
    488  stmt.bindByIndex(2, REAL);
    489  stmt.bindByIndex(3, null);
    490  stmt.bindBlobByIndex(4, BLOB, BLOB.length);
    491  execAsync(stmt);
    492  stmt.finalize();
    493  verifyQuery(
    494    "SELECT string, number, nuller, blober FROM test WHERE id = ?",
    495    insertId,
    496    [TEXT, REAL, null, BLOB]
    497  );
    498  run_next_test();
    499 }
    500 
    501 /**
    502 * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by name.
    503 */
    504 function test_bind_direct_binding_params_by_name() {
    505  var stmt = makeTestStatement(
    506    "INSERT INTO test (id, string, number, nuller, blober) " +
    507      "VALUES (:int, :text, :real, :null, :blob)"
    508  );
    509  let insertId = nextUniqueId++;
    510  stmt.bindByName("int", insertId);
    511  stmt.bindByName("text", TEXT);
    512  stmt.bindByName("real", REAL);
    513  stmt.bindByName("null", null);
    514  stmt.bindBlobByName("blob", BLOB);
    515  execAsync(stmt);
    516  stmt.finalize();
    517  verifyQuery(
    518    "SELECT string, number, nuller, blober FROM test WHERE id = ?",
    519    insertId,
    520    [TEXT, REAL, null, BLOB]
    521  );
    522  run_next_test();
    523 }
    524 
    525 function test_bind_js_params_helper_by_index() {
    526  var stmt = makeTestStatement(
    527    "INSERT INTO test (id, string, number, nuller, blober) " +
    528      "VALUES (?, ?, ?, ?, NULL)"
    529  );
    530  let insertId = nextUniqueId++;
    531  // we cannot bind blobs this way; no blober
    532  stmt.params[3] = null;
    533  stmt.params[2] = REAL;
    534  stmt.params[1] = TEXT;
    535  stmt.params[0] = insertId;
    536  execAsync(stmt);
    537  stmt.finalize();
    538  verifyQuery(
    539    "SELECT string, number, nuller FROM test WHERE id = ?",
    540    insertId,
    541    [TEXT, REAL, null]
    542  );
    543  run_next_test();
    544 }
    545 
    546 function test_bind_js_params_helper_by_name() {
    547  var stmt = makeTestStatement(
    548    "INSERT INTO test (id, string, number, nuller, blober) " +
    549      "VALUES (:int, :text, :real, :null, NULL)"
    550  );
    551  let insertId = nextUniqueId++;
    552  // we cannot bind blobs this way; no blober
    553  stmt.params.null = null;
    554  stmt.params.real = REAL;
    555  stmt.params.text = TEXT;
    556  stmt.params.int = insertId;
    557  execAsync(stmt);
    558  stmt.finalize();
    559  verifyQuery(
    560    "SELECT string, number, nuller FROM test WHERE id = ?",
    561    insertId,
    562    [TEXT, REAL, null]
    563  );
    564  run_next_test();
    565 }
    566 
    567 function test_bind_multiple_rows_by_index() {
    568  const AMOUNT_TO_ADD = 5;
    569  var stmt = makeTestStatement(
    570    "INSERT INTO test (id, string, number, nuller, blober) " +
    571      "VALUES (?, ?, ?, ?, ?)"
    572  );
    573  var array = stmt.newBindingParamsArray();
    574  for (let i = 0; i < AMOUNT_TO_ADD; i++) {
    575    let bp = array.newBindingParams();
    576    bp.bindByIndex(0, INTEGER);
    577    bp.bindByIndex(1, TEXT);
    578    bp.bindByIndex(2, REAL);
    579    bp.bindByIndex(3, null);
    580    bp.bindBlobByIndex(4, BLOB, BLOB.length);
    581    array.addParams(bp);
    582    Assert.equal(array.length, i + 1);
    583  }
    584  stmt.bindParameters(array);
    585 
    586  let rowCount = getTableRowCount("test");
    587  execAsync(stmt);
    588  Assert.equal(rowCount + AMOUNT_TO_ADD, getTableRowCount("test"));
    589  stmt.finalize();
    590  run_next_test();
    591 }
    592 
    593 function test_bind_multiple_rows_by_name() {
    594  const AMOUNT_TO_ADD = 5;
    595  var stmt = makeTestStatement(
    596    "INSERT INTO test (id, string, number, nuller, blober) " +
    597      "VALUES (:int, :text, :real, :null, :blob)"
    598  );
    599  var array = stmt.newBindingParamsArray();
    600  for (let i = 0; i < AMOUNT_TO_ADD; i++) {
    601    let bp = array.newBindingParams();
    602    bp.bindByName("int", INTEGER);
    603    bp.bindByName("text", TEXT);
    604    bp.bindByName("real", REAL);
    605    bp.bindByName("null", null);
    606    bp.bindBlobByName("blob", BLOB);
    607    array.addParams(bp);
    608    Assert.equal(array.length, i + 1);
    609  }
    610  stmt.bindParameters(array);
    611 
    612  let rowCount = getTableRowCount("test");
    613  execAsync(stmt);
    614  Assert.equal(rowCount + AMOUNT_TO_ADD, getTableRowCount("test"));
    615  stmt.finalize();
    616  run_next_test();
    617 }
    618 
    619 /**
    620 * Verify that a mozIStorageStatement instance throws immediately when we
    621 * try and bind to an illegal index.
    622 */
    623 function test_bind_out_of_bounds_sync_immediate() {
    624  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (?)");
    625 
    626  let array = stmt.newBindingParamsArray();
    627  let bp = array.newBindingParams();
    628 
    629  // Check variant binding.
    630  expectError(Cr.NS_ERROR_INVALID_ARG, () => bp.bindByIndex(1, INTEGER));
    631  // Check blob binding.
    632  expectError(Cr.NS_ERROR_INVALID_ARG, () =>
    633    bp.bindBlobByIndex(1, BLOB, BLOB.length)
    634  );
    635 
    636  stmt.finalize();
    637  run_next_test();
    638 }
    639 test_bind_out_of_bounds_sync_immediate.syncOnly = true;
    640 
    641 /**
    642 * Verify that a mozIStorageAsyncStatement reports an error asynchronously when
    643 * we bind to an illegal index.
    644 */
    645 function test_bind_out_of_bounds_async_deferred() {
    646  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (?)");
    647 
    648  let array = stmt.newBindingParamsArray();
    649  let bp = array.newBindingParams();
    650 
    651  // There is no difference between variant and blob binding for async purposes.
    652  bp.bindByIndex(1, INTEGER);
    653  array.addParams(bp);
    654  stmt.bindParameters(array);
    655  execAsync(stmt, { error: Ci.mozIStorageError.RANGE });
    656 
    657  stmt.finalize();
    658  run_next_test();
    659 }
    660 test_bind_out_of_bounds_async_deferred.asyncOnly = true;
    661 
    662 function test_bind_no_such_name_sync_immediate() {
    663  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:foo)");
    664 
    665  let array = stmt.newBindingParamsArray();
    666  let bp = array.newBindingParams();
    667 
    668  // Check variant binding.
    669  expectError(Cr.NS_ERROR_INVALID_ARG, () =>
    670    bp.bindByName("doesnotexist", INTEGER)
    671  );
    672  // Check blob binding.
    673  expectError(Cr.NS_ERROR_INVALID_ARG, () =>
    674    bp.bindBlobByName("doesnotexist", BLOB)
    675  );
    676 
    677  stmt.finalize();
    678  run_next_test();
    679 }
    680 test_bind_no_such_name_sync_immediate.syncOnly = true;
    681 
    682 function test_bind_no_such_name_async_deferred() {
    683  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:foo)");
    684 
    685  let array = stmt.newBindingParamsArray();
    686  let bp = array.newBindingParams();
    687 
    688  bp.bindByName("doesnotexist", INTEGER);
    689  array.addParams(bp);
    690  stmt.bindParameters(array);
    691  execAsync(stmt, { error: Ci.mozIStorageError.RANGE });
    692 
    693  stmt.finalize();
    694  run_next_test();
    695 }
    696 test_bind_no_such_name_async_deferred.asyncOnly = true;
    697 
    698 function test_bind_bogus_type_by_index() {
    699  if (AppConstants.DEBUG) {
    700    // Skip this test as in debug builds this is a fatal assert.
    701    run_next_test();
    702    return;
    703  }
    704 
    705  // We try to bind a JS Object here that should fail to bind.
    706  let stmt = makeTestStatement("INSERT INTO test (blober) VALUES (?)");
    707 
    708  let array = stmt.newBindingParamsArray();
    709  let bp = array.newBindingParams();
    710  Assert.throws(() => bp.bindByIndex(0, run_test), /NS_ERROR_UNEXPECTED/);
    711 
    712  stmt.finalize();
    713  run_next_test();
    714 }
    715 
    716 function test_bind_bogus_type_by_name() {
    717  if (AppConstants.DEBUG) {
    718    // Skip this test as in debug builds this is a fatal assert.
    719    run_next_test();
    720    return;
    721  }
    722 
    723  // We try to bind a JS Object here that should fail to bind.
    724  let stmt = makeTestStatement("INSERT INTO test (blober) VALUES (:blob)");
    725 
    726  let array = stmt.newBindingParamsArray();
    727  let bp = array.newBindingParams();
    728  Assert.throws(() => bp.bindByName("blob", run_test), /NS_ERROR_UNEXPECTED/);
    729 
    730  stmt.finalize();
    731  run_next_test();
    732 }
    733 
    734 function test_bind_params_already_locked() {
    735  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    736 
    737  let array = stmt.newBindingParamsArray();
    738  let bp = array.newBindingParams();
    739  bp.bindByName("int", INTEGER);
    740  array.addParams(bp);
    741 
    742  // We should get an error after we call addParams and try to bind again.
    743  expectError(Cr.NS_ERROR_UNEXPECTED, () => bp.bindByName("int", INTEGER));
    744 
    745  stmt.finalize();
    746  run_next_test();
    747 }
    748 
    749 function test_bind_params_array_already_locked() {
    750  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    751 
    752  let array = stmt.newBindingParamsArray();
    753  let bp1 = array.newBindingParams();
    754  bp1.bindByName("int", INTEGER);
    755  array.addParams(bp1);
    756  let bp2 = array.newBindingParams();
    757  stmt.bindParameters(array);
    758  bp2.bindByName("int", INTEGER);
    759 
    760  // We should get an error after we have bound the array to the statement.
    761  expectError(Cr.NS_ERROR_UNEXPECTED, () => array.addParams(bp2));
    762 
    763  stmt.finalize();
    764  run_next_test();
    765 }
    766 
    767 function test_no_binding_params_from_locked_array() {
    768  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    769 
    770  let array = stmt.newBindingParamsArray();
    771  let bp = array.newBindingParams();
    772  bp.bindByName("int", INTEGER);
    773  array.addParams(bp);
    774  stmt.bindParameters(array);
    775 
    776  // We should not be able to get a new BindingParams object after we have bound
    777  // to the statement.
    778  expectError(Cr.NS_ERROR_UNEXPECTED, () => array.newBindingParams());
    779 
    780  stmt.finalize();
    781  run_next_test();
    782 }
    783 
    784 function test_not_right_owning_array() {
    785  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    786 
    787  let array1 = stmt.newBindingParamsArray();
    788  let array2 = stmt.newBindingParamsArray();
    789  let bp = array1.newBindingParams();
    790  bp.bindByName("int", INTEGER);
    791 
    792  // We should not be able to add bp to array2 since it was created from array1.
    793  expectError(Cr.NS_ERROR_UNEXPECTED, () => array2.addParams(bp));
    794 
    795  stmt.finalize();
    796  run_next_test();
    797 }
    798 
    799 function test_not_right_owning_statement() {
    800  let stmt1 = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    801  let stmt2 = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    802 
    803  let array1 = stmt1.newBindingParamsArray();
    804  stmt2.newBindingParamsArray();
    805  let bp = array1.newBindingParams();
    806  bp.bindByName("int", INTEGER);
    807  array1.addParams(bp);
    808 
    809  // We should not be able to bind array1 since it was created from stmt1.
    810  expectError(Cr.NS_ERROR_UNEXPECTED, () => stmt2.bindParameters(array1));
    811 
    812  stmt1.finalize();
    813  stmt2.finalize();
    814  run_next_test();
    815 }
    816 
    817 function test_bind_empty_array() {
    818  let stmt = makeTestStatement("INSERT INTO test (id) VALUES (:int)");
    819 
    820  let paramsArray = stmt.newBindingParamsArray();
    821 
    822  // We should not be able to bind this array to the statement because it is
    823  // empty.
    824  expectError(Cr.NS_ERROR_UNEXPECTED, () => stmt.bindParameters(paramsArray));
    825 
    826  stmt.finalize();
    827  run_next_test();
    828 }
    829 
    830 function test_multiple_results() {
    831  let expectedResults = getTableRowCount("test");
    832  // Sanity check - we should have more than one result, but let's be sure.
    833  Assert.greater(expectedResults, 1);
    834 
    835  // Now check that we get back two rows of data from our async query.
    836  let stmt = makeTestStatement("SELECT * FROM test");
    837  execAsync(stmt, {}, expectedResults);
    838 
    839  stmt.finalize();
    840  run_next_test();
    841 }
    842 
    843 // Test Runner
    844 
    845 const TEST_PASS_SYNC = 0;
    846 const TEST_PASS_ASYNC = 1;
    847 /**
    848 * We run 2 passes against the test.  One where makeTestStatement generates
    849 * synchronous (mozIStorageStatement) statements and one where it generates
    850 * asynchronous (mozIStorageAsyncStatement) statements.
    851 *
    852 * Because of differences in the ability to know the number of parameters before
    853 * dispatching, some tests are sync/async specific.  These functions are marked
    854 * with 'syncOnly' or 'asyncOnly' attributes and run_next_test knows what to do.
    855 */
    856 var testPass = TEST_PASS_SYNC;
    857 
    858 /**
    859 * Create a statement of the type under test per testPass.
    860 *
    861 * @param aSQL
    862 *        The SQL string from which to build a statement.
    863 * @return a statement of the type under test per testPass.
    864 */
    865 function makeTestStatement(aSQL) {
    866  if (testPass == TEST_PASS_SYNC) {
    867    return getOpenedDatabase().createStatement(aSQL);
    868  }
    869  return getOpenedDatabase().createAsyncStatement(aSQL);
    870 }
    871 
    872 var tests = [
    873  test_illegal_sql_async_deferred,
    874  test_create_table,
    875  test_add_data,
    876  test_get_data,
    877  test_tuple_out_of_bounds,
    878  test_no_listener_works_on_success,
    879  test_no_listener_works_on_results,
    880  test_no_listener_works_on_error,
    881  test_partial_listener_works,
    882  test_immediate_cancellation,
    883  test_double_cancellation,
    884  test_cancellation_after_execution,
    885  test_finalized_statement_does_not_crash,
    886  test_bind_direct_binding_params_by_index,
    887  test_bind_direct_binding_params_by_name,
    888  test_bind_js_params_helper_by_index,
    889  test_bind_js_params_helper_by_name,
    890  test_bind_multiple_rows_by_index,
    891  test_bind_multiple_rows_by_name,
    892  test_bind_out_of_bounds_sync_immediate,
    893  test_bind_out_of_bounds_async_deferred,
    894  test_bind_no_such_name_sync_immediate,
    895  test_bind_no_such_name_async_deferred,
    896  test_bind_bogus_type_by_index,
    897  test_bind_bogus_type_by_name,
    898  test_bind_params_already_locked,
    899  test_bind_params_array_already_locked,
    900  test_bind_empty_array,
    901  test_no_binding_params_from_locked_array,
    902  test_not_right_owning_array,
    903  test_not_right_owning_statement,
    904  test_multiple_results,
    905 ];
    906 var index = 0;
    907 
    908 const STARTING_UNIQUE_ID = 2;
    909 var nextUniqueId = STARTING_UNIQUE_ID;
    910 
    911 function run_next_test() {
    912  function _run_next_test() {
    913    Services.fog.testResetFOG();
    914    // use a loop so we can skip tests...
    915    while (index < tests.length) {
    916      let test = tests[index++];
    917      // skip tests not appropriate to the current test pass
    918      if (
    919        (testPass == TEST_PASS_SYNC && "asyncOnly" in test) ||
    920        (testPass == TEST_PASS_ASYNC && "syncOnly" in test)
    921      ) {
    922        continue;
    923      }
    924 
    925      // Asynchronous tests means that exceptions don't kill the test.
    926      try {
    927        print("****** Running the next test: " + test.name);
    928        test();
    929        return;
    930      } catch (e) {
    931        do_throw(e);
    932      }
    933    }
    934 
    935    // if we only completed the first pass, move to the next pass
    936    if (testPass == TEST_PASS_SYNC) {
    937      print("********* Beginning mozIStorageAsyncStatement pass.");
    938      testPass++;
    939      index = 0;
    940      // a new pass demands a new database
    941      asyncCleanup();
    942      nextUniqueId = STARTING_UNIQUE_ID;
    943      _run_next_test();
    944      return;
    945    }
    946 
    947    // we did some async stuff; we need to clean up.
    948    asyncCleanup();
    949    do_test_finished();
    950  }
    951 
    952  // Don't actually schedule another test if we're quitting.
    953  if (!_quit) {
    954    // For saner stacks, we execute this code RSN.
    955    executeSoon(_run_next_test);
    956  }
    957 }
    958 
    959 function run_test() {
    960  // Thunderbird doesn't have one or more of the probes used in this test.
    961  // Ensure the data is collected anyway.
    962  Services.prefs.setBoolPref(
    963    "toolkit.telemetry.testing.overrideProductsCheck",
    964    true
    965  );
    966 
    967  cleanup();
    968 
    969  do_test_pending();
    970  run_next_test();
    971 }