summaryrefslogtreecommitdiffstats
path: root/storage/test/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /storage/test/gtest
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/test/gtest')
-rw-r--r--storage/test/gtest/moz.build39
-rw-r--r--storage/test/gtest/storage_test_harness.cpp220
-rw-r--r--storage/test/gtest/storage_test_harness.h206
-rw-r--r--storage/test/gtest/test_AsXXX_helpers.cpp111
-rw-r--r--storage/test/gtest/test_StatementCache.cpp132
-rw-r--r--storage/test/gtest/test_asyncStatementExecution_transaction.cpp445
-rw-r--r--storage/test/gtest/test_async_callbacks_with_spun_event_loops.cpp146
-rw-r--r--storage/test/gtest/test_binding_params.cpp186
-rw-r--r--storage/test/gtest/test_deadlock_detector.cpp56
-rw-r--r--storage/test/gtest/test_file_perms.cpp35
-rw-r--r--storage/test/gtest/test_interruptSynchronousConnection.cpp81
-rw-r--r--storage/test/gtest/test_mutex.cpp73
-rw-r--r--storage/test/gtest/test_spinningSynchronousClose.cpp72
-rw-r--r--storage/test/gtest/test_statement_scoper.cpp86
-rw-r--r--storage/test/gtest/test_transaction_helper.cpp184
-rw-r--r--storage/test/gtest/test_true_async.cpp161
-rw-r--r--storage/test/gtest/test_unlock_notify.cpp234
17 files changed, 2467 insertions, 0 deletions
diff --git a/storage/test/gtest/moz.build b/storage/test/gtest/moz.build
new file mode 100644
index 0000000000..62f91b6d95
--- /dev/null
+++ b/storage/test/gtest/moz.build
@@ -0,0 +1,39 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ "storage_test_harness.cpp",
+ "test_AsXXX_helpers.cpp",
+ "test_async_callbacks_with_spun_event_loops.cpp",
+ "test_asyncStatementExecution_transaction.cpp",
+ "test_binding_params.cpp",
+ "test_file_perms.cpp",
+ "test_interruptSynchronousConnection.cpp",
+ "test_mutex.cpp",
+ "test_spinningSynchronousClose.cpp",
+ "test_statement_scoper.cpp",
+ "test_StatementCache.cpp",
+ "test_transaction_helper.cpp",
+ "test_true_async.cpp",
+ "test_unlock_notify.cpp",
+]
+
+if (
+ CONFIG["MOZ_DEBUG"]
+ and CONFIG["OS_ARCH"] not in ("WINNT")
+ and CONFIG["OS_TARGET"] != "Android"
+):
+ # FIXME bug 523392: test_deadlock_detector doesn't like Windows
+ # Bug 1054249: Doesn't work on Android
+ UNIFIED_SOURCES += [
+ "test_deadlock_detector.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "../..",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/storage/test/gtest/storage_test_harness.cpp b/storage/test/gtest/storage_test_harness.cpp
new file mode 100644
index 0000000000..3f5a9ff82b
--- /dev/null
+++ b/storage/test/gtest/storage_test_harness.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 storage_test_harness_
+#define storage_test_harness_
+
+#include "storage_test_harness.h"
+
+already_AddRefed<mozIStorageService> getService() {
+ nsCOMPtr<mozIStorageService> ss =
+ do_CreateInstance("@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(
+ kMozStorageMemoryStorageKey, VoidCString(),
+ mozIStorageService::CONNECTION_DEFAULT, getter_AddRefs(conn));
+ do_check_success(rv);
+ return conn.forget();
+}
+
+already_AddRefed<mozIStorageConnection> getDatabase(nsIFile* aDBFile,
+ uint32_t aConnectionFlags) {
+ nsCOMPtr<nsIFile> dbFile;
+ nsresult rv;
+ if (!aDBFile) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Can't get tmp dir off mainthread.");
+ (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(dbFile));
+ NS_ASSERTION(dbFile, "The directory doesn't exists?!");
+
+ rv = dbFile->Append(u"storage_test_db.sqlite"_ns);
+ do_check_success(rv);
+ } else {
+ dbFile = aDBFile;
+ }
+
+ nsCOMPtr<mozIStorageService> ss = getService();
+ nsCOMPtr<mozIStorageConnection> conn;
+ rv = ss->OpenDatabase(dbFile, aConnectionFlags, getter_AddRefs(conn));
+ do_check_success(rv);
+ return conn.forget();
+}
+
+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.
+ */
+nsIThread* 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) {
+ if (PR_GetCurrentThread() == watched_thread)
+ mutex_used_on_watched_thread = true;
+ else
+ last_non_watched_thread = NS_GetCurrentThread();
+ 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);
+}
+
+/**
+ * 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;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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("SELECT 1"_ns, getter_AddRefs(stmt));
+ blocking_async_execute(stmt);
+ stmt->Finalize();
+
+ nsCOMPtr<nsIThread> asyncThread = last_non_watched_thread;
+
+ // 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);
+ do_check_eq(allegedAsyncThread, asyncThread);
+ return asyncThread.forget();
+}
+
+#endif // storage_test_harness_h__
diff --git a/storage/test/gtest/storage_test_harness.h b/storage/test/gtest/storage_test_harness.h
new file mode 100644
index 0000000000..7df951fedb
--- /dev/null
+++ b/storage/test/gtest/storage_test_harness.h
@@ -0,0 +1,206 @@
+/* -*- 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 storage_test_harness_h__
+#define storage_test_harness_h__
+
+#include "gtest/gtest.h"
+
+#include "prthread.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.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"
+
+#define do_check_true(aCondition) EXPECT_TRUE(aCondition)
+
+#define do_check_false(aCondition) EXPECT_FALSE(aCondition)
+
+#define do_check_success(aResult) do_check_true(NS_SUCCEEDED(aResult))
+
+#define do_check_eq(aExpected, aActual) do_check_true(aExpected == aActual)
+
+#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
+
+already_AddRefed<mozIStorageService> getService();
+
+already_AddRefed<mozIStorageConnection> getMemoryDatabase();
+
+already_AddRefed<mozIStorageConnection> getDatabase(
+ nsIFile* aDBFile = nullptr,
+ uint32_t aConnectionFlags = mozIStorageService::CONNECTION_DEFAULT);
+
+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;
+};
+
+#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);
+
+/**
+ * Invoke AsyncClose on the given connection, blocking the main thread until we
+ * get the completion notification.
+ */
+void blocking_async_close(mozIStorageConnection* db);
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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.
+ */
+
+extern sqlite3_mutex_methods orig_mutex_methods;
+extern sqlite3_mutex_methods wrapped_mutex_methods;
+
+extern bool mutex_used_on_watched_thread;
+extern PRThread* watched_thread;
+/**
+ * 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.
+ */
+extern nsIThread* last_non_watched_thread;
+
+/**
+ * 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);
+
+extern "C" int wrapped_MutexTry(sqlite3_mutex* mutex);
+
+class HookSqliteMutex {
+ public:
+ HookSqliteMutex() {
+ // 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));
+ }
+
+ ~HookSqliteMutex() {
+ do_check_ok(sqlite3_shutdown());
+ do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &orig_mutex_methods));
+ do_check_ok(sqlite3_initialize());
+ }
+};
+
+/**
+ * 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();
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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)
+ : mozilla::Runnable("ThreadWedger"),
+ 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 MOZ_UNANNOTATED;
+ 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);
+
+#endif // storage_test_harness_h__
diff --git a/storage/test/gtest/test_AsXXX_helpers.cpp b/storage/test/gtest/test_AsXXX_helpers.cpp
new file mode 100644
index 0000000000..2320b8383c
--- /dev/null
+++ b/storage/test/gtest/test_AsXXX_helpers.cpp
@@ -0,0 +1,111 @@
+/*
+ *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"
+#include "nsComponentManagerUtils.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:
+ ~Spinner() override = default;
+};
+
+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;
+}
+
+TEST(storage_AsXXX_helpers, NULLFallback)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ (void)db->CreateStatement("SELECT NULL"_ns, 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);
+}
+
+TEST(storage_AsXXX_helpers, asyncNULLFallback)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ (void)db->CreateAsyncStatement("SELECT NULL"_ns, 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();
+}
diff --git a/storage/test/gtest/test_StatementCache.cpp b/storage/test/gtest/test_StatementCache.cpp
new file mode 100644
index 0000000000..9908a34d79
--- /dev/null
+++ b/storage/test/gtest/test_StatementCache.cpp
@@ -0,0 +1,132 @@
+/* -*- 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
+
+// This is some gtest magic that allows us to parameterize tests by |const
+// char[]| and |StringWrapper|.
+template <typename T>
+class storage_StatementCache : public ::testing::Test {};
+typedef ::testing::Types<const char[], StringWrapper> TwoStringTypes;
+
+TYPED_TEST_SUITE(storage_StatementCache, TwoStringTypes);
+TYPED_TEST(storage_StatementCache, GetCachedStatement) {
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ SyncCache cache(db);
+
+ TypeParam 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());
+}
+
+TYPED_TEST_SUITE(storage_StatementCache, TwoStringTypes);
+TYPED_TEST(storage_StatementCache, FinalizeStatements) {
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ SyncCache cache(db);
+
+ TypeParam 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());
+}
+
+TYPED_TEST_SUITE(storage_StatementCache, TwoStringTypes);
+TYPED_TEST(storage_StatementCache, GetCachedAsyncStatement) {
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ AsyncCache cache(db);
+
+ TypeParam 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());
+}
+
+TYPED_TEST_SUITE(storage_StatementCache, TwoStringTypes);
+TYPED_TEST(storage_StatementCache, FinalizeAsyncStatements) {
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ AsyncCache cache(db);
+
+ TypeParam 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));
+}
diff --git a/storage/test/gtest/test_asyncStatementExecution_transaction.cpp b/storage/test/gtest/test_asyncStatementExecution_transaction.cpp
new file mode 100644
index 0000000000..0e7dec1e8a
--- /dev/null
+++ b/storage/test/gtest/test_asyncStatementExecution_transaction.cpp
@@ -0,0 +1,445 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "storage_test_harness.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,
+ const nsTArray<RefPtr<mozIStorageBaseStatement>>& aStmts,
+ 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, 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 < aStmts.Length(); ++i) {
+ aStmts[i]->Finalize();
+ }
+ blocking_async_close(aDB);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Tests
+
+/**
+ * Test that executing multiple readonly AsyncStatements doesn't create a
+ * transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleAsyncReadStatements)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt1;
+ db->CreateAsyncStatement("SELECT * FROM sqlite_master"_ns,
+ getter_AddRefs(stmt1));
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt2;
+ db->CreateAsyncStatement("SELECT * FROM sqlite_master"_ns,
+ getter_AddRefs(stmt2));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt1)),
+ ToRefPtr(std::move(stmt2)),
+ };
+
+ check_transaction(db, stmts.Clone(), false);
+}
+
+/**
+ * Test that executing multiple readonly Statements doesn't create a
+ * transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleReadStatements)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt1;
+ db->CreateStatement("SELECT * FROM sqlite_master"_ns, getter_AddRefs(stmt1));
+
+ nsCOMPtr<mozIStorageStatement> stmt2;
+ db->CreateStatement("SELECT * FROM sqlite_master"_ns, getter_AddRefs(stmt2));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt1)),
+ ToRefPtr(std::move(stmt2)),
+ };
+
+ check_transaction(db, stmts, false);
+}
+
+/**
+ * Test that executing multiple AsyncStatements causing writes creates a
+ * transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction,
+ MultipleAsyncReadWriteStatements)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt1;
+ db->CreateAsyncStatement("SELECT * FROM sqlite_master"_ns,
+ getter_AddRefs(stmt1));
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt2;
+ db->CreateAsyncStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt2));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt1)),
+ ToRefPtr(std::move(stmt2)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing multiple Statements causing writes creates a transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleReadWriteStatements)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt1;
+ db->CreateStatement("SELECT * FROM sqlite_master"_ns, getter_AddRefs(stmt1));
+
+ nsCOMPtr<mozIStorageStatement> stmt2;
+ db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt2));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt1)),
+ ToRefPtr(std::move(stmt2)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing multiple AsyncStatements causing writes creates a
+ * single transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleAsyncWriteStatements)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt1;
+ db->CreateAsyncStatement("CREATE TABLE test1 (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt1));
+
+ nsCOMPtr<mozIStorageAsyncStatement> stmt2;
+ db->CreateAsyncStatement("CREATE TABLE test2 (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt2));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt1)),
+ ToRefPtr(std::move(stmt2)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing multiple Statements causing writes creates a
+ * single transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleWriteStatements)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt1;
+ db->CreateStatement("CREATE TABLE test1 (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt1));
+
+ nsCOMPtr<mozIStorageStatement> stmt2;
+ db->CreateStatement("CREATE TABLE test2 (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt2));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt1)),
+ ToRefPtr(std::move(stmt2)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing a single read-only AsyncStatement doesn't create a
+ * transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, SingleAsyncReadStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ db->CreateAsyncStatement("SELECT * FROM sqlite_master"_ns,
+ getter_AddRefs(stmt));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, false);
+}
+
+/**
+ * Test that executing a single read-only Statement doesn't create a
+ * transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, SingleReadStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt;
+ db->CreateStatement("SELECT * FROM sqlite_master"_ns, getter_AddRefs(stmt));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, false);
+}
+
+/**
+ * Test that executing a single AsyncStatement causing writes creates a
+ * transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, SingleAsyncWriteStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ db->CreateAsyncStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing a single Statement causing writes creates a transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, SingleWriteStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt;
+ db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt));
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing a single read-only AsyncStatement with multiple params
+ * doesn't create a transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction,
+ MultipleParamsAsyncReadStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ db->CreateAsyncStatement("SELECT :param FROM sqlite_master"_ns,
+ 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("param"_ns, 1);
+ paramsArray->AddParams(params);
+ }
+ stmt->BindParameters(paramsArray);
+ paramsArray = nullptr;
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, false);
+}
+
+/**
+ * Test that executing a single read-only Statement with multiple params
+ * doesn't create a transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleParamsReadStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt;
+ db->CreateStatement("SELECT :param FROM sqlite_master"_ns,
+ 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("param"_ns, 1);
+ paramsArray->AddParams(params);
+ }
+ stmt->BindParameters(paramsArray);
+ paramsArray = nullptr;
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, false);
+}
+
+/**
+ * Test that executing a single write AsyncStatement with multiple params
+ * creates a transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction,
+ MultipleParamsAsyncWriteStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create a table for writes
+ nsCOMPtr<mozIStorageStatement> tableStmt;
+ db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(tableStmt));
+ tableStmt->Execute();
+ tableStmt->Finalize();
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ db->CreateAsyncStatement("DELETE FROM test WHERE id = :param"_ns,
+ 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("param"_ns, 1);
+ paramsArray->AddParams(params);
+ }
+ stmt->BindParameters(paramsArray);
+ paramsArray = nullptr;
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, true);
+}
+
+/**
+ * Test that executing a single write Statement with multiple params
+ * creates a transaction.
+ */
+TEST(storage_asyncStatementExecution_transaction, MultipleParamsWriteStatement)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // -- create a table for writes
+ nsCOMPtr<mozIStorageStatement> tableStmt;
+ db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(tableStmt));
+ tableStmt->Execute();
+ tableStmt->Finalize();
+
+ // -- create statements and execute them
+ nsCOMPtr<mozIStorageStatement> stmt;
+ db->CreateStatement("DELETE FROM test WHERE id = :param"_ns,
+ 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("param"_ns, 1);
+ paramsArray->AddParams(params);
+ }
+ stmt->BindParameters(paramsArray);
+ paramsArray = nullptr;
+
+ nsTArray<RefPtr<mozIStorageBaseStatement>> stmts = {
+ ToRefPtr(std::move(stmt)),
+ };
+
+ check_transaction(db, stmts, true);
+}
diff --git a/storage/test/gtest/test_async_callbacks_with_spun_event_loops.cpp b/storage/test/gtest/test_async_callbacks_with_spun_event_loops.cpp
new file mode 100644
index 0000000000..bd437b61ec
--- /dev/null
+++ b/storage/test/gtest/test_async_callbacks_with_spun_event_loops.cpp
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "storage_test_harness.h"
+#include "prthread.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) {
+ MOZ_CRASH("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) {
+ MOZ_CRASH("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
+
+TEST(storage_async_callbacks_with_spun_event_loops,
+ SpinEventsLoopInHandleResult)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Create a test table and populate it.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt));
+ stmt->Execute();
+ stmt->Finalize();
+
+ db->CreateStatement("INSERT INTO test (id) VALUES (?)"_ns,
+ getter_AddRefs(stmt));
+ for (int32_t i = 0; i < 30; ++i) {
+ stmt->BindInt32ByIndex(0, i);
+ stmt->Execute();
+ stmt->Reset();
+ }
+ stmt->Finalize();
+
+ db->CreateStatement("SELECT * FROM test"_ns, 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);
+}
+
+TEST(storage_async_callbacks_with_spun_event_loops, SpinEventsLoopInHandleError)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Create a test table and populate it.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ db->CreateStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ getter_AddRefs(stmt));
+ stmt->Execute();
+ stmt->Finalize();
+
+ db->CreateStatement("INSERT INTO test (id) VALUES (1)"_ns,
+ getter_AddRefs(stmt));
+ stmt->Execute();
+ stmt->Finalize();
+
+ // This will cause a constraint error.
+ db->CreateStatement("INSERT INTO test (id) VALUES (1)"_ns,
+ 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);
+}
diff --git a/storage/test/gtest/test_binding_params.cpp b/storage/test/gtest/test_binding_params.cpp
new file mode 100644
index 0000000000..a9ca1799f9
--- /dev/null
+++ b/storage/test/gtest/test_binding_params.cpp
@@ -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"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozStorageHelper.h"
+
+using namespace mozilla;
+
+/**
+ * This file tests binding and reading out string parameters through the
+ * mozIStorageStatement API.
+ */
+
+TEST(storage_binding_params, ASCIIString)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Create table with a single string column.
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (str STRING)"_ns);
+
+ // Create statements to INSERT and SELECT the string.
+ nsCOMPtr<mozIStorageStatement> insert, select;
+ (void)db->CreateStatement("INSERT INTO test (str) VALUES (?1)"_ns,
+ getter_AddRefs(insert));
+ (void)db->CreateStatement("SELECT str FROM test"_ns, 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("DELETE FROM test"_ns);
+}
+
+TEST(storage_binding_params, CString)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Create table with a single string column.
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (str STRING)"_ns);
+
+ // Create statements to INSERT and SELECT the string.
+ nsCOMPtr<mozIStorageStatement> insert, select;
+ (void)db->CreateStatement("INSERT INTO test (str) VALUES (?1)"_ns,
+ getter_AddRefs(insert));
+ (void)db->CreateStatement("SELECT str FROM test"_ns, 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("DELETE FROM test"_ns);
+}
+
+TEST(storage_binding_params, UTFStrings)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Create table with a single string column.
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (str STRING)"_ns);
+
+ // Create statements to INSERT and SELECT the string.
+ nsCOMPtr<mozIStorageStatement> insert, select;
+ (void)db->CreateStatement("INSERT INTO test (str) VALUES (?1)"_ns,
+ getter_AddRefs(insert));
+ (void)db->CreateStatement("SELECT str FROM test"_ns, getter_AddRefs(select));
+
+ // Roundtrip a UTF8 string through the table, using UTF8 input and output.
+ static const char sCharArray[] = R"(I'm a ûüâäç 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("DELETE FROM test"_ns);
+
+ // 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("DELETE FROM test"_ns);
+}
diff --git a/storage/test/gtest/test_deadlock_detector.cpp b/storage/test/gtest/test_deadlock_detector.cpp
new file mode 100644
index 0000000000..2679cf26f1
--- /dev/null
+++ b/storage/test/gtest/test_deadlock_detector.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=2 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 essentially a copy of
+// xpcom/tests/gtest/TestDeadlockDetector.cpp, but all mutexes were turned into
+// SQLiteMutexes. We use #include and some macros to avoid actual source code
+// duplication.
+
+#include "mozilla/CondVar.h"
+#include "mozilla/RecursiveMutex.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "SQLiteMutex.h"
+
+// We need this one so _gdb_sleep_duration is also in "storage" namespace
+#include "mozilla/gtest/MozHelpers.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+/**
+ * Helper class to allocate a sqlite3_mutex for our SQLiteMutex.
+ */
+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;
+};
+
+// These are the two macros that differentiate this file from the XPCOM one.
+#define MUTEX TestMutex
+#define TESTNAME(name) storage_##name
+
+// Bug 1473531: the test storage_DeadlockDetectorTest.storage_Sanity5DeathTest
+// times out on macosx ccov builds
+#if defined(XP_MACOSX) && defined(MOZ_CODE_COVERAGE)
+# define DISABLE_STORAGE_SANITY5_DEATH_TEST
+#endif
+
+#include "../../../xpcom/tests/gtest/TestDeadlockDetector.cpp"
diff --git a/storage/test/gtest/test_file_perms.cpp b/storage/test/gtest/test_file_perms.cpp
new file mode 100644
index 0000000000..5e54f082ce
--- /dev/null
+++ b/storage/test/gtest/test_file_perms.cpp
@@ -0,0 +1,35 @@
+/* -*- 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
+ */
+
+TEST(storage_file_perms, Test)
+{
+ 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
+ // third_party/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
+}
diff --git a/storage/test/gtest/test_interruptSynchronousConnection.cpp b/storage/test/gtest/test_interruptSynchronousConnection.cpp
new file mode 100644
index 0000000000..dfa19bc86e
--- /dev/null
+++ b/storage/test/gtest/test_interruptSynchronousConnection.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "gtest/gtest.h"
+
+#include "storage_test_harness.h"
+
+#include "mozilla/SpinEventLoopUntil.h"
+
+class SynchronousConnectionInterruptionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ mConnection =
+ getDatabase(nullptr, mozIStorageService::CONNECTION_INTERRUPTIBLE);
+ ASSERT_TRUE(mConnection);
+
+ ASSERT_EQ(NS_OK, NS_NewNamedThread("Test Thread", getter_AddRefs(mThread)));
+ }
+
+ void TearDown() override {
+ // We might close the database connection early in test cases.
+ mozilla::Unused << mConnection->Close();
+
+ ASSERT_EQ(NS_OK, mThread->Shutdown());
+ }
+
+ nsCOMPtr<mozIStorageConnection> mConnection;
+
+ nsCOMPtr<nsIThread> mThread;
+
+ bool mDone = false;
+};
+
+TEST_F(SynchronousConnectionInterruptionTest,
+ shouldBeAbleToInterruptInfiniteOperation) {
+ // Delay is modest because we don't want to get interrupted by
+ // some unrelated hang or memory guard
+ const uint32_t delayMs = 500;
+
+ ASSERT_EQ(NS_OK, mThread->DelayedDispatch(
+ NS_NewRunnableFunction(
+ "InterruptRunnable",
+ [this]() {
+ ASSERT_EQ(NS_OK, mConnection->Interrupt());
+ mDone = true;
+ }),
+ delayMs));
+
+ const nsCString infiniteQuery =
+ "WITH RECURSIVE test(n) "
+ "AS (VALUES(1) UNION ALL SELECT n + 1 FROM test) "
+ "SELECT t.n FROM test, test AS t;"_ns;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ ASSERT_EQ(NS_OK,
+ mConnection->CreateStatement(infiniteQuery, getter_AddRefs(stmt)));
+ ASSERT_EQ(NS_ERROR_ABORT, stmt->Execute());
+ ASSERT_EQ(NS_OK, stmt->Finalize());
+
+ ASSERT_TRUE(mDone);
+
+ ASSERT_EQ(NS_OK, mConnection->Close());
+}
+
+TEST_F(SynchronousConnectionInterruptionTest, interruptAfterCloseWillFail) {
+ ASSERT_EQ(NS_OK, mConnection->Close());
+
+ ASSERT_EQ(
+ NS_OK,
+ mThread->Dispatch(NS_NewRunnableFunction("InterruptRunnable", [this]() {
+ ASSERT_EQ(NS_ERROR_NOT_INITIALIZED, mConnection->Interrupt());
+ mDone = true;
+ })));
+
+ ASSERT_TRUE(mozilla::SpinEventLoopUntil("interruptAfterCloseWillFail"_ns,
+ [this]() { return mDone; }));
+
+ ASSERT_EQ(NS_ERROR_NOT_INITIALIZED, mConnection->Close());
+}
diff --git a/storage/test/gtest/test_mutex.cpp b/storage/test/gtest/test_mutex.cpp
new file mode 100644
index 0000000000..db1a645e09
--- /dev/null
+++ b/storage/test/gtest/test_mutex.cpp
@@ -0,0 +1,73 @@
+/* -*- 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.
+ */
+
+TEST(storage_mutex, AutoLock)
+{
+ int lockTypes[] = {
+ SQLITE_MUTEX_FAST,
+ SQLITE_MUTEX_RECURSIVE,
+ };
+ for (int lockType : lockTypes) {
+ // 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(lockType);
+ 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);
+ }
+}
+
+TEST(storage_mutex, AutoUnlock)
+{
+ int lockTypes[] = {
+ SQLITE_MUTEX_FAST,
+ SQLITE_MUTEX_RECURSIVE,
+ };
+ for (int lockType : lockTypes) {
+ // 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(lockType);
+ 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);
+ }
+}
diff --git a/storage/test/gtest/test_spinningSynchronousClose.cpp b/storage/test/gtest/test_spinningSynchronousClose.cpp
new file mode 100644
index 0000000000..81f9bee11a
--- /dev/null
+++ b/storage/test/gtest/test_spinningSynchronousClose.cpp
@@ -0,0 +1,72 @@
+/* -*- 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 "prinrval.h"
+
+using namespace mozilla;
+
+/**
+ * Helper to verify that the event loop was spun. As long as this is dispatched
+ * prior to a call to Close()/SpinningSynchronousClose() we are guaranteed this
+ * will be run if the event loop is spun to perform a close. This is because
+ * SpinningSynchronousClose must spin the event loop to realize the close
+ * completed and our runnable will already be enqueued and therefore run before
+ * the AsyncCloseConnection's callback. Note that this invariant may be
+ * violated if our runnables end up in different queues thanks to Quantum
+ * changes, so this test may need to be updated if the close dispatch changes.
+ */
+class CompletionRunnable final : public Runnable {
+ public:
+ explicit CompletionRunnable()
+ : Runnable("CompletionRunnable"), mDone(false) {}
+
+ NS_IMETHOD Run() override {
+ mDone = true;
+ return NS_OK;
+ }
+
+ bool mDone;
+};
+
+// Can only run in optimized builds, or it would assert.
+#ifndef DEBUG
+TEST(storage_spinningSynchronousClose, CloseOnAsync)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ // Run an async statement.
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ do_check_success(db->CreateAsyncStatement(
+ "CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns, getter_AddRefs(stmt)));
+ nsCOMPtr<mozIStoragePendingStatement> p;
+ do_check_success(stmt->ExecuteAsync(nullptr, getter_AddRefs(p)));
+ do_check_success(stmt->Finalize());
+
+ // Wrongly use Close() instead of AsyncClose().
+ RefPtr<CompletionRunnable> event = new CompletionRunnable();
+ NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ do_check_false(NS_SUCCEEDED(db->Close()));
+ do_check_true(event->mDone);
+}
+#endif
+
+TEST(storage_spinningSynchronousClose, spinningSynchronousCloseOnAsync)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ // Run an async statement.
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ do_check_success(db->CreateAsyncStatement(
+ "CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns, getter_AddRefs(stmt)));
+ nsCOMPtr<mozIStoragePendingStatement> p;
+ do_check_success(stmt->ExecuteAsync(nullptr, getter_AddRefs(p)));
+ do_check_success(stmt->Finalize());
+
+ // Use the spinning close API.
+ RefPtr<CompletionRunnable> event = new CompletionRunnable();
+ NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+ do_check_success(db->SpinningSynchronousClose());
+ do_check_true(event->mDone);
+}
diff --git a/storage/test/gtest/test_statement_scoper.cpp b/storage/test/gtest/test_statement_scoper.cpp
new file mode 100644
index 0000000000..3327b0ff6b
--- /dev/null
+++ b/storage/test/gtest/test_statement_scoper.cpp
@@ -0,0 +1,86 @@
+/* -*- 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.
+ */
+
+TEST(storage_statement_scoper, automatic_reset)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Need to create a table to populate sqlite_master with an entry.
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ (void)db->CreateStatement("SELECT * FROM sqlite_master"_ns,
+ 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);
+}
+
+TEST(storage_statement_scoper, Abandon)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ // Need to create a table to populate sqlite_master with an entry.
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ (void)db->CreateStatement("SELECT * FROM sqlite_master"_ns,
+ 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);
+}
diff --git a/storage/test/gtest/test_transaction_helper.cpp b/storage/test/gtest/test_transaction_helper.cpp
new file mode 100644
index 0000000000..d11f631967
--- /dev/null
+++ b/storage/test/gtest/test_transaction_helper.cpp
@@ -0,0 +1,184 @@
+/* -*- 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 tests our Transaction helper in mozStorageHelper.h.
+ */
+
+TEST(storage_transaction_helper, 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_success(transaction.Start());
+ do_check_true(has_transaction(db));
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+ (void)transaction.Commit();
+ }
+ do_check_false(has_transaction(db));
+
+ bool exists = false;
+ (void)db->TableExists("test"_ns, &exists);
+ do_check_true(exists);
+}
+
+TEST(storage_transaction_helper, 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_success(transaction.Start());
+ do_check_true(has_transaction(db));
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+ (void)transaction.Rollback();
+ }
+ do_check_false(has_transaction(db));
+
+ bool exists = true;
+ (void)db->TableExists("test"_ns, &exists);
+ do_check_false(exists);
+}
+
+TEST(storage_transaction_helper, 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_success(transaction.Start());
+ do_check_true(has_transaction(db));
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+ }
+ do_check_false(has_transaction(db));
+
+ bool exists = false;
+ (void)db->TableExists("test"_ns, &exists);
+ do_check_true(exists);
+}
+
+TEST(storage_transaction_helper, 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_success(transaction.Start());
+ do_check_true(has_transaction(db));
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+ }
+ do_check_false(has_transaction(db));
+
+ bool exists = true;
+ (void)db->TableExists("test"_ns, &exists);
+ do_check_false(exists);
+}
+
+TEST(storage_transaction_helper, 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_success(transaction.Start());
+ do_check_true(NS_SUCCEEDED(transaction.Commit()));
+ do_check_true(NS_SUCCEEDED(transaction.Rollback()));
+}
+
+TEST(storage_transaction_helper, async_Commit)
+{
+ HookSqliteMutex hook;
+
+ 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_success(transaction.Start());
+ do_check_true(has_transaction(db));
+ (void)db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns);
+ (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("SELECT NULL"_ns, getter_AddRefs(stmt));
+ blocking_async_execute(stmt);
+ stmt->Finalize();
+ do_check_false(has_transaction(db));
+ bool exists = false;
+ (void)db->TableExists("test"_ns, &exists);
+ do_check_true(exists);
+
+ blocking_async_close(db);
+}
+
+TEST(storage_transaction_helper, Nesting)
+{
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+
+ {
+ mozStorageTransaction transaction(db, false);
+ do_check_success(transaction.Start());
+ do_check_true(has_transaction(db));
+ do_check_success(
+ db->ExecuteSimpleSQL("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns));
+
+ {
+ mozStorageTransaction nestedTransaction(db, false);
+ do_check_success(nestedTransaction.Start());
+ do_check_true(has_transaction(db));
+ do_check_success(db->ExecuteSimpleSQL(
+ "CREATE TABLE nested_test (id INTEGER PRIMARY KEY)"_ns));
+
+#ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ do_check_true(transaction.Commit() == NS_ERROR_NOT_AVAILABLE);
+ do_check_true(transaction.Rollback() == NS_ERROR_NOT_AVAILABLE);
+#endif
+ }
+
+ bool exists = false;
+ do_check_success(db->TableExists("nested_test"_ns, &exists));
+ do_check_false(exists);
+
+ (void)transaction.Commit();
+ }
+ do_check_false(has_transaction(db));
+
+ bool exists = false;
+ do_check_success(db->TableExists("test"_ns, &exists));
+ do_check_true(exists);
+}
diff --git a/storage/test/gtest/test_true_async.cpp b/storage/test/gtest/test_true_async.cpp
new file mode 100644
index 0000000000..3b54b73b3a
--- /dev/null
+++ b/storage/test/gtest/test_true_async.cpp
@@ -0,0 +1,161 @@
+/* -*- 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
+
+TEST(storage_true_async, TrueAsyncStatement)
+{
+ HookSqliteMutex hook;
+
+ 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("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ 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("INSERT INTO test (id) VALUES (?)"_ns,
+ 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("INSERT INTO test (id) VALUES (:id)"_ns,
+ getter_AddRefs(stmt));
+ nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
+ stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
+ nsCOMPtr<mozIStorageBindingParams> params;
+ paramsArray->NewBindingParams(getter_AddRefs(params));
+ params->BindInt32ByName("id"_ns, 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("SELECT * FROM test"_ns, 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.
+ */
+TEST(storage_true_async, AsyncCancellation)
+{
+ HookSqliteMutex hook;
+
+ 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(
+ "CREATE TABLE asyncTable (id INTEGER PRIMARY KEY)"_ns,
+ 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("CREATE TABLE syncTable (id INTEGER PRIMARY KEY)"_ns,
+ 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("asyncTable"_ns, &exists);
+ do_check_true(rv == NS_OK);
+ do_check_false(exists);
+ rv = db->TableExists("syncTable"_ns, &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).
+ */
+TEST(storage_true_async, AsyncDestructorFinalizesOnAsyncThread)
+{
+ HookSqliteMutex hook;
+
+ nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
+ watch_for_mutex_use_on_this_thread();
+
+ // -- create an async statement
+ nsCOMPtr<mozIStorageAsyncStatement> stmt;
+ db->CreateAsyncStatement("CREATE TABLE test (id INTEGER PRIMARY KEY)"_ns,
+ 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);
+}
diff --git a/storage/test/gtest/test_unlock_notify.cpp b/storage/test/gtest/test_unlock_notify.cpp
new file mode 100644
index 0000000000..93cc5b09a8
--- /dev/null
+++ b/storage/test/gtest/test_unlock_notify.cpp
@@ -0,0 +1,234 @@
+/* -*- 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, nsIFile* aDBFile = nullptr)
+ : mozilla::Runnable("DatabaseLocker"),
+ monitor("DatabaseLocker::monitor"),
+ mSQL(aSQL),
+ mState(STARTING),
+ mDBFile(aDBFile) {}
+
+ 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(mDBFile));
+
+ 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 MOZ_UNANNOTATED;
+
+ protected:
+ nsCOMPtr<nsIThread> mThread;
+ const char* const mSQL;
+ State mState;
+ nsCOMPtr<nsIFile> mDBFile;
+};
+
+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(nsLiteralCString(
+ "CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)"));
+ do_check_success(rv);
+ rv = db->ExecuteSimpleSQL("INSERT INTO test (data) VALUES ('foo')"_ns);
+ do_check_success(rv);
+ rv = db->ExecuteSimpleSQL("INSERT INTO test (data) VALUES ('bar')"_ns);
+ do_check_success(rv);
+ rv =
+ db->ExecuteSimpleSQL("CREATE UNIQUE INDEX unique_data ON test (data)"_ns);
+ 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(
+ "INSERT INTO test (data) VALUES ('test1')"_ns, getter_AddRefs(stmt));
+ do_check_success(rv);
+
+ nsCOMPtr<nsIFile> dbFile;
+ db->GetDatabaseFile(getter_AddRefs(dbFile));
+ RefPtr<DatabaseLocker> locker(
+ new DatabaseLocker("SELECT * FROM test", dbFile));
+ 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("SELECT * FROM test"_ns, 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("SELECT * FROM test"_ns, 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();
+}
+
+TEST(storage_unlock_notify, Test)
+{
+ // These must execute in order.
+ setup();
+ test_step_locked_does_not_block_main_thread();
+ test_drop_index_does_not_loop();
+ test_drop_table_does_not_loop();
+}