import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

37
storage/test/moz.build Normal file
View file

@ -0,0 +1,37 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
GeckoCppUnitTests([
'test_AsXXX_helpers',
'test_async_callbacks_with_spun_event_loops',
'test_asyncStatementExecution_transaction',
'test_binding_params',
'test_file_perms',
'test_mutex',
'test_service_init_background_thread',
'test_statement_scoper',
'test_StatementCache',
'test_transaction_helper',
'test_true_async',
'test_unlock_notify',
])
if CONFIG['MOZ_DEBUG'] and CONFIG['OS_ARCH'] not in ('WINNT', 'Darwin'):
# FIXME bug 523392: test_deadlock_detector doesn't like Windows
# FIXME bug 523378: also fails on OS X
GeckoCppUnitTests([
'test_deadlock_detector',
])
LOCAL_INCLUDES += [
'..',
]
USE_LIBS += [
'sqlite',
]

View file

@ -0,0 +1,389 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "TestHarness.h"
#include "nsMemory.h"
#include "prthread.h"
#include "nsThreadUtils.h"
#include "nsDirectoryServiceDefs.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatementCallback.h"
#include "mozIStorageCompletionCallback.h"
#include "mozIStorageBindingParamsArray.h"
#include "mozIStorageBindingParams.h"
#include "mozIStorageAsyncStatement.h"
#include "mozIStorageStatement.h"
#include "mozIStoragePendingStatement.h"
#include "mozIStorageError.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIEventTarget.h"
#include "sqlite3.h"
static int gTotalTests = 0;
static int gPassedTests = 0;
#define do_check_true(aCondition) \
PR_BEGIN_MACRO \
gTotalTests++; \
if (aCondition) { \
gPassedTests++; \
} else { \
fail("%s | Expected true, got false at line %d", __FILE__, __LINE__); \
} \
PR_END_MACRO
#define do_check_false(aCondition) \
PR_BEGIN_MACRO \
gTotalTests++; \
if (!aCondition) { \
gPassedTests++; \
} else { \
fail("%s | Expected false, got true at line %d", __FILE__, __LINE__); \
} \
PR_END_MACRO
#define do_check_success(aResult) \
do_check_true(NS_SUCCEEDED(aResult))
#ifdef LINUX
// XXX Linux opt builds on tinderbox are orange due to linking with stdlib.
// This is sad and annoying, but it's a workaround that works.
#define do_check_eq(aExpected, aActual) \
do_check_true(aExpected == aActual)
#else
#include <sstream>
// Print nsresult as uint32_t
std::ostream& operator<<(std::ostream& aStream, const nsresult aInput)
{
return aStream << static_cast<uint32_t>(aInput);
}
#define do_check_eq(aExpected, aActual) \
PR_BEGIN_MACRO \
gTotalTests++; \
if (aExpected == aActual) { \
gPassedTests++; \
} else { \
std::ostringstream temp; \
temp << __FILE__ << " | Expected '" << aExpected << "', got '"; \
temp << aActual <<"' at line " << __LINE__; \
fail(temp.str().c_str()); \
} \
PR_END_MACRO
#endif
#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
already_AddRefed<mozIStorageService>
getService()
{
nsCOMPtr<mozIStorageService> ss =
do_GetService("@mozilla.org/storage/service;1");
do_check_true(ss);
return ss.forget();
}
already_AddRefed<mozIStorageConnection>
getMemoryDatabase()
{
nsCOMPtr<mozIStorageService> ss = getService();
nsCOMPtr<mozIStorageConnection> conn;
nsresult rv = ss->OpenSpecialDatabase("memory", getter_AddRefs(conn));
do_check_success(rv);
return conn.forget();
}
already_AddRefed<mozIStorageConnection>
getDatabase()
{
nsCOMPtr<nsIFile> dbFile;
(void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(dbFile));
NS_ASSERTION(dbFile, "The directory doesn't exists?!");
nsresult rv = dbFile->Append(NS_LITERAL_STRING("storage_test_db.sqlite"));
do_check_success(rv);
nsCOMPtr<mozIStorageService> ss = getService();
nsCOMPtr<mozIStorageConnection> conn;
rv = ss->OpenDatabase(dbFile, getter_AddRefs(conn));
do_check_success(rv);
return conn.forget();
}
class AsyncStatementSpinner : public mozIStorageStatementCallback
, public mozIStorageCompletionCallback
{
public:
NS_DECL_ISUPPORTS
NS_DECL_MOZISTORAGESTATEMENTCALLBACK
NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
AsyncStatementSpinner();
void SpinUntilCompleted();
uint16_t completionReason;
protected:
virtual ~AsyncStatementSpinner() {}
volatile bool mCompleted;
};
NS_IMPL_ISUPPORTS(AsyncStatementSpinner,
mozIStorageStatementCallback,
mozIStorageCompletionCallback)
AsyncStatementSpinner::AsyncStatementSpinner()
: completionReason(0)
, mCompleted(false)
{
}
NS_IMETHODIMP
AsyncStatementSpinner::HandleResult(mozIStorageResultSet *aResultSet)
{
return NS_OK;
}
NS_IMETHODIMP
AsyncStatementSpinner::HandleError(mozIStorageError *aError)
{
int32_t result;
nsresult rv = aError->GetResult(&result);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString message;
rv = aError->GetMessage(message);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString warnMsg;
warnMsg.AppendLiteral("An error occurred while executing an async statement: ");
warnMsg.AppendInt(result);
warnMsg.Append(' ');
warnMsg.Append(message);
NS_WARNING(warnMsg.get());
return NS_OK;
}
NS_IMETHODIMP
AsyncStatementSpinner::HandleCompletion(uint16_t aReason)
{
completionReason = aReason;
mCompleted = true;
return NS_OK;
}
NS_IMETHODIMP
AsyncStatementSpinner::Complete(nsresult, nsISupports*)
{
mCompleted = true;
return NS_OK;
}
void AsyncStatementSpinner::SpinUntilCompleted()
{
nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
nsresult rv = NS_OK;
bool processed = true;
while (!mCompleted && NS_SUCCEEDED(rv)) {
rv = thread->ProcessNextEvent(true, &processed);
}
}
#define NS_DECL_ASYNCSTATEMENTSPINNER \
NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet) override;
////////////////////////////////////////////////////////////////////////////////
//// Async Helpers
/**
* Execute an async statement, blocking the main thread until we get the
* callback completion notification.
*/
void
blocking_async_execute(mozIStorageBaseStatement *stmt)
{
RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
nsCOMPtr<mozIStoragePendingStatement> pendy;
(void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy));
spinner->SpinUntilCompleted();
}
/**
* Invoke AsyncClose on the given connection, blocking the main thread until we
* get the completion notification.
*/
void
blocking_async_close(mozIStorageConnection *db)
{
RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner());
db->AsyncClose(spinner);
spinner->SpinUntilCompleted();
}
////////////////////////////////////////////////////////////////////////////////
//// Mutex Watching
/**
* Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
* the caller (generally main) thread. We do this by decorating the sqlite
* mutex logic with our own code that checks what thread it is being invoked on
* and sets a flag if it is invoked on the main thread. We are able to easily
* decorate the SQLite mutex logic because SQLite allows us to retrieve the
* current function pointers being used and then provide a new set.
*/
sqlite3_mutex_methods orig_mutex_methods;
sqlite3_mutex_methods wrapped_mutex_methods;
bool mutex_used_on_watched_thread = false;
PRThread *watched_thread = nullptr;
/**
* Ugly hack to let us figure out what a connection's async thread is. If we
* were MOZILLA_INTERNAL_API and linked as such we could just include
* mozStorageConnection.h and just ask Connection directly. But that turns out
* poorly.
*
* When the thread a mutex is invoked on isn't watched_thread we save it to this
* variable.
*/
PRThread *last_non_watched_thread = nullptr;
/**
* Set a flag if the mutex is used on the thread we are watching, but always
* call the real mutex function.
*/
extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex)
{
PRThread *curThread = ::PR_GetCurrentThread();
if (curThread == watched_thread)
mutex_used_on_watched_thread = true;
else
last_non_watched_thread = curThread;
orig_mutex_methods.xMutexEnter(mutex);
}
extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex)
{
if (::PR_GetCurrentThread() == watched_thread)
mutex_used_on_watched_thread = true;
return orig_mutex_methods.xMutexTry(mutex);
}
void hook_sqlite_mutex()
{
// We need to initialize and teardown SQLite to get it to set up the
// default mutex handlers for us so we can steal them and wrap them.
do_check_ok(sqlite3_initialize());
do_check_ok(sqlite3_shutdown());
do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
}
/**
* Call to clear the watch state and to set the watching against this thread.
*
* Check |mutex_used_on_watched_thread| to see if the mutex has fired since
* this method was last called. Since we're talking about the current thread,
* there are no race issues to be concerned about
*/
void watch_for_mutex_use_on_this_thread()
{
watched_thread = ::PR_GetCurrentThread();
mutex_used_on_watched_thread = false;
}
////////////////////////////////////////////////////////////////////////////////
//// Thread Wedgers
/**
* A runnable that blocks until code on another thread invokes its unwedge
* method. By dispatching this to a thread you can ensure that no subsequent
* runnables dispatched to the thread will execute until you invoke unwedge.
*
* The wedger is self-dispatching, just construct it with its target.
*/
class ThreadWedger : public mozilla::Runnable
{
public:
explicit ThreadWedger(nsIEventTarget *aTarget)
: mReentrantMonitor("thread wedger")
, unwedged(false)
{
aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
}
NS_IMETHOD Run() override
{
mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
if (!unwedged)
automon.Wait();
return NS_OK;
}
void unwedge()
{
mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor);
unwedged = true;
automon.Notify();
}
private:
mozilla::ReentrantMonitor mReentrantMonitor;
bool unwedged;
};
////////////////////////////////////////////////////////////////////////////////
//// Async Helpers
/**
* A horrible hack to figure out what the connection's async thread is. By
* creating a statement and async dispatching we can tell from the mutex who
* is the async thread, PRThread style. Then we map that to an nsIThread.
*/
already_AddRefed<nsIThread>
get_conn_async_thread(mozIStorageConnection *db)
{
// Make sure we are tracking the current thread as the watched thread
watch_for_mutex_use_on_this_thread();
// - statement with nothing to bind
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(
NS_LITERAL_CSTRING("SELECT 1"),
getter_AddRefs(stmt));
blocking_async_execute(stmt);
stmt->Finalize();
nsCOMPtr<nsIThreadManager> threadMan =
do_GetService("@mozilla.org/thread-manager;1");
nsCOMPtr<nsIThread> asyncThread;
threadMan->GetThreadFromPRThread(last_non_watched_thread,
getter_AddRefs(asyncThread));
// Additionally, check that the thread we get as the background thread is the
// same one as the one we report from getInterface.
nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
PRThread *allegedPRThread;
(void)allegedAsyncThread->GetPRThread(&allegedPRThread);
do_check_eq(allegedPRThread, last_non_watched_thread);
return asyncThread.forget();
}

View file

@ -0,0 +1,28 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#ifndef TEST_NAME
#error "Must #define TEST_NAME before including storage_test_harness_tail.h"
#endif
#ifndef TEST_FILE
#error "Must #define TEST_FILE before include storage_test_harness_tail.h"
#endif
int
main(int aArgc,
char **aArgv)
{
ScopedXPCOM xpcom(TEST_NAME);
for (size_t i = 0; i < mozilla::ArrayLength(gTests); i++)
gTests[i]();
if (gPassedTests == gTotalTests)
passed(TEST_FILE);
(void)printf("%i of %i tests passed\n", gPassedTests, gTotalTests);
}

View file

@ -0,0 +1,127 @@
/*
*Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "storage_test_harness.h"
#include "mozIStorageRow.h"
#include "mozIStorageResultSet.h"
/**
* This file tests AsXXX (AsInt32, AsInt64, ...) helpers.
*/
////////////////////////////////////////////////////////////////////////////////
//// Event Loop Spinning
class Spinner : public AsyncStatementSpinner
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_ASYNCSTATEMENTSPINNER
Spinner() {}
protected:
virtual ~Spinner() {}
};
NS_IMPL_ISUPPORTS_INHERITED0(Spinner,
AsyncStatementSpinner)
NS_IMETHODIMP
Spinner::HandleResult(mozIStorageResultSet *aResultSet)
{
nsCOMPtr<mozIStorageRow> row;
do_check_true(NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row);
do_check_eq(row->AsInt32(0), 0);
do_check_eq(row->AsInt64(0), 0);
do_check_eq(row->AsDouble(0), 0.0);
uint32_t len = 100;
do_check_eq(row->AsSharedUTF8String(0, &len), (const char*)nullptr);
do_check_eq(len, 0);
len = 100;
do_check_eq(row->AsSharedWString(0, &len), (const char16_t*)nullptr);
do_check_eq(len, 0);
len = 100;
do_check_eq(row->AsSharedBlob(0, &len), (const uint8_t*)nullptr);
do_check_eq(len, 0);
do_check_eq(row->IsNull(0), true);
return NS_OK;
}
void
test_NULLFallback()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
nsCOMPtr<mozIStorageStatement> stmt;
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT NULL"
), getter_AddRefs(stmt));
nsCOMPtr<mozIStorageValueArray> valueArray = do_QueryInterface(stmt);
do_check_true(valueArray);
bool hasMore;
do_check_true(NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore);
do_check_eq(stmt->AsInt32(0), 0);
do_check_eq(stmt->AsInt64(0), 0);
do_check_eq(stmt->AsDouble(0), 0.0);
uint32_t len = 100;
do_check_eq(stmt->AsSharedUTF8String(0, &len), (const char*)nullptr);
do_check_eq(len, 0);
len = 100;
do_check_eq(stmt->AsSharedWString(0, &len), (const char16_t*)nullptr);
do_check_eq(len, 0);
len = 100;
do_check_eq(stmt->AsSharedBlob(0, &len), (const uint8_t*)nullptr);
do_check_eq(len, 0);
do_check_eq(stmt->IsNull(0), true);
do_check_eq(valueArray->AsInt32(0), 0);
do_check_eq(valueArray->AsInt64(0), 0);
do_check_eq(valueArray->AsDouble(0), 0.0);
len = 100;
do_check_eq(valueArray->AsSharedUTF8String(0, &len), (const char*)nullptr);
do_check_eq(len, 0);
len = 100;
do_check_eq(valueArray->AsSharedWString(0, &len), (const char16_t*)nullptr);
do_check_eq(len, 0);
len = 100;
do_check_eq(valueArray->AsSharedBlob(0, &len), (const uint8_t*)nullptr);
do_check_eq(len, 0);
do_check_eq(valueArray->IsNull(0), true);
}
void
test_asyncNULLFallback()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
nsCOMPtr<mozIStorageAsyncStatement> stmt;
(void)db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT NULL"
), getter_AddRefs(stmt));
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
do_check_true(NS_SUCCEEDED(stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt))));
do_check_true(pendingStmt);
stmt->Finalize();
RefPtr<Spinner> asyncSpin(new Spinner());
db->AsyncClose(asyncSpin);
asyncSpin->SpinUntilCompleted();
}
void (*gTests[])(void) = {
test_NULLFallback
, test_asyncNULLFallback
};
const char *file = __FILE__;
#define TEST_NAME "AsXXX helpers"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,163 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
#include "mozilla/Attributes.h"
#include "mozilla/storage/StatementCache.h"
using namespace mozilla::storage;
/**
* This file test our statement cache in StatementCache.h.
*/
////////////////////////////////////////////////////////////////////////////////
//// Helpers
class SyncCache : public StatementCache<mozIStorageStatement>
{
public:
explicit SyncCache(nsCOMPtr<mozIStorageConnection>& aConnection)
: StatementCache<mozIStorageStatement>(aConnection)
{
}
};
class AsyncCache : public StatementCache<mozIStorageAsyncStatement>
{
public:
explicit AsyncCache(nsCOMPtr<mozIStorageConnection>& aConnection)
: StatementCache<mozIStorageAsyncStatement>(aConnection)
{
}
};
/**
* Wraps nsCString so we can not implement the same functions twice for each
* type.
*/
class StringWrapper : public nsCString
{
public:
MOZ_IMPLICIT StringWrapper(const char* aOther)
{
this->Assign(aOther);
}
};
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
template<typename StringType>
void
test_GetCachedStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
SyncCache cache(db);
StringType sql = "SELECT * FROM sqlite_master";
// Make sure we get a statement back with the right state.
nsCOMPtr<mozIStorageStatement> stmt = cache.GetCachedStatement(sql);
do_check_true(stmt);
int32_t state;
do_check_success(stmt->GetState(&state));
do_check_eq(mozIStorageBaseStatement::MOZ_STORAGE_STATEMENT_READY, state);
// Check to make sure we get the same copy the second time we ask.
nsCOMPtr<mozIStorageStatement> stmt2 = cache.GetCachedStatement(sql);
do_check_true(stmt2);
do_check_eq(stmt.get(), stmt2.get());
}
template <typename StringType>
void
test_FinalizeStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
SyncCache cache(db);
StringType sql = "SELECT * FROM sqlite_master";
// Get a statement, and then tell the cache to finalize.
nsCOMPtr<mozIStorageStatement> stmt = cache.GetCachedStatement(sql);
do_check_true(stmt);
cache.FinalizeStatements();
// We should be in an invalid state at this point.
int32_t state;
do_check_success(stmt->GetState(&state));
do_check_eq(mozIStorageBaseStatement::MOZ_STORAGE_STATEMENT_INVALID, state);
// Should be able to close the database now too.
do_check_success(db->Close());
}
template<typename StringType>
void
test_GetCachedAsyncStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
AsyncCache cache(db);
StringType sql = "SELECT * FROM sqlite_master";
// Make sure we get a statement back with the right state.
nsCOMPtr<mozIStorageAsyncStatement> stmt = cache.GetCachedStatement(sql);
do_check_true(stmt);
int32_t state;
do_check_success(stmt->GetState(&state));
do_check_eq(mozIStorageBaseStatement::MOZ_STORAGE_STATEMENT_READY, state);
// Check to make sure we get the same copy the second time we ask.
nsCOMPtr<mozIStorageAsyncStatement> stmt2 = cache.GetCachedStatement(sql);
do_check_true(stmt2);
do_check_eq(stmt.get(), stmt2.get());
}
template <typename StringType>
void
test_FinalizeAsyncStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
AsyncCache cache(db);
StringType sql = "SELECT * FROM sqlite_master";
// Get a statement, and then tell the cache to finalize.
nsCOMPtr<mozIStorageAsyncStatement> stmt = cache.GetCachedStatement(sql);
do_check_true(stmt);
cache.FinalizeStatements();
// We should be in an invalid state at this point.
int32_t state;
do_check_success(stmt->GetState(&state));
do_check_eq(mozIStorageBaseStatement::MOZ_STORAGE_STATEMENT_INVALID, state);
// Should be able to close the database now too.
do_check_success(db->AsyncClose(nullptr));
}
////////////////////////////////////////////////////////////////////////////////
//// Test Harness Stuff
void (*gTests[])(void) = {
test_GetCachedStatement<const char []>,
test_GetCachedStatement<StringWrapper>,
test_FinalizeStatements<const char []>,
test_FinalizeStatements<StringWrapper>,
test_GetCachedAsyncStatement<const char []>,
test_GetCachedAsyncStatement<StringWrapper>,
test_FinalizeAsyncStatements<const char []>,
test_FinalizeAsyncStatements<StringWrapper>,
};
const char *file = __FILE__;
#define TEST_NAME "StatementCache"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,509 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
#include "storage_test_harness.h"
#include "nsIEventTarget.h"
#include "mozStorageConnection.h"
#include "sqlite3.h"
using namespace mozilla;
using namespace mozilla::storage;
////////////////////////////////////////////////////////////////////////////////
//// Helpers
/**
* Commit hook to detect transactions.
*
* @param aArg
* An integer pointer that will be incremented for each commit.
*/
int commit_hook(void *aArg)
{
int *arg = static_cast<int *>(aArg);
(*arg)++;
return 0;
}
/**
* Executes the passed-in statements and checks if a transaction is created.
* When done statements are finalized and database connection is closed.
*
* @param aDB
* The database connection.
* @param aStmts
* Vector of statements.
* @param aStmtsLen
* Number of statements.
* @param aTransactionExpected
* Whether a transaction is expected or not.
*/
void
check_transaction(mozIStorageConnection *aDB,
mozIStorageBaseStatement **aStmts,
uint32_t aStmtsLen,
bool aTransactionExpected)
{
// -- install a transaction commit hook.
int commit = 0;
static_cast<Connection *>(aDB)->setCommitHook(commit_hook, &commit);
RefPtr<AsyncStatementSpinner> asyncSpin(new AsyncStatementSpinner());
nsCOMPtr<mozIStoragePendingStatement> asyncPend;
do_check_success(aDB->ExecuteAsync(aStmts, aStmtsLen, asyncSpin,
getter_AddRefs(asyncPend)));
do_check_true(asyncPend);
// -- complete the execution
asyncSpin->SpinUntilCompleted();
// -- uninstall the transaction commit hook.
static_cast<Connection *>(aDB)->setCommitHook(nullptr);
// -- check transaction
do_check_eq(aTransactionExpected, !!commit);
// -- check that only one transaction was created.
if (aTransactionExpected) {
do_check_eq(1, commit);
}
// -- cleanup
for (uint32_t i = 0; i < aStmtsLen; ++i) {
aStmts[i]->Finalize();
}
blocking_async_close(aDB);
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
/**
* Test that executing multiple readonly AsyncStatements doesn't create a
* transaction.
*/
void
test_MultipleAsyncReadStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt1;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt1));
nsCOMPtr<mozIStorageAsyncStatement> stmt2;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt2));
mozIStorageBaseStatement *stmts[] = {
stmt1,
stmt2,
};
check_transaction(db, stmts, ArrayLength(stmts), false);
}
/**
* Test that executing multiple readonly Statements doesn't create a
* transaction.
*/
void
test_MultipleReadStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt1;
db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt1));
nsCOMPtr<mozIStorageStatement> stmt2;
db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt2));
mozIStorageBaseStatement *stmts[] = {
stmt1,
stmt2,
};
check_transaction(db, stmts, ArrayLength(stmts), false);
}
/**
* Test that executing multiple AsyncStatements causing writes creates a
* transaction.
*/
void
test_MultipleAsyncReadWriteStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt1;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt1));
nsCOMPtr<mozIStorageAsyncStatement> stmt2;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt2));
mozIStorageBaseStatement *stmts[] = {
stmt1,
stmt2,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing multiple Statements causing writes creates a transaction.
*/
void
test_MultipleReadWriteStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt1;
db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt1));
nsCOMPtr<mozIStorageStatement> stmt2;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt2));
mozIStorageBaseStatement *stmts[] = {
stmt1,
stmt2,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing multiple AsyncStatements causing writes creates a
* single transaction.
*/
void
test_MultipleAsyncWriteStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt1;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test1 (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt1));
nsCOMPtr<mozIStorageAsyncStatement> stmt2;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test2 (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt2));
mozIStorageBaseStatement *stmts[] = {
stmt1,
stmt2,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing multiple Statements causing writes creates a
* single transaction.
*/
void
test_MultipleWriteStatements()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt1;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test1 (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt1));
nsCOMPtr<mozIStorageStatement> stmt2;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test2 (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt2));
mozIStorageBaseStatement *stmts[] = {
stmt1,
stmt2,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing a single read-only AsyncStatement doesn't create a
* transaction.
*/
void
test_SingleAsyncReadStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt));
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), false);
}
/**
* Test that executing a single read-only Statement doesn't create a
* transaction.
*/
void
test_SingleReadStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt));
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), false);
}
/**
* Test that executing a single AsyncStatement causing writes creates a
* transaction.
*/
void
test_SingleAsyncWriteStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt));
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing a single Statement causing writes creates a transaction.
*/
void
test_SingleWriteStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt));
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing a single read-only AsyncStatement with multiple params
* doesn't create a transaction.
*/
void
test_MultipleParamsAsyncReadStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT :param FROM sqlite_master"
), getter_AddRefs(stmt));
// -- bind multiple BindingParams
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
for (int32_t i = 0; i < 2; i++) {
nsCOMPtr<mozIStorageBindingParams> params;
paramsArray->NewBindingParams(getter_AddRefs(params));
params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
paramsArray->AddParams(params);
}
stmt->BindParameters(paramsArray);
paramsArray = nullptr;
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), false);
}
/**
* Test that executing a single read-only Statement with multiple params
* doesn't create a transaction.
*/
void
test_MultipleParamsReadStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT :param FROM sqlite_master"
), getter_AddRefs(stmt));
// -- bind multiple BindingParams
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
for (int32_t i = 0; i < 2; i++) {
nsCOMPtr<mozIStorageBindingParams> params;
paramsArray->NewBindingParams(getter_AddRefs(params));
params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
paramsArray->AddParams(params);
}
stmt->BindParameters(paramsArray);
paramsArray = nullptr;
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), false);
}
/**
* Test that executing a single write AsyncStatement with multiple params
* creates a transaction.
*/
void
test_MultipleParamsAsyncWriteStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create a table for writes
nsCOMPtr<mozIStorageStatement> tableStmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(tableStmt));
tableStmt->Execute();
tableStmt->Finalize();
// -- create statements and execute them
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"DELETE FROM test WHERE id = :param"
), getter_AddRefs(stmt));
// -- bind multiple BindingParams
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
for (int32_t i = 0; i < 2; i++) {
nsCOMPtr<mozIStorageBindingParams> params;
paramsArray->NewBindingParams(getter_AddRefs(params));
params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
paramsArray->AddParams(params);
}
stmt->BindParameters(paramsArray);
paramsArray = nullptr;
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
/**
* Test that executing a single write Statement with multiple params
* creates a transaction.
*/
void
test_MultipleParamsWriteStatement()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- create a table for writes
nsCOMPtr<mozIStorageStatement> tableStmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(tableStmt));
tableStmt->Execute();
tableStmt->Finalize();
// -- create statements and execute them
nsCOMPtr<mozIStorageStatement> stmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM test WHERE id = :param"
), getter_AddRefs(stmt));
// -- bind multiple BindingParams
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
for (int32_t i = 0; i < 2; i++) {
nsCOMPtr<mozIStorageBindingParams> params;
paramsArray->NewBindingParams(getter_AddRefs(params));
params->BindInt32ByName(NS_LITERAL_CSTRING("param"), 1);
paramsArray->AddParams(params);
}
stmt->BindParameters(paramsArray);
paramsArray = nullptr;
mozIStorageBaseStatement *stmts[] = {
stmt,
};
check_transaction(db, stmts, ArrayLength(stmts), true);
}
void (*gTests[])(void) = {
test_MultipleAsyncReadStatements,
test_MultipleReadStatements,
test_MultipleAsyncReadWriteStatements,
test_MultipleReadWriteStatements,
test_MultipleAsyncWriteStatements,
test_MultipleWriteStatements,
test_SingleAsyncReadStatement,
test_SingleReadStatement,
test_SingleAsyncWriteStatement,
test_SingleWriteStatement,
test_MultipleParamsAsyncReadStatement,
test_MultipleParamsReadStatement,
test_MultipleParamsAsyncWriteStatement,
test_MultipleParamsWriteStatement,
};
const char *file = __FILE__;
#define TEST_NAME "async statement execution transaction"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,174 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
#include "storage_test_harness.h"
#include "prthread.h"
#include "nsIEventTarget.h"
#include "nsIInterfaceRequestorUtils.h"
#include "mozilla/Attributes.h"
#include "sqlite3.h"
////////////////////////////////////////////////////////////////////////////////
//// Async Helpers
/**
* Spins the events loop for current thread until aCondition is true.
*/
void
spin_events_loop_until_true(const bool* const aCondition)
{
nsCOMPtr<nsIThread> thread(::do_GetCurrentThread());
nsresult rv = NS_OK;
bool processed = true;
while (!(*aCondition) && NS_SUCCEEDED(rv)) {
rv = thread->ProcessNextEvent(true, &processed);
}
}
////////////////////////////////////////////////////////////////////////////////
//// mozIStorageStatementCallback implementation
class UnownedCallback final : public mozIStorageStatementCallback
{
public:
NS_DECL_ISUPPORTS
// Whether the object has been destroyed.
static bool sAlive;
// Whether the first result was received.
static bool sResult;
// Whether an error was received.
static bool sError;
explicit UnownedCallback(mozIStorageConnection* aDBConn)
: mDBConn(aDBConn)
, mCompleted(false)
{
sAlive = true;
sResult = false;
sError = false;
}
private:
~UnownedCallback()
{
sAlive = false;
blocking_async_close(mDBConn);
}
public:
NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override
{
sResult = true;
spin_events_loop_until_true(&mCompleted);
if (!sAlive) {
NS_RUNTIMEABORT("The statement callback was destroyed prematurely.");
}
return NS_OK;
}
NS_IMETHOD HandleError(mozIStorageError* aError) override
{
sError = true;
spin_events_loop_until_true(&mCompleted);
if (!sAlive) {
NS_RUNTIMEABORT("The statement callback was destroyed prematurely.");
}
return NS_OK;
}
NS_IMETHOD HandleCompletion(uint16_t aReason) override
{
mCompleted = true;
return NS_OK;
}
protected:
nsCOMPtr<mozIStorageConnection> mDBConn;
bool mCompleted;
};
NS_IMPL_ISUPPORTS(UnownedCallback, mozIStorageStatementCallback)
bool UnownedCallback::sAlive = false;
bool UnownedCallback::sResult = false;
bool UnownedCallback::sError = false;
////////////////////////////////////////////////////////////////////////////////
//// Tests
void
test_SpinEventsLoopInHandleResult()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create a test table and populate it.
nsCOMPtr<mozIStorageStatement> stmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt));
stmt->Execute();
stmt->Finalize();
db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (id) VALUES (?)"
), getter_AddRefs(stmt));
for (int32_t i = 0; i < 30; ++i) {
stmt->BindInt32ByIndex(0, i);
stmt->Execute();
stmt->Reset();
}
stmt->Finalize();
db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM test"
), getter_AddRefs(stmt));
nsCOMPtr<mozIStoragePendingStatement> ps;
do_check_success(stmt->ExecuteAsync(new UnownedCallback(db),
getter_AddRefs(ps)));
stmt->Finalize();
spin_events_loop_until_true(&UnownedCallback::sResult);
}
void
test_SpinEventsLoopInHandleError()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create a test table and populate it.
nsCOMPtr<mozIStorageStatement> stmt;
db->CreateStatement(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
), getter_AddRefs(stmt));
stmt->Execute();
stmt->Finalize();
db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (id) VALUES (1)"
), getter_AddRefs(stmt));
stmt->Execute();
stmt->Finalize();
// This will cause a constraint error.
db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (id) VALUES (1)"
), getter_AddRefs(stmt));
nsCOMPtr<mozIStoragePendingStatement> ps;
do_check_success(stmt->ExecuteAsync(new UnownedCallback(db),
getter_AddRefs(ps)));
stmt->Finalize();
spin_events_loop_until_true(&UnownedCallback::sError);
}
void (*gTests[])(void) = {
test_SpinEventsLoopInHandleResult,
test_SpinEventsLoopInHandleError,
};
const char *file = __FILE__;
#define TEST_NAME "test async callbacks with spun event loops"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,215 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
#include "mozStorageHelper.h"
using namespace mozilla;
/**
* This file tests binding and reading out string parameters through the
* mozIStorageStatement API.
*/
void
test_ASCIIString()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create table with a single string column.
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (str STRING)"
));
// Create statements to INSERT and SELECT the string.
nsCOMPtr<mozIStorageStatement> insert, select;
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (str) VALUES (?1)"
), getter_AddRefs(insert));
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT str FROM test"
), getter_AddRefs(select));
// Roundtrip a string through the table, and ensure it comes out as expected.
nsAutoCString inserted("I'm an ASCII string");
{
mozStorageStatementScoper scoper(insert);
bool hasResult;
do_check_true(NS_SUCCEEDED(insert->BindUTF8StringByIndex(0, inserted)));
do_check_true(NS_SUCCEEDED(insert->ExecuteStep(&hasResult)));
do_check_false(hasResult);
}
nsAutoCString result;
{
mozStorageStatementScoper scoper(select);
bool hasResult;
do_check_true(NS_SUCCEEDED(select->ExecuteStep(&hasResult)));
do_check_true(hasResult);
do_check_true(NS_SUCCEEDED(select->GetUTF8String(0, result)));
}
do_check_true(result == inserted);
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM test"));
}
void
test_CString()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create table with a single string column.
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (str STRING)"
));
// Create statements to INSERT and SELECT the string.
nsCOMPtr<mozIStorageStatement> insert, select;
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (str) VALUES (?1)"
), getter_AddRefs(insert));
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT str FROM test"
), getter_AddRefs(select));
// Roundtrip a string through the table, and ensure it comes out as expected.
static const char sCharArray[] =
"I'm not a \xff\x00\xac\xde\xbb ASCII string!";
nsAutoCString inserted(sCharArray, ArrayLength(sCharArray) - 1);
do_check_true(inserted.Length() == ArrayLength(sCharArray) - 1);
{
mozStorageStatementScoper scoper(insert);
bool hasResult;
do_check_true(NS_SUCCEEDED(insert->BindUTF8StringByIndex(0, inserted)));
do_check_true(NS_SUCCEEDED(insert->ExecuteStep(&hasResult)));
do_check_false(hasResult);
}
{
nsAutoCString result;
mozStorageStatementScoper scoper(select);
bool hasResult;
do_check_true(NS_SUCCEEDED(select->ExecuteStep(&hasResult)));
do_check_true(hasResult);
do_check_true(NS_SUCCEEDED(select->GetUTF8String(0, result)));
do_check_true(result == inserted);
}
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM test"));
}
void
test_UTFStrings()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create table with a single string column.
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (str STRING)"
));
// Create statements to INSERT and SELECT the string.
nsCOMPtr<mozIStorageStatement> insert, select;
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (str) VALUES (?1)"
), getter_AddRefs(insert));
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT str FROM test"
), getter_AddRefs(select));
// Roundtrip a UTF8 string through the table, using UTF8 input and output.
static const char sCharArray[] =
"I'm a \xc3\xbb\xc3\xbc\xc3\xa2\xc3\xa4\xc3\xa7 UTF8 string!";
nsAutoCString insertedUTF8(sCharArray, ArrayLength(sCharArray) - 1);
do_check_true(insertedUTF8.Length() == ArrayLength(sCharArray) - 1);
NS_ConvertUTF8toUTF16 insertedUTF16(insertedUTF8);
do_check_true(insertedUTF8 == NS_ConvertUTF16toUTF8(insertedUTF16));
{
mozStorageStatementScoper scoper(insert);
bool hasResult;
do_check_true(NS_SUCCEEDED(insert->BindUTF8StringByIndex(0, insertedUTF8)));
do_check_true(NS_SUCCEEDED(insert->ExecuteStep(&hasResult)));
do_check_false(hasResult);
}
{
nsAutoCString result;
mozStorageStatementScoper scoper(select);
bool hasResult;
do_check_true(NS_SUCCEEDED(select->ExecuteStep(&hasResult)));
do_check_true(hasResult);
do_check_true(NS_SUCCEEDED(select->GetUTF8String(0, result)));
do_check_true(result == insertedUTF8);
}
// Use UTF8 input and UTF16 output.
{
nsAutoString result;
mozStorageStatementScoper scoper(select);
bool hasResult;
do_check_true(NS_SUCCEEDED(select->ExecuteStep(&hasResult)));
do_check_true(hasResult);
do_check_true(NS_SUCCEEDED(select->GetString(0, result)));
do_check_true(result == insertedUTF16);
}
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM test"));
// Roundtrip the same string using UTF16 input and UTF8 output.
{
mozStorageStatementScoper scoper(insert);
bool hasResult;
do_check_true(NS_SUCCEEDED(insert->BindStringByIndex(0, insertedUTF16)));
do_check_true(NS_SUCCEEDED(insert->ExecuteStep(&hasResult)));
do_check_false(hasResult);
}
{
nsAutoCString result;
mozStorageStatementScoper scoper(select);
bool hasResult;
do_check_true(NS_SUCCEEDED(select->ExecuteStep(&hasResult)));
do_check_true(hasResult);
do_check_true(NS_SUCCEEDED(select->GetUTF8String(0, result)));
do_check_true(result == insertedUTF8);
}
// Use UTF16 input and UTF16 output.
{
nsAutoString result;
mozStorageStatementScoper scoper(select);
bool hasResult;
do_check_true(NS_SUCCEEDED(select->ExecuteStep(&hasResult)));
do_check_true(hasResult);
do_check_true(NS_SUCCEEDED(select->GetString(0, result)));
do_check_true(result == insertedUTF16);
}
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM test"));
}
void (*gTests[])(void) = {
test_ASCIIString,
test_CString,
test_UTFStrings,
};
const char *file = __FILE__;
#define TEST_NAME "binding string params"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,602 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: sw=4 ts=4 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/. */
/**
* Note: This file is a copy of xpcom/tests/TestDeadlockDetector.cpp, but all
* mutexes were turned into SQLiteMutexes.
*/
#include "prenv.h"
#include "prerror.h"
#include "prio.h"
#include "prproces.h"
#include "nsMemory.h"
#include "mozilla/CondVar.h"
#include "mozilla/ReentrantMonitor.h"
#include "SQLiteMutex.h"
#include "TestHarness.h"
using namespace mozilla;
/**
* Helper class to allocate a sqlite3_mutex for our SQLiteMutex. Also makes
* keeping the test files in sync easier.
*/
class TestMutex : public mozilla::storage::SQLiteMutex
{
public:
explicit TestMutex(const char* aName)
: mozilla::storage::SQLiteMutex(aName)
, mInner(sqlite3_mutex_alloc(SQLITE_MUTEX_FAST))
{
NS_ASSERTION(mInner, "could not allocate a sqlite3_mutex");
initWithMutex(mInner);
}
~TestMutex()
{
sqlite3_mutex_free(mInner);
}
void Lock()
{
lock();
}
void Unlock()
{
unlock();
}
private:
sqlite3_mutex *mInner;
};
static PRThread*
spawn(void (*run)(void*), void* arg)
{
return PR_CreateThread(PR_SYSTEM_THREAD,
run,
arg,
PR_PRIORITY_NORMAL,
PR_GLOBAL_THREAD,
PR_JOINABLE_THREAD,
0);
}
#define PASS() \
do { \
passed(__FUNCTION__); \
return NS_OK; \
} while (0)
#define FAIL(why) \
do { \
fail("%s | %s - %s", __FILE__, __FUNCTION__, why); \
return NS_ERROR_FAILURE; \
} while (0)
//-----------------------------------------------------------------------------
static const char* sPathToThisBinary;
static const char* sAssertBehaviorEnv = "XPCOM_DEBUG_BREAK=abort";
class Subprocess
{
public:
// not available until process finishes
int32_t mExitCode;
nsCString mStdout;
nsCString mStderr;
explicit Subprocess(const char* aTestName) {
// set up stdio redirection
PRFileDesc* readStdin; PRFileDesc* writeStdin;
PRFileDesc* readStdout; PRFileDesc* writeStdout;
PRFileDesc* readStderr; PRFileDesc* writeStderr;
PRProcessAttr* pattr = PR_NewProcessAttr();
NS_ASSERTION(pattr, "couldn't allocate process attrs");
NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdin, &writeStdin),
"couldn't create child stdin pipe");
NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(readStdin, true),
"couldn't set child stdin inheritable");
PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardInput, readStdin);
NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStdout, &writeStdout),
"couldn't create child stdout pipe");
NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStdout, true),
"couldn't set child stdout inheritable");
PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardOutput, writeStdout);
NS_ASSERTION(PR_SUCCESS == PR_CreatePipe(&readStderr, &writeStderr),
"couldn't create child stderr pipe");
NS_ASSERTION(PR_SUCCESS == PR_SetFDInheritable(writeStderr, true),
"couldn't set child stderr inheritable");
PR_ProcessAttrSetStdioRedirect(pattr, PR_StandardError, writeStderr);
// set up argv with test name to run
char* const newArgv[3] = {
strdup(sPathToThisBinary),
strdup(aTestName),
0
};
// make sure the child will abort if an assertion fails
NS_ASSERTION(PR_SUCCESS == PR_SetEnv(sAssertBehaviorEnv),
"couldn't set XPCOM_DEBUG_BREAK env var");
PRProcess* proc;
NS_ASSERTION(proc = PR_CreateProcess(sPathToThisBinary,
newArgv,
0, // inherit environment
pattr),
"couldn't create process");
PR_Close(readStdin);
PR_Close(writeStdout);
PR_Close(writeStderr);
mProc = proc;
mStdinfd = writeStdin;
mStdoutfd = readStdout;
mStderrfd = readStderr;
free(newArgv[0]);
free(newArgv[1]);
PR_DestroyProcessAttr(pattr);
}
void RunToCompletion(uint32_t aWaitMs)
{
PR_Close(mStdinfd);
PRPollDesc pollfds[2];
int32_t nfds;
bool stdoutOpen = true, stderrOpen = true;
char buf[4096];
PRIntervalTime now = PR_IntervalNow();
PRIntervalTime deadline = now + PR_MillisecondsToInterval(aWaitMs);
while ((stdoutOpen || stderrOpen) && now < deadline) {
nfds = 0;
if (stdoutOpen) {
pollfds[nfds].fd = mStdoutfd;
pollfds[nfds].in_flags = PR_POLL_READ;
pollfds[nfds].out_flags = 0;
++nfds;
}
if (stderrOpen) {
pollfds[nfds].fd = mStderrfd;
pollfds[nfds].in_flags = PR_POLL_READ;
pollfds[nfds].out_flags = 0;
++nfds;
}
int32_t rv = PR_Poll(pollfds, nfds, deadline - now);
NS_ASSERTION(0 <= rv, PR_ErrorToName(PR_GetError()));
if (0 == rv) { // timeout
fputs("(timed out!)\n", stderr);
Finish(false); // abnormal
return;
}
for (int32_t i = 0; i < nfds; ++i) {
if (!pollfds[i].out_flags)
continue;
bool isStdout = mStdoutfd == pollfds[i].fd;
int32_t len = 0;
if (PR_POLL_READ & pollfds[i].out_flags) {
len = PR_Read(pollfds[i].fd, buf, sizeof(buf) - 1);
NS_ASSERTION(0 <= len, PR_ErrorToName(PR_GetError()));
}
else if (!(PR_POLL_HUP & pollfds[i].out_flags)) {
NS_ERROR(PR_ErrorToName(PR_GetError()));
}
if (0 < len) {
buf[len] = '\0';
if (isStdout)
mStdout += buf;
else
mStderr += buf;
}
else if (isStdout) {
stdoutOpen = false;
}
else {
stderrOpen = false;
}
}
now = PR_IntervalNow();
}
if (stdoutOpen)
fputs("(stdout still open!)\n", stderr);
if (stderrOpen)
fputs("(stderr still open!)\n", stderr);
if (now > deadline)
fputs("(timed out!)\n", stderr);
Finish(!stdoutOpen && !stderrOpen && now <= deadline);
}
private:
void Finish(bool normalExit) {
if (!normalExit) {
PR_KillProcess(mProc);
mExitCode = -1;
int32_t dummy;
PR_WaitProcess(mProc, &dummy);
}
else {
PR_WaitProcess(mProc, &mExitCode); // this had better not block ...
}
PR_Close(mStdoutfd);
PR_Close(mStderrfd);
}
PRProcess* mProc;
PRFileDesc* mStdinfd; // writeable
PRFileDesc* mStdoutfd; // readable
PRFileDesc* mStderrfd; // readable
};
//-----------------------------------------------------------------------------
// Harness for checking detector errors
bool
CheckForDeadlock(const char* test, const char* const* findTokens)
{
Subprocess proc(test);
proc.RunToCompletion(5000);
if (0 == proc.mExitCode)
return false;
int32_t idx = 0;
for (const char* const* tp = findTokens; *tp; ++tp) {
const char* const token = *tp;
#ifdef MOZILLA_INTERNAL_API
idx = proc.mStderr.Find(token, false, idx);
#else
nsCString tokenCString(token);
idx = proc.mStderr.Find(tokenCString, idx);
#endif
if (-1 == idx) {
printf("(missed token '%s' in output)\n", token);
puts("----------------------------------\n");
puts(proc.mStderr.get());
puts("----------------------------------\n");
return false;
}
idx += strlen(token);
}
return true;
}
//-----------------------------------------------------------------------------
// Single-threaded sanity tests
// Stupidest possible deadlock.
int
Sanity_Child()
{
TestMutex m1("dd.sanity.m1");
m1.Lock();
m1.Lock();
return 0; // not reached
}
nsresult
Sanity()
{
const char* const tokens[] = {
"###!!! ERROR: Potential deadlock detected",
"=== Cyclical dependency starts at\n--- Mutex : dd.sanity.m1",
"=== Cycle completed at\n--- Mutex : dd.sanity.m1",
"###!!! Deadlock may happen NOW!", // better catch these easy cases...
"###!!! ASSERTION: Potential deadlock detected",
0
};
if (CheckForDeadlock("Sanity", tokens)) {
PASS();
} else {
FAIL("deadlock not detected");
}
}
// Slightly less stupid deadlock.
int
Sanity2_Child()
{
TestMutex m1("dd.sanity2.m1");
TestMutex m2("dd.sanity2.m2");
m1.Lock();
m2.Lock();
m1.Lock();
return 0; // not reached
}
nsresult
Sanity2()
{
const char* const tokens[] = {
"###!!! ERROR: Potential deadlock detected",
"=== Cyclical dependency starts at\n--- Mutex : dd.sanity2.m1",
"--- Next dependency:\n--- Mutex : dd.sanity2.m2",
"=== Cycle completed at\n--- Mutex : dd.sanity2.m1",
"###!!! Deadlock may happen NOW!", // better catch these easy cases...
"###!!! ASSERTION: Potential deadlock detected",
0
};
if (CheckForDeadlock("Sanity2", tokens)) {
PASS();
} else {
FAIL("deadlock not detected");
}
}
int
Sanity3_Child()
{
TestMutex m1("dd.sanity3.m1");
TestMutex m2("dd.sanity3.m2");
TestMutex m3("dd.sanity3.m3");
TestMutex m4("dd.sanity3.m4");
m1.Lock();
m2.Lock();
m3.Lock();
m4.Lock();
m4.Unlock();
m3.Unlock();
m2.Unlock();
m1.Unlock();
m4.Lock();
m1.Lock();
return 0;
}
nsresult
Sanity3()
{
const char* const tokens[] = {
"###!!! ERROR: Potential deadlock detected",
"=== Cyclical dependency starts at\n--- Mutex : dd.sanity3.m1",
"--- Next dependency:\n--- Mutex : dd.sanity3.m2",
"--- Next dependency:\n--- Mutex : dd.sanity3.m3",
"--- Next dependency:\n--- Mutex : dd.sanity3.m4",
"=== Cycle completed at\n--- Mutex : dd.sanity3.m1",
"###!!! ASSERTION: Potential deadlock detected",
0
};
if (CheckForDeadlock("Sanity3", tokens)) {
PASS();
} else {
FAIL("deadlock not detected");
}
}
int
Sanity4_Child()
{
mozilla::ReentrantMonitor m1("dd.sanity4.m1");
TestMutex m2("dd.sanity4.m2");
m1.Enter();
m2.Lock();
m1.Enter();
return 0;
}
nsresult
Sanity4()
{
const char* const tokens[] = {
"Re-entering ReentrantMonitor after acquiring other resources",
"###!!! ERROR: Potential deadlock detected",
"=== Cyclical dependency starts at\n--- ReentrantMonitor : dd.sanity4.m1",
"--- Next dependency:\n--- Mutex : dd.sanity4.m2",
"=== Cycle completed at\n--- ReentrantMonitor : dd.sanity4.m1",
"###!!! ASSERTION: Potential deadlock detected",
0
};
if (CheckForDeadlock("Sanity4", tokens)) {
PASS();
} else {
FAIL("deadlock not detected");
}
}
//-----------------------------------------------------------------------------
// Multithreaded tests
TestMutex* ttM1;
TestMutex* ttM2;
static void
TwoThreads_thread(void* arg)
{
int32_t m1First = NS_PTR_TO_INT32(arg);
if (m1First) {
ttM1->Lock();
ttM2->Lock();
ttM2->Unlock();
ttM1->Unlock();
}
else {
ttM2->Lock();
ttM1->Lock();
ttM1->Unlock();
ttM2->Unlock();
}
}
int
TwoThreads_Child()
{
ttM1 = new TestMutex("dd.twothreads.m1");
ttM2 = new TestMutex("dd.twothreads.m2");
if (!ttM1 || !ttM2)
NS_RUNTIMEABORT("couldn't allocate mutexes");
PRThread* t1 = spawn(TwoThreads_thread, (void*) 0);
PR_JoinThread(t1);
PRThread* t2 = spawn(TwoThreads_thread, (void*) 1);
PR_JoinThread(t2);
return 0;
}
nsresult
TwoThreads()
{
const char* const tokens[] = {
"###!!! ERROR: Potential deadlock detected",
"=== Cyclical dependency starts at\n--- Mutex : dd.twothreads.m2",
"--- Next dependency:\n--- Mutex : dd.twothreads.m1",
"=== Cycle completed at\n--- Mutex : dd.twothreads.m2",
"###!!! ASSERTION: Potential deadlock detected",
0
};
if (CheckForDeadlock("TwoThreads", tokens)) {
PASS();
} else {
FAIL("deadlock not detected");
}
}
TestMutex* cndMs[4];
const uint32_t K = 100000;
static void
ContentionNoDeadlock_thread(void* arg)
{
int32_t starti = NS_PTR_TO_INT32(arg);
for (uint32_t k = 0; k < K; ++k) {
for (int32_t i = starti; i < (int32_t) ArrayLength(cndMs); ++i)
cndMs[i]->Lock();
// comment out the next two lines for deadlocking fun!
for (int32_t i = ArrayLength(cndMs) - 1; i >= starti; --i)
cndMs[i]->Unlock();
starti = (starti + 1) % 3;
}
}
int
ContentionNoDeadlock_Child()
{
PRThread* threads[3];
for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
cndMs[i] = new TestMutex("dd.cnd.ms");
for (int32_t i = 0; i < (int32_t) ArrayLength(threads); ++i)
threads[i] = spawn(ContentionNoDeadlock_thread, NS_INT32_TO_PTR(i));
for (uint32_t i = 0; i < ArrayLength(threads); ++i)
PR_JoinThread(threads[i]);
for (uint32_t i = 0; i < ArrayLength(cndMs); ++i)
delete cndMs[i];
return 0;
}
nsresult
ContentionNoDeadlock()
{
const char * func = __func__;
Subprocess proc(func);
proc.RunToCompletion(60000);
if (0 != proc.mExitCode) {
printf("(expected 0 == return code, got %d)\n", proc.mExitCode);
puts("(output)\n----------------------------------\n");
puts(proc.mStdout.get());
puts("----------------------------------\n");
puts("(error output)\n----------------------------------\n");
puts(proc.mStderr.get());
puts("----------------------------------\n");
FAIL("deadlock");
}
PASS();
}
//-----------------------------------------------------------------------------
int
main(int argc, char** argv)
{
if (1 < argc) {
// XXX can we run w/o scoped XPCOM?
const char* test = argv[1];
ScopedXPCOM xpcom(test);
if (xpcom.failed())
return 1;
// running in a spawned process. call the specificed child function.
if (!strcmp("Sanity", test))
return Sanity_Child();
if (!strcmp("Sanity2", test))
return Sanity2_Child();
if (!strcmp("Sanity3", test))
return Sanity3_Child();
if (!strcmp("Sanity4", test))
return Sanity4_Child();
if (!strcmp("TwoThreads", test))
return TwoThreads_Child();
if (!strcmp("ContentionNoDeadlock", test))
return ContentionNoDeadlock_Child();
fail("%s | %s - unknown child test", __FILE__, __FUNCTION__);
return 1;
}
ScopedXPCOM xpcom("Storage deadlock detector correctness (" __FILE__ ")");
if (xpcom.failed())
return 1;
// in the first invocation of this process. we will be the "driver".
int rv = 0;
sPathToThisBinary = argv[0];
if (NS_FAILED(Sanity()))
rv = 1;
if (NS_FAILED(Sanity2()))
rv = 1;
if (NS_FAILED(Sanity3()))
rv = 1;
if (NS_FAILED(Sanity4()))
rv = 1;
if (NS_FAILED(TwoThreads()))
rv = 1;
if (NS_FAILED(ContentionNoDeadlock()))
rv = 1;
return rv;
}

View file

@ -0,0 +1,44 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
#include "nsIFile.h"
#include "prio.h"
/**
* This file tests that the file permissions of the sqlite files match what
* we request they be
*/
void
test_file_perms()
{
nsCOMPtr<mozIStorageConnection> db(getDatabase());
nsCOMPtr<nsIFile> dbFile;
do_check_success(db->GetDatabaseFile(getter_AddRefs(dbFile)));
uint32_t perms = 0;
do_check_success(dbFile->GetPermissions(&perms));
// This reflexts the permissions defined by SQLITE_DEFAULT_FILE_PERMISSIONS in
// db/sqlite3/src/Makefile.in and must be kept in sync with that
#ifdef ANDROID
do_check_true(perms == (PR_IRUSR | PR_IWUSR));
#elif defined(XP_WIN)
do_check_true(perms == (PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IWGRP | PR_IROTH | PR_IWOTH));
#else
do_check_true(perms == (PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IROTH));
#endif
}
void (*gTests[])(void) = {
test_file_perms,
};
const char *file = __FILE__;
#define TEST_NAME "file perms"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,85 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
#include "SQLiteMutex.h"
using namespace mozilla;
using namespace mozilla::storage;
/**
* This file test our sqlite3_mutex wrapper in SQLiteMutex.h.
*/
void
test_AutoLock()
{
int lockTypes[] = {
SQLITE_MUTEX_FAST,
SQLITE_MUTEX_RECURSIVE,
};
for (size_t i = 0; i < ArrayLength(lockTypes); i++) {
// Get our test mutex (we have to allocate a SQLite mutex of the right type
// too!).
SQLiteMutex mutex("TestMutex");
sqlite3_mutex *inner = sqlite3_mutex_alloc(lockTypes[i]);
do_check_true(inner);
mutex.initWithMutex(inner);
// And test that our automatic locking wrapper works as expected.
mutex.assertNotCurrentThreadOwns();
{
SQLiteMutexAutoLock lockedScope(mutex);
mutex.assertCurrentThreadOwns();
}
mutex.assertNotCurrentThreadOwns();
// Free the wrapped mutex - we don't need it anymore.
sqlite3_mutex_free(inner);
}
}
void
test_AutoUnlock()
{
int lockTypes[] = {
SQLITE_MUTEX_FAST,
SQLITE_MUTEX_RECURSIVE,
};
for (size_t i = 0; i < ArrayLength(lockTypes); i++) {
// Get our test mutex (we have to allocate a SQLite mutex of the right type
// too!).
SQLiteMutex mutex("TestMutex");
sqlite3_mutex *inner = sqlite3_mutex_alloc(lockTypes[i]);
do_check_true(inner);
mutex.initWithMutex(inner);
// And test that our automatic unlocking wrapper works as expected.
{
SQLiteMutexAutoLock lockedScope(mutex);
{
SQLiteMutexAutoUnlock unlockedScope(mutex);
mutex.assertNotCurrentThreadOwns();
}
mutex.assertCurrentThreadOwns();
}
// Free the wrapped mutex - we don't need it anymore.
sqlite3_mutex_free(inner);
}
}
void (*gTests[])(void) = {
test_AutoLock,
test_AutoUnlock,
};
const char *file = __FILE__;
#define TEST_NAME "SQLiteMutex"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,58 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim set: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/. */
#include "storage_test_harness.h"
#include "nsThreadUtils.h"
/**
* This file tests that the storage service can be initialized off of the main
* thread without issue.
*/
////////////////////////////////////////////////////////////////////////////////
//// Helpers
class ServiceInitializer : public mozilla::Runnable
{
public:
NS_IMETHOD Run() override
{
// Use an explicit do_GetService instead of getService so that the check in
// getService doesn't blow up.
nsCOMPtr<mozIStorageService> service = do_GetService("@mozilla.org/storage/service;1");
do_check_false(service);
return NS_OK;
}
};
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
void
test_service_initialization_on_background_thread()
{
nsCOMPtr<nsIRunnable> event = new ServiceInitializer();
do_check_true(event);
nsCOMPtr<nsIThread> thread;
do_check_success(NS_NewThread(getter_AddRefs(thread)));
do_check_success(thread->Dispatch(event, NS_DISPATCH_NORMAL));
// Shutting down the thread will spin the event loop until all work in its
// event queue is completed. This will act as our thread synchronization.
do_check_success(thread->Shutdown());
}
void (*gTests[])(void) = {
test_service_initialization_on_background_thread,
};
const char *file = __FILE__;
#define TEST_NAME "Background Thread Initialization"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,104 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
#include "mozStorageHelper.h"
/**
* This file test our statement scoper in mozStorageHelper.h.
*/
void
test_automatic_reset()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Need to create a table to populate sqlite_master with an entry.
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
nsCOMPtr<mozIStorageStatement> stmt;
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt));
// Reality check - make sure we start off in the right state.
int32_t state = -1;
(void)stmt->GetState(&state);
do_check_true(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_READY);
// Start executing the statement, which will put it into an executing state.
{
mozStorageStatementScoper scoper(stmt);
bool hasMore;
do_check_true(NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)));
// Reality check that we are executing.
state = -1;
(void)stmt->GetState(&state);
do_check_true(state ==
mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING);
}
// And we should be ready again.
state = -1;
(void)stmt->GetState(&state);
do_check_true(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_READY);
}
void
test_Abandon()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Need to create a table to populate sqlite_master with an entry.
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
nsCOMPtr<mozIStorageStatement> stmt;
(void)db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM sqlite_master"
), getter_AddRefs(stmt));
// Reality check - make sure we start off in the right state.
int32_t state = -1;
(void)stmt->GetState(&state);
do_check_true(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_READY);
// Start executing the statement, which will put it into an executing state.
{
mozStorageStatementScoper scoper(stmt);
bool hasMore;
do_check_true(NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)));
// Reality check that we are executing.
state = -1;
(void)stmt->GetState(&state);
do_check_true(state ==
mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING);
// And call Abandon. We should not reset now when we fall out of scope.
scoper.Abandon();
}
// And we should still be executing.
state = -1;
(void)stmt->GetState(&state);
do_check_true(state == mozIStorageStatement::MOZ_STORAGE_STATEMENT_EXECUTING);
}
void (*gTests[])(void) = {
test_automatic_reset,
test_Abandon,
};
const char *file = __FILE__;
#define TEST_NAME "statement scoper"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,175 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
#include "mozStorageHelper.h"
#include "mozStorageConnection.h"
using namespace mozilla;
using namespace mozilla::storage;
bool has_transaction(mozIStorageConnection* aDB) {
return !(static_cast<Connection *>(aDB)->getAutocommit());
}
/**
* This file test our Transaction helper in mozStorageHelper.h.
*/
void
test_Commit()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create a table in a transaction, call Commit, and make sure that it does
// exists after the transaction falls out of scope.
{
mozStorageTransaction transaction(db, false);
do_check_true(has_transaction(db));
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
(void)transaction.Commit();
}
do_check_false(has_transaction(db));
bool exists = false;
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
do_check_true(exists);
}
void
test_Rollback()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create a table in a transaction, call Rollback, and make sure that it does
// not exists after the transaction falls out of scope.
{
mozStorageTransaction transaction(db, true);
do_check_true(has_transaction(db));
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
(void)transaction.Rollback();
}
do_check_false(has_transaction(db));
bool exists = true;
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
do_check_false(exists);
}
void
test_AutoCommit()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create a table in a transaction, and make sure that it exists after the
// transaction falls out of scope. This means the Commit was successful.
{
mozStorageTransaction transaction(db, true);
do_check_true(has_transaction(db));
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
}
do_check_false(has_transaction(db));
bool exists = false;
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
do_check_true(exists);
}
void
test_AutoRollback()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Create a table in a transaction, and make sure that it does not exists
// after the transaction falls out of scope. This means the Rollback was
// successful.
{
mozStorageTransaction transaction(db, false);
do_check_true(has_transaction(db));
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
}
do_check_false(has_transaction(db));
bool exists = true;
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
do_check_false(exists);
}
void
test_null_database_connection()
{
// We permit the use of the Transaction helper when passing a null database
// in, so we need to make sure this still works without crashing.
mozStorageTransaction transaction(nullptr, false);
do_check_true(NS_SUCCEEDED(transaction.Commit()));
do_check_true(NS_SUCCEEDED(transaction.Rollback()));
}
void
test_async_Commit()
{
// note this will be active for any following test.
hook_sqlite_mutex();
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- wedge the thread
nsCOMPtr<nsIThread> target(get_conn_async_thread(db));
do_check_true(target);
RefPtr<ThreadWedger> wedger (new ThreadWedger(target));
{
mozStorageTransaction transaction(db, false,
mozIStorageConnection::TRANSACTION_DEFERRED,
true);
do_check_true(has_transaction(db));
(void)db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY)"
));
(void)transaction.Commit();
}
do_check_true(has_transaction(db));
// -- unwedge the async thread
wedger->unwedge();
// Ensure the transaction has done its job by enqueueing an async execution.
nsCOMPtr<mozIStorageAsyncStatement> stmt;
(void)db->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT NULL"
), getter_AddRefs(stmt));
blocking_async_execute(stmt);
stmt->Finalize();
do_check_false(has_transaction(db));
bool exists = false;
(void)db->TableExists(NS_LITERAL_CSTRING("test"), &exists);
do_check_true(exists);
blocking_async_close(db);
}
void (*gTests[])(void) = {
test_Commit,
test_Rollback,
test_AutoCommit,
test_AutoRollback,
test_null_database_connection,
test_async_Commit,
};
const char *file = __FILE__;
#define TEST_NAME "transaction helper"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,186 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#include "storage_test_harness.h"
////////////////////////////////////////////////////////////////////////////////
//// Tests
void
test_TrueAsyncStatement()
{
// (only the first test needs to call this)
hook_sqlite_mutex();
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// Start watching for forbidden mutex usage.
watch_for_mutex_use_on_this_thread();
// - statement with nothing to bind
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(
NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
getter_AddRefs(stmt)
);
blocking_async_execute(stmt);
stmt->Finalize();
do_check_false(mutex_used_on_watched_thread);
// - statement with something to bind ordinally
db->CreateAsyncStatement(
NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (?)"),
getter_AddRefs(stmt)
);
stmt->BindInt32ByIndex(0, 1);
blocking_async_execute(stmt);
stmt->Finalize();
do_check_false(mutex_used_on_watched_thread);
// - statement with something to bind by name
db->CreateAsyncStatement(
NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (:id)"),
getter_AddRefs(stmt)
);
nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
nsCOMPtr<mozIStorageBindingParams> params;
paramsArray->NewBindingParams(getter_AddRefs(params));
params->BindInt32ByName(NS_LITERAL_CSTRING("id"), 2);
paramsArray->AddParams(params);
params = nullptr;
stmt->BindParameters(paramsArray);
paramsArray = nullptr;
blocking_async_execute(stmt);
stmt->Finalize();
do_check_false(mutex_used_on_watched_thread);
// - now, make sure creating a sync statement does trigger our guard.
// (If this doesn't happen, our test is bunk and it's important to know that.)
nsCOMPtr<mozIStorageStatement> syncStmt;
db->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM test"),
getter_AddRefs(syncStmt));
syncStmt->Finalize();
do_check_true(mutex_used_on_watched_thread);
blocking_async_close(db);
}
/**
* Test that cancellation before a statement is run successfully stops the
* statement from executing.
*/
void
test_AsyncCancellation()
{
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
// -- wedge the thread
nsCOMPtr<nsIThread> target(get_conn_async_thread(db));
do_check_true(target);
RefPtr<ThreadWedger> wedger (new ThreadWedger(target));
// -- create statements and cancel them
// - async
nsCOMPtr<mozIStorageAsyncStatement> asyncStmt;
db->CreateAsyncStatement(
NS_LITERAL_CSTRING("CREATE TABLE asyncTable (id INTEGER PRIMARY KEY)"),
getter_AddRefs(asyncStmt)
);
RefPtr<AsyncStatementSpinner> asyncSpin(new AsyncStatementSpinner());
nsCOMPtr<mozIStoragePendingStatement> asyncPend;
(void)asyncStmt->ExecuteAsync(asyncSpin, getter_AddRefs(asyncPend));
do_check_true(asyncPend);
asyncPend->Cancel();
// - sync
nsCOMPtr<mozIStorageStatement> syncStmt;
db->CreateStatement(
NS_LITERAL_CSTRING("CREATE TABLE syncTable (id INTEGER PRIMARY KEY)"),
getter_AddRefs(syncStmt)
);
RefPtr<AsyncStatementSpinner> syncSpin(new AsyncStatementSpinner());
nsCOMPtr<mozIStoragePendingStatement> syncPend;
(void)syncStmt->ExecuteAsync(syncSpin, getter_AddRefs(syncPend));
do_check_true(syncPend);
syncPend->Cancel();
// -- unwedge the async thread
wedger->unwedge();
// -- verify that both statements report they were canceled
asyncSpin->SpinUntilCompleted();
do_check_true(asyncSpin->completionReason ==
mozIStorageStatementCallback::REASON_CANCELED);
syncSpin->SpinUntilCompleted();
do_check_true(syncSpin->completionReason ==
mozIStorageStatementCallback::REASON_CANCELED);
// -- verify that neither statement constructed their tables
nsresult rv;
bool exists;
rv = db->TableExists(NS_LITERAL_CSTRING("asyncTable"), &exists);
do_check_true(rv == NS_OK);
do_check_false(exists);
rv = db->TableExists(NS_LITERAL_CSTRING("syncTable"), &exists);
do_check_true(rv == NS_OK);
do_check_false(exists);
// -- cleanup
asyncStmt->Finalize();
syncStmt->Finalize();
blocking_async_close(db);
}
/**
* Test that the destructor for an asynchronous statement which has a
* sqlite3_stmt will dispatch that statement to the async thread for
* finalization rather than trying to finalize it on the main thread
* (and thereby running afoul of our mutex use detector).
*/
void test_AsyncDestructorFinalizesOnAsyncThread()
{
// test_TrueAsyncStatement called hook_sqlite_mutex() for us
nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
watch_for_mutex_use_on_this_thread();
// -- create an async statement
nsCOMPtr<mozIStorageAsyncStatement> stmt;
db->CreateAsyncStatement(
NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
getter_AddRefs(stmt)
);
// -- execute it so it gets a sqlite3_stmt that needs to be finalized
blocking_async_execute(stmt);
do_check_false(mutex_used_on_watched_thread);
// -- forget our reference
stmt = nullptr;
// -- verify the mutex was not touched
do_check_false(mutex_used_on_watched_thread);
// -- make sure the statement actually gets finalized / cleanup
// the close will assert if we failed to finalize!
blocking_async_close(db);
}
void (*gTests[])(void) = {
// this test must be first because it hooks the mutex mechanics
test_TrueAsyncStatement,
test_AsyncCancellation,
test_AsyncDestructorFinalizesOnAsyncThread
};
const char *file = __FILE__;
#define TEST_NAME "true async statement"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

View file

@ -0,0 +1,266 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim set: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/. */
#include "storage_test_harness.h"
#include "mozilla/ReentrantMonitor.h"
#include "nsThreadUtils.h"
#include "mozIStorageStatement.h"
/**
* This file tests that our implementation around sqlite3_unlock_notify works
* as expected.
*/
////////////////////////////////////////////////////////////////////////////////
//// Helpers
enum State {
STARTING,
WRITE_LOCK,
READ_LOCK,
TEST_DONE
};
class DatabaseLocker : public mozilla::Runnable
{
public:
explicit DatabaseLocker(const char* aSQL)
: monitor("DatabaseLocker::monitor")
, mSQL(aSQL)
, mState(STARTING)
{
}
void RunInBackground()
{
(void)NS_NewNamedThread("DatabaseLocker", getter_AddRefs(mThread));
do_check_true(mThread);
do_check_success(mThread->Dispatch(this, NS_DISPATCH_NORMAL));
}
void Shutdown()
{
if (mThread) {
mThread->Shutdown();
}
}
NS_IMETHOD Run() override
{
mozilla::ReentrantMonitorAutoEnter lock(monitor);
nsCOMPtr<mozIStorageConnection> db(getDatabase());
nsCString sql(mSQL);
nsCOMPtr<mozIStorageStatement> stmt;
do_check_success(db->CreateStatement(sql, getter_AddRefs(stmt)));
bool hasResult;
do_check_success(stmt->ExecuteStep(&hasResult));
Notify(WRITE_LOCK);
WaitFor(TEST_DONE);
return NS_OK;
}
void WaitFor(State aState)
{
monitor.AssertCurrentThreadIn();
while (mState != aState) {
do_check_success(monitor.Wait());
}
}
void Notify(State aState)
{
monitor.AssertCurrentThreadIn();
mState = aState;
do_check_success(monitor.Notify());
}
mozilla::ReentrantMonitor monitor;
protected:
nsCOMPtr<nsIThread> mThread;
const char *const mSQL;
State mState;
};
class DatabaseTester : public DatabaseLocker
{
public:
DatabaseTester(mozIStorageConnection *aConnection,
const char* aSQL)
: DatabaseLocker(aSQL)
, mConnection(aConnection)
{
}
NS_IMETHOD Run() override
{
mozilla::ReentrantMonitorAutoEnter lock(monitor);
WaitFor(READ_LOCK);
nsCString sql(mSQL);
nsCOMPtr<mozIStorageStatement> stmt;
do_check_success(mConnection->CreateStatement(sql, getter_AddRefs(stmt)));
bool hasResult;
nsresult rv = stmt->ExecuteStep(&hasResult);
do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
// Finalize our statement and null out our connection before notifying to
// ensure that we close on the proper thread.
rv = stmt->Finalize();
do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
mConnection = nullptr;
Notify(TEST_DONE);
return NS_OK;
}
private:
nsCOMPtr<mozIStorageConnection> mConnection;
};
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
void
setup()
{
nsCOMPtr<mozIStorageConnection> db(getDatabase());
// Create and populate a dummy table.
nsresult rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)"
));
do_check_success(rv);
rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO test (data) VALUES ('foo')"
));
do_check_success(rv);
rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO test (data) VALUES ('bar')"
));
do_check_success(rv);
rv = db->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE UNIQUE INDEX unique_data ON test (data)"
));
do_check_success(rv);
}
void
test_step_locked_does_not_block_main_thread()
{
nsCOMPtr<mozIStorageConnection> db(getDatabase());
// Need to prepare our statement ahead of time so we make sure to only test
// step and not prepare.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO test (data) VALUES ('test1')"
), getter_AddRefs(stmt));
do_check_success(rv);
RefPtr<DatabaseLocker> locker(new DatabaseLocker("SELECT * FROM test"));
do_check_true(locker);
{
mozilla::ReentrantMonitorAutoEnter lock(locker->monitor);
locker->RunInBackground();
// Wait for the locker to notify us that it has locked the database properly.
locker->WaitFor(WRITE_LOCK);
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
locker->Notify(TEST_DONE);
}
locker->Shutdown();
}
void
test_drop_index_does_not_loop()
{
nsCOMPtr<mozIStorageConnection> db(getDatabase());
// Need to prepare our statement ahead of time so we make sure to only test
// step and not prepare.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM test"
), getter_AddRefs(stmt));
do_check_success(rv);
RefPtr<DatabaseTester> tester =
new DatabaseTester(db, "DROP INDEX unique_data");
do_check_true(tester);
{
mozilla::ReentrantMonitorAutoEnter lock(tester->monitor);
tester->RunInBackground();
// Hold a read lock on the database, and then let the tester try to execute.
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
do_check_success(rv);
do_check_true(hasResult);
tester->Notify(READ_LOCK);
// Make sure the tester finishes its test before we move on.
tester->WaitFor(TEST_DONE);
}
tester->Shutdown();
}
void
test_drop_table_does_not_loop()
{
nsCOMPtr<mozIStorageConnection> db(getDatabase());
// Need to prepare our statement ahead of time so we make sure to only test
// step and not prepare.
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = db->CreateStatement(NS_LITERAL_CSTRING(
"SELECT * FROM test"
), getter_AddRefs(stmt));
do_check_success(rv);
RefPtr<DatabaseTester> tester(new DatabaseTester(db, "DROP TABLE test"));
do_check_true(tester);
{
mozilla::ReentrantMonitorAutoEnter lock(tester->monitor);
tester->RunInBackground();
// Hold a read lock on the database, and then let the tester try to execute.
bool hasResult;
rv = stmt->ExecuteStep(&hasResult);
do_check_success(rv);
do_check_true(hasResult);
tester->Notify(READ_LOCK);
// Make sure the tester finishes its test before we move on.
tester->WaitFor(TEST_DONE);
}
tester->Shutdown();
}
void (*gTests[])(void) = {
setup,
test_step_locked_does_not_block_main_thread,
test_drop_index_does_not_loop,
test_drop_table_does_not_loop,
};
const char *file = __FILE__;
#define TEST_NAME "sqlite3_unlock_notify"
#define TEST_FILE file
#include "storage_test_harness_tail.h"

Binary file not shown.

View file

@ -0,0 +1 @@
BACON

View file

@ -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();

View file

@ -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
å

View file

@ -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();
}
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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.

View file

@ -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.

View file

@ -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);
});

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
});
}

View file

@ -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")));
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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;
});

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
});

View file

@ -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);
}

View file

@ -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();
});

View file

@ -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());
}
}

View file

@ -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);

View file

@ -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

View file

@ -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"