diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /storage/test/unit | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'storage/test/unit')
37 files changed, 5588 insertions, 0 deletions
diff --git a/storage/test/unit/corruptDB.sqlite b/storage/test/unit/corruptDB.sqlite Binary files differnew file mode 100644 index 000000000..b234246ca --- /dev/null +++ b/storage/test/unit/corruptDB.sqlite diff --git a/storage/test/unit/fakeDB.sqlite b/storage/test/unit/fakeDB.sqlite new file mode 100644 index 000000000..5f7498bfc --- /dev/null +++ b/storage/test/unit/fakeDB.sqlite @@ -0,0 +1 @@ +BACON diff --git a/storage/test/unit/head_storage.js b/storage/test/unit/head_storage.js new file mode 100644 index 000000000..40374afad --- /dev/null +++ b/storage/test/unit/head_storage.js @@ -0,0 +1,372 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const Ci = Components.interfaces; +const Cc = Components.classes; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); + + +do_get_profile(); +var dirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + +var gDBConn = null; + +function getTestDB() +{ + var db = dirSvc.get("ProfD", Ci.nsIFile); + db.append("test_storage.sqlite"); + return db; +} + +/** + * Obtains a corrupt database to test against. + */ +function getCorruptDB() +{ + return do_get_file("corruptDB.sqlite"); +} + +/** + * Obtains a fake (non-SQLite format) database to test against. + */ +function getFakeDB() +{ + return do_get_file("fakeDB.sqlite"); +} + +/** + * Delete the test database file. + */ +function deleteTestDB() +{ + print("*** Storage Tests: Trying to remove file!"); + var dbFile = getTestDB(); + if (dbFile.exists()) + try { dbFile.remove(false); } catch (e) { /* stupid windows box */ } +} + +function cleanup() +{ + // close the connection + print("*** Storage Tests: Trying to close!"); + getOpenedDatabase().close(); + + // we need to null out the database variable to get a new connection the next + // time getOpenedDatabase is called + gDBConn = null; + + // removing test db + deleteTestDB(); +} + +/** + * Use asyncClose to cleanup a connection. Synchronous by means of internally + * spinning an event loop. + */ +function asyncCleanup() +{ + let closed = false; + + // close the connection + print("*** Storage Tests: Trying to asyncClose!"); + getOpenedDatabase().asyncClose(function () { closed = true; }); + + let curThread = Components.classes["@mozilla.org/thread-manager;1"] + .getService().currentThread; + while (!closed) + curThread.processNextEvent(true); + + // we need to null out the database variable to get a new connection the next + // time getOpenedDatabase is called + gDBConn = null; + + // removing test db + deleteTestDB(); +} + +function getService() +{ + return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService); +} + +/** + * Get a connection to the test database. Creates and caches the connection + * if necessary, otherwise reuses the existing cached connection. This + * connection shares its cache. + * + * @returns the mozIStorageConnection for the file. + */ +function getOpenedDatabase() +{ + if (!gDBConn) { + gDBConn = getService().openDatabase(getTestDB()); + } + return gDBConn; +} + +/** + * Get a connection to the test database. Creates and caches the connection + * if necessary, otherwise reuses the existing cached connection. This + * connection doesn't share its cache. + * + * @returns the mozIStorageConnection for the file. + */ +function getOpenedUnsharedDatabase() +{ + if (!gDBConn) { + gDBConn = getService().openUnsharedDatabase(getTestDB()); + } + return gDBConn; +} + +/** + * Obtains a specific database to use. + * + * @param aFile + * The nsIFile representing the db file to open. + * @returns the mozIStorageConnection for the file. + */ +function getDatabase(aFile) +{ + return getService().openDatabase(aFile); +} + +function createStatement(aSQL) +{ + return getOpenedDatabase().createStatement(aSQL); +} + +/** + * Creates an asynchronous SQL statement. + * + * @param aSQL + * The SQL to parse into a statement. + * @returns a mozIStorageAsyncStatement from aSQL. + */ +function createAsyncStatement(aSQL) +{ + return getOpenedDatabase().createAsyncStatement(aSQL); +} + +/** + * Invoke the given function and assert that it throws an exception expressing + * the provided error code in its 'result' attribute. JS function expressions + * can be used to do this concisely. + * + * Example: + * expectError(Cr.NS_ERROR_INVALID_ARG, () => explodingFunction()); + * + * @param aErrorCode + * The error code to expect from invocation of aFunction. + * @param aFunction + * The function to invoke and expect an XPCOM-style error from. + */ +function expectError(aErrorCode, aFunction) +{ + let exceptionCaught = false; + try { + aFunction(); + } + catch (e) { + if (e.result != aErrorCode) { + do_throw("Got an exception, but the result code was not the expected " + + "one. Expected " + aErrorCode + ", got " + e.result); + } + exceptionCaught = true; + } + if (!exceptionCaught) + do_throw(aFunction + " should have thrown an exception but did not!"); +} + +/** + * Run a query synchronously and verify that we get back the expected results. + * + * @param aSQLString + * The SQL string for the query. + * @param aBind + * The value to bind at index 0. + * @param aResults + * A list of the expected values returned in the sole result row. + * Express blobs as lists. + */ +function verifyQuery(aSQLString, aBind, aResults) +{ + let stmt = getOpenedDatabase().createStatement(aSQLString); + stmt.bindByIndex(0, aBind); + try { + do_check_true(stmt.executeStep()); + let nCols = stmt.numEntries; + if (aResults.length != nCols) + do_throw("Expected " + aResults.length + " columns in result but " + + "there are only " + aResults.length + "!"); + for (let iCol = 0; iCol < nCols; iCol++) { + let expectedVal = aResults[iCol]; + let valType = stmt.getTypeOfIndex(iCol); + if (expectedVal === null) { + do_check_eq(stmt.VALUE_TYPE_NULL, valType); + do_check_true(stmt.getIsNull(iCol)); + } + else if (typeof expectedVal == "number") { + if (Math.floor(expectedVal) == expectedVal) { + do_check_eq(stmt.VALUE_TYPE_INTEGER, valType); + do_check_eq(expectedVal, stmt.getInt32(iCol)); + } + else { + do_check_eq(stmt.VALUE_TYPE_FLOAT, valType); + do_check_eq(expectedVal, stmt.getDouble(iCol)); + } + } + else if (typeof expectedVal == "string") { + do_check_eq(stmt.VALUE_TYPE_TEXT, valType); + do_check_eq(expectedVal, stmt.getUTF8String(iCol)); + } + else { // blob + do_check_eq(stmt.VALUE_TYPE_BLOB, valType); + let count = { value: 0 }, blob = { value: null }; + stmt.getBlob(iCol, count, blob); + do_check_eq(count.value, expectedVal.length); + for (let i = 0; i < count.value; i++) { + do_check_eq(expectedVal[i], blob.value[i]); + } + } + } + } + finally { + stmt.finalize(); + } +} + +/** + * Return the number of rows in the able with the given name using a synchronous + * query. + * + * @param aTableName + * The name of the table. + * @return The number of rows. + */ +function getTableRowCount(aTableName) +{ + var currentRows = 0; + var countStmt = getOpenedDatabase().createStatement( + "SELECT COUNT(1) AS count FROM " + aTableName + ); + try { + do_check_true(countStmt.executeStep()); + currentRows = countStmt.row.count; + } + finally { + countStmt.finalize(); + } + return currentRows; +} + +// Promise-Returning Functions + +function asyncClone(db, readOnly) { + let deferred = Promise.defer(); + db.asyncClone(readOnly, function (status, db2) { + if (Components.isSuccessCode(status)) { + deferred.resolve(db2); + } else { + deferred.reject(status); + } + }); + return deferred.promise; +} + +function asyncClose(db) { + let deferred = Promise.defer(); + db.asyncClose(function (status) { + if (Components.isSuccessCode(status)) { + deferred.resolve(); + } else { + deferred.reject(status); + } + }); + return deferred.promise; +} + +function openAsyncDatabase(file, options) { + let deferred = Promise.defer(); + let properties; + if (options) { + properties = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag); + for (let k in options) { + properties.setProperty(k, options[k]); + } + } + getService().openAsyncDatabase(file, properties, function (status, db) { + if (Components.isSuccessCode(status)) { + deferred.resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection)); + } else { + deferred.reject(status); + } + }); + return deferred.promise; +} + +function executeAsync(statement, onResult) { + let deferred = Promise.defer(); + statement.executeAsync({ + handleError: function (error) { + deferred.reject(error); + }, + handleResult: function (result) { + if (onResult) { + onResult(result); + } + }, + handleCompletion: function (result) { + deferred.resolve(result); + } + }); + return deferred.promise; +} + +function executeMultipleStatementsAsync(db, statements, onResult) { + let deferred = Promise.defer(); + db.executeAsync(statements, statements.length, { + handleError: function (error) { + deferred.reject(error); + }, + handleResult: function (result) { + if (onResult) { + onResult(result); + } + }, + handleCompletion: function (result) { + deferred.resolve(result); + } + }); + return deferred.promise; +} + +function executeSimpleSQLAsync(db, query, onResult) { + let deferred = Promise.defer(); + db.executeSimpleSQLAsync(query, { + handleError(error) { + deferred.reject(error); + }, + handleResult(result) { + if (onResult) { + onResult(result); + } else { + do_throw("No results were expected"); + } + }, + handleCompletion(result) { + deferred.resolve(result); + } + }); + return deferred.promise; +} + +cleanup(); diff --git a/storage/test/unit/locale_collation.txt b/storage/test/unit/locale_collation.txt new file mode 100644 index 000000000..86f50579b --- /dev/null +++ b/storage/test/unit/locale_collation.txt @@ -0,0 +1,174 @@ + +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +^ +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +| +} +~ +ludwig van beethoven +Ludwig van Beethoven +Ludwig van beethoven +Jane +jane +JANE +jAne +jaNe +janE +JAne +JaNe +JanE +JANe +JaNE +JAnE +jANE +Umberto Eco +Umberto eco +umberto eco +umberto Eco +UMBERTO ECO +ace +bash +*ace +!ace +%ace +~ace +#ace +cork +denizen +[denizen] +(denizen) +{denizen} +/denizen/ +#denizen# +$denizen$ +@denizen +elf +full +gnome +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Japanese +gnomic investigation of typological factors in the grammaticalization process of Malayo-Polynesian substaratum in the protoAltaic vocabulary core in the proto-layers of pre-historic Javanese +hint +Internationalization +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization +Zinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizatio +n +Zinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalization internationalizationinternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTioninternationalizationinternationalizationinternationalizationinternationalization +ZinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizationinternationalizaTion +jostle +kin +Laymen +lumens +Lumens +motleycrew +motley crew +niven's creative talents +nivens creative talents +opie +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rockies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the Rokkies +posh restaurants in surbanized and still urban incorporated subsection of this beautifl city in the rockies +quilt's +quilts +quilt +Rondo +street +tamale oxidization and iodization in rapid progress +tamale oxidization and iodization in rapid Progress +until +vera +Wobble +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoneme +Xanadu's legenary imaginary floccinaucinihilipilification in localization of theoretical portions of glottochronological understanding of the phoname +yearn +zodiac +a +å diff --git a/storage/test/unit/test_bug-365166.js b/storage/test/unit/test_bug-365166.js new file mode 100644 index 000000000..ebcdc6900 --- /dev/null +++ b/storage/test/unit/test_bug-365166.js @@ -0,0 +1,26 @@ +// Testcase for bug 365166 - crash [@ strlen] calling +// mozIStorageStatement::getColumnName of a statement created with +// "PRAGMA user_version" or "PRAGMA schema_version" +function run_test() { + test('user'); + test('schema'); + + function test(param) + { + var colName = param + "_version"; + var sql = "PRAGMA " + colName; + + var file = getTestDB(); + var storageService = Components.classes["@mozilla.org/storage/service;1"]. + getService(Components.interfaces.mozIStorageService); + var conn = storageService.openDatabase(file); + var statement = conn.createStatement(sql); + try { + // This shouldn't crash: + do_check_eq(statement.getColumnName(0), colName); + } finally { + statement.reset(); + statement.finalize(); + } + } +} diff --git a/storage/test/unit/test_bug-393952.js b/storage/test/unit/test_bug-393952.js new file mode 100644 index 000000000..f3c61f62b --- /dev/null +++ b/storage/test/unit/test_bug-393952.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Testcase for bug 393952: crash when I try to VACUUM and one of the tables +// has a UNIQUE text column. StorageUnicodeFunctions::likeFunction() +// needs to handle null aArgv[0] and aArgv[1] + +function setup() +{ + getOpenedDatabase().createTable("t1", "x TEXT UNIQUE"); + + var stmt = createStatement("INSERT INTO t1 (x) VALUES ('a')"); + stmt.execute(); + stmt.reset(); + stmt.finalize(); +} + +function test_vacuum() +{ + var stmt = createStatement("VACUUM;"); + stmt.executeStep(); + stmt.reset(); + stmt.finalize(); +} + +var tests = [test_vacuum]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) + tests[i](); + + cleanup(); +} + diff --git a/storage/test/unit/test_bug-429521.js b/storage/test/unit/test_bug-429521.js new file mode 100644 index 000000000..a9eafc1c2 --- /dev/null +++ b/storage/test/unit/test_bug-429521.js @@ -0,0 +1,46 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function setup() { + getOpenedDatabase().createTable("t1", "x TEXT"); + + var stmt = createStatement("INSERT INTO t1 (x) VALUES ('/mozilla.org/20070129_1/Europe/Berlin')"); + stmt.execute(); + stmt.finalize(); +} + +function test_bug429521() { + var stmt = createStatement( + "SELECT DISTINCT(zone) FROM (" + + "SELECT x AS zone FROM t1 WHERE x LIKE '/mozilla.org%'" + + ");"); + + print("*** test_bug429521: started"); + + try { + while (stmt.executeStep()) { + print("*** test_bug429521: step() Read wrapper.row.zone"); + + // BUG: the print commands after the following statement + // are never executed. Script stops immediately. + var tzId = stmt.row.zone; + + print("*** test_bug429521: step() Read wrapper.row.zone finished"); + } + } catch (e) { + print("*** test_bug429521: " + e); + } + + print("*** test_bug429521: finished"); + + stmt.finalize(); +} + +function run_test() { + setup(); + + test_bug429521(); + + cleanup(); +} diff --git a/storage/test/unit/test_bug-444233.js b/storage/test/unit/test_bug-444233.js new file mode 100644 index 000000000..eb315f934 --- /dev/null +++ b/storage/test/unit/test_bug-444233.js @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function setup() { + // Create the table + getOpenedDatabase().createTable("test_bug444233", + "id INTEGER PRIMARY KEY, value TEXT"); + + // Insert dummy data, using wrapper methods + var stmt = createStatement("INSERT INTO test_bug444233 (value) VALUES (:value)"); + stmt.params.value = "value1"; + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO test_bug444233 (value) VALUES (:value)"); + stmt.params.value = "value2"; + stmt.execute(); + stmt.finalize(); +} + +function test_bug444233() { + print("*** test_bug444233: started"); + + // Check that there are 2 results + var stmt = createStatement("SELECT COUNT(*) AS number FROM test_bug444233"); + do_check_true(stmt.executeStep()); + do_check_eq(2, stmt.row.number); + stmt.reset(); + stmt.finalize(); + + print("*** test_bug444233: doing delete"); + + // Now try to delete using IN + // Cheating since we know ids are 1,2 + try { + var ids = [1, 2]; + stmt = createStatement("DELETE FROM test_bug444233 WHERE id IN (:ids)"); + stmt.params.ids = ids; + } catch (e) { + print("*** test_bug444233: successfully caught exception"); + } + stmt.finalize(); +} + +function run_test() { + setup(); + test_bug444233(); + cleanup(); +} + diff --git a/storage/test/unit/test_cache_size.js b/storage/test/unit/test_cache_size.js new file mode 100644 index 000000000..e0a5e8723 --- /dev/null +++ b/storage/test/unit/test_cache_size.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This file tests that dbs of various page sizes are using the right cache +// size (bug 703113). + +/** + * In order to change the cache size, we must open a DB, change the page + * size, create a table, close the DB, then re-open the DB. We then check + * the cache size after reopening. + * + * @param dbOpener + * function that opens the DB specified in file + * @param file + * file holding the database + * @param pageSize + * the DB's page size + * @param expectedCacheSize + * the expected cache size for the given page size + */ +function check_size(dbOpener, file, pageSize, expectedCacheSize) +{ + // Open the DB, immediately change its page size. + let db = dbOpener(file); + db.executeSimpleSQL("PRAGMA page_size = " + pageSize); + + // Check the page size change worked. + let stmt = db.createStatement("PRAGMA page_size"); + do_check_true(stmt.executeStep()); + do_check_eq(stmt.row.page_size, pageSize); + stmt.finalize(); + + // Create a simple table. + db.executeSimpleSQL("CREATE TABLE test ( id INTEGER PRIMARY KEY )"); + + // Close and re-open the DB. + db.close(); + db = dbOpener(file); + + // Check cache size is as expected. + stmt = db.createStatement("PRAGMA cache_size"); + do_check_true(stmt.executeStep()); + do_check_eq(stmt.row.cache_size, expectedCacheSize); + stmt.finalize(); +} + +function new_file(name) +{ + let file = dirSvc.get("ProfD", Ci.nsIFile); + file.append(name + ".sqlite"); + do_check_false(file.exists()); + return file; +} + +function run_test() +{ + const kExpectedCacheSize = -2048; // 2MiB + + let pageSizes = [ + 1024, + 4096, + 32768, + ]; + + for (let i = 0; i < pageSizes.length; i++) { + let pageSize = pageSizes[i]; + check_size(getDatabase, + new_file("shared" + pageSize), pageSize, kExpectedCacheSize); + check_size(getService().openUnsharedDatabase, + new_file("unshared" + pageSize), pageSize, kExpectedCacheSize); + } +} diff --git a/storage/test/unit/test_chunk_growth.js b/storage/test/unit/test_chunk_growth.js new file mode 100644 index 000000000..2cf91fe16 --- /dev/null +++ b/storage/test/unit/test_chunk_growth.js @@ -0,0 +1,52 @@ +// This file tests SQLITE_FCNTL_CHUNK_SIZE behaves as expected + +function run_sql(d, sql) { + var stmt = d.createStatement(sql); + stmt.execute(); + stmt.finalize(); +} + +function new_file(name) { + var file = dirSvc.get("ProfD", Ci.nsIFile); + file.append(name); + return file; +} + +function get_size(name) { + return new_file(name).fileSize; +} + +function run_test() { + const filename = "chunked.sqlite"; + const CHUNK_SIZE = 512 * 1024; + var d = getDatabase(new_file(filename)); + try { + d.setGrowthIncrement(CHUNK_SIZE, ""); + } catch (e) { + if (e.result != Cr.NS_ERROR_FILE_TOO_BIG) { + throw e; + } + print("Too little free space to set CHUNK_SIZE!"); + return; + } + run_sql(d, "CREATE TABLE bloat(data varchar)"); + + var orig_size = get_size(filename); + /* Dump in at least 32K worth of data. + * While writing ensure that the file size growth in chunksize set above. + */ + const str1024 = new Array(1024).join("T"); + for (var i = 0; i < 32; i++) { + run_sql(d, "INSERT INTO bloat VALUES('" + str1024 + "')"); + var size = get_size(filename); + // Must not grow in small increments. + do_check_true(size == orig_size || size >= CHUNK_SIZE); + } + /* In addition to growing in chunk-size increments, the db + * should shrink in chunk-size increments too. + */ + run_sql(d, "DELETE FROM bloat"); + run_sql(d, "VACUUM"); + do_check_true(get_size(filename) >= CHUNK_SIZE); +} + diff --git a/storage/test/unit/test_connection_asyncClose.js b/storage/test/unit/test_connection_asyncClose.js new file mode 100644 index 000000000..7704dcc81 --- /dev/null +++ b/storage/test/unit/test_connection_asyncClose.js @@ -0,0 +1,125 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Thorough branch coverage for asyncClose. + * + * Coverage of asyncClose by connection state at time of AsyncClose invocation: + * - (asyncThread && mDBConn) => AsyncCloseConnection used, actually closes + * - test_asyncClose_does_not_complete_before_statements + * - test_double_asyncClose_throws + * - test_asyncClose_does_not_throw_without_callback + * - (asyncThread && !mDBConn) => AsyncCloseConnection used, although no close + * is required. Note that this is only possible in the event that + * openAsyncDatabase was used and we failed to open the database. + * Additionally, the async connection will never be exposed to the caller and + * AsyncInitDatabase will be the one to (automatically) call AsyncClose. + * - test_asyncClose_failed_open + * - (!asyncThread && mDBConn) => Close() invoked, actually closes + * - test_asyncClose_on_sync_db + * - (!asyncThread && !mDBConn) => Close() invoked, no close needed, errors. + * This happens if the database has already been closed. + * - test_double_asyncClose_throws + */ + + +/** + * Sanity check that our close indeed happens after asynchronously executed + * statements scheduled during the same turn of the event loop. Note that we + * just care that the statement says it completed without error, we're not + * worried that the close will happen and then the statement will magically + * complete. + */ +add_task(function* test_asyncClose_does_not_complete_before_statements() { + let db = getService().openDatabase(getTestDB()); + let stmt = db.createStatement("SELECT * FROM sqlite_master"); + // Issue the executeAsync but don't yield for it... + let asyncStatementPromise = executeAsync(stmt); + stmt.finalize(); + + // Issue the close. (And now the order of yielding doesn't matter.) + // Branch coverage: (asyncThread && mDBConn) + yield asyncClose(db); + equal((yield asyncStatementPromise), + Ci.mozIStorageStatementCallback.REASON_FINISHED); +}); + +/** + * Open an async database (ensures the async thread is created) and then invoke + * AsyncClose() twice without yielding control flow. The first will initiate + * the actual async close after calling setClosedState which synchronously + * impacts what the second call will observe. The second call will then see the + * async thread is not available and fall back to invoking Close() which will + * notice the mDBConn is already gone. + */ +add_task(function* test_double_asyncClose_throws() { + let db = yield openAsyncDatabase(getTestDB()); + + // (Don't yield control flow yet, save the promise for after we make the + // second call.) + // Branch coverage: (asyncThread && mDBConn) + let realClosePromise = yield asyncClose(db); + try { + // Branch coverage: (!asyncThread && !mDBConn) + db.asyncClose(); + ok(false, "should have thrown"); + } catch (e) { + equal(e.result, Cr.NS_ERROR_NOT_INITIALIZED); + } + + yield realClosePromise; +}); + +/** + * Create a sync db connection and never take it asynchronous and then call + * asyncClose on it. This will bring the async thread to life to perform the + * shutdown to avoid blocking the main thread, although we won't be able to + * tell the difference between this happening and the method secretly shunting + * to close(). + */ +add_task(function* test_asyncClose_on_sync_db() { + let db = getService().openDatabase(getTestDB()); + + // Branch coverage: (!asyncThread && mDBConn) + yield asyncClose(db); + ok(true, 'closed sync connection asynchronously'); +}); + +/** + * Fail to asynchronously open a DB in order to get an async thread existing + * without having an open database and asyncClose invoked. As per the file + * doc-block, note that asyncClose will automatically be invoked by the + * AsyncInitDatabase when it fails to open the database. We will never be + * provided with a reference to the connection and so cannot call AsyncClose on + * it ourselves. + */ +add_task(function* test_asyncClose_failed_open() { + // This will fail and the promise will be rejected. + let openPromise = openAsyncDatabase(getFakeDB()); + yield openPromise.then( + () => { + ok(false, 'we should have failed to open the db; this test is broken!'); + }, + () => { + ok(true, 'correctly failed to open db; bg asyncClose should happen'); + } + ); + // (NB: we are unable to observe the thread shutdown, but since we never open + // a database, this test is not going to interfere with other tests so much.) +}); + +// THE TEST BELOW WANTS TO BE THE LAST TEST WE RUN. DO NOT MAKE IT SAD. +/** + * Verify that asyncClose without a callback does not explode. Without a + * callback the shutdown is not actually observable, so we run this test last + * in order to avoid weird overlaps. + */ +add_task(function* test_asyncClose_does_not_throw_without_callback() { + let db = yield openAsyncDatabase(getTestDB()); + // Branch coverage: (asyncThread && mDBConn) + db.asyncClose(); + ok(true, 'if we shutdown cleanly and do not crash, then we succeeded'); +}); +// OBEY SHOUTING UPPER-CASE COMMENTS. +// ADD TESTS ABOVE THE FORMER TEST, NOT BELOW IT. diff --git a/storage/test/unit/test_connection_executeAsync.js b/storage/test/unit/test_connection_executeAsync.js new file mode 100644 index 000000000..e56d98e55 --- /dev/null +++ b/storage/test/unit/test_connection_executeAsync.js @@ -0,0 +1,171 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file tests the functionality of mozIStorageConnection::executeAsync for + * both mozIStorageStatement and mozIStorageAsyncStatement. + * + * A single database connection is used for the entirety of the test, which is + * a legacy thing, but we otherwise use the modern promise-based driver and + * async helpers. + */ + +const INTEGER = 1; +const TEXT = "this is test text"; +const REAL = 3.23; +const BLOB = [1, 2]; + +add_task(function* test_first_create_and_add() { + // synchronously open the database and let gDBConn hold onto it because we + // use this database + let db = getOpenedDatabase(); + // synchronously set up our table *that will be used for the rest of the file* + db.executeSimpleSQL( + "CREATE TABLE test (" + + "id INTEGER, " + + "string TEXT, " + + "number REAL, " + + "nuller NULL, " + + "blober BLOB" + + ")" + ); + + let stmts = []; + stmts[0] = db.createStatement( + "INSERT INTO test (id, string, number, nuller, blober) VALUES (?, ?, ?, ?, ?)" + ); + stmts[0].bindByIndex(0, INTEGER); + stmts[0].bindByIndex(1, TEXT); + stmts[0].bindByIndex(2, REAL); + stmts[0].bindByIndex(3, null); + stmts[0].bindBlobByIndex(4, BLOB, BLOB.length); + stmts[1] = getOpenedDatabase().createAsyncStatement( + "INSERT INTO test (string, number, nuller, blober) VALUES (?, ?, ?, ?)" + ); + stmts[1].bindByIndex(0, TEXT); + stmts[1].bindByIndex(1, REAL); + stmts[1].bindByIndex(2, null); + stmts[1].bindBlobByIndex(3, BLOB, BLOB.length); + + // asynchronously execute the statements + let execResult = yield executeMultipleStatementsAsync( + db, + stmts, + function(aResultSet) { + ok(false, 'we only did inserts so we should not have gotten results!'); + }); + equal(Ci.mozIStorageStatementCallback.REASON_FINISHED, execResult, + 'execution should have finished successfully.'); + + // Check that the result is in the table + let stmt = db.createStatement( + "SELECT string, number, nuller, blober FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, INTEGER); + try { + do_check_true(stmt.executeStep()); + do_check_eq(TEXT, stmt.getString(0)); + do_check_eq(REAL, stmt.getDouble(1)); + do_check_true(stmt.getIsNull(2)); + let count = { value: 0 }; + let blob = { value: null }; + stmt.getBlob(3, count, blob); + do_check_eq(BLOB.length, count.value); + for (let i = 0; i < BLOB.length; i++) + do_check_eq(BLOB[i], blob.value[i]); + } + finally { + stmt.finalize(); + } + + // Make sure we have two rows in the table + stmt = db.createStatement( + "SELECT COUNT(1) FROM test" + ); + try { + do_check_true(stmt.executeStep()); + do_check_eq(2, stmt.getInt32(0)); + } + finally { + stmt.finalize(); + } + + stmts[0].finalize(); + stmts[1].finalize(); +}); + +add_task(function* test_last_multiple_bindings_on_statements() { + // This tests to make sure that we pass all the statements multiply bound + // parameters when we call executeAsync. + const AMOUNT_TO_ADD = 5; + const ITERATIONS = 5; + + let stmts = []; + let db = getOpenedDatabase(); + let sqlString = "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (:int, :text, :real, :null, :blob)"; + // We run the same statement twice, and should insert 2 * AMOUNT_TO_ADD. + for (let i = 0; i < ITERATIONS; i++) { + // alternate the type of statement we create + if (i % 2) + stmts[i] = db.createStatement(sqlString); + else + stmts[i] = db.createAsyncStatement(sqlString); + + let params = stmts[i].newBindingParamsArray(); + for (let j = 0; j < AMOUNT_TO_ADD; j++) { + let bp = params.newBindingParams(); + bp.bindByName("int", INTEGER); + bp.bindByName("text", TEXT); + bp.bindByName("real", REAL); + bp.bindByName("null", null); + bp.bindBlobByName("blob", BLOB, BLOB.length); + params.addParams(bp); + } + stmts[i].bindParameters(params); + } + + // Get our current number of rows in the table. + let currentRows = 0; + let countStmt = getOpenedDatabase().createStatement( + "SELECT COUNT(1) AS count FROM test" + ); + try { + do_check_true(countStmt.executeStep()); + currentRows = countStmt.row.count; + } + finally { + countStmt.reset(); + } + + // Execute asynchronously. + let execResult = yield executeMultipleStatementsAsync( + db, + stmts, + function(aResultSet) { + ok(false, 'we only did inserts so we should not have gotten results!'); + }); + equal(Ci.mozIStorageStatementCallback.REASON_FINISHED, execResult, + 'execution should have finished successfully.'); + + // Check to make sure we added all of our rows. + try { + do_check_true(countStmt.executeStep()); + do_check_eq(currentRows + (ITERATIONS * AMOUNT_TO_ADD), + countStmt.row.count); + } + finally { + countStmt.finalize(); + } + + stmts.forEach(stmt => stmt.finalize()); + + // we are the last test using this connection and since it has gone async + // we *must* call asyncClose on it. + yield asyncClose(db); + gDBConn = null; +}); + +// If you add a test down here you will need to move the asyncClose or clean +// things up a little more. diff --git a/storage/test/unit/test_connection_executeSimpleSQLAsync.js b/storage/test/unit/test_connection_executeSimpleSQLAsync.js new file mode 100644 index 000000000..142cc8e14 --- /dev/null +++ b/storage/test/unit/test_connection_executeSimpleSQLAsync.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file tests the functionality of + * mozIStorageAsyncConnection::executeSimpleSQLAsync. + */ + +const INTEGER = 1; +const TEXT = "this is test text"; +const REAL = 3.23; + +add_task(function* test_create_and_add() { + let adb = yield openAsyncDatabase(getTestDB()); + + let completion = yield executeSimpleSQLAsync(adb, + "CREATE TABLE test (id INTEGER, string TEXT, number REAL)"); + + do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, completion); + + completion = yield executeSimpleSQLAsync(adb, + "INSERT INTO test (id, string, number) " + + "VALUES (" + INTEGER + ", \"" + TEXT + "\", " + REAL + ")"); + + do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, completion); + + let result = null; + + completion = yield executeSimpleSQLAsync(adb, + "SELECT string, number FROM test WHERE id = 1", + function (aResultSet) { + result = aResultSet.getNextRow(); + do_check_eq(2, result.numEntries); + do_check_eq(TEXT, result.getString(0)); + do_check_eq(REAL, result.getDouble(1)); + } + ); + + do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, completion); + do_check_neq(result, null); + result = null; + + yield executeSimpleSQLAsync(adb, "SELECT COUNT(0) FROM test", + function (aResultSet) { + result = aResultSet.getNextRow(); + do_check_eq(1, result.getInt32(0)); + }); + + do_check_neq(result, null); + + yield asyncClose(adb); +}); + + +add_task(function* test_asyncClose_does_not_complete_before_statement() { + let adb = yield openAsyncDatabase(getTestDB()); + let executed = false; + + let reason = yield executeSimpleSQLAsync(adb, "SELECT * FROM test", + function (aResultSet) { + let result = aResultSet.getNextRow(); + + do_check_neq(result, null); + do_check_eq(3, result.numEntries); + do_check_eq(INTEGER, result.getInt32(0)); + do_check_eq(TEXT, result.getString(1)); + do_check_eq(REAL, result.getDouble(2)); + executed = true; + } + ); + + do_check_eq(Ci.mozIStorageStatementCallback.REASON_FINISHED, reason); + + // Ensure that the statement executed to completion. + do_check_true(executed); + + yield asyncClose(adb); +}); diff --git a/storage/test/unit/test_js_helpers.js b/storage/test/unit/test_js_helpers.js new file mode 100644 index 000000000..dde9fac20 --- /dev/null +++ b/storage/test/unit/test_js_helpers.js @@ -0,0 +1,125 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sw=2 ts=2 sts=2 et : */ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/** + * This file tests that the JS language helpers in various ways. + */ + +// Test Functions + +function test_params_enumerate() +{ + let stmt = createStatement( + "SELECT * FROM test WHERE id IN (:a, :b, :c)" + ); + + // Make sure they are right. + let expected = ["a", "b", "c"]; + let index = 0; + for (let name in stmt.params) { + if (name == "QueryInterface") + continue; + do_check_eq(name, expected[index++]); + } +} + +function test_params_prototype() +{ + let stmt = createStatement( + "SELECT * FROM sqlite_master" + ); + + // Set a property on the prototype and make sure it exist (will not be a + // bindable parameter, however). + Object.getPrototypeOf(stmt.params).test = 2; + do_check_eq(stmt.params.test, 2); + stmt.finalize(); +} + +function test_row_prototype() +{ + let stmt = createStatement( + "SELECT * FROM sqlite_master" + ); + + do_check_true(stmt.executeStep()); + + // Set a property on the prototype and make sure it exists (will not be in the + // results, however). + Object.getPrototypeOf(stmt.row).test = 2; + do_check_eq(stmt.row.test, 2); + + // Clean up after ourselves. + delete Object.getPrototypeOf(stmt.row).test; + stmt.finalize(); +} + +function test_params_gets_sync() +{ + // Added for bug 562866. + /* + let stmt = createStatement( + "SELECT * FROM test WHERE id IN (:a, :b, :c)" + ); + + // Make sure we do not assert in getting the value. + let originalCount = Object.getOwnPropertyNames(stmt.params).length; + let expected = ["a", "b", "c"]; + for (let name of expected) { + stmt.params[name]; + } + + // Now make sure we didn't magically get any additional properties. + let finalCount = Object.getOwnPropertyNames(stmt.params).length; + do_check_eq(originalCount + expected.length, finalCount); + */ +} + +function test_params_gets_async() +{ + // Added for bug 562866. + /* + let stmt = createAsyncStatement( + "SELECT * FROM test WHERE id IN (:a, :b, :c)" + ); + + // Make sure we do not assert in getting the value. + let originalCount = Object.getOwnPropertyNames(stmt.params).length; + let expected = ["a", "b", "c"]; + for (let name of expected) { + stmt.params[name]; + } + + // Now make sure we didn't magically get any additional properties. + let finalCount = Object.getOwnPropertyNames(stmt.params).length; + do_check_eq(originalCount + expected.length, finalCount); + */ +} + +// Test Runner + +var tests = [ + test_params_enumerate, + test_params_prototype, + test_row_prototype, + test_params_gets_sync, + test_params_gets_async, +]; +function run_test() +{ + cleanup(); + + // Create our database. + getOpenedDatabase().executeSimpleSQL( + "CREATE TABLE test (" + + "id INTEGER PRIMARY KEY " + + ")" + ); + + // Run the tests. + tests.forEach(test => test()); +} diff --git a/storage/test/unit/test_levenshtein.js b/storage/test/unit/test_levenshtein.js new file mode 100644 index 000000000..ced141abd --- /dev/null +++ b/storage/test/unit/test_levenshtein.js @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the Levenshtein Distance function we've registered. + +function createUtf16Database() +{ + print("Creating the in-memory UTF-16-encoded database."); + let conn = getService().openSpecialDatabase("memory"); + conn.executeSimpleSQL("PRAGMA encoding = 'UTF-16'"); + + print("Make sure the encoding was set correctly and is now UTF-16."); + let stmt = conn.createStatement("PRAGMA encoding"); + do_check_true(stmt.executeStep()); + let enc = stmt.getString(0); + stmt.finalize(); + + // The value returned will actually be UTF-16le or UTF-16be. + do_check_true(enc === "UTF-16le" || enc === "UTF-16be"); + + return conn; +} + +function check_levenshtein(db, s, t, expectedDistance) +{ + var stmt = db.createStatement("SELECT levenshteinDistance(:s, :t) AS result"); + stmt.params.s = s; + stmt.params.t = t; + try { + do_check_true(stmt.executeStep()); + do_check_eq(expectedDistance, stmt.row.result); + } finally { + stmt.reset(); + stmt.finalize(); + } +} + +function testLevenshtein(db) +{ + // Basic tests. + check_levenshtein(db, "", "", 0); + check_levenshtein(db, "foo", "", 3); + check_levenshtein(db, "", "bar", 3); + check_levenshtein(db, "yellow", "hello", 2); + check_levenshtein(db, "gumbo", "gambol", 2); + check_levenshtein(db, "kitten", "sitten", 1); + check_levenshtein(db, "sitten", "sittin", 1); + check_levenshtein(db, "sittin", "sitting", 1); + check_levenshtein(db, "kitten", "sitting", 3); + check_levenshtein(db, "Saturday", "Sunday", 3); + check_levenshtein(db, "YHCQPGK", "LAHYQQKPGKA", 6); + + // Test SQL NULL handling. + check_levenshtein(db, "foo", null, null); + check_levenshtein(db, null, "bar", null); + check_levenshtein(db, null, null, null); + + // The levenshteinDistance function allocates temporary memory on the stack + // if it can. Test some strings long enough to force a heap allocation. + var dots1000 = Array(1001).join("."); + var dashes1000 = Array(1001).join("-"); + check_levenshtein(db, dots1000, dashes1000, 1000); +} + +function run_test() +{ + testLevenshtein(getOpenedDatabase()); + testLevenshtein(createUtf16Database()); +} + + + + diff --git a/storage/test/unit/test_like.js b/storage/test/unit/test_like.js new file mode 100644 index 000000000..b42f2eb27 --- /dev/null +++ b/storage/test/unit/test_like.js @@ -0,0 +1,202 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests our LIKE implementation since we override it for unicode + +function setup() +{ + getOpenedDatabase().createTable("t1", "x TEXT"); + + var stmt = createStatement("INSERT INTO t1 (x) VALUES ('a')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('ab')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('abc')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('abcd')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('acd')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('abd')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('bc')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('bcd')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('xyz')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('ABC')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('CDE')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES ('ABC abc xyz')"); + stmt.execute(); + stmt.finalize(); +} + +function test_count() +{ + var stmt = createStatement("SELECT count(*) FROM t1;"); + do_check_true(stmt.executeStep()); + do_check_eq(stmt.getInt32(0), 12); + stmt.reset(); + stmt.finalize(); +} + +function test_like_1() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'abc'); + var solutions = ["abc", "ABC"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_2() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'ABC'); + var solutions = ["abc", "ABC"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_3() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'aBc'); + var solutions = ["abc", "ABC"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_4() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'abc%'); + var solutions = ["abc", "abcd", "ABC", "ABC abc xyz"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_5() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'a_c'); + var solutions = ["abc", "ABC"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_6() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'ab%d'); + var solutions = ["abcd", "abd"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_7() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, 'a_c%'); + var solutions = ["abc", "abcd", "ABC", "ABC abc xyz"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +function test_like_8() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?;"); + stmt.bindByIndex(0, '%bcd'); + var solutions = ["abcd", "bcd"]; + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_true(stmt.executeStep()); + do_check_true(solutions.indexOf(stmt.getString(0)) != -1); + do_check_false(stmt.executeStep()); + stmt.reset(); + stmt.finalize(); +} + +var tests = [test_count, test_like_1, test_like_2, test_like_3, test_like_4, + test_like_5, test_like_6, test_like_7, test_like_8]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} + diff --git a/storage/test/unit/test_like_escape.js b/storage/test/unit/test_like_escape.js new file mode 100644 index 000000000..414f6237c --- /dev/null +++ b/storage/test/unit/test_like_escape.js @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const LATIN1_AE = "\xc6"; +const LATIN1_ae = "\xe6"; + +function setup() +{ + getOpenedDatabase().createTable("t1", "x TEXT"); + + var stmt = createStatement("INSERT INTO t1 (x) VALUES ('foo/bar_baz%20cheese')"); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("INSERT INTO t1 (x) VALUES (?1)"); + // insert LATIN_ae, but search on LATIN_AE + stmt.bindByIndex(0, "foo%20" + LATIN1_ae + "/_bar"); + stmt.execute(); + stmt.finalize(); +} + +function test_escape_for_like_ascii() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?1 ESCAPE '/'"); + var paramForLike = stmt.escapeStringForLIKE("oo/bar_baz%20chees", '/'); + // verify that we escaped / _ and % + do_check_eq(paramForLike, "oo//bar/_baz/%20chees"); + // prepend and append with % for "contains" + stmt.bindByIndex(0, "%" + paramForLike + "%"); + stmt.executeStep(); + do_check_eq("foo/bar_baz%20cheese", stmt.getString(0)); + stmt.finalize(); +} + +function test_escape_for_like_non_ascii() +{ + var stmt = createStatement("SELECT x FROM t1 WHERE x LIKE ?1 ESCAPE '/'"); + var paramForLike = stmt.escapeStringForLIKE("oo%20" + LATIN1_AE + "/_ba", '/'); + // verify that we escaped / _ and % + do_check_eq(paramForLike, "oo/%20" + LATIN1_AE + "///_ba"); + // prepend and append with % for "contains" + stmt.bindByIndex(0, "%" + paramForLike + "%"); + stmt.executeStep(); + do_check_eq("foo%20" + LATIN1_ae + "/_bar", stmt.getString(0)); + stmt.finalize(); +} + +var tests = [test_escape_for_like_ascii, test_escape_for_like_non_ascii]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} diff --git a/storage/test/unit/test_locale_collation.js b/storage/test/unit/test_locale_collation.js new file mode 100644 index 000000000..12ba2b943 --- /dev/null +++ b/storage/test/unit/test_locale_collation.js @@ -0,0 +1,304 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Bug 499990 - Locale-aware collation + * + * Tests our custom, locale-aware collating sequences. + */ + +// The name of the file containing the strings we'll sort during this test. +// The file's data is taken from intl/locale/tests/sort/us-ascii_base.txt and +// and intl/locale/tests/sort/us-ascii_sort.txt. +const DATA_BASENAME = "locale_collation.txt"; + +// The test data from DATA_BASENAME is read into this array. +var gStrings; + +// A collation created from the application's locale. Used by localeCompare(). +var gLocaleCollation; + +// A connection to our in-memory UTF-16-encoded database. +var gUtf16Conn; + +// Helper Functions + +/** + * Since we create a UTF-16 database we have to clean it up, in addition to + * the normal cleanup of Storage tests. + */ +function cleanupLocaleTests() +{ + print("-- Cleaning up test_locale_collation.js suite."); + gUtf16Conn.close(); + cleanup(); +} + +/** + * Creates a test database similar to the default one created in + * head_storage.js, except that this one uses UTF-16 encoding. + * + * @return A connection to the database. + */ +function createUtf16Database() +{ + print("Creating the in-memory UTF-16-encoded database."); + let conn = getService().openSpecialDatabase("memory"); + conn.executeSimpleSQL("PRAGMA encoding = 'UTF-16'"); + + print("Make sure the encoding was set correctly and is now UTF-16."); + let stmt = conn.createStatement("PRAGMA encoding"); + do_check_true(stmt.executeStep()); + let enc = stmt.getString(0); + stmt.finalize(); + + // The value returned will actually be UTF-16le or UTF-16be. + do_check_true(enc === "UTF-16le" || enc === "UTF-16be"); + + return conn; +} + +/** + * Compares aActual to aExpected, ensuring that the numbers and orderings of + * the two arrays' elements are the same. + * + * @param aActual + * An array of strings retrieved from the database. + * @param aExpected + * An array of strings to which aActual should be equivalent. + */ +function ensureResultsAreCorrect(aActual, aExpected) +{ + print("Actual results: " + aActual); + print("Expected results: " + aExpected); + + do_check_eq(aActual.length, aExpected.length); + for (let i = 0; i < aActual.length; i++) + do_check_eq(aActual[i], aExpected[i]); +} + +/** + * Synchronously SELECTs all rows from the test table of the given database + * using the given collation. + * + * @param aCollation + * The name of one of our custom locale collations. The rows are + * ordered by this collation. + * @param aConn + * A connection to either the UTF-8 database or the UTF-16 database. + * @return The resulting strings in an array. + */ +function getResults(aCollation, aConn) +{ + let results = []; + let stmt = aConn.createStatement("SELECT t FROM test " + + "ORDER BY t COLLATE " + aCollation + " ASC"); + while (stmt.executeStep()) + results.push(stmt.row.t); + stmt.finalize(); + return results; +} + +/** + * Inserts strings into our test table of the given database in the order given. + * + * @param aStrings + * An array of strings. + * @param aConn + * A connection to either the UTF-8 database or the UTF-16 database. + */ +function initTableWithStrings(aStrings, aConn) +{ + print("Initializing test table."); + + aConn.executeSimpleSQL("DROP TABLE IF EXISTS test"); + aConn.createTable("test", "t TEXT"); + let stmt = aConn.createStatement("INSERT INTO test (t) VALUES (:t)"); + aStrings.forEach(function (str) { + stmt.params.t = str; + stmt.execute(); + stmt.reset(); + }); + stmt.finalize(); +} + +/** + * Returns a sorting function suitable for passing to Array.prototype.sort(). + * The returned function uses the application's locale to compare strings. + * + * @param aCollation + * The name of one of our custom locale collations. The sorting + * strength is computed from this value. + * @return A function to use as a sorting callback. + */ +function localeCompare(aCollation) +{ + var strength; + + switch (aCollation) { + case "locale": + strength = Ci.nsICollation.kCollationCaseInSensitive; + break; + case "locale_case_sensitive": + strength = Ci.nsICollation.kCollationAccentInsenstive; + break; + case "locale_accent_sensitive": + strength = Ci.nsICollation.kCollationCaseInsensitiveAscii; + break; + case "locale_case_accent_sensitive": + strength = Ci.nsICollation.kCollationCaseSensitive; + break; + default: + do_throw("Error in test: unknown collation '" + aCollation + "'"); + break; + } + return function (aStr1, aStr2) { + return gLocaleCollation.compareString(strength, aStr1, aStr2); + }; +} + +/** + * Reads in the test data from the file DATA_BASENAME and returns it as an array + * of strings. + * + * @return The test data as an array of strings. + */ +function readTestData() +{ + print("Reading in test data."); + + let file = do_get_file(DATA_BASENAME); + + let istream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + istream.init(file, -1, -1, 0); + istream.QueryInterface(Components.interfaces.nsILineInputStream); + + let line = {}; + let lines = []; + while (istream.readLine(line)) { + lines.push(line.value); + } + istream.close(); + + return lines; +} + +/** + * Gets the results from the given database using the given collation and + * ensures that they match gStrings sorted by the same collation. + * + * @param aCollation + * The name of one of our custom locale collations. The rows from the + * database and the expected results are ordered by this collation. + * @param aConn + * A connection to either the UTF-8 database or the UTF-16 database. + */ +function runTest(aCollation, aConn) +{ + ensureResultsAreCorrect(getResults(aCollation, aConn), + gStrings.slice(0).sort(localeCompare(aCollation))); +} + +/** + * Gets the results from the UTF-8 database using the given collation and + * ensures that they match gStrings sorted by the same collation. + * + * @param aCollation + * The name of one of our custom locale collations. The rows from the + * database and the expected results are ordered by this collation. + */ +function runUtf8Test(aCollation) +{ + runTest(aCollation, getOpenedDatabase()); +} + +/** + * Gets the results from the UTF-16 database using the given collation and + * ensures that they match gStrings sorted by the same collation. + * + * @param aCollation + * The name of one of our custom locale collations. The rows from the + * database and the expected results are ordered by this collation. + */ +function runUtf16Test(aCollation) +{ + runTest(aCollation, gUtf16Conn); +} + +/** + * Sets up the test suite. + */ +function setup() +{ + print("-- Setting up the test_locale_collation.js suite."); + + gStrings = readTestData(); + + initTableWithStrings(gStrings, getOpenedDatabase()); + + gUtf16Conn = createUtf16Database(); + initTableWithStrings(gStrings, gUtf16Conn); + + let localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"]. + getService(Ci.nsILocaleService); + let collFact = Cc["@mozilla.org/intl/collation-factory;1"]. + createInstance(Ci.nsICollationFactory); + gLocaleCollation = collFact.CreateCollation(localeSvc.getApplicationLocale()); +} + +// Test Runs + +var gTests = [ + { + desc: "Case and accent sensitive UTF-8", + run: () => runUtf8Test("locale_case_accent_sensitive") + }, + + { + desc: "Case sensitive, accent insensitive UTF-8", + run: () => runUtf8Test("locale_case_sensitive") + }, + + { + desc: "Case insensitive, accent sensitive UTF-8", + run: () => runUtf8Test("locale_accent_sensitive") + }, + + { + desc: "Case and accent insensitive UTF-8", + run: () => runUtf8Test("locale") + }, + + { + desc: "Case and accent sensitive UTF-16", + run: () => runUtf16Test("locale_case_accent_sensitive") + }, + + { + desc: "Case sensitive, accent insensitive UTF-16", + run: () => runUtf16Test("locale_case_sensitive") + }, + + { + desc: "Case insensitive, accent sensitive UTF-16", + run: () => runUtf16Test("locale_accent_sensitive") + }, + + { + desc: "Case and accent insensitive UTF-16", + run: () => runUtf16Test("locale") + }, +]; + +function run_test() +{ + setup(); + gTests.forEach(function (test) { + print("-- Running test: " + test.desc); + test.run(); + }); +} diff --git a/storage/test/unit/test_page_size_is_32k.js b/storage/test/unit/test_page_size_is_32k.js new file mode 100644 index 000000000..a2548d1e6 --- /dev/null +++ b/storage/test/unit/test_page_size_is_32k.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This file tests that dbs are using 32k pagesize + +const kExpectedPageSize = 32768; // 32K +const kExpectedCacheSize = -2048; // 2MiB + +function check_size(db) +{ + var stmt = db.createStatement("PRAGMA page_size"); + stmt.executeStep(); + do_check_eq(stmt.getInt32(0), kExpectedPageSize); + stmt.finalize(); + stmt = db.createStatement("PRAGMA cache_size"); + stmt.executeStep(); + do_check_eq(stmt.getInt32(0), kExpectedCacheSize); + stmt.finalize(); +} + +function new_file(name) +{ + var file = dirSvc.get("ProfD", Ci.nsIFile); + file.append(name + ".sqlite"); + do_check_false(file.exists()); + return file; +} + +function run_test() +{ + check_size(getDatabase(new_file("shared32k"))); + check_size(getService().openUnsharedDatabase(new_file("unshared32k"))); +} + diff --git a/storage/test/unit/test_sqlite_secure_delete.js b/storage/test/unit/test_sqlite_secure_delete.js new file mode 100644 index 000000000..1eff34f70 --- /dev/null +++ b/storage/test/unit/test_sqlite_secure_delete.js @@ -0,0 +1,80 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + *vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This file tests to make sure that SQLite was compiled with + * SQLITE_SECURE_DELETE=1. + */ + +// Helper Methods + +/** + * Reads the contents of a file and returns it as a string. + * + * @param aFile + * The file to return from. + * @return the contents of the file in the form of a string. + */ +function getFileContents(aFile) +{ + let fstream = Cc["@mozilla.org/network/file-input-stream;1"]. + createInstance(Ci.nsIFileInputStream); + fstream.init(aFile, -1, 0, 0); + + let bstream = Cc["@mozilla.org/binaryinputstream;1"]. + createInstance(Ci.nsIBinaryInputStream); + bstream.setInputStream(fstream); + return bstream.readBytes(bstream.available()); +} + +// Tests + +add_test(function test_delete_removes_data() { + const TEST_STRING = "SomeRandomStringToFind"; + + let file = getTestDB(); + let db = getService().openDatabase(file); + + // Create the table and insert the data. + db.createTable("test", "data TEXT"); + let stmt = db.createStatement("INSERT INTO test VALUES(:data)"); + stmt.params.data = TEST_STRING; + try { + stmt.execute(); + } + finally { + stmt.finalize(); + } + + // Make sure this test is actually testing what it thinks by making sure the + // string shows up in the database. Because the previous statement was + // automatically wrapped in a transaction, the contents are already on disk. + let contents = getFileContents(file); + do_check_neq(-1, contents.indexOf(TEST_STRING)); + + // Delete the data, and then close the database. + stmt = db.createStatement("DELETE FROM test WHERE data = :data"); + stmt.params.data = TEST_STRING; + try { + stmt.execute(); + } + finally { + stmt.finalize(); + } + db.close(); + + // Check the file to see if the string can be found. + contents = getFileContents(file); + do_check_eq(-1, contents.indexOf(TEST_STRING)); + + run_next_test(); +}); + +function run_test() +{ + cleanup(); + run_next_test(); +} diff --git a/storage/test/unit/test_statement_executeAsync.js b/storage/test/unit/test_statement_executeAsync.js new file mode 100644 index 000000000..edcecb999 --- /dev/null +++ b/storage/test/unit/test_statement_executeAsync.js @@ -0,0 +1,998 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This file tests the functionality of mozIStorageBaseStatement::executeAsync + * for both mozIStorageStatement and mozIStorageAsyncStatement. + */ + +const INTEGER = 1; +const TEXT = "this is test text"; +const REAL = 3.23; +const BLOB = [1, 2]; + +/** + * Execute the given statement asynchronously, spinning an event loop until the + * async statement completes. + * + * @param aStmt + * The statement to execute. + * @param [aOptions={}] + * @param [aOptions.error=false] + * If true we should expect an error whose code we do not care about. If + * a numeric value, that's the error code we expect and require. If we + * are expecting an error, we expect a completion reason of REASON_ERROR. + * Otherwise we expect no error notification and a completion reason of + * REASON_FINISHED. + * @param [aOptions.cancel] + * If true we cancel the pending statement and additionally return the + * pending statement in case you want to further manipulate it. + * @param [aOptions.returnPending=false] + * If true we keep the pending statement around and return it to you. We + * normally avoid doing this to try and minimize the amount of time a + * reference is held to the returned pending statement. + * @param [aResults] + * If omitted, we assume no results rows are expected. If it is a + * number, we assume it is the number of results rows expected. If it is + * a function, we assume it is a function that takes the 1) result row + * number, 2) result tuple, 3) call stack for the original call to + * execAsync as arguments. If it is a list, we currently assume it is a + * list of functions where each function is intended to evaluate the + * result row at that ordinal position and takes the result tuple and + * the call stack for the original call. + */ +function execAsync(aStmt, aOptions, aResults) +{ + let caller = Components.stack.caller; + if (aOptions == null) + aOptions = {}; + + let resultsExpected; + let resultsChecker; + if (aResults == null) { + resultsExpected = 0; + } + else if (typeof aResults == "number") { + resultsExpected = aResults; + } + else if (typeof aResults == "function") { + resultsChecker = aResults; + } + else { // array + resultsExpected = aResults.length; + resultsChecker = function (aResultNum, aTup, aCaller) { + aResults[aResultNum](aTup, aCaller); + }; + } + let resultsSeen = 0; + + let errorCodeExpected = false; + let reasonExpected = Ci.mozIStorageStatementCallback.REASON_FINISHED; + let altReasonExpected = null; + if ("error" in aOptions) { + errorCodeExpected = aOptions.error; + if (errorCodeExpected) + reasonExpected = Ci.mozIStorageStatementCallback.REASON_ERROR; + } + let errorCodeSeen = false; + + if ("cancel" in aOptions && aOptions.cancel) + altReasonExpected = Ci.mozIStorageStatementCallback.REASON_CANCELED; + + let completed = false; + + let listener = { + handleResult(aResultSet) + { + let row, resultsSeenThisCall = 0; + while ((row = aResultSet.getNextRow()) != null) { + if (resultsChecker) + resultsChecker(resultsSeen, row, caller); + resultsSeen++; + resultsSeenThisCall++; + } + + if (!resultsSeenThisCall) + do_throw("handleResult invoked with 0 result rows!"); + }, + handleError(aError) + { + if (errorCodeSeen != false) + do_throw("handleError called when we already had an error!"); + errorCodeSeen = aError.result; + }, + handleCompletion(aReason) + { + if (completed) // paranoia check + do_throw("Received a second handleCompletion notification!", caller); + + if (resultsSeen != resultsExpected) + do_throw("Expected " + resultsExpected + " rows of results but " + + "got " + resultsSeen + " rows!", caller); + + if (errorCodeExpected == true && errorCodeSeen == false) + do_throw("Expected an error, but did not see one.", caller); + else if (errorCodeExpected != errorCodeSeen) + do_throw("Expected error code " + errorCodeExpected + " but got " + + errorCodeSeen, caller); + + if (aReason != reasonExpected && aReason != altReasonExpected) + do_throw("Expected reason " + reasonExpected + + (altReasonExpected ? (" or " + altReasonExpected) : "") + + " but got " + aReason, caller); + + completed = true; + } + }; + + let pending; + // Only get a pending reference if we're supposed to do. + // (note: This does not stop XPConnect from holding onto one currently.) + if (("cancel" in aOptions && aOptions.cancel) || + ("returnPending" in aOptions && aOptions.returnPending)) { + pending = aStmt.executeAsync(listener); + } + else { + aStmt.executeAsync(listener); + } + + if ("cancel" in aOptions && aOptions.cancel) + pending.cancel(); + + let curThread = Components.classes["@mozilla.org/thread-manager;1"] + .getService().currentThread; + while (!completed && !_quit) + curThread.processNextEvent(true); + + return pending; +} + +/** + * Make sure that illegal SQL generates the expected runtime error and does not + * result in any crashes. Async-only since the synchronous case generates the + * error synchronously (and is tested elsewhere). + */ +function test_illegal_sql_async_deferred() +{ + // gibberish + let stmt = makeTestStatement("I AM A ROBOT. DO AS I SAY."); + execAsync(stmt, {error: Ci.mozIStorageError.ERROR}); + stmt.finalize(); + + // legal SQL syntax, but with semantics issues. + stmt = makeTestStatement("SELECT destination FROM funkytown"); + execAsync(stmt, {error: Ci.mozIStorageError.ERROR}); + stmt.finalize(); + + run_next_test(); +} +test_illegal_sql_async_deferred.asyncOnly = true; + +function test_create_table() +{ + // Ensure our table doesn't exist + do_check_false(getOpenedDatabase().tableExists("test")); + + var stmt = makeTestStatement( + "CREATE TABLE test (" + + "id INTEGER, " + + "string TEXT, " + + "number REAL, " + + "nuller NULL, " + + "blober BLOB" + + ")" + ); + execAsync(stmt); + stmt.finalize(); + + // Check that the table has been created + do_check_true(getOpenedDatabase().tableExists("test")); + + // Verify that it's created correctly (this will throw if it wasn't) + let checkStmt = getOpenedDatabase().createStatement( + "SELECT id, string, number, nuller, blober FROM test" + ); + checkStmt.finalize(); + run_next_test(); +} + +function test_add_data() +{ + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (?, ?, ?, ?, ?)" + ); + stmt.bindBlobByIndex(4, BLOB, BLOB.length); + stmt.bindByIndex(3, null); + stmt.bindByIndex(2, REAL); + stmt.bindByIndex(1, TEXT); + stmt.bindByIndex(0, INTEGER); + + execAsync(stmt); + stmt.finalize(); + + // Check that the result is in the table + verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?", + INTEGER, + [TEXT, REAL, null, BLOB]); + run_next_test(); +} + +function test_get_data() +{ + var stmt = makeTestStatement( + "SELECT string, number, nuller, blober, id FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, INTEGER); + execAsync(stmt, {}, [ + function (tuple) { + do_check_neq(null, tuple); + + // Check that it's what we expect + do_check_false(tuple.getIsNull(0)); + do_check_eq(tuple.getResultByName("string"), tuple.getResultByIndex(0)); + do_check_eq(TEXT, tuple.getResultByName("string")); + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_TEXT, + tuple.getTypeOfIndex(0)); + + do_check_false(tuple.getIsNull(1)); + do_check_eq(tuple.getResultByName("number"), tuple.getResultByIndex(1)); + do_check_eq(REAL, tuple.getResultByName("number")); + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT, + tuple.getTypeOfIndex(1)); + + do_check_true(tuple.getIsNull(2)); + do_check_eq(tuple.getResultByName("nuller"), tuple.getResultByIndex(2)); + do_check_eq(null, tuple.getResultByName("nuller")); + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_NULL, + tuple.getTypeOfIndex(2)); + + do_check_false(tuple.getIsNull(3)); + var blobByName = tuple.getResultByName("blober"); + do_check_eq(BLOB.length, blobByName.length); + var blobByIndex = tuple.getResultByIndex(3); + do_check_eq(BLOB.length, blobByIndex.length); + for (let i = 0; i < BLOB.length; i++) { + do_check_eq(BLOB[i], blobByName[i]); + do_check_eq(BLOB[i], blobByIndex[i]); + } + var count = { value: 0 }; + var blob = { value: null }; + tuple.getBlob(3, count, blob); + do_check_eq(BLOB.length, count.value); + for (let i = 0; i < BLOB.length; i++) + do_check_eq(BLOB[i], blob.value[i]); + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_BLOB, + tuple.getTypeOfIndex(3)); + + do_check_false(tuple.getIsNull(4)); + do_check_eq(tuple.getResultByName("id"), tuple.getResultByIndex(4)); + do_check_eq(INTEGER, tuple.getResultByName("id")); + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER, + tuple.getTypeOfIndex(4)); + }]); + stmt.finalize(); + run_next_test(); +} + +function test_tuple_out_of_bounds() +{ + var stmt = makeTestStatement( + "SELECT string FROM test" + ); + execAsync(stmt, {}, [ + function (tuple) { + do_check_neq(null, tuple); + + // Check all out of bounds - should throw + var methods = [ + "getTypeOfIndex", + "getInt32", + "getInt64", + "getDouble", + "getUTF8String", + "getString", + "getIsNull", + ]; + for (var i in methods) { + try { + tuple[methods[i]](tuple.numEntries); + do_throw("did not throw :("); + } + catch (e) { + do_check_eq(Cr.NS_ERROR_ILLEGAL_VALUE, e.result); + } + } + + // getBlob requires more args... + try { + var blob = { value: null }; + var size = { value: 0 }; + tuple.getBlob(tuple.numEntries, blob, size); + do_throw("did not throw :("); + } + catch (e) { + do_check_eq(Cr.NS_ERROR_ILLEGAL_VALUE, e.result); + } + }]); + stmt.finalize(); + run_next_test(); +} + +function test_no_listener_works_on_success() +{ + var stmt = makeTestStatement( + "DELETE FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, 0); + stmt.executeAsync(); + stmt.finalize(); + + // Run the next test. + run_next_test(); +} + +function test_no_listener_works_on_results() +{ + var stmt = makeTestStatement( + "SELECT ?" + ); + stmt.bindByIndex(0, 1); + stmt.executeAsync(); + stmt.finalize(); + + // Run the next test. + run_next_test(); +} + +function test_no_listener_works_on_error() +{ + // commit without a transaction will trigger an error + var stmt = makeTestStatement( + "COMMIT" + ); + stmt.executeAsync(); + stmt.finalize(); + + // Run the next test. + run_next_test(); +} + +function test_partial_listener_works() +{ + var stmt = makeTestStatement( + "DELETE FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, 0); + stmt.executeAsync({ + handleResult(aResultSet) {} + }); + stmt.executeAsync({ + handleError(aError) {} + }); + stmt.executeAsync({ + handleCompletion(aReason) {} + }); + stmt.finalize(); + + // Run the next test. + run_next_test(); +} + +/** + * Dubious cancellation test that depends on system loading may or may not + * succeed in canceling things. It does at least test if calling cancel blows + * up. test_AsyncCancellation in test_true_async.cpp is our test that canceling + * actually works correctly. + */ +function test_immediate_cancellation() +{ + var stmt = makeTestStatement( + "DELETE FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, 0); + execAsync(stmt, {cancel: true}); + stmt.finalize(); + run_next_test(); +} + +/** + * Test that calling cancel twice throws the second time. + */ +function test_double_cancellation() +{ + var stmt = makeTestStatement( + "DELETE FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, 0); + let pendingStatement = execAsync(stmt, {cancel: true}); + // And cancel again - expect an exception + expectError(Cr.NS_ERROR_UNEXPECTED, + () => pendingStatement.cancel()); + + stmt.finalize(); + run_next_test(); +} + +/** + * Verify that nothing untoward happens if we try and cancel something after it + * has fully run to completion. + */ +function test_cancellation_after_execution() +{ + var stmt = makeTestStatement( + "DELETE FROM test WHERE id = ?" + ); + stmt.bindByIndex(0, 0); + let pendingStatement = execAsync(stmt, {returnPending: true}); + // (the statement has fully executed at this point) + // canceling after the statement has run to completion should not throw! + pendingStatement.cancel(); + + stmt.finalize(); + run_next_test(); +} + +/** + * Verifies that a single statement can be executed more than once. Might once + * have been intended to also ensure that callback notifications were not + * incorrectly interleaved, but that part was brittle (it's totally fine for + * handleResult to get called multiple times) and not comprehensive. + */ +function test_double_execute() +{ + var stmt = makeTestStatement( + "SELECT 1" + ); + execAsync(stmt, null, 1); + execAsync(stmt, null, 1); + stmt.finalize(); + run_next_test(); +} + +function test_finalized_statement_does_not_crash() +{ + var stmt = makeTestStatement( + "SELECT * FROM TEST" + ); + stmt.finalize(); + // we are concerned about a crash here; an error is fine. + try { + stmt.executeAsync(); + } catch (ex) { + // Do nothing. + } + + // Run the next test. + run_next_test(); +} + +/** + * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by index. + */ +function test_bind_direct_binding_params_by_index() +{ + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (?, ?, ?, ?, ?)" + ); + let insertId = nextUniqueId++; + stmt.bindByIndex(0, insertId); + stmt.bindByIndex(1, TEXT); + stmt.bindByIndex(2, REAL); + stmt.bindByIndex(3, null); + stmt.bindBlobByIndex(4, BLOB, BLOB.length); + execAsync(stmt); + stmt.finalize(); + verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?", + insertId, + [TEXT, REAL, null, BLOB]); + run_next_test(); +} + +/** + * Bind by mozIStorageBindingParams on the mozIStorageBaseStatement by name. + */ +function test_bind_direct_binding_params_by_name() +{ + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (:int, :text, :real, :null, :blob)" + ); + let insertId = nextUniqueId++; + stmt.bindByName("int", insertId); + stmt.bindByName("text", TEXT); + stmt.bindByName("real", REAL); + stmt.bindByName("null", null); + stmt.bindBlobByName("blob", BLOB, BLOB.length); + execAsync(stmt); + stmt.finalize(); + verifyQuery("SELECT string, number, nuller, blober FROM test WHERE id = ?", + insertId, + [TEXT, REAL, null, BLOB]); + run_next_test(); +} + +function test_bind_js_params_helper_by_index() +{ + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (?, ?, ?, ?, NULL)" + ); + let insertId = nextUniqueId++; + // we cannot bind blobs this way; no blober + stmt.params[3] = null; + stmt.params[2] = REAL; + stmt.params[1] = TEXT; + stmt.params[0] = insertId; + execAsync(stmt); + stmt.finalize(); + verifyQuery("SELECT string, number, nuller FROM test WHERE id = ?", insertId, + [TEXT, REAL, null]); + run_next_test(); +} + +function test_bind_js_params_helper_by_name() +{ + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (:int, :text, :real, :null, NULL)" + ); + let insertId = nextUniqueId++; + // we cannot bind blobs this way; no blober + stmt.params.null = null; + stmt.params.real = REAL; + stmt.params.text = TEXT; + stmt.params.int = insertId; + execAsync(stmt); + stmt.finalize(); + verifyQuery("SELECT string, number, nuller FROM test WHERE id = ?", insertId, + [TEXT, REAL, null]); + run_next_test(); +} + +function test_bind_multiple_rows_by_index() +{ + const AMOUNT_TO_ADD = 5; + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (?, ?, ?, ?, ?)" + ); + var array = stmt.newBindingParamsArray(); + for (let i = 0; i < AMOUNT_TO_ADD; i++) { + let bp = array.newBindingParams(); + bp.bindByIndex(0, INTEGER); + bp.bindByIndex(1, TEXT); + bp.bindByIndex(2, REAL); + bp.bindByIndex(3, null); + bp.bindBlobByIndex(4, BLOB, BLOB.length); + array.addParams(bp); + do_check_eq(array.length, i + 1); + } + stmt.bindParameters(array); + + let rowCount = getTableRowCount("test"); + execAsync(stmt); + do_check_eq(rowCount + AMOUNT_TO_ADD, getTableRowCount("test")); + stmt.finalize(); + run_next_test(); +} + +function test_bind_multiple_rows_by_name() +{ + const AMOUNT_TO_ADD = 5; + var stmt = makeTestStatement( + "INSERT INTO test (id, string, number, nuller, blober) " + + "VALUES (:int, :text, :real, :null, :blob)" + ); + var array = stmt.newBindingParamsArray(); + for (let i = 0; i < AMOUNT_TO_ADD; i++) { + let bp = array.newBindingParams(); + bp.bindByName("int", INTEGER); + bp.bindByName("text", TEXT); + bp.bindByName("real", REAL); + bp.bindByName("null", null); + bp.bindBlobByName("blob", BLOB, BLOB.length); + array.addParams(bp); + do_check_eq(array.length, i + 1); + } + stmt.bindParameters(array); + + let rowCount = getTableRowCount("test"); + execAsync(stmt); + do_check_eq(rowCount + AMOUNT_TO_ADD, getTableRowCount("test")); + stmt.finalize(); + run_next_test(); +} + +/** + * Verify that a mozIStorageStatement instance throws immediately when we + * try and bind to an illegal index. + */ +function test_bind_out_of_bounds_sync_immediate() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (?)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + + // Check variant binding. + expectError(Cr.NS_ERROR_INVALID_ARG, + () => bp.bindByIndex(1, INTEGER)); + // Check blob binding. + expectError(Cr.NS_ERROR_INVALID_ARG, + () => bp.bindBlobByIndex(1, BLOB, BLOB.length)); + + stmt.finalize(); + run_next_test(); +} +test_bind_out_of_bounds_sync_immediate.syncOnly = true; + +/** + * Verify that a mozIStorageAsyncStatement reports an error asynchronously when + * we bind to an illegal index. + */ +function test_bind_out_of_bounds_async_deferred() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (?)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + + // There is no difference between variant and blob binding for async purposes. + bp.bindByIndex(1, INTEGER); + array.addParams(bp); + stmt.bindParameters(array); + execAsync(stmt, {error: Ci.mozIStorageError.RANGE}); + + stmt.finalize(); + run_next_test(); +} +test_bind_out_of_bounds_async_deferred.asyncOnly = true; + +function test_bind_no_such_name_sync_immediate() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:foo)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + + // Check variant binding. + expectError(Cr.NS_ERROR_INVALID_ARG, + () => bp.bindByName("doesnotexist", INTEGER)); + // Check blob binding. + expectError(Cr.NS_ERROR_INVALID_ARG, + () => bp.bindBlobByName("doesnotexist", BLOB, BLOB.length)); + + stmt.finalize(); + run_next_test(); +} +test_bind_no_such_name_sync_immediate.syncOnly = true; + +function test_bind_no_such_name_async_deferred() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:foo)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + + bp.bindByName("doesnotexist", INTEGER); + array.addParams(bp); + stmt.bindParameters(array); + execAsync(stmt, {error: Ci.mozIStorageError.RANGE}); + + stmt.finalize(); + run_next_test(); +} +test_bind_no_such_name_async_deferred.asyncOnly = true; + +function test_bind_bogus_type_by_index() +{ + // We try to bind a JS Object here that should fail to bind. + let stmt = makeTestStatement( + "INSERT INTO test (blober) " + + "VALUES (?)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + Assert.throws(() => bp.bindByIndex(0, run_test), /NS_ERROR_UNEXPECTED/); + + stmt.finalize(); + run_next_test(); +} + +function test_bind_bogus_type_by_name() +{ + // We try to bind a JS Object here that should fail to bind. + let stmt = makeTestStatement( + "INSERT INTO test (blober) " + + "VALUES (:blob)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + Assert.throws(() => bp.bindByName("blob", run_test), /NS_ERROR_UNEXPECTED/); + + stmt.finalize(); + run_next_test(); +} + +function test_bind_params_already_locked() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + bp.bindByName("int", INTEGER); + array.addParams(bp); + + // We should get an error after we call addParams and try to bind again. + expectError(Cr.NS_ERROR_UNEXPECTED, + () => bp.bindByName("int", INTEGER)); + + stmt.finalize(); + run_next_test(); +} + +function test_bind_params_array_already_locked() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + + let array = stmt.newBindingParamsArray(); + let bp1 = array.newBindingParams(); + bp1.bindByName("int", INTEGER); + array.addParams(bp1); + let bp2 = array.newBindingParams(); + stmt.bindParameters(array); + bp2.bindByName("int", INTEGER); + + // We should get an error after we have bound the array to the statement. + expectError(Cr.NS_ERROR_UNEXPECTED, + () => array.addParams(bp2)); + + stmt.finalize(); + run_next_test(); +} + +function test_no_binding_params_from_locked_array() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + + let array = stmt.newBindingParamsArray(); + let bp = array.newBindingParams(); + bp.bindByName("int", INTEGER); + array.addParams(bp); + stmt.bindParameters(array); + + // We should not be able to get a new BindingParams object after we have bound + // to the statement. + expectError(Cr.NS_ERROR_UNEXPECTED, + () => array.newBindingParams()); + + stmt.finalize(); + run_next_test(); +} + +function test_not_right_owning_array() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + + let array1 = stmt.newBindingParamsArray(); + let array2 = stmt.newBindingParamsArray(); + let bp = array1.newBindingParams(); + bp.bindByName("int", INTEGER); + + // We should not be able to add bp to array2 since it was created from array1. + expectError(Cr.NS_ERROR_UNEXPECTED, + () => array2.addParams(bp)); + + stmt.finalize(); + run_next_test(); +} + +function test_not_right_owning_statement() +{ + let stmt1 = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + let stmt2 = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + + let array1 = stmt1.newBindingParamsArray(); + let array2 = stmt2.newBindingParamsArray(); + let bp = array1.newBindingParams(); + bp.bindByName("int", INTEGER); + array1.addParams(bp); + + // We should not be able to bind array1 since it was created from stmt1. + expectError(Cr.NS_ERROR_UNEXPECTED, + () => stmt2.bindParameters(array1)); + + stmt1.finalize(); + stmt2.finalize(); + run_next_test(); +} + +function test_bind_empty_array() +{ + let stmt = makeTestStatement( + "INSERT INTO test (id) " + + "VALUES (:int)" + ); + + let paramsArray = stmt.newBindingParamsArray(); + + // We should not be able to bind this array to the statement because it is + // empty. + expectError(Cr.NS_ERROR_UNEXPECTED, + () => stmt.bindParameters(paramsArray)); + + stmt.finalize(); + run_next_test(); +} + +function test_multiple_results() +{ + let expectedResults = getTableRowCount("test"); + // Sanity check - we should have more than one result, but let's be sure. + do_check_true(expectedResults > 1); + + // Now check that we get back two rows of data from our async query. + let stmt = makeTestStatement("SELECT * FROM test"); + execAsync(stmt, {}, expectedResults); + + stmt.finalize(); + run_next_test(); +} + +// Test Runner + +const TEST_PASS_SYNC = 0; +const TEST_PASS_ASYNC = 1; +/** + * We run 2 passes against the test. One where makeTestStatement generates + * synchronous (mozIStorageStatement) statements and one where it generates + * asynchronous (mozIStorageAsyncStatement) statements. + * + * Because of differences in the ability to know the number of parameters before + * dispatching, some tests are sync/async specific. These functions are marked + * with 'syncOnly' or 'asyncOnly' attributes and run_next_test knows what to do. + */ +var testPass = TEST_PASS_SYNC; + +/** + * Create a statement of the type under test per testPass. + * + * @param aSQL + * The SQL string from which to build a statement. + * @return a statement of the type under test per testPass. + */ +function makeTestStatement(aSQL) { + if (testPass == TEST_PASS_SYNC) { + return getOpenedDatabase().createStatement(aSQL); + } + return getOpenedDatabase().createAsyncStatement(aSQL); +} + +var tests = [ + test_illegal_sql_async_deferred, + test_create_table, + test_add_data, + test_get_data, + test_tuple_out_of_bounds, + test_no_listener_works_on_success, + test_no_listener_works_on_results, + test_no_listener_works_on_error, + test_partial_listener_works, + test_immediate_cancellation, + test_double_cancellation, + test_cancellation_after_execution, + test_double_execute, + test_finalized_statement_does_not_crash, + test_bind_direct_binding_params_by_index, + test_bind_direct_binding_params_by_name, + test_bind_js_params_helper_by_index, + test_bind_js_params_helper_by_name, + test_bind_multiple_rows_by_index, + test_bind_multiple_rows_by_name, + test_bind_out_of_bounds_sync_immediate, + test_bind_out_of_bounds_async_deferred, + test_bind_no_such_name_sync_immediate, + test_bind_no_such_name_async_deferred, + test_bind_bogus_type_by_index, + test_bind_bogus_type_by_name, + test_bind_params_already_locked, + test_bind_params_array_already_locked, + test_bind_empty_array, + test_no_binding_params_from_locked_array, + test_not_right_owning_array, + test_not_right_owning_statement, + test_multiple_results, +]; +var index = 0; + +const STARTING_UNIQUE_ID = 2; +var nextUniqueId = STARTING_UNIQUE_ID; + +function run_next_test() +{ + function _run_next_test() { + // use a loop so we can skip tests... + while (index < tests.length) { + let test = tests[index++]; + // skip tests not appropriate to the current test pass + if ((testPass == TEST_PASS_SYNC && ("asyncOnly" in test)) || + (testPass == TEST_PASS_ASYNC && ("syncOnly" in test))) + continue; + + // Asynchronous tests means that exceptions don't kill the test. + try { + print("****** Running the next test: " + test.name); + test(); + return; + } + catch (e) { + do_throw(e); + } + } + + // if we only completed the first pass, move to the next pass + if (testPass == TEST_PASS_SYNC) { + print("********* Beginning mozIStorageAsyncStatement pass."); + testPass++; + index = 0; + // a new pass demands a new database + asyncCleanup(); + nextUniqueId = STARTING_UNIQUE_ID; + _run_next_test(); + return; + } + + // we did some async stuff; we need to clean up. + asyncCleanup(); + do_test_finished(); + } + + // Don't actually schedule another test if we're quitting. + if (!_quit) { + // For saner stacks, we execute this code RSN. + do_execute_soon(_run_next_test); + } +} + +function run_test() +{ + cleanup(); + + do_test_pending(); + run_next_test(); +} diff --git a/storage/test/unit/test_statement_wrapper_automatically.js b/storage/test/unit/test_statement_wrapper_automatically.js new file mode 100644 index 000000000..58b27dd2d --- /dev/null +++ b/storage/test/unit/test_statement_wrapper_automatically.js @@ -0,0 +1,167 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + vim:set ts=2 sw=2 sts=2 et: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the functions of mozIStorageStatementWrapper + +function setup() +{ + getOpenedDatabase().createTable("test", "id INTEGER PRIMARY KEY, val NONE," + + "alt_val NONE"); +} + +/** + * A convenience wrapper for do_check_eq. Calls do_check_eq on aActualVal + * and aReturnedVal, with one caveat. + * + * Date objects are converted before parameter binding to PRTime's (microsecs + * since epoch). They are not reconverted when retrieved from the database. + * This function abstracts away this reconversion so that you can pass in, + * for example: + * + * checkVal(new Date(), aReturnedVal) // this + * checkVal(new Date().valueOf() * 1000.0, aReturnedVal) // instead of this + * + * Should any other types require conversion in the future, their conversions + * may also be abstracted away here. + * + * @param aActualVal + * the value inserted into the database + * @param aReturnedVal + * the value retrieved from the database + */ +function checkVal(aActualVal, aReturnedVal) +{ + if (aActualVal instanceof Date) aActualVal = aActualVal.valueOf() * 1000.0; + do_check_eq(aActualVal, aReturnedVal); +} + +/** + * Removes all rows from our test table. + */ +function clearTable() +{ + var stmt = createStatement("DELETE FROM test"); + stmt.execute(); + stmt.finalize(); + ensureNumRows(0); +} + +/** + * Ensures that the number of rows in our test table is equal to aNumRows. + * Calls do_check_eq on aNumRows and the value retrieved by SELECT'ing COUNT(*). + * + * @param aNumRows + * the number of rows our test table should contain + */ +function ensureNumRows(aNumRows) +{ + var stmt = createStatement("SELECT COUNT(*) AS number FROM test"); + do_check_true(stmt.step()); + do_check_eq(aNumRows, stmt.row.number); + stmt.reset(); + stmt.finalize(); +} + +/** + * Inserts aVal into our test table and checks that insertion was successful by + * retrieving the newly inserted value from the database and comparing it + * against aVal. aVal is bound to a single parameter. + * + * @param aVal + * value to insert into our test table and check + */ +function insertAndCheckSingleParam(aVal) +{ + clearTable(); + + var stmt = createStatement("INSERT INTO test (val) VALUES (:val)"); + stmt.params.val = aVal; + stmt.execute(); + stmt.finalize(); + + ensureNumRows(1); + + stmt = createStatement("SELECT val FROM test WHERE id = 1"); + do_check_true(stmt.step()); + checkVal(aVal, stmt.row.val); + stmt.reset(); + stmt.finalize(); +} + +/** + * Inserts aVal into our test table and checks that insertion was successful by + * retrieving the newly inserted value from the database and comparing it + * against aVal. aVal is bound to two separate parameters, both of which are + * checked against aVal. + * + * @param aVal + * value to insert into our test table and check + */ +function insertAndCheckMultipleParams(aVal) +{ + clearTable(); + + var stmt = createStatement("INSERT INTO test (val, alt_val) " + + "VALUES (:val, :val)"); + stmt.params.val = aVal; + stmt.execute(); + stmt.finalize(); + + ensureNumRows(1); + + stmt = createStatement("SELECT val, alt_val FROM test WHERE id = 1"); + do_check_true(stmt.step()); + checkVal(aVal, stmt.row.val); + checkVal(aVal, stmt.row.alt_val); + stmt.reset(); + stmt.finalize(); +} + +/** + * A convenience function that prints out a description of aVal using + * aVal.toString and aVal.toSource. Output is useful when the test fails. + * + * @param aVal + * a value inserted or to be inserted into our test table + */ +function printValDesc(aVal) +{ + try { + var toSource = aVal.toSource(); + } catch (ex) { + toSource = ""; + } + print("Testing value: toString=" + aVal + + (toSource ? " toSource=" + toSource : "")); +} + +function run_test() +{ + setup(); + + // function JSValStorageStatementBinder in + // storage/mozStorageStatementParams.cpp tells us that the following types + // and only the following types are valid as statement parameters: + var vals = [ + 1337, // int + 3.1337, // double + "foo", // string + true, // boolean + null, // null + new Date(), // Date object + ]; + + vals.forEach(function (val) + { + printValDesc(val); + print("Single parameter"); + insertAndCheckSingleParam(val); + print("Multiple parameters"); + insertAndCheckMultipleParams(val); + }); + + cleanup(); +} diff --git a/storage/test/unit/test_storage_aggregates.js b/storage/test/unit/test_storage_aggregates.js new file mode 100644 index 000000000..400aba836 --- /dev/null +++ b/storage/test/unit/test_storage_aggregates.js @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the custom aggregate functions + +var testNums = [1, 2, 3, 4]; + +function setup() +{ + getOpenedDatabase().createTable("function_tests", "id INTEGER PRIMARY KEY"); + + var stmt = createStatement("INSERT INTO function_tests (id) VALUES(?1)"); + for (let i = 0; i < testNums.length; ++i) { + stmt.bindByIndex(0, testNums[i]); + stmt.execute(); + } + stmt.reset(); + stmt.finalize(); +} + +var testSquareAndSumFunction = { + calls: 0, + _sas: 0, + + reset() { + this.calls = 0; + this._sas = 0; + }, + + onStep(val) { + ++this.calls; + this._sas += val.getInt32(0) * val.getInt32(0); + }, + + onFinal() { + var retval = this._sas; + this._sas = 0; // Prepare for next group + return retval; + } +}; + +function test_aggregate_registration() +{ + var msc = getOpenedDatabase(); + msc.createAggregateFunction("test_sas_aggr", 1, testSquareAndSumFunction); +} + +function test_aggregate_no_double_registration() +{ + var msc = getOpenedDatabase(); + try { + msc.createAggregateFunction("test_sas_aggr", 2, testSquareAndSumFunction); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + } +} + +function test_aggregate_removal() +{ + var msc = getOpenedDatabase(); + msc.removeFunction("test_sas_aggr"); + // Should be Ok now + msc.createAggregateFunction("test_sas_aggr", 1, testSquareAndSumFunction); +} + +function test_aggregate_no_aliases() +{ + var msc = getOpenedDatabase(); + try { + msc.createAggregateFunction("test_sas_aggr2", 1, testSquareAndSumFunction); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + } +} + +function test_aggregate_call() +{ + var stmt = createStatement("SELECT test_sas_aggr(id) FROM function_tests"); + while (stmt.executeStep()) { + // Do nothing. + } + do_check_eq(testNums.length, testSquareAndSumFunction.calls); + testSquareAndSumFunction.reset(); + stmt.finalize(); +} + +function test_aggregate_result() +{ + var sas = 0; + for (var i = 0; i < testNums.length; ++i) { + sas += testNums[i] * testNums[i]; + } + var stmt = createStatement("SELECT test_sas_aggr(id) FROM function_tests"); + stmt.executeStep(); + do_check_eq(sas, stmt.getInt32(0)); + testSquareAndSumFunction.reset(); + stmt.finalize(); +} + +var tests = [test_aggregate_registration, test_aggregate_no_double_registration, + test_aggregate_removal, test_aggregate_no_aliases, test_aggregate_call, + test_aggregate_result]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} diff --git a/storage/test/unit/test_storage_connection.js b/storage/test/unit/test_storage_connection.js new file mode 100644 index 000000000..ce98d0891 --- /dev/null +++ b/storage/test/unit/test_storage_connection.js @@ -0,0 +1,763 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the functions of mozIStorageConnection + +// Test Functions + +add_task(function* test_connectionReady_open() { + // there doesn't seem to be a way for the connection to not be ready (unless + // we close it with mozIStorageConnection::Close(), but we don't for this). + // It can only fail if GetPath fails on the database file, or if we run out + // of memory trying to use an in-memory database + + var msc = getOpenedDatabase(); + do_check_true(msc.connectionReady); +}); + +add_task(function* test_connectionReady_closed() { + // This also tests mozIStorageConnection::Close() + + var msc = getOpenedDatabase(); + msc.close(); + do_check_false(msc.connectionReady); + gDBConn = null; // this is so later tests don't start to fail. +}); + +add_task(function* test_databaseFile() { + var msc = getOpenedDatabase(); + do_check_true(getTestDB().equals(msc.databaseFile)); +}); + +add_task(function* test_tableExists_not_created() { + var msc = getOpenedDatabase(); + do_check_false(msc.tableExists("foo")); +}); + +add_task(function* test_indexExists_not_created() { + var msc = getOpenedDatabase(); + do_check_false(msc.indexExists("foo")); +}); + +add_task(function* test_temp_tableExists_and_indexExists() { + var msc = getOpenedDatabase(); + msc.executeSimpleSQL("CREATE TEMP TABLE test_temp(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"); + do_check_true(msc.tableExists("test_temp")); + + msc.executeSimpleSQL("CREATE INDEX test_temp_ind ON test_temp (name)"); + do_check_true(msc.indexExists("test_temp_ind")); + + msc.executeSimpleSQL("DROP INDEX test_temp_ind"); + msc.executeSimpleSQL("DROP TABLE test_temp"); +}); + +add_task(function* test_createTable_not_created() { + var msc = getOpenedDatabase(); + msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT"); + do_check_true(msc.tableExists("test")); +}); + +add_task(function* test_indexExists_created() { + var msc = getOpenedDatabase(); + msc.executeSimpleSQL("CREATE INDEX name_ind ON test (name)"); + do_check_true(msc.indexExists("name_ind")); +}); + +add_task(function* test_createTable_already_created() { + var msc = getOpenedDatabase(); + do_check_true(msc.tableExists("test")); + Assert.throws(() => msc.createTable("test", "id INTEGER PRIMARY KEY, name TEXT"), + /NS_ERROR_FAILURE/); +}); + +add_task(function* test_attach_createTable_tableExists_indexExists() { + var msc = getOpenedDatabase(); + var file = do_get_file("storage_attach.sqlite", true); + var msc2 = getDatabase(file); + msc.executeSimpleSQL("ATTACH DATABASE '" + file.path + "' AS sample"); + + do_check_false(msc.tableExists("sample.test")); + msc.createTable("sample.test", "id INTEGER PRIMARY KEY, name TEXT"); + do_check_true(msc.tableExists("sample.test")); + Assert.throws(() => msc.createTable("sample.test", "id INTEGER PRIMARY KEY, name TEXT"), + /NS_ERROR_FAILURE/); + + do_check_false(msc.indexExists("sample.test_ind")); + msc.executeSimpleSQL("CREATE INDEX sample.test_ind ON test (name)"); + do_check_true(msc.indexExists("sample.test_ind")); + + msc.executeSimpleSQL("DETACH DATABASE sample"); + msc2.close(); + try { + file.remove(false); + } catch (e) { + // Do nothing. + } +}); + +add_task(function* test_lastInsertRowID() { + var msc = getOpenedDatabase(); + msc.executeSimpleSQL("INSERT INTO test (name) VALUES ('foo')"); + do_check_eq(1, msc.lastInsertRowID); +}); + +add_task(function* test_transactionInProgress_no() { + var msc = getOpenedDatabase(); + do_check_false(msc.transactionInProgress); +}); + +add_task(function* test_transactionInProgress_yes() { + var msc = getOpenedDatabase(); + msc.beginTransaction(); + do_check_true(msc.transactionInProgress); + msc.commitTransaction(); + do_check_false(msc.transactionInProgress); + + msc.beginTransaction(); + do_check_true(msc.transactionInProgress); + msc.rollbackTransaction(); + do_check_false(msc.transactionInProgress); +}); + +add_task(function* test_commitTransaction_no_transaction() { + var msc = getOpenedDatabase(); + do_check_false(msc.transactionInProgress); + Assert.throws(() => msc.commitTransaction(), /NS_ERROR_UNEXPECTED/); +}); + +add_task(function* test_rollbackTransaction_no_transaction() { + var msc = getOpenedDatabase(); + do_check_false(msc.transactionInProgress); + Assert.throws(() => msc.rollbackTransaction(), /NS_ERROR_UNEXPECTED/); +}); + +add_task(function* test_get_schemaVersion_not_set() { + do_check_eq(0, getOpenedDatabase().schemaVersion); +}); + +add_task(function* test_set_schemaVersion() { + var msc = getOpenedDatabase(); + const version = 1; + msc.schemaVersion = version; + do_check_eq(version, msc.schemaVersion); +}); + +add_task(function* test_set_schemaVersion_same() { + var msc = getOpenedDatabase(); + const version = 1; + msc.schemaVersion = version; // should still work ok + do_check_eq(version, msc.schemaVersion); +}); + +add_task(function* test_set_schemaVersion_negative() { + var msc = getOpenedDatabase(); + const version = -1; + msc.schemaVersion = version; + do_check_eq(version, msc.schemaVersion); +}); + +add_task(function* test_createTable() { + var temp = getTestDB().parent; + temp.append("test_db_table"); + try { + var con = getService().openDatabase(temp); + con.createTable("a", ""); + } catch (e) { + if (temp.exists()) { + try { + temp.remove(false); + } catch (e2) { + // Do nothing. + } + } + do_check_true(e.result == Cr.NS_ERROR_NOT_INITIALIZED || + e.result == Cr.NS_ERROR_FAILURE); + } finally { + if (con) { + con.close(); + } + } +}); + +add_task(function* test_defaultSynchronousAtNormal() { + var msc = getOpenedDatabase(); + var stmt = createStatement("PRAGMA synchronous;"); + try { + stmt.executeStep(); + do_check_eq(1, stmt.getInt32(0)); + } + finally { + stmt.reset(); + stmt.finalize(); + } +}); + +// must be ran before executeAsync tests +add_task(function* test_close_does_not_spin_event_loop() { + // We want to make sure that the event loop on the calling thread does not + // spin when close is called. + let event = { + ran: false, + run() { + this.ran = true; + }, + }; + + // Post the event before we call close, so it would run if the event loop was + // spun during close. + let thread = Cc["@mozilla.org/thread-manager;1"]. + getService(Ci.nsIThreadManager). + currentThread; + thread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL); + + // Sanity check, then close the database. Afterwards, we should not have ran! + do_check_false(event.ran); + getOpenedDatabase().close(); + do_check_false(event.ran); + + // Reset gDBConn so that later tests will get a new connection object. + gDBConn = null; +}); + +add_task(function* test_asyncClose_succeeds_with_finalized_async_statement() { + // XXX this test isn't perfect since we can't totally control when events will + // run. If this paticular function fails randomly, it means we have a + // real bug. + + // We want to make sure we create a cached async statement to make sure that + // when we finalize our statement, we end up finalizing the async one too so + // close will succeed. + let stmt = createStatement("SELECT * FROM test"); + stmt.executeAsync(); + stmt.finalize(); + + yield asyncClose(getOpenedDatabase()); + // Reset gDBConn so that later tests will get a new connection object. + gDBConn = null; +}); + +add_task(function* test_close_then_release_statement() { + // Testing the behavior in presence of a bad client that finalizes + // statements after the database has been closed (typically by + // letting the gc finalize the statement). + let db = getOpenedDatabase(); + let stmt = createStatement("SELECT * FROM test -- test_close_then_release_statement"); + db.close(); + stmt.finalize(); // Finalize too late - this should not crash + + // Reset gDBConn so that later tests will get a new connection object. + gDBConn = null; +}); + +add_task(function* test_asyncClose_then_release_statement() { + // Testing the behavior in presence of a bad client that finalizes + // statements after the database has been async closed (typically by + // letting the gc finalize the statement). + let db = getOpenedDatabase(); + let stmt = createStatement("SELECT * FROM test -- test_asyncClose_then_release_statement"); + yield asyncClose(db); + stmt.finalize(); // Finalize too late - this should not crash + + // Reset gDBConn so that later tests will get a new connection object. + gDBConn = null; +}); + +add_task(function* test_close_fails_with_async_statement_ran() { + let deferred = Promise.defer(); + let stmt = createStatement("SELECT * FROM test"); + stmt.executeAsync(); + stmt.finalize(); + + let db = getOpenedDatabase(); + Assert.throws(() => db.close(), /NS_ERROR_UNEXPECTED/); + + // Clean up after ourselves. + db.asyncClose(function () { + // Reset gDBConn so that later tests will get a new connection object. + gDBConn = null; + deferred.resolve(); + }); + + yield deferred.promise; +}); + +add_task(function* test_clone_optional_param() { + let db1 = getService().openUnsharedDatabase(getTestDB()); + let db2 = db1.clone(); + do_check_true(db2.connectionReady); + + // A write statement should not fail here. + let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); + stmt.params.name = "dwitte"; + stmt.execute(); + stmt.finalize(); + + // And a read statement should succeed. + stmt = db2.createStatement("SELECT * FROM test"); + do_check_true(stmt.executeStep()); + stmt.finalize(); + + // Additionally check that it is a connection on the same database. + do_check_true(db1.databaseFile.equals(db2.databaseFile)); + + db1.close(); + db2.close(); +}); + +function* standardAsyncTest(promisedDB, name, shouldInit = false) { + do_print("Performing standard async test " + name); + + let adb = yield promisedDB; + do_check_true(adb instanceof Ci.mozIStorageAsyncConnection); + do_check_false(adb instanceof Ci.mozIStorageConnection); + + if (shouldInit) { + let stmt = adb.createAsyncStatement("CREATE TABLE test(name TEXT)"); + yield executeAsync(stmt); + stmt.finalize(); + } + + // Generate a name to insert and fetch back + name = "worker bee " + Math.random() + " (" + name + ")"; + + let stmt = adb.createAsyncStatement("INSERT INTO test (name) VALUES (:name)"); + stmt.params.name = name; + let result = yield executeAsync(stmt); + do_print("Request complete"); + stmt.finalize(); + do_check_true(Components.isSuccessCode(result)); + do_print("Extracting data"); + stmt = adb.createAsyncStatement("SELECT * FROM test"); + let found = false; + yield executeAsync(stmt, function (results) { + do_print("Data has been extracted"); + for (let row = results.getNextRow(); row != null; row = results.getNextRow()) { + if (row.getResultByName("name") == name) { + found = true; + break; + } + } + }); + do_check_true(found); + stmt.finalize(); + yield asyncClose(adb); + + do_print("Standard async test " + name + " complete"); +} + +add_task(function* test_open_async() { + yield standardAsyncTest(openAsyncDatabase(getTestDB(), null), "default"); + yield standardAsyncTest(openAsyncDatabase(getTestDB()), "no optional arg"); + yield standardAsyncTest(openAsyncDatabase(getTestDB(), + {shared: false, growthIncrement: 54}), "non-default options"); + yield standardAsyncTest(openAsyncDatabase("memory"), + "in-memory database", true); + yield standardAsyncTest(openAsyncDatabase("memory", + {shared: false}), + "in-memory database and options", true); + + do_print("Testing async opening with bogus options 0"); + let raised = false; + let adb = null; + + try { + adb = yield openAsyncDatabase("memory", {shared: false, growthIncrement: 54}); + } catch (ex) { + raised = true; + } finally { + if (adb) { + yield asyncClose(adb); + } + } + do_check_true(raised); + + do_print("Testing async opening with bogus options 1"); + raised = false; + adb = null; + try { + adb = yield openAsyncDatabase(getTestDB(), {shared: "forty-two"}); + } catch (ex) { + raised = true; + } finally { + if (adb) { + yield asyncClose(adb); + } + } + do_check_true(raised); + + do_print("Testing async opening with bogus options 2"); + raised = false; + adb = null; + try { + adb = yield openAsyncDatabase(getTestDB(), {growthIncrement: "forty-two"}); + } catch (ex) { + raised = true; + } finally { + if (adb) { + yield asyncClose(adb); + } + } + do_check_true(raised); +}); + + +add_task(function* test_async_open_with_shared_cache() { + do_print("Testing that opening with a shared cache doesn't break stuff"); + let adb = yield openAsyncDatabase(getTestDB(), {shared: true}); + + let stmt = adb.createAsyncStatement("INSERT INTO test (name) VALUES (:name)"); + stmt.params.name = "clockworker"; + let result = yield executeAsync(stmt); + do_print("Request complete"); + stmt.finalize(); + do_check_true(Components.isSuccessCode(result)); + do_print("Extracting data"); + stmt = adb.createAsyncStatement("SELECT * FROM test"); + let found = false; + yield executeAsync(stmt, function (results) { + do_print("Data has been extracted"); + for (let row = results.getNextRow(); row != null; row = results.getNextRow()) { + if (row.getResultByName("name") == "clockworker") { + found = true; + break; + } + } + }); + do_check_true(found); + stmt.finalize(); + yield asyncClose(adb); +}); + +add_task(function* test_clone_trivial_async() { + do_print("Open connection"); + let db = getService().openDatabase(getTestDB()); + do_check_true(db instanceof Ci.mozIStorageAsyncConnection); + do_print("AsyncClone connection"); + let clone = yield asyncClone(db, true); + do_check_true(clone instanceof Ci.mozIStorageAsyncConnection); + do_print("Close connection"); + yield asyncClose(db); + do_print("Close clone"); + yield asyncClose(clone); +}); + +add_task(function* test_clone_no_optional_param_async() { + "use strict"; + do_print("Testing async cloning"); + let adb1 = yield openAsyncDatabase(getTestDB(), null); + do_check_true(adb1 instanceof Ci.mozIStorageAsyncConnection); + + do_print("Cloning database"); + + let adb2 = yield asyncClone(adb1); + do_print("Testing that the cloned db is a mozIStorageAsyncConnection " + + "and not a mozIStorageConnection"); + do_check_true(adb2 instanceof Ci.mozIStorageAsyncConnection); + do_check_false(adb2 instanceof Ci.mozIStorageConnection); + + do_print("Inserting data into source db"); + let stmt = adb1. + createAsyncStatement("INSERT INTO test (name) VALUES (:name)"); + + stmt.params.name = "yoric"; + let result = yield executeAsync(stmt); + do_print("Request complete"); + stmt.finalize(); + do_check_true(Components.isSuccessCode(result)); + do_print("Extracting data from clone db"); + stmt = adb2.createAsyncStatement("SELECT * FROM test"); + let found = false; + yield executeAsync(stmt, function (results) { + do_print("Data has been extracted"); + for (let row = results.getNextRow(); row != null; row = results.getNextRow()) { + if (row.getResultByName("name") == "yoric") { + found = true; + break; + } + } + }); + do_check_true(found); + stmt.finalize(); + do_print("Closing databases"); + yield asyncClose(adb2); + do_print("First db closed"); + + yield asyncClose(adb1); + do_print("Second db closed"); +}); + +add_task(function* test_clone_readonly() { + let db1 = getService().openUnsharedDatabase(getTestDB()); + let db2 = db1.clone(true); + do_check_true(db2.connectionReady); + + // A write statement should fail here. + let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); + stmt.params.name = "reed"; + expectError(Cr.NS_ERROR_FILE_READ_ONLY, () => stmt.execute()); + stmt.finalize(); + + // And a read statement should succeed. + stmt = db2.createStatement("SELECT * FROM test"); + do_check_true(stmt.executeStep()); + stmt.finalize(); + + db1.close(); + db2.close(); +}); + +add_task(function* test_clone_shared_readonly() { + let db1 = getService().openDatabase(getTestDB()); + let db2 = db1.clone(true); + do_check_true(db2.connectionReady); + + let stmt = db2.createStatement("INSERT INTO test (name) VALUES (:name)"); + stmt.params.name = "parker"; + // TODO currently SQLite does not actually work correctly here. The behavior + // we want is commented out, and the current behavior is being tested + // for. Our IDL comments will have to be updated when this starts to + // work again. + stmt.execute(); + // expectError(Components.results.NS_ERROR_FILE_READ_ONLY, () => stmt.execute()); + stmt.finalize(); + + // And a read statement should succeed. + stmt = db2.createStatement("SELECT * FROM test"); + do_check_true(stmt.executeStep()); + stmt.finalize(); + + db1.close(); + db2.close(); +}); + +add_task(function* test_close_clone_fails() { + let calls = [ + "openDatabase", + "openUnsharedDatabase", + ]; + calls.forEach(function (methodName) { + let db = getService()[methodName](getTestDB()); + db.close(); + expectError(Cr.NS_ERROR_NOT_INITIALIZED, () => db.clone()); + }); +}); + +add_task(function* test_memory_clone_fails() { + let db = getService().openSpecialDatabase("memory"); + db.close(); + expectError(Cr.NS_ERROR_NOT_INITIALIZED, () => db.clone()); +}); + +add_task(function* test_clone_copies_functions() { + const FUNC_NAME = "test_func"; + let calls = [ + "openDatabase", + "openUnsharedDatabase", + ]; + let functionMethods = [ + "createFunction", + "createAggregateFunction", + ]; + calls.forEach(function (methodName) { + [true, false].forEach(function (readOnly) { + functionMethods.forEach(function (functionMethod) { + let db1 = getService()[methodName](getTestDB()); + // Create a function for db1. + db1[functionMethod](FUNC_NAME, 1, { + onFunctionCall: () => 0, + onStep: () => 0, + onFinal: () => 0, + }); + + // Clone it, and make sure the function exists still. + let db2 = db1.clone(readOnly); + // Note: this would fail if the function did not exist. + let stmt = db2.createStatement("SELECT " + FUNC_NAME + "(id) FROM test"); + stmt.finalize(); + db1.close(); + db2.close(); + }); + }); + }); +}); + +add_task(function* test_clone_copies_overridden_functions() { + const FUNC_NAME = "lower"; + function test_func() { + this.called = false; + } + test_func.prototype = { + onFunctionCall() { + this.called = true; + }, + onStep() { + this.called = true; + }, + onFinal: () => 0, + }; + + let calls = [ + "openDatabase", + "openUnsharedDatabase", + ]; + let functionMethods = [ + "createFunction", + "createAggregateFunction", + ]; + calls.forEach(function (methodName) { + [true, false].forEach(function (readOnly) { + functionMethods.forEach(function (functionMethod) { + let db1 = getService()[methodName](getTestDB()); + // Create a function for db1. + let func = new test_func(); + db1[functionMethod](FUNC_NAME, 1, func); + do_check_false(func.called); + + // Clone it, and make sure the function gets called. + let db2 = db1.clone(readOnly); + let stmt = db2.createStatement("SELECT " + FUNC_NAME + "(id) FROM test"); + stmt.executeStep(); + do_check_true(func.called); + stmt.finalize(); + db1.close(); + db2.close(); + }); + }); + }); +}); + +add_task(function* test_clone_copies_pragmas() { + const PRAGMAS = [ + { name: "cache_size", value: 500, copied: true }, + { name: "temp_store", value: 2, copied: true }, + { name: "foreign_keys", value: 1, copied: true }, + { name: "journal_size_limit", value: 524288, copied: true }, + { name: "synchronous", value: 2, copied: true }, + { name: "wal_autocheckpoint", value: 16, copied: true }, + { name: "busy_timeout", value: 50, copied: true }, + { name: "ignore_check_constraints", value: 1, copied: false }, + ]; + + let db1 = getService().openUnsharedDatabase(getTestDB()); + + // Sanity check initial values are different from enforced ones. + PRAGMAS.forEach(function (pragma) { + let stmt = db1.createStatement("PRAGMA " + pragma.name); + do_check_true(stmt.executeStep()); + do_check_neq(pragma.value, stmt.getInt32(0)); + stmt.finalize(); + }); + // Execute pragmas. + PRAGMAS.forEach(function (pragma) { + db1.executeSimpleSQL("PRAGMA " + pragma.name + " = " + pragma.value); + }); + + let db2 = db1.clone(); + do_check_true(db2.connectionReady); + + // Check cloned connection inherited pragma values. + PRAGMAS.forEach(function (pragma) { + let stmt = db2.createStatement("PRAGMA " + pragma.name); + do_check_true(stmt.executeStep()); + let validate = pragma.copied ? do_check_eq : do_check_neq; + validate(pragma.value, stmt.getInt32(0)); + stmt.finalize(); + }); + + db1.close(); + db2.close(); +}); + +add_task(function* test_readonly_clone_copies_pragmas() { + const PRAGMAS = [ + { name: "cache_size", value: 500, copied: true }, + { name: "temp_store", value: 2, copied: true }, + { name: "foreign_keys", value: 1, copied: false }, + { name: "journal_size_limit", value: 524288, copied: false }, + { name: "synchronous", value: 2, copied: false }, + { name: "wal_autocheckpoint", value: 16, copied: false }, + { name: "busy_timeout", value: 50, copied: false }, + { name: "ignore_check_constraints", value: 1, copied: false }, + ]; + + let db1 = getService().openUnsharedDatabase(getTestDB()); + + // Sanity check initial values are different from enforced ones. + PRAGMAS.forEach(function (pragma) { + let stmt = db1.createStatement("PRAGMA " + pragma.name); + do_check_true(stmt.executeStep()); + do_check_neq(pragma.value, stmt.getInt32(0)); + stmt.finalize(); + }); + // Execute pragmas. + PRAGMAS.forEach(function (pragma) { + db1.executeSimpleSQL("PRAGMA " + pragma.name + " = " + pragma.value); + }); + + let db2 = db1.clone(true); + do_check_true(db2.connectionReady); + + // Check cloned connection inherited pragma values. + PRAGMAS.forEach(function (pragma) { + let stmt = db2.createStatement("PRAGMA " + pragma.name); + do_check_true(stmt.executeStep()); + let validate = pragma.copied ? do_check_eq : do_check_neq; + validate(pragma.value, stmt.getInt32(0)); + stmt.finalize(); + }); + + db1.close(); + db2.close(); +}); + +add_task(function* test_clone_attach_database() { + let db1 = getService().openUnsharedDatabase(getTestDB()); + + let c = 0; + function attachDB(conn, name) { + let file = dirSvc.get("ProfD", Ci.nsIFile); + file.append("test_storage_" + (++c) + ".sqlite"); + let db = getService().openUnsharedDatabase(file); + conn.executeSimpleSQL(`ATTACH DATABASE '${db.databaseFile.path}' AS ${name}`); + db.close(); + } + attachDB(db1, "attached_1"); + attachDB(db1, "attached_2"); + + // These should not throw. + db1.createStatement("SELECT * FROM attached_1.sqlite_master"); + db1.createStatement("SELECT * FROM attached_2.sqlite_master"); + + // R/W clone. + let db2 = db1.clone(); + do_check_true(db2.connectionReady); + + // These should not throw. + db2.createStatement("SELECT * FROM attached_1.sqlite_master"); + db2.createStatement("SELECT * FROM attached_2.sqlite_master"); + + // R/O clone. + let db3 = db1.clone(true); + do_check_true(db3.connectionReady); + + // These should not throw. + db3.createStatement("SELECT * FROM attached_1.sqlite_master"); + db3.createStatement("SELECT * FROM attached_2.sqlite_master"); + + db1.close(); + db2.close(); + db3.close(); +}); + +add_task(function* test_getInterface() { + let db = getOpenedDatabase(); + let target = db.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIEventTarget); + // Just check that target is non-null. Other tests will ensure that it has + // the correct value. + do_check_true(target != null); + + yield asyncClose(db); + gDBConn = null; +}); diff --git a/storage/test/unit/test_storage_fulltextindex.js b/storage/test/unit/test_storage_fulltextindex.js new file mode 100644 index 000000000..1f7614067 --- /dev/null +++ b/storage/test/unit/test_storage_fulltextindex.js @@ -0,0 +1,86 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests support for the fts3 (full-text index) module. + +// Example statements in these tests are taken from the Full Text Index page +// on the SQLite wiki: http://www.sqlite.org/cvstrac/wiki?p=FullTextIndex + +function test_table_creation() +{ + var msc = getOpenedUnsharedDatabase(); + + msc.executeSimpleSQL( + "CREATE VIRTUAL TABLE recipe USING fts3(name, ingredients)"); + + do_check_true(msc.tableExists("recipe")); +} + +function test_insertion() +{ + var msc = getOpenedUnsharedDatabase(); + + msc.executeSimpleSQL("INSERT INTO recipe (name, ingredients) VALUES " + + "('broccoli stew', 'broccoli peppers cheese tomatoes')"); + msc.executeSimpleSQL("INSERT INTO recipe (name, ingredients) VALUES " + + "('pumpkin stew', 'pumpkin onions garlic celery')"); + msc.executeSimpleSQL("INSERT INTO recipe (name, ingredients) VALUES " + + "('broccoli pie', 'broccoli cheese onions flour')"); + msc.executeSimpleSQL("INSERT INTO recipe (name, ingredients) VALUES " + + "('pumpkin pie', 'pumpkin sugar flour butter')"); + + var stmt = msc.createStatement("SELECT COUNT(*) FROM recipe"); + stmt.executeStep(); + + do_check_eq(stmt.getInt32(0), 4); + + stmt.reset(); + stmt.finalize(); +} + +function test_selection() +{ + var msc = getOpenedUnsharedDatabase(); + + var stmt = msc.createStatement( + "SELECT rowid, name, ingredients FROM recipe WHERE name MATCH 'pie'"); + + do_check_true(stmt.executeStep()); + do_check_eq(stmt.getInt32(0), 3); + do_check_eq(stmt.getString(1), "broccoli pie"); + do_check_eq(stmt.getString(2), "broccoli cheese onions flour"); + + do_check_true(stmt.executeStep()); + do_check_eq(stmt.getInt32(0), 4); + do_check_eq(stmt.getString(1), "pumpkin pie"); + do_check_eq(stmt.getString(2), "pumpkin sugar flour butter"); + + do_check_false(stmt.executeStep()); + + stmt.reset(); + stmt.finalize(); +} + +var tests = [test_table_creation, test_insertion, test_selection]; + +function run_test() +{ + // It's extra important to start from scratch, since these tests won't work + // with an existing shared cache connection, so we do it even though the last + // test probably did it already. + cleanup(); + + try { + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + } + // It's extra important to clean up afterwards, since later tests that use + // a shared cache connection will not be able to read the database we create, + // so we do this in a finally block to ensure it happens even if some of our + // tests fail. + finally { + cleanup(); + } +} diff --git a/storage/test/unit/test_storage_function.js b/storage/test/unit/test_storage_function.js new file mode 100644 index 000000000..6532709ec --- /dev/null +++ b/storage/test/unit/test_storage_function.js @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the custom functions + +var testNums = [1, 2, 3, 4]; + +function setup() +{ + getOpenedDatabase().createTable("function_tests", "id INTEGER PRIMARY KEY"); + + var stmt = createStatement("INSERT INTO function_tests (id) VALUES(?1)"); + for (let i = 0; i < testNums.length; ++i) { + stmt.bindByIndex(0, testNums[i]); + stmt.execute(); + } + stmt.reset(); + stmt.finalize(); +} + +var testSquareFunction = { + calls: 0, + + onFunctionCall(val) { + ++this.calls; + return val.getInt32(0) * val.getInt32(0); + } +}; + +function test_function_registration() +{ + var msc = getOpenedDatabase(); + msc.createFunction("test_square", 1, testSquareFunction); +} + +function test_function_no_double_registration() +{ + var msc = getOpenedDatabase(); + try { + msc.createFunction("test_square", 2, testSquareFunction); + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_FAILURE, e.result); + } +} + +function test_function_removal() +{ + var msc = getOpenedDatabase(); + msc.removeFunction("test_square"); + // Should be Ok now + msc.createFunction("test_square", 1, testSquareFunction); +} + +function test_function_aliases() +{ + var msc = getOpenedDatabase(); + msc.createFunction("test_square2", 1, testSquareFunction); +} + +function test_function_call() +{ + var stmt = createStatement("SELECT test_square(id) FROM function_tests"); + while (stmt.executeStep()) { + // Do nothing. + } + do_check_eq(testNums.length, testSquareFunction.calls); + testSquareFunction.calls = 0; + stmt.finalize(); +} + +function test_function_result() +{ + var stmt = createStatement("SELECT test_square(42) FROM function_tests"); + stmt.executeStep(); + do_check_eq(42 * 42, stmt.getInt32(0)); + testSquareFunction.calls = 0; + stmt.finalize(); +} + +var tests = [test_function_registration, test_function_no_double_registration, + test_function_removal, test_function_aliases, test_function_call, + test_function_result]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} diff --git a/storage/test/unit/test_storage_progresshandler.js b/storage/test/unit/test_storage_progresshandler.js new file mode 100644 index 000000000..c06a57e83 --- /dev/null +++ b/storage/test/unit/test_storage_progresshandler.js @@ -0,0 +1,111 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the custom progress handlers + +function setup() +{ + var msc = getOpenedDatabase(); + msc.createTable("handler_tests", "id INTEGER PRIMARY KEY, num INTEGER"); + msc.beginTransaction(); + + var stmt = createStatement("INSERT INTO handler_tests (id, num) VALUES(?1, ?2)"); + for (let i = 0; i < 100; ++i) { + stmt.bindByIndex(0, i); + stmt.bindByIndex(1, Math.floor(Math.random() * 1000)); + stmt.execute(); + } + stmt.reset(); + msc.commitTransaction(); + stmt.finalize(); +} + +var testProgressHandler = { + calls: 0, + abort: false, + + onProgress(comm) { + ++this.calls; + return this.abort; + } +}; + +function test_handler_registration() +{ + var msc = getOpenedDatabase(); + msc.setProgressHandler(10, testProgressHandler); +} + +function test_handler_return() +{ + var msc = getOpenedDatabase(); + var oldH = msc.setProgressHandler(5, testProgressHandler); + do_check_true(oldH instanceof Ci.mozIStorageProgressHandler); +} + +function test_handler_removal() +{ + var msc = getOpenedDatabase(); + msc.removeProgressHandler(); + var oldH = msc.removeProgressHandler(); + do_check_eq(oldH, null); +} + +function test_handler_call() +{ + var msc = getOpenedDatabase(); + msc.setProgressHandler(50, testProgressHandler); + // Some long-executing request + var stmt = createStatement( + "SELECT SUM(t1.num * t2.num) FROM handler_tests AS t1, handler_tests AS t2"); + while (stmt.executeStep()) { + // Do nothing. + } + do_check_true(testProgressHandler.calls > 0); + stmt.finalize(); +} + +function test_handler_abort() +{ + var msc = getOpenedDatabase(); + testProgressHandler.abort = true; + msc.setProgressHandler(50, testProgressHandler); + // Some long-executing request + var stmt = createStatement( + "SELECT SUM(t1.num * t2.num) FROM handler_tests AS t1, handler_tests AS t2"); + + const SQLITE_INTERRUPT = 9; + try { + while (stmt.executeStep()) { + // Do nothing. + } + do_throw("We shouldn't get here!"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_ABORT, e.result); + do_check_eq(SQLITE_INTERRUPT, msc.lastError); + } + try { + stmt.finalize(); + do_throw("We shouldn't get here!"); + } catch (e) { + // finalize should return the error code since we encountered an error + do_check_eq(Cr.NS_ERROR_ABORT, e.result); + do_check_eq(SQLITE_INTERRUPT, msc.lastError); + } +} + +var tests = [test_handler_registration, test_handler_return, + test_handler_removal, test_handler_call, + test_handler_abort]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} diff --git a/storage/test/unit/test_storage_service.js b/storage/test/unit/test_storage_service.js new file mode 100644 index 000000000..9cf46620e --- /dev/null +++ b/storage/test/unit/test_storage_service.js @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the functions of mozIStorageService except for +// openUnsharedDatabase, which is tested by test_storage_service_unshared.js. + +const BACKUP_FILE_NAME = "test_storage.sqlite.backup"; + +function test_openSpecialDatabase_invalid_arg() +{ + try { + getService().openSpecialDatabase("abcd"); + do_throw("We should not get here!"); + } catch (e) { + print(e); + print("e.result is " + e.result); + do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); + } +} + +function test_openDatabase_null_file() +{ + try { + getService().openDatabase(null); + do_throw("We should not get here!"); + } catch (e) { + print(e); + print("e.result is " + e.result); + do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); + } +} + +function test_openUnsharedDatabase_null_file() +{ + try { + getService().openUnsharedDatabase(null); + do_throw("We should not get here!"); + } catch (e) { + print(e); + print("e.result is " + e.result); + do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); + } +} + +function test_openDatabase_file_DNE() +{ + // the file should be created after calling + var db = getTestDB(); + do_check_false(db.exists()); + getService().openDatabase(db); + do_check_true(db.exists()); +} + +function test_openDatabase_file_exists() +{ + // it should already exist from our last test + var db = getTestDB(); + do_check_true(db.exists()); + getService().openDatabase(db); + do_check_true(db.exists()); +} + +function test_corrupt_db_throws_with_openDatabase() +{ + try { + getDatabase(getCorruptDB()); + do_throw("should not be here"); + } + catch (e) { + do_check_eq(Cr.NS_ERROR_FILE_CORRUPTED, e.result); + } +} + +function test_fake_db_throws_with_openDatabase() +{ + try { + getDatabase(getFakeDB()); + do_throw("should not be here"); + } + catch (e) { + do_check_eq(Cr.NS_ERROR_FILE_CORRUPTED, e.result); + } +} + +function test_backup_not_new_filename() +{ + const fname = getTestDB().leafName; + + var backup = getService().backupDatabaseFile(getTestDB(), fname); + do_check_neq(fname, backup.leafName); + + backup.remove(false); +} + +function test_backup_new_filename() +{ + var backup = getService().backupDatabaseFile(getTestDB(), BACKUP_FILE_NAME); + do_check_eq(BACKUP_FILE_NAME, backup.leafName); + + backup.remove(false); +} + +function test_backup_new_folder() +{ + var parentDir = getTestDB().parent; + parentDir.append("test_storage_temp"); + if (parentDir.exists()) + parentDir.remove(true); + parentDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + do_check_true(parentDir.exists()); + + var backup = getService().backupDatabaseFile(getTestDB(), BACKUP_FILE_NAME, + parentDir); + do_check_eq(BACKUP_FILE_NAME, backup.leafName); + do_check_true(parentDir.equals(backup.parent)); + + parentDir.remove(true); +} + +var tests = [ + test_openSpecialDatabase_invalid_arg, + test_openDatabase_null_file, + test_openUnsharedDatabase_null_file, + test_openDatabase_file_DNE, + test_openDatabase_file_exists, + test_corrupt_db_throws_with_openDatabase, + test_fake_db_throws_with_openDatabase, + test_backup_not_new_filename, + test_backup_new_filename, + test_backup_new_folder, +]; + +function run_test() +{ + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} + diff --git a/storage/test/unit/test_storage_service_unshared.js b/storage/test/unit/test_storage_service_unshared.js new file mode 100644 index 000000000..70efb2a43 --- /dev/null +++ b/storage/test/unit/test_storage_service_unshared.js @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the openUnsharedDatabase function of mozIStorageService. + +function test_openUnsharedDatabase_file_DNE() +{ + // the file should be created after calling + var db = getTestDB(); + do_check_false(db.exists()); + getService().openUnsharedDatabase(db); + do_check_true(db.exists()); +} + +function test_openUnsharedDatabase_file_exists() +{ + // it should already exist from our last test + var db = getTestDB(); + do_check_true(db.exists()); + getService().openUnsharedDatabase(db); + do_check_true(db.exists()); +} + +var tests = [test_openUnsharedDatabase_file_DNE, + test_openUnsharedDatabase_file_exists]; + +function run_test() +{ + for (var i = 0; i < tests.length; i++) + tests[i](); + + cleanup(); +} + diff --git a/storage/test/unit/test_storage_statement.js b/storage/test/unit/test_storage_statement.js new file mode 100644 index 000000000..026e271ac --- /dev/null +++ b/storage/test/unit/test_storage_statement.js @@ -0,0 +1,184 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the functions of mozIStorageStatement + +function setup() +{ + getOpenedDatabase().createTable("test", "id INTEGER PRIMARY KEY, name TEXT"); +} + +function test_parameterCount_none() +{ + var stmt = createStatement("SELECT * FROM test"); + do_check_eq(0, stmt.parameterCount); + stmt.reset(); + stmt.finalize(); +} + +function test_parameterCount_one() +{ + var stmt = createStatement("SELECT * FROM test WHERE id = ?1"); + do_check_eq(1, stmt.parameterCount); + stmt.reset(); + stmt.finalize(); +} + +function test_getParameterName() +{ + var stmt = createStatement("SELECT * FROM test WHERE id = :id"); + do_check_eq(":id", stmt.getParameterName(0)); + stmt.reset(); + stmt.finalize(); +} + +function test_getParameterIndex_different() +{ + var stmt = createStatement("SELECT * FROM test WHERE id = :id OR name = :name"); + do_check_eq(0, stmt.getParameterIndex("id")); + do_check_eq(1, stmt.getParameterIndex("name")); + stmt.reset(); + stmt.finalize(); +} + +function test_getParameterIndex_same() +{ + var stmt = createStatement("SELECT * FROM test WHERE id = :test OR name = :test"); + do_check_eq(0, stmt.getParameterIndex("test")); + stmt.reset(); + stmt.finalize(); +} + +function test_columnCount() +{ + var stmt = createStatement("SELECT * FROM test WHERE id = ?1 OR name = ?2"); + do_check_eq(2, stmt.columnCount); + stmt.reset(); + stmt.finalize(); +} + +function test_getColumnName() +{ + var stmt = createStatement("SELECT name, id FROM test"); + do_check_eq("id", stmt.getColumnName(1)); + do_check_eq("name", stmt.getColumnName(0)); + stmt.reset(); + stmt.finalize(); +} + +function test_getColumnIndex_same_case() +{ + var stmt = createStatement("SELECT name, id FROM test"); + do_check_eq(0, stmt.getColumnIndex("name")); + do_check_eq(1, stmt.getColumnIndex("id")); + stmt.reset(); + stmt.finalize(); +} + +function test_getColumnIndex_different_case() +{ + var stmt = createStatement("SELECT name, id FROM test"); + try { + do_check_eq(0, stmt.getColumnIndex("NaMe")); + do_throw("should not get here"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); + } + try { + do_check_eq(1, stmt.getColumnIndex("Id")); + do_throw("should not get here"); + } catch (e) { + do_check_eq(Cr.NS_ERROR_INVALID_ARG, e.result); + } + stmt.reset(); + stmt.finalize(); +} + +function test_state_ready() +{ + var stmt = createStatement("SELECT name, id FROM test"); + do_check_eq(Ci.mozIStorageStatement.MOZ_STORAGE_STATEMENT_READY, stmt.state); + stmt.reset(); + stmt.finalize(); +} + +function test_state_executing() +{ + var stmt = createStatement("INSERT INTO test (name) VALUES ('foo')"); + stmt.execute(); + stmt.execute(); + stmt.finalize(); + + stmt = createStatement("SELECT name, id FROM test"); + stmt.executeStep(); + do_check_eq(Ci.mozIStorageStatement.MOZ_STORAGE_STATEMENT_EXECUTING, + stmt.state); + stmt.executeStep(); + do_check_eq(Ci.mozIStorageStatement.MOZ_STORAGE_STATEMENT_EXECUTING, + stmt.state); + stmt.reset(); + do_check_eq(Ci.mozIStorageStatement.MOZ_STORAGE_STATEMENT_READY, stmt.state); + stmt.finalize(); +} + +function test_state_after_finalize() +{ + var stmt = createStatement("SELECT name, id FROM test"); + stmt.executeStep(); + stmt.finalize(); + do_check_eq(Ci.mozIStorageStatement.MOZ_STORAGE_STATEMENT_INVALID, stmt.state); +} + +function test_failed_execute() +{ + var stmt = createStatement("INSERT INTO test (name) VALUES ('foo')"); + stmt.execute(); + stmt.finalize(); + var id = getOpenedDatabase().lastInsertRowID; + stmt = createStatement("INSERT INTO test(id, name) VALUES(:id, 'bar')"); + stmt.params.id = id; + try { + // Should throw a constraint error + stmt.execute(); + do_throw("Should have seen a constraint error"); + } + catch (e) { + do_check_eq(getOpenedDatabase().lastError, Ci.mozIStorageError.CONSTRAINT); + } + do_check_eq(Ci.mozIStorageStatement.MOZ_STORAGE_STATEMENT_READY, stmt.state); + // Should succeed without needing to reset the statement manually + stmt.finalize(); +} + +function test_bind_undefined() +{ + var stmt = createStatement("INSERT INTO test (name) VALUES ('foo')"); + + expectError(Cr.NS_ERROR_ILLEGAL_VALUE, + () => stmt.bindParameters(undefined)); + + stmt.finalize(); +} + +var tests = [test_parameterCount_none, test_parameterCount_one, + test_getParameterName, test_getParameterIndex_different, + test_getParameterIndex_same, test_columnCount, + test_getColumnName, test_getColumnIndex_same_case, + test_getColumnIndex_different_case, test_state_ready, + test_state_executing, test_state_after_finalize, + test_failed_execute, + test_bind_undefined, +]; + +function run_test() +{ + setup(); + + for (var i = 0; i < tests.length; i++) { + tests[i](); + } + + cleanup(); +} + diff --git a/storage/test/unit/test_storage_value_array.js b/storage/test/unit/test_storage_value_array.js new file mode 100644 index 000000000..27bd23992 --- /dev/null +++ b/storage/test/unit/test_storage_value_array.js @@ -0,0 +1,182 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the functions of mozIStorageValueArray + +add_task(function* setup() { + getOpenedDatabase().createTable("test", "id INTEGER PRIMARY KEY, name TEXT," + + "number REAL, nuller NULL, blobber BLOB"); + + var stmt = createStatement("INSERT INTO test (name, number, blobber) " + + "VALUES (?1, ?2, ?3)"); + stmt.bindByIndex(0, "foo"); + stmt.bindByIndex(1, 2.34); + stmt.bindBlobByIndex(2, [], 0); + stmt.execute(); + + stmt.bindByIndex(0, ""); + stmt.bindByIndex(1, 1.23); + stmt.bindBlobByIndex(2, [1, 2], 2); + stmt.execute(); + + stmt.reset(); + stmt.finalize(); + + do_register_cleanup(cleanup); +}); + +add_task(function* test_getIsNull_for_null() { + var stmt = createStatement("SELECT nuller, blobber FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 1); + do_check_true(stmt.executeStep()); + + do_check_true(stmt.getIsNull(0)); // null field + do_check_true(stmt.getIsNull(1)); // data is null if size is 0 + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_getIsNull_for_non_null() { + var stmt = createStatement("SELECT name, blobber FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_false(stmt.getIsNull(0)); + do_check_false(stmt.getIsNull(1)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_value_type_null() { + var stmt = createStatement("SELECT nuller FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 1); + do_check_true(stmt.executeStep()); + + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_NULL, + stmt.getTypeOfIndex(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_value_type_integer() { + var stmt = createStatement("SELECT id FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 1); + do_check_true(stmt.executeStep()); + + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_INTEGER, + stmt.getTypeOfIndex(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_value_type_float() { + var stmt = createStatement("SELECT number FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 1); + do_check_true(stmt.executeStep()); + + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_FLOAT, + stmt.getTypeOfIndex(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_value_type_text() { + var stmt = createStatement("SELECT name FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 1); + do_check_true(stmt.executeStep()); + + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_TEXT, + stmt.getTypeOfIndex(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_value_type_blob() { + var stmt = createStatement("SELECT blobber FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_eq(Ci.mozIStorageValueArray.VALUE_TYPE_BLOB, + stmt.getTypeOfIndex(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_numEntries_one() { + var stmt = createStatement("SELECT blobber FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_eq(1, stmt.numEntries); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_numEntries_all() { + var stmt = createStatement("SELECT * FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_eq(5, stmt.numEntries); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_getInt() { + var stmt = createStatement("SELECT id FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_eq(2, stmt.getInt32(0)); + do_check_eq(2, stmt.getInt64(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_getDouble() { + var stmt = createStatement("SELECT number FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_eq(1.23, stmt.getDouble(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_getUTF8String() { + var stmt = createStatement("SELECT name FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 1); + do_check_true(stmt.executeStep()); + + do_check_eq("foo", stmt.getUTF8String(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_getString() { + var stmt = createStatement("SELECT name FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + do_check_eq("", stmt.getString(0)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_getBlob() { + var stmt = createStatement("SELECT blobber FROM test WHERE id = ?1"); + stmt.bindByIndex(0, 2); + do_check_true(stmt.executeStep()); + + var count = { value: 0 }; + var arr = { value: null }; + stmt.getBlob(0, count, arr); + do_check_eq(2, count.value); + do_check_eq(1, arr.value[0]); + do_check_eq(2, arr.value[1]); + stmt.reset(); + stmt.finalize(); +}); + + diff --git a/storage/test/unit/test_telemetry_vfs.js b/storage/test/unit/test_telemetry_vfs.js new file mode 100644 index 000000000..0822fe3e7 --- /dev/null +++ b/storage/test/unit/test_telemetry_vfs.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Make sure that there are telemetry entries created by sqlite io + +function run_sql(d, sql) { + var stmt = d.createStatement(sql); + stmt.execute(); + stmt.finalize(); +} + +function new_file(name) +{ + var file = dirSvc.get("ProfD", Ci.nsIFile); + file.append(name); + return file; +} +function run_test() +{ + const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + let read_hgram = Telemetry.getHistogramById("MOZ_SQLITE_OTHER_READ_B"); + let old_sum = read_hgram.snapshot().sum; + const file = new_file("telemetry.sqlite"); + var d = getDatabase(file); + run_sql(d, "CREATE TABLE bloat(data varchar)"); + run_sql(d, "DROP TABLE bloat"); + do_check_true(read_hgram.snapshot().sum > old_sum); +} + diff --git a/storage/test/unit/test_unicode.js b/storage/test/unit/test_unicode.js new file mode 100644 index 000000000..7753bbfdb --- /dev/null +++ b/storage/test/unit/test_unicode.js @@ -0,0 +1,83 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file tests the unicode functions that we have added + +const LATIN1_AE = "\xc6"; // "Æ" +const LATIN1_ae = "\xe6"; // "æ" + +add_task(function* setup() { + getOpenedDatabase().createTable("test", "id INTEGER PRIMARY KEY, name TEXT"); + + var stmt = createStatement("INSERT INTO test (name, id) VALUES (?1, ?2)"); + stmt.bindByIndex(0, LATIN1_AE); + stmt.bindByIndex(1, 1); + stmt.execute(); + stmt.bindByIndex(0, "A"); + stmt.bindByIndex(1, 2); + stmt.execute(); + stmt.bindByIndex(0, "b"); + stmt.bindByIndex(1, 3); + stmt.execute(); + stmt.bindByIndex(0, LATIN1_ae); + stmt.bindByIndex(1, 4); + stmt.execute(); + stmt.finalize(); + + do_register_cleanup(cleanup); +}); + +add_task(function* test_upper_ascii() { + var stmt = createStatement("SELECT name, id FROM test WHERE name = upper('a')"); + do_check_true(stmt.executeStep()); + do_check_eq("A", stmt.getString(0)); + do_check_eq(2, stmt.getInt32(1)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_upper_non_ascii() { + var stmt = createStatement("SELECT name, id FROM test WHERE name = upper(?1)"); + stmt.bindByIndex(0, LATIN1_ae); + do_check_true(stmt.executeStep()); + do_check_eq(LATIN1_AE, stmt.getString(0)); + do_check_eq(1, stmt.getInt32(1)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_lower_ascii() { + var stmt = createStatement("SELECT name, id FROM test WHERE name = lower('B')"); + do_check_true(stmt.executeStep()); + do_check_eq("b", stmt.getString(0)); + do_check_eq(3, stmt.getInt32(1)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_lower_non_ascii() { + var stmt = createStatement("SELECT name, id FROM test WHERE name = lower(?1)"); + stmt.bindByIndex(0, LATIN1_AE); + do_check_true(stmt.executeStep()); + do_check_eq(LATIN1_ae, stmt.getString(0)); + do_check_eq(4, stmt.getInt32(1)); + stmt.reset(); + stmt.finalize(); +}); + +add_task(function* test_like_search_different() { + var stmt = createStatement("SELECT COUNT(*) FROM test WHERE name LIKE ?1"); + stmt.bindByIndex(0, LATIN1_AE); + do_check_true(stmt.executeStep()); + do_check_eq(2, stmt.getInt32(0)); + stmt.finalize(); +}); + +add_task(function* test_like_search_same() { + var stmt = createStatement("SELECT COUNT(*) FROM test WHERE name LIKE ?1"); + stmt.bindByIndex(0, LATIN1_ae); + do_check_true(stmt.executeStep()); + do_check_eq(2, stmt.getInt32(0)); + stmt.finalize(); +}); diff --git a/storage/test/unit/test_vacuum.js b/storage/test/unit/test_vacuum.js new file mode 100644 index 000000000..e284f78c7 --- /dev/null +++ b/storage/test/unit/test_vacuum.js @@ -0,0 +1,335 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This file tests the Vacuum Manager. + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * Loads a test component that will register as a vacuum-participant. + * If other participants are found they will be unregistered, to avoid conflicts + * with the test itself. + */ +function load_test_vacuum_component() +{ + const CATEGORY_NAME = "vacuum-participant"; + + do_load_manifest("vacuumParticipant.manifest"); + + // This is a lazy check, there could be more participants than just this test + // we just mind that the test exists though. + const EXPECTED_ENTRIES = ["vacuumParticipant"]; + let catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + let found = false; + let entries = catMan.enumerateCategory(CATEGORY_NAME); + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; + print("Check if the found category entry (" + entry + ") is expected."); + if (EXPECTED_ENTRIES.indexOf(entry) != -1) { + print("Check that only one test entry exists."); + do_check_false(found); + found = true; + } + else { + // Temporary unregister other participants for this test. + catMan.deleteCategoryEntry("vacuum-participant", entry, false); + } + } + print("Check the test entry exists."); + do_check_true(found); +} + +/** + * Sends a fake idle-daily notification to the VACUUM Manager. + */ +function synthesize_idle_daily() +{ + let vm = Cc["@mozilla.org/storage/vacuum;1"].getService(Ci.nsIObserver); + vm.observe(null, "idle-daily", null); +} + +/** + * Returns a new nsIFile reference for a profile database. + * @param filename for the database, excluded the .sqlite extension. + */ +function new_db_file(name) +{ + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(name + ".sqlite"); + return file; +} + +function run_test() +{ + do_test_pending(); + + // Change initial page size. Do it immediately since it would require an + // additional vacuum op to do it later. As a bonus this makes the page size + // change test really fast since it only has to check results. + let conn = getDatabase(new_db_file("testVacuum")); + conn.executeSimpleSQL("PRAGMA page_size = 1024"); + print("Check current page size."); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, 1024); + } + } + finally { + stmt.finalize(); + } + + load_test_vacuum_component(); + + run_next_test(); +} + +const TESTS = [ + + function test_common_vacuum() + { + print("\n*** Test that a VACUUM correctly happens and all notifications are fired."); + // Wait for VACUUM begin. + let beginVacuumReceived = false; + Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { + Services.obs.removeObserver(onVacuum, aTopic); + beginVacuumReceived = true; + }, "test-begin-vacuum", false); + + // Wait for heavy IO notifications. + let heavyIOTaskBeginReceived = false; + let heavyIOTaskEndReceived = false; + Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { + if (heavyIOTaskBeginReceived && heavyIOTaskEndReceived) { + Services.obs.removeObserver(onVacuum, aTopic); + } + + if (aData == "vacuum-begin") { + heavyIOTaskBeginReceived = true; + } + else if (aData == "vacuum-end") { + heavyIOTaskEndReceived = true; + } + }, "heavy-io-task", false); + + // Wait for VACUUM end. + Services.obs.addObserver(function onVacuum(aSubject, aTopic, aData) { + Services.obs.removeObserver(onVacuum, aTopic); + print("Check we received onBeginVacuum"); + do_check_true(beginVacuumReceived); + print("Check we received heavy-io-task notifications"); + do_check_true(heavyIOTaskBeginReceived); + do_check_true(heavyIOTaskEndReceived); + print("Received onEndVacuum"); + run_next_test(); + }, "test-end-vacuum", false); + + synthesize_idle_daily(); + }, + + function test_skipped_if_recent_vacuum() + { + print("\n*** Test that a VACUUM is skipped if it was run recently."); + Services.prefs.setIntPref("storage.vacuum.last.testVacuum.sqlite", + parseInt(Date.now() / 1000)); + + // Wait for VACUUM begin. + let vacuumObserver = { + gotNotification: false, + observe: function VO_observe(aSubject, aTopic, aData) { + this.gotNotification = true; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); + + // Check after a couple seconds that no VACUUM has been run. + do_timeout(2000, function () { + print("Check VACUUM did not run."); + do_check_false(vacuumObserver.gotNotification); + Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); + run_next_test(); + }); + + synthesize_idle_daily(); + }, + + function test_page_size_change() + { + print("\n*** Test that a VACUUM changes page_size"); + + // We did setup the database with a small page size, the previous vacuum + // should have updated it. + print("Check that page size was updated."); + let conn = getDatabase(new_db_file("testVacuum")); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, conn.defaultPageSize); + } + } + finally { + stmt.finalize(); + } + + run_next_test(); + }, + + function test_skipped_optout_vacuum() + { + print("\n*** Test that a VACUUM is skipped if the participant wants to opt-out."); + Services.obs.notifyObservers(null, "test-options", "opt-out"); + + // Wait for VACUUM begin. + let vacuumObserver = { + gotNotification: false, + observe: function VO_observe(aSubject, aTopic, aData) { + this.gotNotification = true; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); + + // Check after a couple seconds that no VACUUM has been run. + do_timeout(2000, function () { + print("Check VACUUM did not run."); + do_check_false(vacuumObserver.gotNotification); + Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); + run_next_test(); + }); + + synthesize_idle_daily(); + }, + + /* Changing page size on WAL is not supported till Bug 634374 is properly fixed. + function test_page_size_change_with_wal() + { + print("\n*** Test that a VACUUM changes page_size with WAL mode"); + Services.obs.notifyObservers(null, "test-options", "wal"); + + // Set a small page size. + let conn = getDatabase(new_db_file("testVacuum2")); + conn.executeSimpleSQL("PRAGMA page_size = 1024"); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, 1024); + } + } + finally { + stmt.finalize(); + } + + // Use WAL journal mode. + conn.executeSimpleSQL("PRAGMA journal_mode = WAL"); + stmt = conn.createStatement("PRAGMA journal_mode"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.journal_mode, "wal"); + } + } + finally { + stmt.finalize(); + } + + // Wait for VACUUM end. + let vacuumObserver = { + observe: function VO_observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(this, aTopic); + print("Check page size has been updated."); + let stmt = conn.createStatement("PRAGMA page_size"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.page_size, Ci.mozIStorageConnection.DEFAULT_PAGE_SIZE); + } + } + finally { + stmt.finalize(); + } + + print("Check journal mode has been restored."); + stmt = conn.createStatement("PRAGMA journal_mode"); + try { + while (stmt.executeStep()) { + do_check_eq(stmt.row.journal_mode, "wal"); + } + } + finally { + stmt.finalize(); + } + + run_next_test(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + } + Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false); + + synthesize_idle_daily(); + }, + */ + + function test_memory_database_crash() + { + print("\n*** Test that we don't crash trying to vacuum a memory database"); + Services.obs.notifyObservers(null, "test-options", "memory"); + + // Wait for VACUUM begin. + let vacuumObserver = { + gotNotification: false, + observe: function VO_observe(aSubject, aTopic, aData) { + this.gotNotification = true; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + }; + Services.obs.addObserver(vacuumObserver, "test-begin-vacuum", false); + + // Check after a couple seconds that no VACUUM has been run. + do_timeout(2000, function () { + print("Check VACUUM did not run."); + do_check_false(vacuumObserver.gotNotification); + Services.obs.removeObserver(vacuumObserver, "test-begin-vacuum"); + run_next_test(); + }); + + synthesize_idle_daily(); + }, + + /* Changing page size on WAL is not supported till Bug 634374 is properly fixed. + function test_wal_restore_fail() + { + print("\n*** Test that a failing WAL restoration notifies failure"); + Services.obs.notifyObservers(null, "test-options", "wal-fail"); + + // Wait for VACUUM end. + let vacuumObserver = { + observe: function VO_observe(aSubject, aTopic, aData) { + Services.obs.removeObserver(vacuumObserver, "test-end-vacuum"); + print("Check WAL restoration failed."); + do_check_false(aData); + run_next_test(); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]) + } + Services.obs.addObserver(vacuumObserver, "test-end-vacuum", false); + + synthesize_idle_daily(); + }, + */ +]; + +function run_next_test() +{ + if (TESTS.length == 0) { + Services.obs.notifyObservers(null, "test-options", "dispose"); + do_test_finished(); + } + else { + // Set last VACUUM to a date in the past. + Services.prefs.setIntPref("storage.vacuum.last.testVacuum.sqlite", + parseInt(Date.now() / 1000 - 31 * 86400)); + do_execute_soon(TESTS.shift()); + } +} diff --git a/storage/test/unit/vacuumParticipant.js b/storage/test/unit/vacuumParticipant.js new file mode 100644 index 000000000..01b980178 --- /dev/null +++ b/storage/test/unit/vacuumParticipant.js @@ -0,0 +1,125 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This testing component is used in test_vacuum* tests. + +const Cc = Components.classes; +const Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * Returns a new nsIFile reference for a profile database. + * @param filename for the database, excluded the .sqlite extension. + */ +function new_db_file(name) +{ + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(name + ".sqlite"); + return file; +} + +/** + * Opens and returns a connection to the provided database file. + * @param nsIFile interface to the database file. + */ +function getDatabase(aFile) +{ + return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService) + .openDatabase(aFile); +} + +function vacuumParticipant() +{ + this._dbConn = getDatabase(new_db_file("testVacuum")); + Services.obs.addObserver(this, "test-options", false); +} + +vacuumParticipant.prototype = +{ + classDescription: "vacuumParticipant", + classID: Components.ID("{52aa0b22-b82f-4e38-992a-c3675a3355d2}"), + contractID: "@unit.test.com/test-vacuum-participant;1", + + get expectedDatabasePageSize() { + return this._dbConn.defaultPageSize; + }, + get databaseConnection() { + return this._dbConn; + }, + + _grant: true, + onBeginVacuum: function TVP_onBeginVacuum() + { + if (!this._grant) { + this._grant = true; + return false; + } + Services.obs.notifyObservers(null, "test-begin-vacuum", null); + return true; + }, + onEndVacuum: function TVP_EndVacuum(aSucceeded) + { + if (this._stmt) { + this._stmt.finalize(); + } + Services.obs.notifyObservers(null, "test-end-vacuum", aSucceeded); + }, + + observe: function TVP_observe(aSubject, aTopic, aData) + { + if (aData == "opt-out") { + this._grant = false; + } + else if (aData == "wal") { + try { + this._dbConn.close(); + } catch (e) { + // Do nothing. + } + this._dbConn = getDatabase(new_db_file("testVacuum2")); + } + else if (aData == "wal-fail") { + try { + this._dbConn.close(); + } catch (e) { + // Do nothing. + } + this._dbConn = getDatabase(new_db_file("testVacuum3")); + // Use WAL journal mode. + this._dbConn.executeSimpleSQL("PRAGMA journal_mode = WAL"); + // Create a not finalized statement. + this._stmt = this._dbConn.createStatement("SELECT :test"); + this._stmt.params.test = 1; + this._stmt.executeStep(); + } + else if (aData == "memory") { + try { + this._dbConn.asyncClose(); + } catch (e) { + // Do nothing. + } + this._dbConn = Cc["@mozilla.org/storage/service;1"]. + getService(Ci.mozIStorageService). + openSpecialDatabase("memory"); + } + else if (aData == "dispose") { + Services.obs.removeObserver(this, "test-options"); + try { + this._dbConn.asyncClose(); + } catch (e) { + // Do nothing. + } + } + }, + + QueryInterface: XPCOMUtils.generateQI([ + Ci.mozIStorageVacuumParticipant, + Ci.nsIObserver, + ]) +}; + +var gComponentsArray = [vacuumParticipant]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(gComponentsArray); diff --git a/storage/test/unit/vacuumParticipant.manifest b/storage/test/unit/vacuumParticipant.manifest new file mode 100644 index 000000000..cf359a80f --- /dev/null +++ b/storage/test/unit/vacuumParticipant.manifest @@ -0,0 +1,3 @@ +component {52aa0b22-b82f-4e38-992a-c3675a3355d2} vacuumParticipant.js +contract @unit.test.com/test-vacuum-participant;1 {52aa0b22-b82f-4e38-992a-c3675a3355d2} +category vacuum-participant vacuumParticipant @unit.test.com/test-vacuum-participant;1 diff --git a/storage/test/unit/xpcshell.ini b/storage/test/unit/xpcshell.ini new file mode 100644 index 000000000..e93c7d5b9 --- /dev/null +++ b/storage/test/unit/xpcshell.ini @@ -0,0 +1,46 @@ +[DEFAULT] +head = head_storage.js +tail = +support-files = + corruptDB.sqlite + fakeDB.sqlite + locale_collation.txt + vacuumParticipant.js + vacuumParticipant.manifest + +[test_bug-365166.js] +[test_bug-393952.js] +[test_bug-429521.js] +[test_bug-444233.js] +[test_cache_size.js] +[test_chunk_growth.js] +# Bug 676981: test fails consistently on Android +fail-if = os == "android" +[test_connection_asyncClose.js] +[test_connection_executeAsync.js] +[test_connection_executeSimpleSQLAsync.js] +[test_js_helpers.js] +[test_levenshtein.js] +[test_like.js] +[test_like_escape.js] +[test_locale_collation.js] +[test_page_size_is_32k.js] +[test_sqlite_secure_delete.js] +[test_statement_executeAsync.js] +[test_statement_wrapper_automatically.js] +[test_storage_aggregates.js] +[test_storage_connection.js] +# Bug 676981: test fails consistently on Android +fail-if = os == "android" +[test_storage_fulltextindex.js] +[test_storage_function.js] +[test_storage_progresshandler.js] +[test_storage_service.js] +[test_storage_service_unshared.js] +[test_storage_statement.js] +[test_storage_value_array.js] +[test_unicode.js] +[test_vacuum.js] +[test_telemetry_vfs.js] +# Bug 676981: test fails consistently on Android +# fail-if = os == "android" |