summaryrefslogtreecommitdiffstats
path: root/storage/test/unit/head_storage.js
diff options
context:
space:
mode:
Diffstat (limited to 'storage/test/unit/head_storage.js')
-rw-r--r--storage/test/unit/head_storage.js412
1 files changed, 412 insertions, 0 deletions
diff --git a/storage/test/unit/head_storage.js b/storage/test/unit/head_storage.js
new file mode 100644
index 0000000000..3cddc36801
--- /dev/null
+++ b/storage/test/unit/head_storage.js
@@ -0,0 +1,412 @@
+/* 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/. */
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+ TestUtils: "resource://testing-common/TestUtils.sys.mjs",
+});
+
+const OPEN_HISTOGRAM = "SQLITE_STORE_OPEN";
+const QUERY_HISTOGRAM = "SQLITE_STORE_QUERY";
+
+const TELEMETRY_VALUES = {
+ success: 0,
+ failure: 1,
+ access: 2,
+ diskio: 3,
+ corrupt: 4,
+ busy: 5,
+ misuse: 6,
+ diskspace: 7,
+};
+
+do_get_profile();
+var gDBConn = null;
+
+const TEST_DB_NAME = "test_storage.sqlite";
+function getTestDB() {
+ var db = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ db.append(TEST_DB_NAME);
+ 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 tm = Cc["@mozilla.org/thread-manager;1"].getService();
+ tm.spinEventLoopUntil("Test(head_storage.js:asyncCleanup)", () => closed);
+
+ // 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();
+}
+
+/**
+ * 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(connectionFlags = 0) {
+ if (!gDBConn) {
+ gDBConn = Services.storage.openDatabase(getTestDB(), connectionFlags);
+
+ // Clear out counts for any queries that occured while opening the database.
+ TelemetryTestUtils.getAndClearKeyedHistogram(OPEN_HISTOGRAM);
+ TelemetryTestUtils.getAndClearKeyedHistogram(QUERY_HISTOGRAM);
+ }
+ 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 = Services.storage.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 Services.storage.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 {
+ Assert.ok(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) {
+ Assert.equal(stmt.VALUE_TYPE_NULL, valType);
+ Assert.ok(stmt.getIsNull(iCol));
+ } else if (typeof expectedVal == "number") {
+ if (Math.floor(expectedVal) == expectedVal) {
+ Assert.equal(stmt.VALUE_TYPE_INTEGER, valType);
+ Assert.equal(expectedVal, stmt.getInt32(iCol));
+ } else {
+ Assert.equal(stmt.VALUE_TYPE_FLOAT, valType);
+ Assert.equal(expectedVal, stmt.getDouble(iCol));
+ }
+ } else if (typeof expectedVal == "string") {
+ Assert.equal(stmt.VALUE_TYPE_TEXT, valType);
+ Assert.equal(expectedVal, stmt.getUTF8String(iCol));
+ } else {
+ // blob
+ Assert.equal(stmt.VALUE_TYPE_BLOB, valType);
+ let count = { value: 0 },
+ blob = { value: null };
+ stmt.getBlob(iCol, count, blob);
+ Assert.equal(count.value, expectedVal.length);
+ for (let i = 0; i < count.value; i++) {
+ Assert.equal(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 {
+ Assert.ok(countStmt.executeStep());
+ currentRows = countStmt.row.count;
+ } finally {
+ countStmt.finalize();
+ }
+ return currentRows;
+}
+
+// Promise-Returning Functions
+
+function asyncClone(db, readOnly) {
+ return new Promise((resolve, reject) => {
+ db.asyncClone(readOnly, function (status, db2) {
+ if (Components.isSuccessCode(status)) {
+ resolve(db2);
+ } else {
+ reject(status);
+ }
+ });
+ });
+}
+
+function asyncClose(db) {
+ return new Promise((resolve, reject) => {
+ db.asyncClose(function (status) {
+ if (Components.isSuccessCode(status)) {
+ resolve();
+ } else {
+ reject(status);
+ }
+ });
+ });
+}
+
+function mapOptionsToFlags(aOptions, aMapping) {
+ let result = aMapping.default;
+ Object.entries(aOptions || {}).forEach(([optionName, isTrue]) => {
+ if (aMapping.hasOwnProperty(optionName) && isTrue) {
+ result |= aMapping[optionName];
+ }
+ });
+ return result;
+}
+
+function getOpenFlagsMap() {
+ return {
+ default: Ci.mozIStorageService.OPEN_DEFAULT,
+ shared: Ci.mozIStorageService.OPEN_SHARED,
+ readOnly: Ci.mozIStorageService.OPEN_READONLY,
+ ignoreLockingMode: Ci.mozIStorageService.OPEN_IGNORE_LOCKING_MODE,
+ };
+}
+
+function getConnectionFlagsMap() {
+ return {
+ default: Ci.mozIStorageService.CONNECTION_DEFAULT,
+ interruptible: Ci.mozIStorageService.CONNECTION_INTERRUPTIBLE,
+ };
+}
+
+function openAsyncDatabase(file, options) {
+ return new Promise((resolve, reject) => {
+ const openFlags = mapOptionsToFlags(options, getOpenFlagsMap());
+ const connectionFlags = mapOptionsToFlags(options, getConnectionFlagsMap());
+
+ Services.storage.openAsyncDatabase(
+ file,
+ openFlags,
+ connectionFlags,
+ function (status, db) {
+ if (Components.isSuccessCode(status)) {
+ resolve(db.QueryInterface(Ci.mozIStorageAsyncConnection));
+ } else {
+ reject(status);
+ }
+ }
+ );
+ });
+}
+
+function executeAsync(statement, onResult) {
+ return new Promise((resolve, reject) => {
+ statement.executeAsync({
+ handleError(error) {
+ reject(error);
+ },
+ handleResult(result) {
+ if (onResult) {
+ onResult(result);
+ }
+ },
+ handleCompletion(result) {
+ resolve(result);
+ },
+ });
+ });
+}
+
+function executeMultipleStatementsAsync(db, statements, onResult) {
+ return new Promise((resolve, reject) => {
+ db.executeAsync(statements, {
+ handleError(error) {
+ reject(error);
+ },
+ handleResult(result) {
+ if (onResult) {
+ onResult(result);
+ }
+ },
+ handleCompletion(result) {
+ resolve(result);
+ },
+ });
+ });
+}
+
+function executeSimpleSQLAsync(db, query, onResult) {
+ return new Promise((resolve, reject) => {
+ db.executeSimpleSQLAsync(query, {
+ handleError(error) {
+ reject(error);
+ },
+ handleResult(result) {
+ if (onResult) {
+ onResult(result);
+ } else {
+ do_throw("No results were expected");
+ }
+ },
+ handleCompletion(result) {
+ resolve(result);
+ },
+ });
+ });
+}
+
+cleanup();