michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: michael@0: michael@0: do_get_profile(); michael@0: var dirSvc = Cc["@mozilla.org/file/directory_service;1"]. michael@0: getService(Ci.nsIProperties); michael@0: michael@0: function getTestDB() michael@0: { michael@0: var db = dirSvc.get("ProfD", Ci.nsIFile); michael@0: db.append("test_storage.sqlite"); michael@0: return db; michael@0: } michael@0: michael@0: /** michael@0: * Obtains a corrupt database to test against. michael@0: */ michael@0: function getCorruptDB() michael@0: { michael@0: return do_get_file("corruptDB.sqlite"); michael@0: } michael@0: michael@0: /** michael@0: * Obtains a fake (non-SQLite format) database to test against. michael@0: */ michael@0: function getFakeDB() michael@0: { michael@0: return do_get_file("fakeDB.sqlite"); michael@0: } michael@0: michael@0: function cleanup() michael@0: { michael@0: // close the connection michael@0: print("*** Storage Tests: Trying to close!"); michael@0: getOpenedDatabase().close(); michael@0: michael@0: // we need to null out the database variable to get a new connection the next michael@0: // time getOpenedDatabase is called michael@0: gDBConn = null; michael@0: michael@0: // removing test db michael@0: print("*** Storage Tests: Trying to remove file!"); michael@0: var dbFile = getTestDB(); michael@0: if (dbFile.exists()) michael@0: try { dbFile.remove(false); } catch(e) { /* stupid windows box */ } michael@0: } michael@0: michael@0: /** michael@0: * Use asyncClose to cleanup a connection. Synchronous by means of internally michael@0: * spinning an event loop. michael@0: */ michael@0: function asyncCleanup() michael@0: { michael@0: let closed = false; michael@0: michael@0: // close the connection michael@0: print("*** Storage Tests: Trying to asyncClose!"); michael@0: getOpenedDatabase().asyncClose(function() { closed = true; }); michael@0: michael@0: let curThread = Components.classes["@mozilla.org/thread-manager;1"] michael@0: .getService().currentThread; michael@0: while (!closed) michael@0: curThread.processNextEvent(true); michael@0: michael@0: // we need to null out the database variable to get a new connection the next michael@0: // time getOpenedDatabase is called michael@0: gDBConn = null; michael@0: michael@0: // removing test db michael@0: print("*** Storage Tests: Trying to remove file!"); michael@0: var dbFile = getTestDB(); michael@0: if (dbFile.exists()) michael@0: try { dbFile.remove(false); } catch(e) { /* stupid windows box */ } michael@0: } michael@0: michael@0: function getService() michael@0: { michael@0: return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService); michael@0: } michael@0: michael@0: var gDBConn = null; michael@0: michael@0: /** michael@0: * Get a connection to the test database. Creates and caches the connection michael@0: * if necessary, otherwise reuses the existing cached connection. This michael@0: * connection shares its cache. michael@0: * michael@0: * @returns the mozIStorageConnection for the file. michael@0: */ michael@0: function getOpenedDatabase() michael@0: { michael@0: if (!gDBConn) { michael@0: gDBConn = getService().openDatabase(getTestDB()); michael@0: } michael@0: return gDBConn; michael@0: } michael@0: michael@0: /** michael@0: * Get a connection to the test database. Creates and caches the connection michael@0: * if necessary, otherwise reuses the existing cached connection. This michael@0: * connection doesn't share its cache. michael@0: * michael@0: * @returns the mozIStorageConnection for the file. michael@0: */ michael@0: function getOpenedUnsharedDatabase() michael@0: { michael@0: if (!gDBConn) { michael@0: gDBConn = getService().openUnsharedDatabase(getTestDB()); michael@0: } michael@0: return gDBConn; michael@0: } michael@0: michael@0: /** michael@0: * Obtains a specific database to use. michael@0: * michael@0: * @param aFile michael@0: * The nsIFile representing the db file to open. michael@0: * @returns the mozIStorageConnection for the file. michael@0: */ michael@0: function getDatabase(aFile) michael@0: { michael@0: return getService().openDatabase(aFile); michael@0: } michael@0: michael@0: function createStatement(aSQL) michael@0: { michael@0: return getOpenedDatabase().createStatement(aSQL); michael@0: } michael@0: michael@0: /** michael@0: * Creates an asynchronous SQL statement. michael@0: * michael@0: * @param aSQL michael@0: * The SQL to parse into a statement. michael@0: * @returns a mozIStorageAsyncStatement from aSQL. michael@0: */ michael@0: function createAsyncStatement(aSQL) michael@0: { michael@0: return getOpenedDatabase().createAsyncStatement(aSQL); michael@0: } michael@0: michael@0: /** michael@0: * Invoke the given function and assert that it throws an exception expressing michael@0: * the provided error code in its 'result' attribute. JS function expressions michael@0: * can be used to do this concisely. michael@0: * michael@0: * Example: michael@0: * expectError(Cr.NS_ERROR_INVALID_ARG, function() explodingFunction()); michael@0: * michael@0: * @param aErrorCode michael@0: * The error code to expect from invocation of aFunction. michael@0: * @param aFunction michael@0: * The function to invoke and expect an XPCOM-style error from. michael@0: */ michael@0: function expectError(aErrorCode, aFunction) michael@0: { michael@0: let exceptionCaught = false; michael@0: try { michael@0: aFunction(); michael@0: } michael@0: catch(e) { michael@0: if (e.result != aErrorCode) { michael@0: do_throw("Got an exception, but the result code was not the expected " + michael@0: "one. Expected " + aErrorCode + ", got " + e.result); michael@0: } michael@0: exceptionCaught = true; michael@0: } michael@0: if (!exceptionCaught) michael@0: do_throw(aFunction + " should have thrown an exception but did not!"); michael@0: } michael@0: michael@0: /** michael@0: * Run a query synchronously and verify that we get back the expected results. michael@0: * michael@0: * @param aSQLString michael@0: * The SQL string for the query. michael@0: * @param aBind michael@0: * The value to bind at index 0. michael@0: * @param aResults michael@0: * A list of the expected values returned in the sole result row. michael@0: * Express blobs as lists. michael@0: */ michael@0: function verifyQuery(aSQLString, aBind, aResults) michael@0: { michael@0: let stmt = getOpenedDatabase().createStatement(aSQLString); michael@0: stmt.bindByIndex(0, aBind); michael@0: try { michael@0: do_check_true(stmt.executeStep()); michael@0: let nCols = stmt.numEntries; michael@0: if (aResults.length != nCols) michael@0: do_throw("Expected " + aResults.length + " columns in result but " + michael@0: "there are only " + aResults.length + "!"); michael@0: for (let iCol = 0; iCol < nCols; iCol++) { michael@0: let expectedVal = aResults[iCol]; michael@0: let valType = stmt.getTypeOfIndex(iCol); michael@0: if (expectedVal === null) { michael@0: do_check_eq(stmt.VALUE_TYPE_NULL, valType); michael@0: do_check_true(stmt.getIsNull(iCol)); michael@0: } michael@0: else if (typeof(expectedVal) == "number") { michael@0: if (Math.floor(expectedVal) == expectedVal) { michael@0: do_check_eq(stmt.VALUE_TYPE_INTEGER, valType); michael@0: do_check_eq(expectedVal, stmt.getInt32(iCol)); michael@0: } michael@0: else { michael@0: do_check_eq(stmt.VALUE_TYPE_FLOAT, valType); michael@0: do_check_eq(expectedVal, stmt.getDouble(iCol)); michael@0: } michael@0: } michael@0: else if (typeof(expectedVal) == "string") { michael@0: do_check_eq(stmt.VALUE_TYPE_TEXT, valType); michael@0: do_check_eq(expectedVal, stmt.getUTF8String(iCol)); michael@0: } michael@0: else { // blob michael@0: do_check_eq(stmt.VALUE_TYPE_BLOB, valType); michael@0: let count = { value: 0 }, blob = { value: null }; michael@0: stmt.getBlob(iCol, count, blob); michael@0: do_check_eq(count.value, expectedVal.length); michael@0: for (let i = 0; i < count.value; i++) { michael@0: do_check_eq(expectedVal[i], blob.value[i]); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: finally { michael@0: stmt.finalize(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Return the number of rows in the able with the given name using a synchronous michael@0: * query. michael@0: * michael@0: * @param aTableName michael@0: * The name of the table. michael@0: * @return The number of rows. michael@0: */ michael@0: function getTableRowCount(aTableName) michael@0: { michael@0: var currentRows = 0; michael@0: var countStmt = getOpenedDatabase().createStatement( michael@0: "SELECT COUNT(1) AS count FROM " + aTableName michael@0: ); michael@0: try { michael@0: do_check_true(countStmt.executeStep()); michael@0: currentRows = countStmt.row.count; michael@0: } michael@0: finally { michael@0: countStmt.finalize(); michael@0: } michael@0: return currentRows; michael@0: } michael@0: michael@0: cleanup(); michael@0: