2012-05-21 15:12:37 +04:00
|
|
|
/* 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/. */
|
2007-06-12 07:59:26 +04:00
|
|
|
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Cr = Components.results;
|
2013-06-27 17:00:59 +04:00
|
|
|
const Cu = Components.utils;
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
2014-03-18 18:16:02 +04:00
|
|
|
"resource://gre/modules/Promise.jsm");
|
2013-06-27 17:00:59 +04:00
|
|
|
|
2007-06-12 07:59:26 +04:00
|
|
|
|
2009-10-01 01:17:06 +04:00
|
|
|
do_get_profile();
|
2007-06-12 07:59:26 +04:00
|
|
|
var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
|
|
|
|
getService(Ci.nsIProperties);
|
|
|
|
|
2015-12-04 19:00:03 +03:00
|
|
|
var gDBConn = null;
|
|
|
|
|
2016-12-31 05:47:25 +03:00
|
|
|
function getTestDB() {
|
2009-10-01 01:17:06 +04:00
|
|
|
var db = dirSvc.get("ProfD", Ci.nsIFile);
|
2007-06-12 07:59:26 +04:00
|
|
|
db.append("test_storage.sqlite");
|
|
|
|
return db;
|
|
|
|
}
|
|
|
|
|
2008-03-25 01:14:38 +03:00
|
|
|
/**
|
|
|
|
* Obtains a corrupt database to test against.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function getCorruptDB() {
|
2009-03-21 18:20:00 +03:00
|
|
|
return do_get_file("corruptDB.sqlite");
|
2008-03-25 01:14:38 +03:00
|
|
|
}
|
|
|
|
|
2011-11-09 18:06:40 +04:00
|
|
|
/**
|
|
|
|
* Obtains a fake (non-SQLite format) database to test against.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function getFakeDB() {
|
2011-11-09 18:06:40 +04:00
|
|
|
return do_get_file("fakeDB.sqlite");
|
|
|
|
}
|
|
|
|
|
2016-01-07 19:18:00 +03:00
|
|
|
/**
|
|
|
|
* Delete the test database file.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function deleteTestDB() {
|
2016-01-07 19:18:00 +03:00
|
|
|
print("*** Storage Tests: Trying to remove file!");
|
|
|
|
var dbFile = getTestDB();
|
|
|
|
if (dbFile.exists())
|
|
|
|
try { dbFile.remove(false); } catch (e) { /* stupid windows box */ }
|
|
|
|
}
|
|
|
|
|
2016-12-31 05:47:25 +03:00
|
|
|
function cleanup() {
|
2007-09-19 07:26:51 +04:00
|
|
|
// 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;
|
|
|
|
|
2007-06-12 07:59:26 +04:00
|
|
|
// removing test db
|
2016-01-07 19:18:00 +03:00
|
|
|
deleteTestDB();
|
2007-06-12 07:59:26 +04:00
|
|
|
}
|
|
|
|
|
2010-03-24 10:32:40 +03:00
|
|
|
/**
|
|
|
|
* Use asyncClose to cleanup a connection. Synchronous by means of internally
|
|
|
|
* spinning an event loop.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function asyncCleanup() {
|
2010-03-24 10:32:40 +03:00
|
|
|
let closed = false;
|
|
|
|
|
|
|
|
// close the connection
|
|
|
|
print("*** Storage Tests: Trying to asyncClose!");
|
2016-11-11 19:10:51 +03:00
|
|
|
getOpenedDatabase().asyncClose(function() { closed = true; });
|
2010-03-24 10:32:40 +03:00
|
|
|
|
|
|
|
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
|
2016-01-07 19:18:00 +03:00
|
|
|
deleteTestDB();
|
2010-03-24 10:32:40 +03:00
|
|
|
}
|
|
|
|
|
2016-12-31 05:47:25 +03:00
|
|
|
function getService() {
|
2007-06-12 07:59:26 +04:00
|
|
|
return Cc["@mozilla.org/storage/service;1"].getService(Ci.mozIStorageService);
|
|
|
|
}
|
|
|
|
|
2008-01-30 02:34:19 +03:00
|
|
|
/**
|
|
|
|
* Get a connection to the test database. Creates and caches the connection
|
2012-01-03 18:12:54 +04:00
|
|
|
* if necessary, otherwise reuses the existing cached connection. This
|
|
|
|
* connection shares its cache.
|
2008-01-30 02:34:19 +03:00
|
|
|
*
|
2008-02-09 22:05:49 +03:00
|
|
|
* @returns the mozIStorageConnection for the file.
|
2008-01-30 02:34:19 +03:00
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function getOpenedDatabase() {
|
2007-09-19 07:26:51 +04:00
|
|
|
if (!gDBConn) {
|
2012-01-03 18:12:54 +04:00
|
|
|
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.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function getOpenedUnsharedDatabase() {
|
2012-01-03 18:12:54 +04:00
|
|
|
if (!gDBConn) {
|
|
|
|
gDBConn = getService().openUnsharedDatabase(getTestDB());
|
2007-09-19 07:26:51 +04:00
|
|
|
}
|
|
|
|
return gDBConn;
|
2007-06-12 07:59:26 +04:00
|
|
|
}
|
|
|
|
|
2008-02-09 22:05:49 +03:00
|
|
|
/**
|
|
|
|
* Obtains a specific database to use.
|
|
|
|
*
|
|
|
|
* @param aFile
|
|
|
|
* The nsIFile representing the db file to open.
|
|
|
|
* @returns the mozIStorageConnection for the file.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function getDatabase(aFile) {
|
2008-02-09 22:05:49 +03:00
|
|
|
return getService().openDatabase(aFile);
|
|
|
|
}
|
|
|
|
|
2016-12-31 05:47:25 +03:00
|
|
|
function createStatement(aSQL) {
|
2007-06-12 07:59:26 +04:00
|
|
|
return getOpenedDatabase().createStatement(aSQL);
|
|
|
|
}
|
|
|
|
|
2010-05-12 01:14:54 +04:00
|
|
|
/**
|
|
|
|
* Creates an asynchronous SQL statement.
|
|
|
|
*
|
|
|
|
* @param aSQL
|
|
|
|
* The SQL to parse into a statement.
|
|
|
|
* @returns a mozIStorageAsyncStatement from aSQL.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function createAsyncStatement(aSQL) {
|
2010-05-12 01:14:54 +04:00
|
|
|
return getOpenedDatabase().createAsyncStatement(aSQL);
|
|
|
|
}
|
|
|
|
|
2010-03-24 10:32:40 +03:00
|
|
|
/**
|
|
|
|
* 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:
|
2015-09-23 12:42:19 +03:00
|
|
|
* expectError(Cr.NS_ERROR_INVALID_ARG, () => explodingFunction());
|
2010-03-24 10:32:40 +03:00
|
|
|
*
|
|
|
|
* @param aErrorCode
|
|
|
|
* The error code to expect from invocation of aFunction.
|
|
|
|
* @param aFunction
|
|
|
|
* The function to invoke and expect an XPCOM-style error from.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function expectError(aErrorCode, aFunction) {
|
2010-03-24 10:32:40 +03:00
|
|
|
let exceptionCaught = false;
|
|
|
|
try {
|
|
|
|
aFunction();
|
2016-12-31 05:47:25 +03:00
|
|
|
} catch (e) {
|
2010-03-24 10:32:40 +03:00
|
|
|
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.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function verifyQuery(aSQLString, aBind, aResults) {
|
2010-03-24 10:32:40 +03:00
|
|
|
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));
|
2016-12-31 05:47:25 +03:00
|
|
|
} else if (typeof expectedVal == "number") {
|
2010-03-24 10:32:40 +03:00
|
|
|
if (Math.floor(expectedVal) == expectedVal) {
|
|
|
|
do_check_eq(stmt.VALUE_TYPE_INTEGER, valType);
|
|
|
|
do_check_eq(expectedVal, stmt.getInt32(iCol));
|
2016-12-31 05:47:25 +03:00
|
|
|
} else {
|
2010-03-24 10:32:40 +03:00
|
|
|
do_check_eq(stmt.VALUE_TYPE_FLOAT, valType);
|
|
|
|
do_check_eq(expectedVal, stmt.getDouble(iCol));
|
|
|
|
}
|
2016-12-31 05:47:25 +03:00
|
|
|
} else if (typeof expectedVal == "string") {
|
2010-03-24 10:32:40 +03:00
|
|
|
do_check_eq(stmt.VALUE_TYPE_TEXT, valType);
|
|
|
|
do_check_eq(expectedVal, stmt.getUTF8String(iCol));
|
2016-12-31 05:47:25 +03:00
|
|
|
} else { // blob
|
2010-03-24 10:32:40 +03:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-12-31 05:47:25 +03:00
|
|
|
} finally {
|
2010-03-24 10:32:40 +03:00
|
|
|
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.
|
|
|
|
*/
|
2016-12-31 05:47:25 +03:00
|
|
|
function getTableRowCount(aTableName) {
|
2010-03-24 10:32:40 +03:00
|
|
|
var currentRows = 0;
|
|
|
|
var countStmt = getOpenedDatabase().createStatement(
|
|
|
|
"SELECT COUNT(1) AS count FROM " + aTableName
|
|
|
|
);
|
|
|
|
try {
|
|
|
|
do_check_true(countStmt.executeStep());
|
|
|
|
currentRows = countStmt.row.count;
|
2016-12-31 05:47:25 +03:00
|
|
|
} finally {
|
2010-03-24 10:32:40 +03:00
|
|
|
countStmt.finalize();
|
|
|
|
}
|
|
|
|
return currentRows;
|
|
|
|
}
|
|
|
|
|
2016-11-03 21:42:06 +03:00
|
|
|
// Promise-Returning Functions
|
2016-01-13 01:27:33 +03:00
|
|
|
|
|
|
|
function asyncClone(db, readOnly) {
|
|
|
|
let deferred = Promise.defer();
|
2016-11-11 19:10:51 +03:00
|
|
|
db.asyncClone(readOnly, function(status, db2) {
|
2016-01-13 01:27:33 +03:00
|
|
|
if (Components.isSuccessCode(status)) {
|
|
|
|
deferred.resolve(db2);
|
|
|
|
} else {
|
|
|
|
deferred.reject(status);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
2007-06-12 07:59:26 +04:00
|
|
|
|
2016-01-13 01:27:33 +03:00
|
|
|
function asyncClose(db) {
|
|
|
|
let deferred = Promise.defer();
|
2016-11-11 19:10:51 +03:00
|
|
|
db.asyncClose(function(status) {
|
2016-01-13 01:27:33 +03:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
}
|
2016-11-11 19:10:51 +03:00
|
|
|
getService().openAsyncDatabase(file, properties, function(status, db) {
|
2016-01-13 01:27:33 +03:00
|
|
|
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({
|
2016-12-30 02:34:54 +03:00
|
|
|
handleError(error) {
|
2016-01-13 01:27:33 +03:00
|
|
|
deferred.reject(error);
|
|
|
|
},
|
2016-12-30 02:34:54 +03:00
|
|
|
handleResult(result) {
|
2016-01-13 01:27:33 +03:00
|
|
|
if (onResult) {
|
|
|
|
onResult(result);
|
|
|
|
}
|
|
|
|
},
|
2016-12-30 02:34:54 +03:00
|
|
|
handleCompletion(result) {
|
2016-01-13 01:27:33 +03:00
|
|
|
deferred.resolve(result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
|
|
|
|
2016-01-07 19:18:00 +03:00
|
|
|
function executeMultipleStatementsAsync(db, statements, onResult) {
|
|
|
|
let deferred = Promise.defer();
|
|
|
|
db.executeAsync(statements, statements.length, {
|
2016-12-30 02:34:54 +03:00
|
|
|
handleError(error) {
|
2016-01-07 19:18:00 +03:00
|
|
|
deferred.reject(error);
|
|
|
|
},
|
2016-12-30 02:34:54 +03:00
|
|
|
handleResult(result) {
|
2016-01-07 19:18:00 +03:00
|
|
|
if (onResult) {
|
|
|
|
onResult(result);
|
|
|
|
}
|
|
|
|
},
|
2016-12-30 02:34:54 +03:00
|
|
|
handleCompletion(result) {
|
2016-01-07 19:18:00 +03:00
|
|
|
deferred.resolve(result);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return deferred.promise;
|
|
|
|
}
|
|
|
|
|
2016-01-13 01:27:33 +03:00
|
|
|
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();
|