summaryrefslogtreecommitdiffstats
path: root/dom/quota/test/gtest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/quota/test/gtest
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/quota/test/gtest')
-rw-r--r--dom/quota/test/gtest/Common.cpp28
-rw-r--r--dom/quota/test/gtest/Common.h32
-rw-r--r--dom/quota/test/gtest/PQuotaTest.ipdl27
-rw-r--r--dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp263
-rw-r--r--dom/quota/test/gtest/QuotaManagerDependencyFixture.h137
-rw-r--r--dom/quota/test/gtest/QuotaTestChild.h23
-rw-r--r--dom/quota/test/gtest/QuotaTestParent.h36
-rw-r--r--dom/quota/test/gtest/TestCheckedUnsafePtr.cpp145
-rw-r--r--dom/quota/test/gtest/TestClientUsageArray.cpp17
-rw-r--r--dom/quota/test/gtest/TestEncryptedStream.cpp804
-rw-r--r--dom/quota/test/gtest/TestFileOutputStream.cpp227
-rw-r--r--dom/quota/test/gtest/TestFlatten.cpp81
-rw-r--r--dom/quota/test/gtest/TestForwardDecls.cpp12
-rw-r--r--dom/quota/test/gtest/TestOriginParser.cpp25
-rw-r--r--dom/quota/test/gtest/TestOriginScope.cpp107
-rw-r--r--dom/quota/test/gtest/TestPersistenceType.cpp44
-rw-r--r--dom/quota/test/gtest/TestQMResult.cpp72
-rw-r--r--dom/quota/test/gtest/TestQuotaCommon.cpp2288
-rw-r--r--dom/quota/test/gtest/TestQuotaManager.cpp1611
-rw-r--r--dom/quota/test/gtest/TestResultExtensions.cpp333
-rw-r--r--dom/quota/test/gtest/TestScopedLogExtraInfo.cpp65
-rw-r--r--dom/quota/test/gtest/TestStorageConnection.cpp283
-rw-r--r--dom/quota/test/gtest/TestStorageOriginAttributes.cpp165
-rw-r--r--dom/quota/test/gtest/TestStringifyUtils.cpp60
-rw-r--r--dom/quota/test/gtest/TestTelemetry.cpp185
-rw-r--r--dom/quota/test/gtest/TestUsageInfo.cpp136
-rw-r--r--dom/quota/test/gtest/moz.build50
27 files changed, 7256 insertions, 0 deletions
diff --git a/dom/quota/test/gtest/Common.cpp b/dom/quota/test/gtest/Common.cpp
new file mode 100644
index 0000000000..efbfd94775
--- /dev/null
+++ b/dom/quota/test/gtest/Common.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "Common.h"
+
+#include "mozilla/dom/QMResult.h"
+
+namespace mozilla::dom::quota {
+
+#ifdef QM_ERROR_STACKS_ENABLED
+uint64_t DOM_Quota_Test::sExpectedStackId;
+
+// static
+void DOM_Quota_Test::SetUpTestCase() {
+ sExpectedStackId = QMResult().StackId();
+}
+
+// static
+void DOM_Quota_Test::IncreaseExpectedStackId() { sExpectedStackId++; }
+
+// static
+uint64_t DOM_Quota_Test::ExpectedStackId() { return sExpectedStackId; }
+#endif
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/Common.h b/dom/quota/test/gtest/Common.h
new file mode 100644
index 0000000000..eac58bd7fb
--- /dev/null
+++ b/dom/quota/test/gtest/Common.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 DOM_QUOTA_TEST_GTEST_COMMON_H_
+#define DOM_QUOTA_TEST_GTEST_COMMON_H_
+
+#include <cstdint>
+#include "gtest/gtest.h"
+#include "mozilla/dom/quota/Config.h"
+
+namespace mozilla::dom::quota {
+
+class DOM_Quota_Test : public testing::Test {
+#ifdef QM_ERROR_STACKS_ENABLED
+ public:
+ static void SetUpTestCase();
+
+ static void IncreaseExpectedStackId();
+
+ static uint64_t ExpectedStackId();
+
+ private:
+ static uint64_t sExpectedStackId;
+#endif
+};
+
+} // namespace mozilla::dom::quota
+
+#endif // DOM_QUOTA_TEST_GTEST_COMMON_H_
diff --git a/dom/quota/test/gtest/PQuotaTest.ipdl b/dom/quota/test/gtest/PQuotaTest.ipdl
new file mode 100644
index 0000000000..3ea2d0c3f9
--- /dev/null
+++ b/dom/quota/test/gtest/PQuotaTest.ipdl
@@ -0,0 +1,27 @@
+/* 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/. */
+
+namespace mozilla {
+namespace dom {
+namespace quota {
+
+[ParentProc=any, ChildProc=any]
+sync protocol PQuotaTest {
+ parent:
+ sync Try_Success_CustomErr_QmIpcFail()
+ returns (bool tryDidNotReturn);
+
+ sync Try_Success_CustomErr_IpcFail()
+ returns (bool tryDidNotReturn);
+
+ sync TryInspect_Success_CustomErr_QmIpcFail()
+ returns (bool tryDidNotReturn);
+
+ sync TryInspect_Success_CustomErr_IpcFail()
+ returns (bool tryDidNotReturn);
+};
+
+} // namespace quota
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp
new file mode 100644
index 0000000000..2d3fe17705
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp
@@ -0,0 +1,263 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "QuotaManagerDependencyFixture.h"
+
+#include "mozIStorageService.h"
+#include "mozStorageCID.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/quota/QuotaManagerService.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIQuotaCallbacks.h"
+#include "nsIQuotaRequests.h"
+#include "nsIVariant.h"
+#include "nsScriptSecurityManager.h"
+
+namespace mozilla::dom::quota::test {
+
+namespace {
+
+class RequestResolver final : public nsIQuotaCallback {
+ public:
+ RequestResolver() : mDone(false) {}
+
+ bool Done() const { return mDone; }
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD OnComplete(nsIQuotaRequest* aRequest) override {
+ mDone = true;
+
+ return NS_OK;
+ }
+
+ private:
+ ~RequestResolver() = default;
+
+ bool mDone;
+};
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(RequestResolver, nsIQuotaCallback)
+
+// static
+void QuotaManagerDependencyFixture::InitializeFixture() {
+ // Some QuotaManagerService methods fail if the testing pref is not set.
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->SetBoolPref("dom.quotaManager.testing", true);
+
+ // The first initialization of storage service must be done on the main
+ // thread.
+ nsCOMPtr<mozIStorageService> storageService =
+ do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ ASSERT_TRUE(storageService);
+
+ nsIObserver* observer = QuotaManager::GetObserver();
+ ASSERT_TRUE(observer);
+
+ nsresult rv = observer->Observe(nullptr, "profile-do-change", nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ ASSERT_NO_FATAL_FAILURE(EnsureQuotaManager());
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->OwningThread());
+
+ sBackgroundTarget = quotaManager->OwningThread();
+}
+
+// static
+void QuotaManagerDependencyFixture::ShutdownFixture() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->SetBoolPref("dom.quotaManager.testing", false);
+
+ nsIObserver* observer = QuotaManager::GetObserver();
+ ASSERT_TRUE(observer);
+
+ nsresult rv = observer->Observe(nullptr, "profile-before-change-qm", nullptr);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ PerformOnBackgroundThread([]() { QuotaManager::Reset(); });
+
+ sBackgroundTarget = nullptr;
+}
+
+// static
+void QuotaManagerDependencyFixture::InitializeStorage() {
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->InitializeStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+}
+
+// static
+void QuotaManagerDependencyFixture::StorageInitialized(bool* aResult) {
+ ASSERT_TRUE(aResult);
+
+ PerformOnBackgroundThread([aResult]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->StorageInitialized()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [aResult, &done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ *aResult = aValue.ResolveValue();
+ } else {
+ *aResult = false;
+ }
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+}
+
+// static
+void QuotaManagerDependencyFixture::AssertStorageInitialized() {
+ bool result;
+ ASSERT_NO_FATAL_FAILURE(StorageInitialized(&result));
+ ASSERT_TRUE(result);
+}
+
+// static
+void QuotaManagerDependencyFixture::AssertStorageNotInitialized() {
+ bool result;
+ ASSERT_NO_FATAL_FAILURE(StorageInitialized(&result));
+ ASSERT_FALSE(result);
+}
+
+// static
+void QuotaManagerDependencyFixture::ShutdownStorage() {
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->ShutdownStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+}
+
+// static
+void QuotaManagerDependencyFixture::ClearStoragesForOrigin(
+ const OriginMetadata& aOriginMetadata) {
+ PerformOnBackgroundThread([&aOriginMetadata]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ nsCOMPtr<nsIPrincipal> principal =
+ BasePrincipal::CreateContentPrincipal(aOriginMetadata.mOrigin);
+ QM_TRY(MOZ_TO_RESULT(principal), QM_TEST_FAIL);
+
+ mozilla::ipc::PrincipalInfo principalInfo;
+ QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(principal, &principalInfo)),
+ QM_TEST_FAIL);
+
+ bool done = false;
+
+ quotaManager
+ ->ClearStoragesForOrigin(/* aPersistenceType */ Nothing(),
+ principalInfo, /* aClientType */ Nothing())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+}
+
+// static
+OriginMetadata QuotaManagerDependencyFixture::GetTestOriginMetadata() {
+ return {""_ns,
+ "example.com"_ns,
+ "http://example.com"_ns,
+ "http://example.com"_ns,
+ /* aIsPrivate */ false,
+ PERSISTENCE_TYPE_DEFAULT};
+}
+
+// static
+ClientMetadata QuotaManagerDependencyFixture::GetTestClientMetadata() {
+ return {GetTestOriginMetadata(), Client::SDB};
+}
+
+// static
+OriginMetadata QuotaManagerDependencyFixture::GetOtherTestOriginMetadata() {
+ return {""_ns,
+ "other-example.com"_ns,
+ "http://other-example.com"_ns,
+ "http://other-example.com"_ns,
+ /* aIsPrivate */ false,
+ PERSISTENCE_TYPE_DEFAULT};
+}
+
+// static
+ClientMetadata QuotaManagerDependencyFixture::GetOtherTestClientMetadata() {
+ return {GetOtherTestOriginMetadata(), Client::SDB};
+}
+
+// static
+void QuotaManagerDependencyFixture::EnsureQuotaManager() {
+ AutoJSAPI jsapi;
+
+ bool ok = jsapi.Init(xpc::PrivilegedJunkScope());
+ ASSERT_TRUE(ok);
+
+ nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate();
+ ASSERT_TRUE(qms);
+
+ // In theory, any nsIQuotaManagerService method which ensures quota manager
+ // on the PBackground thread, could be called here. `StorageName` was chosen
+ // because it doesn't need to do any directory locking or IO.
+ // XXX Consider adding a dedicated nsIQuotaManagerService method for this.
+ nsCOMPtr<nsIQuotaRequest> request;
+ nsresult rv = qms->StorageName(getter_AddRefs(request));
+ ASSERT_NS_SUCCEEDED(rv);
+
+ RefPtr<RequestResolver> resolver = new RequestResolver();
+
+ rv = request->SetCallback(resolver);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns,
+ [&resolver]() { return resolver->Done(); });
+}
+
+nsCOMPtr<nsISerialEventTarget> QuotaManagerDependencyFixture::sBackgroundTarget;
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/QuotaManagerDependencyFixture.h b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h
new file mode 100644
index 0000000000..639db91c55
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 DOM_QUOTA_TEST_GTEST_QUOTAMANAGERDEPENDENCYFIXTURE_H_
+#define DOM_QUOTA_TEST_GTEST_QUOTAMANAGERDEPENDENCYFIXTURE_H_
+
+#include "gtest/gtest.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/dom/quota/DirectoryLock.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+
+#define QM_TEST_FAIL [](nsresult) { FAIL(); }
+
+namespace mozilla::dom::quota::test {
+
+class QuotaManagerDependencyFixture : public testing::Test {
+ public:
+ static void InitializeFixture();
+ static void ShutdownFixture();
+
+ static void InitializeStorage();
+ static void StorageInitialized(bool* aResult);
+ static void AssertStorageInitialized();
+ static void AssertStorageNotInitialized();
+ static void ShutdownStorage();
+
+ static void ClearStoragesForOrigin(const OriginMetadata& aOriginMetadata);
+
+ /* Convenience method for tasks which must be called on PBackground thread */
+ template <class Invokable, class... Args>
+ static void PerformOnBackgroundThread(Invokable&& aInvokable,
+ Args&&... aArgs) {
+ bool done = false;
+ auto boundTask =
+ // For c++17, bind is cleaner than tuple for parameter pack forwarding
+ // NOLINTNEXTLINE(modernize-avoid-bind)
+ std::bind(std::forward<Invokable>(aInvokable),
+ std::forward<Args>(aArgs)...);
+ InvokeAsync(BackgroundTargetStrongRef(), __func__,
+ [boundTask = std::move(boundTask)]() mutable {
+ boundTask();
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& /* aValue */) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ }
+
+ /* Convenience method for tasks which must be executed on IO thread */
+ template <class Invokable, class... Args>
+ static void PerformOnIOThread(Invokable&& aInvokable, Args&&... aArgs) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+ auto boundTask =
+ // For c++17, bind is cleaner than tuple for parameter pack forwarding
+ // NOLINTNEXTLINE(modernize-avoid-bind)
+ std::bind(std::forward<Invokable>(aInvokable),
+ std::forward<Args>(aArgs)...);
+ InvokeAsync(quotaManager->IOThread(), __func__,
+ [boundTask = std::move(boundTask)]() mutable {
+ boundTask();
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& value) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ }
+
+ template <class Task>
+ static void PerformClientDirectoryTest(const ClientMetadata& aClientMetadata,
+ Task&& aTask) {
+ PerformOnBackgroundThread([clientMetadata = aClientMetadata,
+ task = std::forward<Task>(aTask)]() mutable {
+ RefPtr<ClientDirectoryLock> directoryLock;
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->OpenClientDirectory(clientMetadata)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock,
+ &done](RefPtr<ClientDirectoryLock> aResolveValue) {
+ directoryLock = std::move(aResolveValue);
+
+ done = true;
+ },
+ [&done](const nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ ASSERT_TRUE(directoryLock);
+
+ PerformOnIOThread(std::move(task), directoryLock->Id());
+
+ directoryLock = nullptr;
+ });
+ }
+
+ static const nsCOMPtr<nsISerialEventTarget>& BackgroundTargetStrongRef() {
+ return sBackgroundTarget;
+ }
+
+ static OriginMetadata GetTestOriginMetadata();
+ static ClientMetadata GetTestClientMetadata();
+
+ static OriginMetadata GetOtherTestOriginMetadata();
+ static ClientMetadata GetOtherTestClientMetadata();
+
+ private:
+ static void EnsureQuotaManager();
+
+ static nsCOMPtr<nsISerialEventTarget> sBackgroundTarget;
+};
+
+} // namespace mozilla::dom::quota::test
+
+#endif // DOM_QUOTA_TEST_GTEST_QUOTAMANAGERDEPENDENCYFIXTURE_H_
diff --git a/dom/quota/test/gtest/QuotaTestChild.h b/dom/quota/test/gtest/QuotaTestChild.h
new file mode 100644
index 0000000000..d0d87d8267
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaTestChild.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 DOM_QUOTA_TEST_GTEST_QUOTATESTCHILD_H_
+#define DOM_QUOTA_TEST_GTEST_QUOTATESTCHILD_H_
+
+#include "mozilla/dom/quota/PQuotaTestChild.h"
+
+namespace mozilla::dom::quota {
+
+class QuotaTestChild : public PQuotaTestChild {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaTestChild, override)
+
+ private:
+ ~QuotaTestChild() = default;
+};
+
+} // namespace mozilla::dom::quota
+
+#endif // DOM_QUOTA_TEST_GTEST_QUOTATESTCHILD_H_
diff --git a/dom/quota/test/gtest/QuotaTestParent.h b/dom/quota/test/gtest/QuotaTestParent.h
new file mode 100644
index 0000000000..5ec1e128b3
--- /dev/null
+++ b/dom/quota/test/gtest/QuotaTestParent.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 DOM_QUOTA_TEST_GTEST_QUOTATESTPARENT_H_
+#define DOM_QUOTA_TEST_GTEST_QUOTATESTPARENT_H_
+
+#include "mozilla/dom/quota/PQuotaTestParent.h"
+
+namespace mozilla::dom::quota {
+
+class QuotaTestParent : public PQuotaTestParent {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaTestParent, override)
+
+ public:
+ mozilla::ipc::IPCResult RecvTry_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn);
+
+ mozilla::ipc::IPCResult RecvTry_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn);
+
+ mozilla::ipc::IPCResult RecvTryInspect_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn);
+
+ mozilla::ipc::IPCResult RecvTryInspect_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn);
+
+ private:
+ ~QuotaTestParent() = default;
+};
+
+} // namespace mozilla::dom::quota
+
+#endif // DOM_QUOTA_TEST_GTEST_QUOTATESTPARENT_H_
diff --git a/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp b/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp
new file mode 100644
index 0000000000..14abb6631d
--- /dev/null
+++ b/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp
@@ -0,0 +1,145 @@
+/* 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 "mozilla/dom/quota/CheckedUnsafePtr.h"
+
+#include "gtest/gtest.h"
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include "mozilla/fallible.h"
+
+using namespace mozilla;
+
+class NoCheckTestType
+ : public SupportsCheckedUnsafePtr<DoNotCheckCheckedUnsafePtrs> {};
+
+#if __cplusplus < 202002L
+static_assert(std::is_literal_type_v<CheckedUnsafePtr<NoCheckTestType>>);
+#endif
+
+static_assert(
+ std::is_trivially_copy_constructible_v<CheckedUnsafePtr<NoCheckTestType>>);
+static_assert(
+ std::is_trivially_copy_assignable_v<CheckedUnsafePtr<NoCheckTestType>>);
+static_assert(
+ std::is_trivially_move_constructible_v<CheckedUnsafePtr<NoCheckTestType>>);
+static_assert(
+ std::is_trivially_move_assignable_v<CheckedUnsafePtr<NoCheckTestType>>);
+
+class TestCheckingPolicy : public CheckCheckedUnsafePtrs<TestCheckingPolicy> {
+ protected:
+ explicit TestCheckingPolicy(bool& aPassedCheck)
+ : mPassedCheck(aPassedCheck) {}
+
+ private:
+ friend class mozilla::CheckingPolicyAccess;
+ void NotifyCheckFailure() { mPassedCheck = false; }
+
+ bool& mPassedCheck;
+};
+
+struct BasePointee : public SupportsCheckedUnsafePtr<TestCheckingPolicy> {
+ explicit BasePointee(bool& aCheckPassed)
+ : SupportsCheckedUnsafePtr<TestCheckingPolicy>(aCheckPassed) {}
+};
+
+struct DerivedPointee : public BasePointee {
+ using BasePointee::BasePointee;
+};
+
+class CheckedUnsafePtrTest : public ::testing::Test {
+ protected:
+ bool mPassedCheck = true;
+};
+
+TEST_F(CheckedUnsafePtrTest, PointeeWithNoCheckedUnsafePtrs) {
+ { DerivedPointee pointee{mPassedCheck}; }
+ ASSERT_TRUE(mPassedCheck);
+}
+
+template <typename PointerType>
+class TypedCheckedUnsafePtrTest : public CheckedUnsafePtrTest {};
+
+TYPED_TEST_SUITE_P(TypedCheckedUnsafePtrTest);
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest, PointeeWithOneCheckedUnsafePtr) {
+ {
+ DerivedPointee pointee{this->mPassedCheck};
+ CheckedUnsafePtr<TypeParam> ptr = &pointee;
+ }
+ ASSERT_TRUE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest, CheckedUnsafePtrCopyConstructed) {
+ {
+ DerivedPointee pointee{this->mPassedCheck};
+ CheckedUnsafePtr<TypeParam> ptr1 = &pointee;
+ CheckedUnsafePtr<TypeParam> ptr2 = ptr1;
+ }
+ ASSERT_TRUE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest, CheckedUnsafePtrCopyAssigned) {
+ {
+ DerivedPointee pointee{this->mPassedCheck};
+ CheckedUnsafePtr<TypeParam> ptr1 = &pointee;
+ CheckedUnsafePtr<TypeParam> ptr2;
+ ptr2 = ptr1;
+ }
+ ASSERT_TRUE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneDanglingCheckedUnsafePtr) {
+ [this]() -> CheckedUnsafePtr<TypeParam> {
+ DerivedPointee pointee{this->mPassedCheck};
+ return &pointee;
+ }();
+ ASSERT_FALSE(this->mPassedCheck);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneCopiedDanglingCheckedUnsafePtr) {
+ const auto dangling1 = [this]() -> CheckedUnsafePtr<DerivedPointee> {
+ DerivedPointee pointee{this->mPassedCheck};
+ return &pointee;
+ }();
+ EXPECT_FALSE(this->mPassedCheck);
+
+ // With AddressSanitizer we would hopefully detect if the copy constructor
+ // tries to add dangling2 to the now-gone pointee's unsafe pointer array. No
+ // promises though, since it might be optimized away.
+ CheckedUnsafePtr<TypeParam> dangling2{dangling1};
+ ASSERT_TRUE(dangling2);
+}
+
+TYPED_TEST_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr) {
+ const auto dangling1 = [this]() -> CheckedUnsafePtr<DerivedPointee> {
+ DerivedPointee pointee{this->mPassedCheck};
+ return &pointee;
+ }();
+ EXPECT_FALSE(this->mPassedCheck);
+
+ // With AddressSanitizer we would hopefully detect if the assignment tries to
+ // add dangling2 to the now-gone pointee's unsafe pointer array. No promises
+ // though, since it might be optimized away.
+ CheckedUnsafePtr<TypeParam> dangling2;
+ dangling2 = dangling1;
+ ASSERT_TRUE(dangling2);
+}
+
+REGISTER_TYPED_TEST_SUITE_P(TypedCheckedUnsafePtrTest,
+ PointeeWithOneCheckedUnsafePtr,
+ CheckedUnsafePtrCopyConstructed,
+ CheckedUnsafePtrCopyAssigned,
+ PointeeWithOneDanglingCheckedUnsafePtr,
+ PointeeWithOneCopiedDanglingCheckedUnsafePtr,
+ PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr);
+
+using BothTypes = ::testing::Types<BasePointee, DerivedPointee>;
+INSTANTIATE_TYPED_TEST_SUITE_P(InstantiationOf, TypedCheckedUnsafePtrTest,
+ BothTypes);
diff --git a/dom/quota/test/gtest/TestClientUsageArray.cpp b/dom/quota/test/gtest/TestClientUsageArray.cpp
new file mode 100644
index 0000000000..f5db984ccd
--- /dev/null
+++ b/dom/quota/test/gtest/TestClientUsageArray.cpp
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "ClientUsageArray.h"
+#include "gtest/gtest.h"
+
+using namespace mozilla::dom::quota;
+
+TEST(DOM_Quota_ClientUsageArray, Deserialize)
+{
+ ClientUsageArray clientUsages;
+ nsresult rv = clientUsages.Deserialize("I872215 C8404073805 L161709"_ns);
+ ASSERT_EQ(rv, NS_OK);
+}
diff --git a/dom/quota/test/gtest/TestEncryptedStream.cpp b/dom/quota/test/gtest/TestEncryptedStream.cpp
new file mode 100644
index 0000000000..8937b0c4e7
--- /dev/null
+++ b/dom/quota/test/gtest/TestEncryptedStream.cpp
@@ -0,0 +1,804 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <algorithm>
+#include <cstdint>
+#include <cstdlib>
+#include <new>
+#include <numeric>
+#include <ostream>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "ErrorList.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FixedBufferOutputStream.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/SafeRefPtr.h"
+#include "mozilla/dom/quota/DecryptingInputStream_impl.h"
+#include "mozilla/dom/quota/DummyCipherStrategy.h"
+#include "mozilla/dom/quota/EncryptedBlock.h"
+#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
+#include "mozilla/dom/quota/NSSCipherStrategy.h"
+#include "mozilla/fallible.h"
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsICloneableInputStream.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "nsISupports.h"
+#include "nsITellableStream.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nscore.h"
+#include "nss.h"
+
+namespace mozilla::dom::quota {
+
+// Similar to ArrayBufferInputStream from netwerk/base/ArrayBufferInputStream.h,
+// but this is initialized from a Span on construction, rather than lazily from
+// a JS ArrayBuffer.
+class ArrayBufferInputStream : public nsIInputStream,
+ public nsISeekableStream,
+ public nsICloneableInputStream {
+ public:
+ explicit ArrayBufferInputStream(mozilla::Span<const uint8_t> aData);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSITELLABLESTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSICLONEABLEINPUTSTREAM
+
+ private:
+ virtual ~ArrayBufferInputStream() = default;
+
+ mozilla::UniquePtr<char[]> mArrayBuffer;
+ uint32_t mBufferLength;
+ uint32_t mPos;
+ bool mClosed;
+};
+
+NS_IMPL_ADDREF(ArrayBufferInputStream);
+NS_IMPL_RELEASE(ArrayBufferInputStream);
+
+NS_INTERFACE_MAP_BEGIN(ArrayBufferInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
+ NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
+NS_INTERFACE_MAP_END
+
+ArrayBufferInputStream::ArrayBufferInputStream(
+ mozilla::Span<const uint8_t> aData)
+ : mArrayBuffer(MakeUnique<char[]>(aData.Length())),
+ mBufferLength(aData.Length()),
+ mPos(0),
+ mClosed(false) {
+ std::copy(aData.cbegin(), aData.cend(), mArrayBuffer.get());
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Close() {
+ mClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Available(uint64_t* aCount) {
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (mArrayBuffer) {
+ *aCount = mBufferLength ? mBufferLength - mPos : 0;
+ } else {
+ *aCount = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::StreamStatus() {
+ return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aReadCount) {
+ return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
+ uint32_t aCount, uint32_t* result) {
+ MOZ_ASSERT(result, "null ptr");
+ MOZ_ASSERT(mBufferLength >= mPos, "bad stream state");
+
+ if (mClosed) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength),
+ "stream inited incorrectly");
+
+ *result = 0;
+ while (mPos < mBufferLength) {
+ uint32_t remaining = mBufferLength - mPos;
+ MOZ_ASSERT(mArrayBuffer);
+
+ uint32_t count = std::min(aCount, remaining);
+ if (count == 0) {
+ break;
+ }
+
+ uint32_t written;
+ nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count,
+ &written);
+ if (NS_FAILED(rv)) {
+ // InputStreams do not propagate errors to caller.
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(written <= count,
+ "writer should not write more than we asked it to write");
+ mPos += written;
+ *result += written;
+ aCount -= written;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) {
+ // Actually, the stream never blocks, but we lie about it because of the
+ // assumptions in DecryptingInputStream.
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::Tell(int64_t* const aRetval) {
+ MOZ_ASSERT(aRetval);
+
+ *aRetval = mPos;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::Seek(const int32_t aWhence,
+ const int64_t aOffset) {
+ // XXX This is not safe. it's hard to use CheckedInt here, though. As long as
+ // the class is only used for testing purposes, that's probably fine.
+
+ int32_t newPos = mPos;
+ switch (aWhence) {
+ case NS_SEEK_SET:
+ newPos = aOffset;
+ break;
+ case NS_SEEK_CUR:
+ newPos += aOffset;
+ break;
+ case NS_SEEK_END:
+ newPos = mBufferLength;
+ newPos += aOffset;
+ break;
+ default:
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (newPos < 0 || static_cast<uint32_t>(newPos) > mBufferLength) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ mPos = newPos;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::SetEOF() {
+ // Truncating is not supported on a read-only stream.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::GetCloneable(bool* aCloneable) {
+ *aCloneable = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ArrayBufferInputStream::Clone(nsIInputStream** _retval) {
+ *_retval = MakeAndAddRef<ArrayBufferInputStream>(
+ AsBytes(Span{mArrayBuffer.get(), mBufferLength}))
+ .take();
+
+ return NS_OK;
+}
+} // namespace mozilla::dom::quota
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+class DOM_Quota_EncryptedStream : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ // Do this only once, do not tear it down per test case.
+ if (!sNssContext) {
+ sNssContext.reset(
+ NSS_InitContext("", "", "", "", nullptr,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT));
+ }
+ }
+
+ static void TearDownTestCase() { sNssContext = nullptr; }
+
+ private:
+ struct NSSInitContextDeleter {
+ void operator()(NSSInitContext* p) { NSS_ShutdownContext(p); }
+ };
+ inline static std::unique_ptr<NSSInitContext, NSSInitContextDeleter>
+ sNssContext;
+};
+
+enum struct FlushMode { AfterEachChunk, Never };
+enum struct ChunkSize { SingleByte, Unaligned, DataSize };
+
+using PackedTestParams =
+ std::tuple<size_t, ChunkSize, ChunkSize, size_t, FlushMode>;
+
+static size_t EffectiveChunkSize(const ChunkSize aChunkSize,
+ const size_t aDataSize) {
+ switch (aChunkSize) {
+ case ChunkSize::SingleByte:
+ return 1;
+ case ChunkSize::Unaligned:
+ return 17;
+ case ChunkSize::DataSize:
+ return aDataSize;
+ }
+ MOZ_CRASH("Unknown ChunkSize");
+}
+
+struct TestParams {
+ MOZ_IMPLICIT constexpr TestParams(const PackedTestParams& aPackedParams)
+ : mDataSize(std::get<0>(aPackedParams)),
+ mWriteChunkSize(std::get<1>(aPackedParams)),
+ mReadChunkSize(std::get<2>(aPackedParams)),
+ mBlockSize(std::get<3>(aPackedParams)),
+ mFlushMode(std::get<4>(aPackedParams)) {}
+
+ constexpr size_t DataSize() const { return mDataSize; }
+
+ size_t EffectiveWriteChunkSize() const {
+ return EffectiveChunkSize(mWriteChunkSize, mDataSize);
+ }
+
+ size_t EffectiveReadChunkSize() const {
+ return EffectiveChunkSize(mReadChunkSize, mDataSize);
+ }
+
+ constexpr size_t BlockSize() const { return mBlockSize; }
+
+ constexpr enum FlushMode FlushMode() const { return mFlushMode; }
+
+ private:
+ size_t mDataSize;
+
+ ChunkSize mWriteChunkSize;
+ ChunkSize mReadChunkSize;
+
+ size_t mBlockSize;
+ enum FlushMode mFlushMode;
+};
+
+std::string TestParamToString(
+ const testing::TestParamInfo<PackedTestParams>& aTestParams) {
+ const TestParams& testParams = aTestParams.param;
+
+ static constexpr char kSeparator[] = "_";
+
+ std::stringstream ss;
+ ss << "data" << testParams.DataSize() << kSeparator << "writechunk"
+ << testParams.EffectiveWriteChunkSize() << kSeparator << "readchunk"
+ << testParams.EffectiveReadChunkSize() << kSeparator << "block"
+ << testParams.BlockSize() << kSeparator;
+ switch (testParams.FlushMode()) {
+ case FlushMode::Never:
+ ss << "FlushNever";
+ break;
+ case FlushMode::AfterEachChunk:
+ ss << "FlushAfterEachChunk";
+ break;
+ };
+ return ss.str();
+}
+
+class ParametrizedCryptTest
+ : public DOM_Quota_EncryptedStream,
+ public testing::WithParamInterface<PackedTestParams> {};
+
+static auto MakeTestData(const size_t aDataSize) {
+ auto data = nsTArray<uint8_t>();
+ data.SetLength(aDataSize);
+ std::iota(data.begin(), data.end(), 0);
+ return data;
+}
+
+template <typename CipherStrategy>
+static void WriteTestData(nsCOMPtr<nsIOutputStream>&& aBaseOutputStream,
+ const Span<const uint8_t> aData,
+ const size_t aWriteChunkSize, const size_t aBlockSize,
+ const typename CipherStrategy::KeyType& aKey,
+ const FlushMode aFlushMode) {
+ auto outStream = MakeSafeRefPtr<EncryptingOutputStream<CipherStrategy>>(
+ std::move(aBaseOutputStream), aBlockSize, aKey);
+
+ for (auto remaining = aData; !remaining.IsEmpty();) {
+ auto [currentChunk, newRemaining] =
+ remaining.SplitAt(std::min(aWriteChunkSize, remaining.Length()));
+ remaining = newRemaining;
+
+ uint32_t written;
+ EXPECT_EQ(NS_OK, outStream->Write(
+ reinterpret_cast<const char*>(currentChunk.Elements()),
+ currentChunk.Length(), &written));
+ EXPECT_EQ(currentChunk.Length(), written);
+
+ if (aFlushMode == FlushMode::AfterEachChunk) {
+ outStream->Flush();
+ }
+ }
+
+ // Close explicitly so we can check the result.
+ EXPECT_EQ(NS_OK, outStream->Close());
+}
+
+template <typename CipherStrategy>
+static void NoExtraChecks(DecryptingInputStream<CipherStrategy>& aInputStream,
+ Span<const uint8_t> aExpectedData,
+ Span<const uint8_t> aRemainder) {}
+
+template <typename CipherStrategy,
+ typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
+static void ReadTestData(
+ DecryptingInputStream<CipherStrategy>& aDecryptingInputStream,
+ const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize,
+ const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
+ auto readData = nsTArray<uint8_t>();
+ readData.SetLength(aReadChunkSize);
+ for (auto remainder = aExpectedData; !remainder.IsEmpty();) {
+ auto [currentExpected, newExpectedRemainder] =
+ remainder.SplitAt(std::min(aReadChunkSize, remainder.Length()));
+ remainder = newExpectedRemainder;
+
+ uint32_t read;
+ EXPECT_EQ(NS_OK, aDecryptingInputStream.Read(
+ reinterpret_cast<char*>(readData.Elements()),
+ currentExpected.Length(), &read));
+ EXPECT_EQ(currentExpected.Length(), read);
+ EXPECT_EQ(currentExpected,
+ Span{readData}.First(currentExpected.Length()).AsConst());
+
+ aExtraChecks(aDecryptingInputStream, aExpectedData, remainder);
+ }
+
+ // Expect EOF.
+ uint32_t read;
+ EXPECT_EQ(NS_OK, aDecryptingInputStream.Read(
+ reinterpret_cast<char*>(readData.Elements()),
+ readData.Length(), &read));
+ EXPECT_EQ(0u, read);
+}
+
+template <typename CipherStrategy,
+ typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
+static auto ReadTestData(
+ MovingNotNull<nsCOMPtr<nsIInputStream>>&& aBaseInputStream,
+ const Span<const uint8_t> aExpectedData, const size_t aReadChunkSize,
+ const size_t aBlockSize, const typename CipherStrategy::KeyType& aKey,
+ const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
+ auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
+ std::move(aBaseInputStream), aBlockSize, aKey);
+
+ ReadTestData(*inStream, aExpectedData, aReadChunkSize, aExtraChecks);
+
+ return inStream;
+}
+
+// XXX Change to return the buffer instead.
+template <typename CipherStrategy,
+ typename ExtraChecks = decltype(NoExtraChecks<CipherStrategy>)>
+static RefPtr<FixedBufferOutputStream> DoRoundtripTest(
+ const size_t aDataSize, const size_t aWriteChunkSize,
+ const size_t aReadChunkSize, const size_t aBlockSize,
+ const typename CipherStrategy::KeyType& aKey, const FlushMode aFlushMode,
+ const ExtraChecks& aExtraChecks = NoExtraChecks<CipherStrategy>) {
+ // XXX Add deduction guide for RefPtr from already_AddRefed
+ const auto baseOutputStream = WrapNotNull(
+ RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
+
+ const auto data = MakeTestData(aDataSize);
+
+ WriteTestData<CipherStrategy>(
+ nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
+ aWriteChunkSize, aBlockSize, aKey, aFlushMode);
+
+ const auto baseInputStream =
+ MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
+
+ ReadTestData<CipherStrategy>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data},
+ aReadChunkSize, aBlockSize, aKey, aExtraChecks);
+
+ return baseOutputStream;
+}
+
+TEST_P(ParametrizedCryptTest, NSSCipherStrategy) {
+ using CipherStrategy = NSSCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ auto keyOrErr = CipherStrategy::GenerateKey();
+ ASSERT_FALSE(keyOrErr.isErr());
+
+ DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ keyOrErr.unwrap(), testParams.FlushMode());
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_CheckOutput) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ const auto encryptedDataStream = DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode());
+
+ if (HasFailure()) {
+ return;
+ }
+
+ const auto encryptedData = encryptedDataStream->WrittenData();
+ const auto encryptedDataSpan = AsBytes(Span(encryptedData));
+
+ const auto plainTestData = MakeTestData(testParams.DataSize());
+ auto encryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength,
+ DummyCipherStrategy::BasicBlockSize>{
+ testParams.BlockSize(),
+ };
+ for (auto [encryptedRemainder, plainRemainder] =
+ std::pair(encryptedDataSpan, Span(plainTestData));
+ !encryptedRemainder.IsEmpty();) {
+ const auto [currentBlock, newEncryptedRemainder] =
+ encryptedRemainder.SplitAt(testParams.BlockSize());
+ encryptedRemainder = newEncryptedRemainder;
+
+ std::copy(currentBlock.cbegin(), currentBlock.cend(),
+ encryptedBlock.MutableWholeBlock().begin());
+
+ ASSERT_FALSE(plainRemainder.IsEmpty());
+ const auto [currentPlain, newPlainRemainder] =
+ plainRemainder.SplitAt(encryptedBlock.ActualPayloadLength());
+ plainRemainder = newPlainRemainder;
+
+ const auto pseudoIV = encryptedBlock.CipherPrefix();
+ const auto payload = encryptedBlock.Payload();
+
+ EXPECT_EQ(Span(DummyCipherStrategy::MakeBlockPrefix()), pseudoIV);
+
+ auto untransformedPayload = nsTArray<uint8_t>();
+ untransformedPayload.SetLength(testParams.BlockSize());
+ DummyCipherStrategy::DummyTransform(payload, untransformedPayload);
+
+ EXPECT_EQ(
+ currentPlain,
+ Span(untransformedPayload).AsConst().First(currentPlain.Length()));
+ }
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Tell) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode(),
+ [](auto& inStream, Span<const uint8_t> expectedData,
+ Span<const uint8_t> remainder) {
+ // Check that Tell tells the right position.
+ int64_t pos;
+ EXPECT_EQ(NS_OK, inStream.Tell(&pos));
+ EXPECT_EQ(expectedData.Length() - remainder.Length(),
+ static_cast<uint64_t>(pos));
+ });
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Available) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ DoRoundtripTest<CipherStrategy>(
+ testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode(),
+ [](auto& inStream, Span<const uint8_t> expectedData,
+ Span<const uint8_t> remainder) {
+ // Check that Available tells the right remainder.
+ uint64_t available;
+ EXPECT_EQ(NS_OK, inStream.Available(&available));
+ EXPECT_EQ(remainder.Length(), available);
+ });
+}
+
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_Clone) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ // XXX Add deduction guide for RefPtr from already_AddRefed
+ const auto baseOutputStream = WrapNotNull(
+ RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
+
+ const auto data = MakeTestData(testParams.DataSize());
+
+ WriteTestData<CipherStrategy>(
+ nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
+ testParams.EffectiveWriteChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{}, testParams.FlushMode());
+
+ const auto baseInputStream =
+ MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
+
+ const auto inStream = ReadTestData<CipherStrategy>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data},
+ testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
+ CipherStrategy::KeyType{});
+
+ nsCOMPtr<nsIInputStream> clonedInputStream;
+ EXPECT_EQ(NS_OK, inStream->Clone(getter_AddRefs(clonedInputStream)));
+
+ ReadTestData(
+ static_cast<DecryptingInputStream<CipherStrategy>&>(*clonedInputStream),
+ Span{data}, testParams.EffectiveReadChunkSize());
+}
+
+// XXX This test is actually only parametrized on the block size.
+TEST_P(ParametrizedCryptTest, DummyCipherStrategy_IncompleteBlock) {
+ using CipherStrategy = DummyCipherStrategy;
+ const TestParams& testParams = GetParam();
+
+ // Provide half a block, content doesn't matter.
+ nsTArray<uint8_t> data;
+ data.SetLength(testParams.BlockSize() / 2);
+
+ const auto baseInputStream = MakeRefPtr<ArrayBufferInputStream>(data);
+
+ const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}),
+ testParams.BlockSize(), CipherStrategy::KeyType{});
+
+ nsTArray<uint8_t> readData;
+ readData.SetLength(testParams.BlockSize());
+ uint32_t read;
+ EXPECT_EQ(NS_ERROR_CORRUPTED_CONTENT,
+ inStream->Read(reinterpret_cast<char*>(readData.Elements()),
+ readData.Length(), &read));
+}
+
+TEST_P(ParametrizedCryptTest, zeroInitializedEncryptedBlock) {
+ const TestParams& testParams = GetParam();
+
+ using EncryptedBlock = EncryptedBlock<DummyCipherStrategy::BlockPrefixLength,
+ DummyCipherStrategy::BasicBlockSize>;
+
+ EncryptedBlock encryptedBlock{testParams.BlockSize()};
+ auto firstBlock =
+ encryptedBlock.WholeBlock().First<DummyCipherStrategy::BasicBlockSize>();
+ auto unusedBytesInFirstBlock = firstBlock.from(sizeof(uint16_t));
+
+ EXPECT_TRUE(std::all_of(unusedBytesInFirstBlock.begin(),
+ unusedBytesInFirstBlock.end(),
+ [](const auto& e) { return 0ul == e; }));
+}
+
+enum struct SeekOffset {
+ Zero,
+ MinusHalfDataSize,
+ PlusHalfDataSize,
+ PlusDataSize,
+ MinusDataSize
+};
+using SeekOp = std::pair<int32_t, SeekOffset>;
+
+using PackedSeekTestParams = std::tuple<size_t, size_t, std::vector<SeekOp>>;
+
+struct SeekTestParams {
+ size_t mDataSize;
+ size_t mBlockSize;
+ std::vector<SeekOp> mSeekOps;
+
+ MOZ_IMPLICIT SeekTestParams(const PackedSeekTestParams& aPackedParams)
+ : mDataSize(std::get<0>(aPackedParams)),
+ mBlockSize(std::get<1>(aPackedParams)),
+ mSeekOps(std::get<2>(aPackedParams)) {}
+};
+
+std::string SeekTestParamToString(
+ const testing::TestParamInfo<PackedSeekTestParams>& aTestParams) {
+ const SeekTestParams& testParams = aTestParams.param;
+
+ static constexpr char kSeparator[] = "_";
+
+ std::stringstream ss;
+ ss << "data" << testParams.mDataSize << kSeparator << "writechunk"
+ << testParams.mBlockSize << kSeparator;
+ for (const auto& seekOp : testParams.mSeekOps) {
+ switch (seekOp.first) {
+ case nsISeekableStream::NS_SEEK_SET:
+ ss << "Set";
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ ss << "Cur";
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ ss << "End";
+ break;
+ };
+ switch (seekOp.second) {
+ case SeekOffset::Zero:
+ ss << "Zero";
+ break;
+ case SeekOffset::MinusHalfDataSize:
+ ss << "MinusHalfDataSize";
+ break;
+ case SeekOffset::PlusHalfDataSize:
+ ss << "PlusHalfDataSize";
+ break;
+ case SeekOffset::MinusDataSize:
+ ss << "MinusDataSize";
+ break;
+ case SeekOffset::PlusDataSize:
+ ss << "PlusDataSize";
+ break;
+ };
+ }
+ return ss.str();
+}
+
+class ParametrizedSeekCryptTest
+ : public DOM_Quota_EncryptedStream,
+ public testing::WithParamInterface<PackedSeekTestParams> {};
+
+TEST_P(ParametrizedSeekCryptTest, DummyCipherStrategy_Seek) {
+ using CipherStrategy = DummyCipherStrategy;
+ const SeekTestParams& testParams = GetParam();
+
+ const auto baseOutputStream = WrapNotNull(
+ RefPtr<FixedBufferOutputStream>{FixedBufferOutputStream::Create(2048)});
+
+ const auto data = MakeTestData(testParams.mDataSize);
+
+ WriteTestData<CipherStrategy>(
+ nsCOMPtr<nsIOutputStream>{baseOutputStream.get()}, Span{data},
+ testParams.mDataSize, testParams.mBlockSize, CipherStrategy::KeyType{},
+ FlushMode::Never);
+
+ const auto baseInputStream =
+ MakeRefPtr<ArrayBufferInputStream>(baseOutputStream->WrittenData());
+
+ const auto inStream = MakeSafeRefPtr<DecryptingInputStream<CipherStrategy>>(
+ WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}),
+ testParams.mBlockSize, CipherStrategy::KeyType{});
+
+ uint32_t accumulatedOffset = 0;
+ for (const auto& seekOp : testParams.mSeekOps) {
+ const auto offset = [offsetKind = seekOp.second,
+ dataSize = testParams.mDataSize]() -> int64_t {
+ switch (offsetKind) {
+ case SeekOffset::Zero:
+ return 0;
+ case SeekOffset::MinusHalfDataSize:
+ return -static_cast<int64_t>(dataSize) / 2;
+ case SeekOffset::PlusHalfDataSize:
+ return dataSize / 2;
+ case SeekOffset::MinusDataSize:
+ return -static_cast<int64_t>(dataSize);
+ case SeekOffset::PlusDataSize:
+ return dataSize;
+ }
+ MOZ_CRASH("Unknown SeekOffset");
+ }();
+ switch (seekOp.first) {
+ case nsISeekableStream::NS_SEEK_SET:
+ accumulatedOffset = offset;
+ break;
+ case nsISeekableStream::NS_SEEK_CUR:
+ accumulatedOffset += offset;
+ break;
+ case nsISeekableStream::NS_SEEK_END:
+ accumulatedOffset = testParams.mDataSize + offset;
+ break;
+ }
+ EXPECT_EQ(NS_OK, inStream->Seek(seekOp.first, offset));
+ }
+
+ {
+ int64_t actualOffset;
+ EXPECT_EQ(NS_OK, inStream->Tell(&actualOffset));
+
+ EXPECT_EQ(actualOffset, accumulatedOffset);
+ }
+
+ auto readData = nsTArray<uint8_t>();
+ readData.SetLength(data.Length());
+ uint32_t read;
+ EXPECT_EQ(NS_OK, inStream->Read(reinterpret_cast<char*>(readData.Elements()),
+ readData.Length(), &read));
+ // XXX Or should 'read' indicate the actual number of bytes read,
+ // including the encryption overhead?
+ EXPECT_EQ(testParams.mDataSize - accumulatedOffset, read);
+ EXPECT_EQ(Span{data}.SplitAt(accumulatedOffset).second,
+ Span{readData}.First(read).AsConst());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ DOM_Quota_EncryptedStream_Parametrized, ParametrizedCryptTest,
+ testing::Combine(
+ /* dataSize */ testing::Values(0u, 16u, 256u, 512u, 513u),
+ /* writeChunkSize */
+ testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned,
+ ChunkSize::DataSize),
+ /* readChunkSize */
+ testing::Values(ChunkSize::SingleByte, ChunkSize::Unaligned,
+ ChunkSize::DataSize),
+ /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/),
+ /* flushMode */
+ testing::Values(FlushMode::Never, FlushMode::AfterEachChunk)),
+ TestParamToString);
+
+INSTANTIATE_TEST_SUITE_P(
+ DOM_IndexedDB_EncryptedStream_ParametrizedSeek, ParametrizedSeekCryptTest,
+ testing::Combine(
+ /* dataSize */ testing::Values(0u, 16u, 256u, 512u, 513u),
+ /* blockSize */ testing::Values(256u, 1024u /*, 8192u*/),
+ /* seekOperations */
+ testing::Values(/* NS_SEEK_SET only, single ops */
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET,
+ SeekOffset::PlusDataSize}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_SET,
+ SeekOffset::PlusHalfDataSize}},
+ /* NS_SEEK_SET only, multiple ops */
+ std::vector<SeekOp>{
+ {nsISeekableStream::NS_SEEK_SET,
+ SeekOffset::PlusHalfDataSize},
+ {nsISeekableStream::NS_SEEK_SET, SeekOffset::Zero}},
+ /* NS_SEEK_CUR only, single ops */
+ std::vector<SeekOp>{
+ {nsISeekableStream::NS_SEEK_CUR, SeekOffset::Zero}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
+ SeekOffset::PlusDataSize}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_CUR,
+ SeekOffset::PlusHalfDataSize}},
+ /* NS_SEEK_END only, single ops */
+ std::vector<SeekOp>{
+ {nsISeekableStream::NS_SEEK_END, SeekOffset::Zero}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
+ SeekOffset::MinusDataSize}},
+ std::vector<SeekOp>{{nsISeekableStream::NS_SEEK_END,
+ SeekOffset::MinusHalfDataSize}})),
+ SeekTestParamToString);
diff --git a/dom/quota/test/gtest/TestFileOutputStream.cpp b/dom/quota/test/gtest/TestFileOutputStream.cpp
new file mode 100644
index 0000000000..231003fd6c
--- /dev/null
+++ b/dom/quota/test/gtest/TestFileOutputStream.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/CommonMetadata.h"
+#include "mozilla/dom/quota/DirectoryLock.h"
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "QuotaManagerDependencyFixture.h"
+
+namespace mozilla::dom::quota::test {
+
+quota::OriginMetadata GetOutputStreamTestOriginMetadata() {
+ return quota::OriginMetadata{""_ns,
+ "example.com"_ns,
+ "http://example.com"_ns,
+ "http://example.com"_ns,
+ /* aIsPrivate */ false,
+ quota::PERSISTENCE_TYPE_DEFAULT};
+}
+
+class TestFileOutputStream : public QuotaManagerDependencyFixture {
+ public:
+ static void SetUpTestCase() {
+ ASSERT_NO_FATAL_FAILURE(InitializeFixture());
+
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->SetIntPref("dom.quotaManager.temporaryStorage.fixedLimit",
+ mQuotaLimit);
+ }
+
+ static void TearDownTestCase() {
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+ prefs->ClearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
+
+ EXPECT_NO_FATAL_FAILURE(
+ ClearStoragesForOrigin(GetOutputStreamTestOriginMetadata()));
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownFixture());
+ }
+
+ static const int32_t mQuotaLimit = 8192;
+};
+
+TEST_F(TestFileOutputStream, extendFileStreamWithSetEOF) {
+ auto backgroundTask = []() {
+ auto ioTask = []() {
+ quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
+
+ auto originMetadata = GetOutputStreamTestOriginMetadata();
+
+ {
+ ASSERT_NS_SUCCEEDED(
+ quotaManager->EnsureTemporaryStorageIsInitializedInternal());
+
+ auto res = quotaManager->EnsureTemporaryOriginIsInitialized(
+ quota::PERSISTENCE_TYPE_DEFAULT, originMetadata);
+ ASSERT_TRUE(res.isOk());
+ }
+
+ const int64_t groupLimit =
+ static_cast<int64_t>(quotaManager->GetGroupLimit());
+ ASSERT_TRUE(mQuotaLimit * 1024LL == groupLimit);
+
+ // We don't use the tested stream itself to check the file size as it
+ // may report values which have not been written to disk.
+ RefPtr<quota::FileOutputStream> check =
+ MakeRefPtr<quota::FileOutputStream>(quota::PERSISTENCE_TYPE_DEFAULT,
+ originMetadata,
+ quota::Client::Type::SDB);
+
+ RefPtr<quota::FileOutputStream> stream =
+ MakeRefPtr<quota::FileOutputStream>(quota::PERSISTENCE_TYPE_DEFAULT,
+ originMetadata,
+ quota::Client::Type::SDB);
+
+ {
+ auto testPathRes = quotaManager->GetOriginDirectory(originMetadata);
+
+ ASSERT_TRUE(testPathRes.isOk());
+
+ nsCOMPtr<nsIFile> testPath = testPathRes.unwrap();
+
+ ASSERT_NS_SUCCEEDED(testPath->AppendRelativePath(u"sdb"_ns));
+
+ ASSERT_NS_SUCCEEDED(
+ testPath->AppendRelativePath(u"tTestFileOutputStream.txt"_ns));
+
+ bool exists = true;
+ ASSERT_NS_SUCCEEDED(testPath->Exists(&exists));
+
+ if (exists) {
+ ASSERT_NS_SUCCEEDED(testPath->Remove(/* recursive */ false));
+ }
+
+ ASSERT_NS_SUCCEEDED(testPath->Exists(&exists));
+ ASSERT_FALSE(exists);
+
+ ASSERT_NS_SUCCEEDED(testPath->Create(nsIFile::NORMAL_FILE_TYPE, 0666));
+
+ ASSERT_NS_SUCCEEDED(testPath->Exists(&exists));
+ ASSERT_TRUE(exists);
+
+ nsCOMPtr<nsIFile> checkPath;
+ ASSERT_NS_SUCCEEDED(testPath->Clone(getter_AddRefs(checkPath)));
+
+ const int32_t IOFlags = -1;
+ const int32_t perm = -1;
+ const int32_t behaviorFlags = 0;
+ ASSERT_NS_SUCCEEDED(
+ stream->Init(testPath, IOFlags, perm, behaviorFlags));
+
+ ASSERT_NS_SUCCEEDED(
+ check->Init(testPath, IOFlags, perm, behaviorFlags));
+ }
+
+ // Check that we start with an empty file
+ int64_t avail = 42;
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(0 == avail);
+
+ // Enlarge the file
+ const int64_t toSize = groupLimit;
+ ASSERT_NS_SUCCEEDED(stream->Seek(nsISeekableStream::NS_SEEK_SET, toSize));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(0 == avail);
+
+ ASSERT_NS_SUCCEEDED(stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ // Try to enlarge the file past the limit
+ const int64_t overGroupLimit = groupLimit + 1;
+
+ // Seeking is allowed
+ ASSERT_NS_SUCCEEDED(
+ stream->Seek(nsISeekableStream::NS_SEEK_SET, overGroupLimit));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ // Setting file size to exceed quota should yield no device space error
+ ASSERT_TRUE(NS_ERROR_FILE_NO_DEVICE_SPACE == stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ // Shrink the file
+ const int64_t toHalfSize = toSize / 2;
+ ASSERT_NS_SUCCEEDED(
+ stream->Seek(nsISeekableStream::NS_SEEK_SET, toHalfSize));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toSize == avail);
+
+ ASSERT_NS_SUCCEEDED(stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toHalfSize == avail);
+
+ // Shrink the file back to nothing
+ ASSERT_NS_SUCCEEDED(stream->Seek(nsISeekableStream::NS_SEEK_SET, 0));
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(toHalfSize == avail);
+
+ ASSERT_NS_SUCCEEDED(stream->SetEOF());
+
+ ASSERT_NS_SUCCEEDED(check->GetSize(&avail));
+
+ ASSERT_TRUE(0 == avail);
+ };
+
+ RefPtr<ClientDirectoryLock> directoryLock;
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager
+ ->OpenClientDirectory(
+ {GetOutputStreamTestOriginMetadata(), Client::SDB})
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock, &done](RefPtr<ClientDirectoryLock> aResolveValue) {
+ directoryLock = std::move(aResolveValue);
+
+ done = true;
+ },
+ [&done](const nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ ASSERT_TRUE(directoryLock);
+
+ PerformOnIOThread(std::move(ioTask));
+
+ directoryLock = nullptr;
+ };
+
+ PerformOnBackgroundThread(std::move(backgroundTask));
+}
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/TestFlatten.cpp b/dom/quota/test/gtest/TestFlatten.cpp
new file mode 100644
index 0000000000..5ca7675887
--- /dev/null
+++ b/dom/quota/test/gtest/TestFlatten.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "Flatten.h"
+
+#include "gtest/gtest.h"
+
+#include "mozilla/Unused.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom::quota {
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunreachable-code-loop-increment"
+#endif
+TEST(Flatten, FlatEmpty)
+{
+ for (const auto& item : Flatten<int>(nsTArray<int>{})) {
+ Unused << item;
+ FAIL();
+ }
+}
+
+TEST(Flatten, NestedOuterEmpty)
+{
+ for (const auto& item : Flatten<int>(nsTArray<CopyableTArray<int>>{})) {
+ Unused << item;
+ FAIL();
+ }
+}
+
+TEST(Flatten, NestedInnerEmpty)
+{
+ for (const auto& item :
+ Flatten<int>(nsTArray<CopyableTArray<int>>{CopyableTArray<int>{}})) {
+ Unused << item;
+ FAIL();
+ }
+}
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+TEST(Flatten, NestedInnerSingular)
+{
+ nsTArray<int> flattened;
+ for (const auto& item :
+ Flatten<int>(nsTArray<CopyableTArray<int>>{CopyableTArray<int>{1}})) {
+ flattened.AppendElement(item);
+ }
+
+ EXPECT_EQ(nsTArray{1}, flattened);
+}
+
+TEST(Flatten, NestedInnerSingulars)
+{
+ nsTArray<int> flattened;
+ for (const auto& item : Flatten<int>(nsTArray<CopyableTArray<int>>{
+ CopyableTArray<int>{1}, CopyableTArray<int>{2}})) {
+ flattened.AppendElement(item);
+ }
+
+ EXPECT_EQ((nsTArray<int>{{1, 2}}), flattened);
+}
+
+TEST(Flatten, NestedInnerNonSingulars)
+{
+ nsTArray<int> flattened;
+ for (const auto& item : Flatten<int>(nsTArray<CopyableTArray<int>>{
+ CopyableTArray<int>{1, 2}, CopyableTArray<int>{3, 4}})) {
+ flattened.AppendElement(item);
+ }
+
+ EXPECT_EQ((nsTArray<int>{{1, 2, 3, 4}}), flattened);
+}
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestForwardDecls.cpp b/dom/quota/test/gtest/TestForwardDecls.cpp
new file mode 100644
index 0000000000..223f83af03
--- /dev/null
+++ b/dom/quota/test/gtest/TestForwardDecls.cpp
@@ -0,0 +1,12 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <type_traits>
+#include "mozilla/dom/quota/ForwardDecls.h"
+
+using namespace mozilla;
+
+static_assert(std::is_same_v<OkOrErr, Result<Ok, QMResult>>);
diff --git a/dom/quota/test/gtest/TestOriginParser.cpp b/dom/quota/test/gtest/TestOriginParser.cpp
new file mode 100644
index 0000000000..5cd4d2682b
--- /dev/null
+++ b/dom/quota/test/gtest/TestOriginParser.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "OriginParser.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla::dom::quota;
+
+TEST(OriginParser_IsUUIDOrigin, Valid)
+{ EXPECT_TRUE(IsUUIDOrigin("uuid://1ef9867c-e754-4303-a18b-684f0321f6e2"_ns)); }
+
+TEST(OriginParser_IsUUIDOrigin, Invalid)
+{
+ EXPECT_FALSE(IsUUIDOrigin("Invalid UUID Origin"_ns));
+
+ EXPECT_FALSE(IsUUIDOrigin("1ef9867c-e754-4303-a18b-684f0321f6e2"_ns));
+
+ EXPECT_FALSE(IsUUIDOrigin("uuid://1ef9867c-e754-4303-a18b"_ns));
+
+ EXPECT_FALSE(IsUUIDOrigin("uuid+++1ef9867c-e754-4303-a18b-684f0321f6e2"_ns));
+}
diff --git a/dom/quota/test/gtest/TestOriginScope.cpp b/dom/quota/test/gtest/TestOriginScope.cpp
new file mode 100644
index 0000000000..4afb45c1b4
--- /dev/null
+++ b/dom/quota/test/gtest/TestOriginScope.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/OriginScope.h"
+#include "nsStringFwd.h"
+#include "nsTDependentString.h"
+#include "nsTLiteralString.h"
+
+namespace mozilla::dom::quota {
+
+namespace {
+
+struct OriginTest {
+ const char* mOrigin;
+ bool mMatch;
+};
+
+void CheckOriginScopeMatchesOrigin(const OriginScope& aOriginScope,
+ const char* aOrigin, bool aMatch) {
+ bool result = aOriginScope.Matches(
+ OriginScope::FromOrigin(nsDependentCString(aOrigin)));
+
+ EXPECT_TRUE(result == aMatch);
+}
+
+} // namespace
+
+TEST(DOM_Quota_OriginScope, SanityChecks)
+{
+ OriginScope originScope;
+
+ // Sanity checks.
+
+ {
+ constexpr auto origin = "http://www.mozilla.org"_ns;
+ originScope.SetFromOrigin(origin);
+ EXPECT_TRUE(originScope.IsOrigin());
+ EXPECT_TRUE(originScope.GetOrigin().Equals(origin));
+ EXPECT_TRUE(originScope.GetOriginNoSuffix().Equals(origin));
+ }
+
+ {
+ constexpr auto prefix = "http://www.mozilla.org"_ns;
+ originScope.SetFromPrefix(prefix);
+ EXPECT_TRUE(originScope.IsPrefix());
+ EXPECT_TRUE(originScope.GetOriginNoSuffix().Equals(prefix));
+ }
+
+ {
+ originScope.SetFromNull();
+ EXPECT_TRUE(originScope.IsNull());
+ }
+}
+
+TEST(DOM_Quota_OriginScope, MatchesOrigin)
+{
+ OriginScope originScope;
+
+ // Test each origin scope type against particular origins.
+
+ {
+ originScope.SetFromOrigin("http://www.mozilla.org"_ns);
+
+ static const OriginTest tests[] = {
+ {"http://www.mozilla.org", true},
+ {"http://www.example.org", false},
+ };
+
+ for (const auto& test : tests) {
+ CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+ }
+ }
+
+ {
+ originScope.SetFromPrefix("http://www.mozilla.org"_ns);
+
+ static const OriginTest tests[] = {
+ {"http://www.mozilla.org", true},
+ {"http://www.mozilla.org^userContextId=1", true},
+ {"http://www.example.org^userContextId=1", false},
+ };
+
+ for (const auto& test : tests) {
+ CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+ }
+ }
+
+ {
+ originScope.SetFromNull();
+
+ static const OriginTest tests[] = {
+ {"http://www.mozilla.org", true},
+ {"http://www.mozilla.org^userContextId=1", true},
+ {"http://www.example.org^userContextId=1", true},
+ };
+
+ for (const auto& test : tests) {
+ CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+ }
+ }
+}
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestPersistenceType.cpp b/dom/quota/test/gtest/TestPersistenceType.cpp
new file mode 100644
index 0000000000..c4f1e5d581
--- /dev/null
+++ b/dom/quota/test/gtest/TestPersistenceType.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/PersistenceType.h"
+
+#include "gtest/gtest.h"
+
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+
+namespace mozilla::dom::quota {
+
+TEST(PersistenceType, FromFile)
+{
+ nsCOMPtr<nsIFile> base;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ EXPECT_EQ(rv, NS_OK);
+
+ const auto testPersistenceType = [&base](const nsLiteralString& aString,
+ const Maybe<PersistenceType> aType) {
+ nsCOMPtr<nsIFile> file;
+
+ nsresult rv = base->Clone(getter_AddRefs(file));
+ EXPECT_EQ(rv, NS_OK);
+
+ rv = file->Append(aString);
+ EXPECT_EQ(rv, NS_OK);
+
+ auto maybePersistenceType = PersistenceTypeFromFile(*file, fallible);
+ EXPECT_EQ(maybePersistenceType, aType);
+ };
+
+ testPersistenceType(u"permanent"_ns, Some(PERSISTENCE_TYPE_PERSISTENT));
+ testPersistenceType(u"temporary"_ns, Some(PERSISTENCE_TYPE_TEMPORARY));
+ testPersistenceType(u"default"_ns, Some(PERSISTENCE_TYPE_DEFAULT));
+ testPersistenceType(u"persistent"_ns, Nothing());
+ testPersistenceType(u"foobar"_ns, Nothing());
+}
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestQMResult.cpp b/dom/quota/test/gtest/TestQMResult.cpp
new file mode 100644
index 0000000000..94cbec7364
--- /dev/null
+++ b/dom/quota/test/gtest/TestQMResult.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "Common.h"
+#include "gtest/gtest.h"
+#include "mozilla/dom/QMResult.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+class DOM_Quota_QMResult : public DOM_Quota_Test {};
+
+#ifdef QM_ERROR_STACKS_ENABLED
+TEST_F(DOM_Quota_QMResult, Construct_Default) {
+ QMResult res;
+
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res.StackId(), ExpectedStackId());
+ ASSERT_EQ(res.FrameId(), 1u);
+ ASSERT_EQ(res.NSResult(), NS_OK);
+}
+#endif
+
+TEST_F(DOM_Quota_QMResult, Construct_FromNSResult) {
+ QMResult res(NS_ERROR_FAILURE);
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res.StackId(), ExpectedStackId());
+ ASSERT_EQ(res.FrameId(), 1u);
+ ASSERT_EQ(res.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(res, NS_ERROR_FAILURE);
+#endif
+}
+
+#ifdef QM_ERROR_STACKS_ENABLED
+TEST_F(DOM_Quota_QMResult, Propagate) {
+ QMResult res1(NS_ERROR_FAILURE);
+
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res1.StackId(), ExpectedStackId());
+ ASSERT_EQ(res1.FrameId(), 1u);
+ ASSERT_EQ(res1.NSResult(), NS_ERROR_FAILURE);
+
+ QMResult res2 = res1.Propagate();
+
+ ASSERT_EQ(res2.StackId(), ExpectedStackId());
+ ASSERT_EQ(res2.FrameId(), 2u);
+ ASSERT_EQ(res2.NSResult(), NS_ERROR_FAILURE);
+}
+#endif
+
+TEST_F(DOM_Quota_QMResult, ToQMResult) {
+ auto res = ToQMResult(NS_ERROR_FAILURE);
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(res.StackId(), ExpectedStackId());
+ ASSERT_EQ(res.FrameId(), 1u);
+ ASSERT_EQ(res.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(res, NS_ERROR_FAILURE);
+#endif
+}
diff --git a/dom/quota/test/gtest/TestQuotaCommon.cpp b/dom/quota/test/gtest/TestQuotaCommon.cpp
new file mode 100644
index 0000000000..2367f08cd6
--- /dev/null
+++ b/dom/quota/test/gtest/TestQuotaCommon.cpp
@@ -0,0 +1,2288 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/QuotaCommon.h"
+
+#include "gtest/gtest.h"
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <map>
+#include <new>
+#include <ostream>
+#include <type_traits>
+#include <utility>
+#include <vector>
+#include "ErrorList.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Unused.h"
+#include "mozilla/fallible.h"
+#include "mozilla/dom/quota/QuotaTestParent.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsTLiteralString.h"
+
+class nsISupports;
+
+namespace mozilla::dom::quota {
+
+namespace {
+
+void CheckUnknownFileEntry(nsIFile& aBase, const nsAString& aName,
+ const bool aWarnIfFile, const bool aWarnIfDir) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aBase.Clone(getter_AddRefs(file));
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Append(aName);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ ASSERT_EQ(rv, NS_OK);
+
+ auto okOrErr = WARN_IF_FILE_IS_UNKNOWN(*file);
+ ASSERT_TRUE(okOrErr.isOk());
+
+#ifdef DEBUG
+ EXPECT_TRUE(okOrErr.inspect() == aWarnIfFile);
+#else
+ EXPECT_TRUE(okOrErr.inspect() == false);
+#endif
+
+ rv = file->Remove(false);
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = file->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ ASSERT_EQ(rv, NS_OK);
+
+ okOrErr = WARN_IF_FILE_IS_UNKNOWN(*file);
+ ASSERT_TRUE(okOrErr.isOk());
+
+#ifdef DEBUG
+ EXPECT_TRUE(okOrErr.inspect() == aWarnIfDir);
+#else
+ EXPECT_TRUE(okOrErr.inspect() == false);
+#endif
+
+ rv = file->Remove(false);
+ ASSERT_EQ(rv, NS_OK);
+}
+
+} // namespace
+
+TEST(QuotaCommon_WarnIfFileIsUnknown, Basics)
+{
+ nsCOMPtr<nsIFile> base;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base));
+ ASSERT_EQ(rv, NS_OK);
+
+ rv = base->Append(u"mozquotatests"_ns);
+ ASSERT_EQ(rv, NS_OK);
+
+ base->Remove(true);
+
+ rv = base->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ ASSERT_EQ(rv, NS_OK);
+
+ CheckUnknownFileEntry(*base, u"foo.bar"_ns, true, true);
+ CheckUnknownFileEntry(*base, u".DS_Store"_ns, false, true);
+ CheckUnknownFileEntry(*base, u".desktop"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"desktop.ini"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"DESKTOP.INI"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"thumbs.db"_ns, false, true);
+ CheckUnknownFileEntry(*base, u"THUMBS.DB"_ns, false, true);
+ CheckUnknownFileEntry(*base, u".xyz"_ns, false, true);
+
+ rv = base->Remove(true);
+ ASSERT_EQ(rv, NS_OK);
+}
+
+mozilla::ipc::IPCResult QuotaTestParent::RecvTry_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_IPC_FAIL(this));
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult QuotaTestParent::RecvTry_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), IPC_FAIL(this, "Custom why"));
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+QuotaTestParent::RecvTryInspect_Success_CustomErr_QmIpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY_INSPECT(const auto& x, (mozilla::Result<int32_t, nsresult>{42}),
+ QM_IPC_FAIL(this));
+ Unused << x;
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+QuotaTestParent::RecvTryInspect_Success_CustomErr_IpcFail(
+ bool* aTryDidNotReturn) {
+ QM_TRY_INSPECT(const auto& x, (mozilla::Result<int32_t, nsresult>{42}),
+ IPC_FAIL(this, "Custom why"));
+ Unused << x;
+
+ *aTryDidNotReturn = true;
+
+ return IPC_OK();
+}
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunreachable-code"
+#endif
+
+TEST(QuotaCommon_Try, Success)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Success_CustomErr_QmIpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTry_Success_CustomErr_QmIpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+TEST(QuotaCommon_Try, Success_CustomErr_IpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTry_Success_CustomErr_IpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+#ifdef DEBUG
+TEST(QuotaCommon_Try, Success_CustomErr_AssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Success_NoErr_AssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ [&tryDidNotReturn]() -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE_VOID);
+
+ tryDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+}
+#else
+# if defined(QM_ASSERT_UNREACHABLE) || defined(QM_ASSERT_UNREACHABLE_VOID)
+#error QM_ASSERT_UNREACHABLE and QM_ASSERT_UNREACHABLE_VOID should not be defined.
+# endif
+#endif
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+TEST(QuotaCommon_Try, Success_CustomErr_DiagnosticAssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Success_NoErr_DiagnosticAssertUnreachable)
+{
+ bool tryDidNotReturn = false;
+
+ [&tryDidNotReturn]() -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID);
+
+ tryDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryDidNotReturn);
+}
+#else
+# if defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE) || \
+ defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID)
+#error QM_DIAGNOSTIC_ASSERT_UNREACHABLE and QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID should not be defined.
+# endif
+#endif
+
+TEST(QuotaCommon_Try, Success_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryDidNotReturn = false; \
+ \
+ nsresult rv = [&tryDidNotReturn]() -> nsresult { \
+ QM_TRY(MOZ_TO_RESULT(NS_OK), [](__VA_ARGS__) { return aRv; }); \
+ \
+ tryDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_TRUE(tryDidNotReturn); \
+ EXPECT_EQ(rv, NS_OK); \
+ }
+
+ SUBTEST(const char*, nsresult aRv);
+ SUBTEST(nsresult aRv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_Try, Success_WithCleanup)
+{
+ bool tryCleanupRan = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_PROPAGATE,
+ [&tryCleanupRan](const auto&) { tryCleanupRan = true; });
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryCleanupRan);
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, Failure_PropagateErr)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, Failure_CustomErr)
+{
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), NS_ERROR_UNEXPECTED);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_Try, Failure_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryDidNotReturn = false; \
+ \
+ nsresult rv = [&tryDidNotReturn]() -> nsresult { \
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), \
+ [](__VA_ARGS__) { return NS_ERROR_UNEXPECTED; }); \
+ \
+ tryDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_FALSE(tryDidNotReturn); \
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED); \
+ }
+
+ SUBTEST(const char* aFunc, nsresult);
+ SUBTEST(nsresult rv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_Try, Failure_NoErr)
+{
+ bool tryDidNotReturn = false;
+
+ [&tryDidNotReturn]() -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID);
+
+ tryDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+}
+
+TEST(QuotaCommon_Try, Failure_WithCleanup)
+{
+ bool tryCleanupRan = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE,
+ [&tryCleanupRan](const auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ tryCleanupRan = true;
+ });
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryCleanupRan);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, Failure_WithCleanup_UnwrapErr)
+{
+ bool tryCleanupRan = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv;
+
+ [&tryCleanupRan, &tryDidNotReturn](nsresult& aRv) -> void {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID,
+ ([&tryCleanupRan, &aRv](auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ aRv = result;
+
+ tryCleanupRan = true;
+ }));
+
+ tryDidNotReturn = true;
+
+ aRv = NS_OK;
+ }(rv);
+
+ EXPECT_TRUE(tryCleanupRan);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, Failure_WithCleanupAndPredicate)
+{
+ auto predicate = []() {
+ static bool calledOnce = false;
+ const bool result = !calledOnce;
+ calledOnce = true;
+ return result;
+ };
+
+ {
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&predicate, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE, QM_NO_CLEANUP,
+ predicate);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+ }
+
+ {
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&predicate, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE, QM_NO_CLEANUP,
+ predicate);
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+ }
+}
+
+TEST(QuotaCommon_Try, SameLine)
+{
+ // clang-format off
+ QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID); QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID);
+ // clang-format on
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Success)
+{
+ bool nestedTryDidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTryDidNotReturn, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTryDidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTryDidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryDidNotReturn);
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Failure)
+{
+ bool nestedTryDidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTryDidNotReturn, &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTryDidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ nestedTryDidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTryDidNotReturn);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Multiple_Success)
+{
+ bool nestedTry1DidNotReturn = false;
+ bool nestedTry2DidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
+ &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTry1DidNotReturn,
+ &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry1DidNotReturn = true;
+
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry2DidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTry1DidNotReturn);
+ EXPECT_TRUE(nestedTry2DidNotReturn);
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Multiple_Failure1)
+{
+ bool nestedTry1DidNotReturn = false;
+ bool nestedTry2DidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
+ &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTry1DidNotReturn,
+ &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ nestedTry1DidNotReturn = true;
+
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry2DidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTry1DidNotReturn);
+ EXPECT_FALSE(nestedTry2DidNotReturn);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Try, NestingMadness_Multiple_Failure2)
+{
+ bool nestedTry1DidNotReturn = false;
+ bool nestedTry2DidNotReturn = false;
+ bool tryDidNotReturn = false;
+
+ nsresult rv = [&nestedTry1DidNotReturn, &nestedTry2DidNotReturn,
+ &tryDidNotReturn]() -> nsresult {
+ QM_TRY(([&nestedTry1DidNotReturn,
+ &nestedTry2DidNotReturn]() -> Result<Ok, nsresult> {
+ QM_TRY(MOZ_TO_RESULT(NS_OK));
+
+ nestedTry1DidNotReturn = true;
+
+ QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ nestedTry2DidNotReturn = true;
+
+ return Ok();
+ }()));
+
+ tryDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTry1DidNotReturn);
+ EXPECT_FALSE(nestedTry2DidNotReturn);
+ EXPECT_FALSE(tryDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, Success)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, Success_CustomErr_QmIpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTryInspect_Success_CustomErr_QmIpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+TEST(QuotaCommon_TryInspect, Success_CustomErr_IpcFail)
+{
+ auto foo = MakeRefPtr<QuotaTestParent>();
+
+ bool tryDidNotReturn = false;
+
+ auto res = foo->RecvTryInspect_Success_CustomErr_IpcFail(&tryDidNotReturn);
+
+ EXPECT_TRUE(tryDidNotReturn);
+ EXPECT_TRUE(res);
+}
+
+#ifdef DEBUG
+TEST(QuotaCommon_TryInspect, Success_CustomErr_AssertUnreachable)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}),
+ QM_ASSERT_UNREACHABLE);
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, Success_NoErr_AssertUnreachable)
+{
+ bool tryInspectDidNotReturn = false;
+
+ [&tryInspectDidNotReturn]() -> void {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}),
+ QM_ASSERT_UNREACHABLE_VOID);
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryInspectDidNotReturn);
+}
+#endif
+
+TEST(QuotaCommon_TryInspect, Success_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryInspectDidNotReturn = false; \
+ \
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult { \
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}), \
+ [](__VA_ARGS__) { return aRv; }); \
+ EXPECT_EQ(x, 42); \
+ \
+ tryInspectDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_TRUE(tryInspectDidNotReturn); \
+ EXPECT_EQ(rv, NS_OK); \
+ }
+
+ SUBTEST(const char*, nsresult aRv);
+ SUBTEST(nsresult aRv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryInspect, Success_WithCleanup)
+{
+ bool tryInspectCleanupRan = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectCleanupRan, &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& x, (Result<int32_t, nsresult>{42}), QM_PROPAGATE,
+ [&tryInspectCleanupRan](const auto&) { tryInspectCleanupRan = true; });
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryInspectCleanupRan);
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_PropagateErr)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_CustomErr)
+{
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ NS_ERROR_UNEXPECTED);
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryInspectDidNotReturn = false; \
+ \
+ nsresult rv = [&tryInspectDidNotReturn]() -> nsresult { \
+ QM_TRY_INSPECT(const auto& x, \
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), \
+ [](__VA_ARGS__) { return NS_ERROR_UNEXPECTED; }); \
+ Unused << x; \
+ \
+ tryInspectDidNotReturn = true; \
+ \
+ return NS_OK; \
+ }(); \
+ \
+ EXPECT_FALSE(tryInspectDidNotReturn); \
+ EXPECT_EQ(rv, NS_ERROR_UNEXPECTED); \
+ }
+
+ SUBTEST(const char*, nsresult);
+ SUBTEST(nsresult);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryInspect, Failure_NoErr)
+{
+ bool tryInspectDidNotReturn = false;
+
+ [&tryInspectDidNotReturn]() -> void {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), QM_VOID);
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryInspectDidNotReturn);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_WithCleanup)
+{
+ bool tryInspectCleanupRan = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&tryInspectCleanupRan, &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ QM_PROPAGATE, [&tryInspectCleanupRan](const auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ tryInspectCleanupRan = true;
+ });
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(tryInspectCleanupRan);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, Failure_WithCleanup_UnwrapErr)
+{
+ bool tryInspectCleanupRan = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv;
+
+ [&tryInspectCleanupRan, &tryInspectDidNotReturn](nsresult& aRv) -> void {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), QM_VOID,
+ ([&tryInspectCleanupRan, &aRv](auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ aRv = result;
+
+ tryInspectCleanupRan = true;
+ }));
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ aRv = NS_OK;
+ }(rv);
+
+ EXPECT_TRUE(tryInspectCleanupRan);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, ConstDecl)
+{
+ QM_TRY_INSPECT(const int32_t& x, (Result<int32_t, nsresult>{42}), QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), const int32_t&>);
+
+ EXPECT_EQ(x, 42);
+}
+
+TEST(QuotaCommon_TryInspect, SameScopeDecl)
+{
+ QM_TRY_INSPECT(const int32_t& x, (Result<int32_t, nsresult>{42}), QM_VOID);
+ EXPECT_EQ(x, 42);
+
+ QM_TRY_INSPECT(const int32_t& y, (Result<int32_t, nsresult>{42}), QM_VOID);
+ EXPECT_EQ(y, 42);
+}
+
+TEST(QuotaCommon_TryInspect, SameLine)
+{
+ // clang-format off
+ QM_TRY_INSPECT(const auto &x, (Result<int32_t, nsresult>{42}), QM_VOID); QM_TRY_INSPECT(const auto &y, (Result<int32_t, nsresult>{42}), QM_VOID);
+ // clang-format on
+
+ EXPECT_EQ(x, 42);
+ EXPECT_EQ(y, 42);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Success)
+{
+ bool nestedTryInspectDidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspectDidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& x,
+ ([&nestedTryInspectDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspectDidNotReturn = true;
+
+ return x;
+ }()));
+ EXPECT_EQ(x, 42);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryInspectDidNotReturn);
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Failure)
+{
+ bool nestedTryInspectDidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspectDidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& x,
+ ([&nestedTryInspectDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryInspectDidNotReturn = true;
+
+ return x;
+ }()));
+ Unused << x;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTryInspectDidNotReturn);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Success)
+{
+ bool nestedTryInspect1DidNotReturn = false;
+ bool nestedTryInspect2DidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& z,
+ ([&nestedTryInspect1DidNotReturn,
+ &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect1DidNotReturn = true;
+
+ QM_TRY_INSPECT(const auto& y, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect2DidNotReturn = true;
+
+ return x + y;
+ }()));
+ EXPECT_EQ(z, 84);
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryInspect1DidNotReturn);
+ EXPECT_TRUE(nestedTryInspect2DidNotReturn);
+ EXPECT_TRUE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_OK);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Failure1)
+{
+ bool nestedTryInspect1DidNotReturn = false;
+ bool nestedTryInspect2DidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& z,
+ ([&nestedTryInspect1DidNotReturn,
+ &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryInspect1DidNotReturn = true;
+
+ QM_TRY_INSPECT(const auto& y, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect2DidNotReturn = true;
+
+ return x + y;
+ }()));
+ Unused << z;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(nestedTryInspect1DidNotReturn);
+ EXPECT_FALSE(nestedTryInspect2DidNotReturn);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryInspect, NestingMadness_Multiple_Failure2)
+{
+ bool nestedTryInspect1DidNotReturn = false;
+ bool nestedTryInspect2DidNotReturn = false;
+ bool tryInspectDidNotReturn = false;
+
+ nsresult rv = [&nestedTryInspect1DidNotReturn, &nestedTryInspect2DidNotReturn,
+ &tryInspectDidNotReturn]() -> nsresult {
+ QM_TRY_INSPECT(
+ const auto& z,
+ ([&nestedTryInspect1DidNotReturn,
+ &nestedTryInspect2DidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_INSPECT(const auto& x, (Result<int32_t, nsresult>{42}));
+
+ nestedTryInspect1DidNotReturn = true;
+
+ QM_TRY_INSPECT(const auto& y,
+ (Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryInspect2DidNotReturn = true;
+
+ return x + y;
+ }()));
+ Unused << z;
+
+ tryInspectDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(nestedTryInspect1DidNotReturn);
+ EXPECT_FALSE(nestedTryInspect2DidNotReturn);
+ EXPECT_FALSE(tryInspectDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+// We are not repeating all QM_TRY_INSPECT test cases for QM_TRY_UNWRAP, since
+// they are largely based on the same implementation. We just add some where
+// inspecting and unwrapping differ.
+
+TEST(QuotaCommon_TryUnwrap, NonConstDecl)
+{
+ QM_TRY_UNWRAP(int32_t x, (Result<int32_t, nsresult>{42}), QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), int32_t>);
+
+ EXPECT_EQ(x, 42);
+}
+
+TEST(QuotaCommon_TryUnwrap, RvalueDecl)
+{
+ QM_TRY_UNWRAP(int32_t && x, (Result<int32_t, nsresult>{42}), QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), int32_t&&>);
+
+ EXPECT_EQ(x, 42);
+}
+
+TEST(QuotaCommon_TryUnwrap, ParenDecl)
+{
+ QM_TRY_UNWRAP(
+ (auto&& [x, y]),
+ (Result<std::pair<int32_t, bool>, nsresult>{std::pair{42, true}}),
+ QM_VOID);
+
+ static_assert(std::is_same_v<decltype(x), int32_t>);
+ static_assert(std::is_same_v<decltype(y), bool>);
+
+ EXPECT_EQ(x, 42);
+ EXPECT_EQ(y, true);
+}
+
+TEST(QuotaCommon_TryReturn, Success)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{42}));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, Success_nsresult)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+}
+
+#ifdef DEBUG
+TEST(QuotaCommon_TryReturn, Success_CustomErr_AssertUnreachable)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{42}), QM_ASSERT_UNREACHABLE);
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+#endif
+
+TEST(QuotaCommon_TryReturn, Success_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryReturnDidNotReturn = false; \
+ \
+ auto res = [&tryReturnDidNotReturn]() -> Result<Ok, nsresult> { \
+ QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK), \
+ [](__VA_ARGS__) { return Err(aRv); }); \
+ \
+ tryReturnDidNotReturn = true; \
+ }(); \
+ \
+ EXPECT_FALSE(tryReturnDidNotReturn); \
+ EXPECT_TRUE(res.isOk()); \
+ }
+
+ SUBTEST(const char*, nsresult aRv);
+ SUBTEST(nsresult aRv);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryReturn, Success_WithCleanup)
+{
+ bool tryReturnCleanupRan = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnCleanupRan,
+ &tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN(
+ (Result<int32_t, nsresult>{42}), QM_PROPAGATE,
+ [&tryReturnCleanupRan](const auto&) { tryReturnCleanupRan = true; });
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnCleanupRan);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_PropagateErr)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_PropagateErr_nsresult)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn] {
+ QM_TRY_RETURN(MOZ_TO_RESULT(NS_ERROR_FAILURE));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_CustomErr)
+{
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ Err(NS_ERROR_UNEXPECTED));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_TryReturn, Failure_CustomErr_CustomLambda)
+{
+#define SUBTEST(...) \
+ { \
+ bool tryReturnDidNotReturn = false; \
+ \
+ auto res = [&tryReturnDidNotReturn]() -> Result<int32_t, nsresult> { \
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}), \
+ [](__VA_ARGS__) { return Err(NS_ERROR_UNEXPECTED); }); \
+ \
+ tryReturnDidNotReturn = true; \
+ }(); \
+ \
+ EXPECT_FALSE(tryReturnDidNotReturn); \
+ EXPECT_TRUE(res.isErr()); \
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED); \
+ }
+
+ SUBTEST(const char*, nsresult);
+ SUBTEST(nsresult);
+
+#undef SUBTEST
+}
+
+TEST(QuotaCommon_TryReturn, Failure_WithCleanup)
+{
+ bool tryReturnCleanupRan = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&tryReturnCleanupRan,
+ &tryReturnDidNotReturn]() -> Result<int32_t, nsresult> {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}),
+ QM_PROPAGATE, [&tryReturnCleanupRan](const auto& result) {
+ EXPECT_EQ(result, NS_ERROR_FAILURE);
+
+ tryReturnCleanupRan = true;
+ });
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_TRUE(tryReturnCleanupRan);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_TryReturn, SameLine)
+{
+ // clang-format off
+ auto res1 = [] { QM_TRY_RETURN((Result<int32_t, nsresult>{42})); }(); auto res2 = []() -> Result<int32_t, nsresult> { QM_TRY_RETURN((Result<int32_t, nsresult>{42})); }();
+ // clang-format on
+
+ EXPECT_TRUE(res1.isOk());
+ EXPECT_EQ(res1.unwrap(), 42);
+ EXPECT_TRUE(res2.isOk());
+ EXPECT_EQ(res2.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, NestingMadness_Success)
+{
+ bool nestedTryReturnDidNotReturn = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&nestedTryReturnDidNotReturn, &tryReturnDidNotReturn] {
+ QM_TRY_RETURN(([&nestedTryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{42}));
+
+ nestedTryReturnDidNotReturn = true;
+ }()));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(nestedTryReturnDidNotReturn);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_TryReturn, NestingMadness_Failure)
+{
+ bool nestedTryReturnDidNotReturn = false;
+ bool tryReturnDidNotReturn = false;
+
+ auto res = [&nestedTryReturnDidNotReturn, &tryReturnDidNotReturn] {
+ QM_TRY_RETURN(([&nestedTryReturnDidNotReturn] {
+ QM_TRY_RETURN((Result<int32_t, nsresult>{Err(NS_ERROR_FAILURE)}));
+
+ nestedTryReturnDidNotReturn = true;
+ }()));
+
+ tryReturnDidNotReturn = true;
+ }();
+
+ EXPECT_FALSE(nestedTryReturnDidNotReturn);
+ EXPECT_FALSE(tryReturnDidNotReturn);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Fail, ReturnValue)
+{
+ bool failDidNotReturn = false;
+
+ nsresult rv = [&failDidNotReturn]() -> nsresult {
+ QM_FAIL(NS_ERROR_FAILURE);
+
+ failDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_FALSE(failDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_Fail, ReturnValue_WithCleanup)
+{
+ bool failCleanupRan = false;
+ bool failDidNotReturn = false;
+
+ nsresult rv = [&failCleanupRan, &failDidNotReturn]() -> nsresult {
+ QM_FAIL(NS_ERROR_FAILURE, [&failCleanupRan]() { failCleanupRan = true; });
+
+ failDidNotReturn = true;
+
+ return NS_OK;
+ }();
+
+ EXPECT_TRUE(failCleanupRan);
+ EXPECT_FALSE(failDidNotReturn);
+ EXPECT_EQ(rv, NS_ERROR_FAILURE);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Success)
+{
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(true));
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Success_WithCleanup)
+{
+ bool warnOnlyTryCleanupRan = false;
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryCleanupRan,
+ &warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(true), [&warnOnlyTryCleanupRan](const auto&) {
+ warnOnlyTryCleanupRan = true;
+ });
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(warnOnlyTryCleanupRan);
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Failure)
+{
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(false));
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTry, Failure_WithCleanup)
+{
+ bool warnOnlyTryCleanupRan = false;
+ bool warnOnlyTryDidNotReturn = false;
+
+ const auto res =
+ [&warnOnlyTryCleanupRan,
+ &warnOnlyTryDidNotReturn]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY(OkIf(false), ([&warnOnlyTryCleanupRan](const auto&) {
+ warnOnlyTryCleanupRan = true;
+ }));
+
+ warnOnlyTryDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryCleanupRan);
+ EXPECT_TRUE(warnOnlyTryDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Success)
+{
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{42}));
+ EXPECT_TRUE(x);
+ EXPECT_EQ(*x, 42);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Success_WithCleanup)
+{
+ bool warnOnlyTryUnwrapCleanupRan = false;
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapCleanupRan,
+ &warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{42}),
+ [&warnOnlyTryUnwrapCleanupRan](const auto&) {
+ warnOnlyTryUnwrapCleanupRan = true;
+ });
+ EXPECT_TRUE(x);
+ EXPECT_EQ(*x, 42);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(warnOnlyTryUnwrapCleanupRan);
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Failure)
+{
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x,
+ (Result<int32_t, NotOk>{Err(NotOk{})}));
+ EXPECT_FALSE(x);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_WarnOnlyTryUnwrap, Failure_WithCleanup)
+{
+ bool warnOnlyTryUnwrapCleanupRan = false;
+ bool warnOnlyTryUnwrapDidNotReturn = false;
+
+ const auto res = [&warnOnlyTryUnwrapCleanupRan,
+ &warnOnlyTryUnwrapDidNotReturn]()
+ -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_WARNONLY_TRY_UNWRAP(const auto x, (Result<int32_t, NotOk>{Err(NotOk{})}),
+ [&warnOnlyTryUnwrapCleanupRan](const auto&) {
+ warnOnlyTryUnwrapCleanupRan = true;
+ });
+ EXPECT_FALSE(x);
+
+ warnOnlyTryUnwrapDidNotReturn = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(warnOnlyTryUnwrapCleanupRan);
+ EXPECT_TRUE(warnOnlyTryUnwrapDidNotReturn);
+}
+
+TEST(QuotaCommon_OrElseWarn, Success)
+{
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN(OkIf(true), ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{
+ mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarn, Failure_MappedToSuccess)
+{
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ // XXX Consider allowing to set a custom error handler, so that we can
+ // actually assert that a warning was emitted.
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN(OkIf(false), ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{
+ mozilla::Ok{}};
+ })));
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarn, Failure_MappedToError)
+{
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ // XXX Consider allowing to set a custom error handler, so that we can
+ // actually assert that a warning was emitted.
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN(OkIf(false), ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{
+ NotOk{}};
+ })));
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_FALSE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Success)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(true),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return false;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_FALSE(predicateRun);
+ EXPECT_FALSE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsFalse)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(false),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return false;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_TRUE(predicateRun);
+ EXPECT_FALSE(fallbackRun);
+ EXPECT_FALSE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsTrue_MappedToSuccess)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(false),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return true;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::Ok{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_TRUE(predicateRun);
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_TRUE(tryContinued);
+}
+
+TEST(QuotaCommon_OrElseWarnIf, Failure_PredicateReturnsTrue_MappedToError)
+{
+ bool predicateRun = false;
+ bool fallbackRun = false;
+ bool tryContinued = false;
+
+ const auto res = [&]() -> mozilla::Result<mozilla::Ok, NotOk> {
+ QM_TRY(QM_OR_ELSE_WARN_IF(
+ OkIf(false),
+ [&predicateRun](const NotOk) {
+ predicateRun = true;
+ return true;
+ },
+ ([&fallbackRun](const NotOk) {
+ fallbackRun = true;
+ return mozilla::Result<mozilla::Ok, NotOk>{mozilla::NotOk{}};
+ })));
+
+ tryContinued = true;
+ return mozilla::Ok{};
+ }();
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_TRUE(predicateRun);
+ EXPECT_TRUE(fallbackRun);
+ EXPECT_FALSE(tryContinued);
+}
+
+TEST(QuotaCommon_OkIf, True)
+{
+ auto res = OkIf(true);
+
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_OkIf, False)
+{
+ auto res = OkIf(false);
+
+ EXPECT_TRUE(res.isErr());
+}
+
+TEST(QuotaCommon_OkToOk, Bool_True)
+{
+ auto res = OkToOk<true>(Ok());
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), true);
+}
+
+TEST(QuotaCommon_OkToOk, Bool_False)
+{
+ auto res = OkToOk<false>(Ok());
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), false);
+}
+
+TEST(QuotaCommon_OkToOk, Int_42)
+{
+ auto res = OkToOk<42>(Ok());
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_True)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, true>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), true);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_True_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, true>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_False)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, false>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), false);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Bool_False_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, false>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Int_42)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, 42>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, Int_42_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, 42>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, NsCOMPtr_nullptr)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, nullptr, nsCOMPtr<nsISupports>>(
+ NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+TEST(QuotaCommon_ErrToOkOrErr, NsCOMPtr_nullptr_Err)
+{
+ auto res = ErrToOkOrErr<NS_ERROR_FAILURE, nullptr, nsCOMPtr<nsISupports>>(
+ NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, Ok)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, Ok>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, Ok_Err)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, Ok>(NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, NsCOMPtr)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, nsCOMPtr<nsISupports>>(
+ NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+TEST(QuotaCommon_ErrToDefaultOkOrErr, NsCOMPtr_Err)
+{
+ auto res = ErrToDefaultOkOrErr<NS_ERROR_FAILURE, nsCOMPtr<nsISupports>>(
+ NS_ERROR_UNEXPECTED);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_UNEXPECTED);
+}
+
+TEST(QuotaCommon_IsSpecificError, Match)
+{ EXPECT_TRUE(IsSpecificError<NS_ERROR_FAILURE>(NS_ERROR_FAILURE)); }
+
+TEST(QuotaCommon_IsSpecificError, Mismatch)
+{ EXPECT_FALSE(IsSpecificError<NS_ERROR_FAILURE>(NS_ERROR_UNEXPECTED)); }
+
+TEST(QuotaCommon_ErrToOk, Bool_True)
+{
+ auto res = ErrToOk<true>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), true);
+}
+
+TEST(QuotaCommon_ErrToOk, Bool_False)
+{
+ auto res = ErrToOk<false>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), false);
+}
+
+TEST(QuotaCommon_ErrToOk, Int_42)
+{
+ auto res = ErrToOk<42>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(QuotaCommon_ErrToOk, NsCOMPtr_nullptr)
+{
+ auto res = ErrToOk<nullptr, nsCOMPtr<nsISupports>>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+TEST(QuotaCommon_ErrToDefaultOk, Ok)
+{
+ auto res = ErrToDefaultOk<Ok>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_ErrToDefaultOk, NsCOMPtr)
+{
+ auto res = ErrToDefaultOk<nsCOMPtr<nsISupports>>(NS_ERROR_FAILURE);
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), nullptr);
+}
+
+class StringPairParameterized
+ : public ::testing::TestWithParam<std::pair<const char*, const char*>> {};
+
+TEST_P(StringPairParameterized, AnonymizedOriginString) {
+ const auto [in, expectedAnonymized] = GetParam();
+ const auto anonymized = AnonymizedOriginString(nsDependentCString(in));
+ EXPECT_STREQ(anonymized.get(), expectedAnonymized);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ QuotaCommon, StringPairParameterized,
+ ::testing::Values(
+ // XXX Do we really want to anonymize about: origins?
+ std::pair("about:home", "about:aaaa"),
+ std::pair("https://foo.bar.com", "https://aaa.aaa.aaa"),
+ std::pair("https://foo.bar.com:8000", "https://aaa.aaa.aaa:DDDD"),
+ std::pair("file://UNIVERSAL_FILE_ORIGIN",
+ "file://aaaaaaaaa_aaaa_aaaaaa")));
+
+// BEGIN COPY FROM mfbt/tests/TestResult.cpp
+struct Failed {};
+
+static GenericErrorResult<Failed> Fail() { return Err(Failed()); }
+
+static Result<Ok, Failed> Task1(bool pass) {
+ if (!pass) {
+ return Fail(); // implicit conversion from GenericErrorResult to Result
+ }
+ return Ok();
+}
+// END COPY FROM mfbt/tests/TestResult.cpp
+
+static Result<bool, Failed> Condition(bool aNoError, bool aResult) {
+ return Task1(aNoError).map([aResult](auto) { return aResult; });
+}
+
+TEST(QuotaCommon_CollectWhileTest, NoFailures)
+{
+ const size_t loopCount = 5;
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(true, conditionExecutions <= loopCount);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(true);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(loopCount == bodyExecutions);
+ MOZ_RELEASE_ASSERT(1 + loopCount == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, BodyFailsImmediately)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(true, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(false);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(1 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(1 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, BodyFailsOnSecondExecution)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(true, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(bodyExecutions < 2);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(2 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(2 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, ConditionFailsImmediately)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(false, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(true);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(0 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(1 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectWhileTest, ConditionFailsOnSecondExecution)
+{
+ size_t conditionExecutions = 0;
+ size_t bodyExecutions = 0;
+ auto result = CollectWhile(
+ [&conditionExecutions] {
+ ++conditionExecutions;
+ return Condition(conditionExecutions < 2, true);
+ },
+ [&bodyExecutions] {
+ ++bodyExecutions;
+ return Task1(true);
+ });
+ static_assert(std::is_same_v<decltype(result), Result<Ok, Failed>>);
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(1 == bodyExecutions);
+ MOZ_RELEASE_ASSERT(2 == conditionExecutions);
+}
+
+TEST(QuotaCommon_CollectEachInRange, Success)
+{
+ size_t bodyExecutions = 0;
+ const auto result = CollectEachInRange(
+ std::array<int, 5>{{1, 2, 3, 4, 5}},
+ [&bodyExecutions](const int val) -> Result<Ok, nsresult> {
+ ++bodyExecutions;
+ return Ok{};
+ });
+
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(5 == bodyExecutions);
+}
+
+TEST(QuotaCommon_CollectEachInRange, FailureShortCircuit)
+{
+ size_t bodyExecutions = 0;
+ const auto result = CollectEachInRange(
+ std::array<int, 5>{{1, 2, 3, 4, 5}},
+ [&bodyExecutions](const int val) -> Result<Ok, nsresult> {
+ ++bodyExecutions;
+ return val == 3 ? Err(NS_ERROR_FAILURE) : Result<Ok, nsresult>{Ok{}};
+ });
+
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(NS_ERROR_FAILURE == result.inspectErr());
+ MOZ_RELEASE_ASSERT(3 == bodyExecutions);
+}
+
+TEST(QuotaCommon_ReduceEach, Success)
+{
+ const auto result = ReduceEach(
+ [i = int{0}]() mutable -> Result<int, Failed> {
+ if (i < 5) {
+ return ++i;
+ }
+ return 0;
+ },
+ 0, [](int val, int add) -> Result<int, Failed> { return val + add; });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(15 == result.inspect());
+}
+
+TEST(QuotaCommon_ReduceEach, StepError)
+{
+ const auto result = ReduceEach(
+ [i = int{0}]() mutable -> Result<int, Failed> {
+ if (i < 5) {
+ return ++i;
+ }
+ return 0;
+ },
+ 0,
+ [](int val, int add) -> Result<int, Failed> {
+ if (val > 2) {
+ return Err(Failed{});
+ }
+ return val + add;
+ });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isErr());
+}
+
+TEST(QuotaCommon_ReduceEach, GeneratorError)
+{
+ size_t generatorExecutions = 0;
+ const auto result = ReduceEach(
+ [i = int{0}, &generatorExecutions]() mutable -> Result<int, Failed> {
+ ++generatorExecutions;
+ if (i < 1) {
+ return ++i;
+ }
+ return Err(Failed{});
+ },
+ 0,
+ [](int val, int add) -> Result<int, Failed> {
+ if (val > 2) {
+ return Err(Failed{});
+ }
+ return val + add;
+ });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isErr());
+ MOZ_RELEASE_ASSERT(2 == generatorExecutions);
+}
+
+TEST(QuotaCommon_Reduce, Success)
+{
+ const auto range = std::vector{0, 1, 2, 3, 4, 5};
+ const auto result = Reduce(
+ range, 0, [](int val, Maybe<const int&> add) -> Result<int, Failed> {
+ return val + add.ref();
+ });
+ static_assert(std::is_same_v<decltype(result), const Result<int, Failed>>);
+
+ MOZ_RELEASE_ASSERT(result.isOk());
+ MOZ_RELEASE_ASSERT(15 == result.inspect());
+}
+
+TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, NoFailures)
+{
+ uint32_t tries = 0;
+
+ auto res = CallWithDelayedRetriesIfAccessDenied(
+ [&tries]() -> Result<Ok, nsresult> {
+ ++tries;
+ return Ok{};
+ },
+ 10, 2);
+
+ EXPECT_EQ(tries, 1u);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, PermanentFailures)
+{
+ uint32_t tries = 0;
+
+ auto res = CallWithDelayedRetriesIfAccessDenied(
+ [&tries]() -> Result<Ok, nsresult> {
+ ++tries;
+ return Err(NS_ERROR_FILE_IS_LOCKED);
+ },
+ 10, 2);
+
+ EXPECT_EQ(tries, 11u);
+ EXPECT_TRUE(res.isErr());
+}
+
+TEST(QuotaCommon_CallWithDelayedRetriesIfAccessDenied, FailuresAndSuccess)
+{
+ uint32_t tries = 0;
+
+ auto res = CallWithDelayedRetriesIfAccessDenied(
+ [&tries]() -> Result<Ok, nsresult> {
+ if (++tries == 5) {
+ return Ok{};
+ }
+ return Err(NS_ERROR_FILE_ACCESS_DENIED);
+ },
+ 10, 2);
+
+ EXPECT_EQ(tries, 5u);
+ EXPECT_TRUE(res.isOk());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, ThisSourceFile)
+{
+ static constexpr auto thisSourceFileRelativePath =
+ "dom/quota/test/gtest/TestQuotaCommon.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ nsLiteralCString(__FILE__))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(), thisSourceFileRelativePath.get());
+}
+
+static nsCString MakeTreePath(const nsACString& aBasePath,
+ const nsACString& aRelativePath) {
+ nsCString path{aBasePath};
+
+ path.Append("/");
+ path.Append(aRelativePath);
+
+ return path;
+}
+
+static nsCString MakeSourceTreePath(const nsACString& aRelativePath) {
+ return MakeTreePath(mozilla::dom::quota::detail::GetSourceTreeBase(),
+ aRelativePath);
+}
+
+static nsCString MakeObjdirDistIncludeTreePath(
+ const nsACString& aRelativePath) {
+ return MakeTreePath(
+ mozilla::dom::quota::detail::GetObjdirDistIncludeTreeBase(),
+ aRelativePath);
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, DomQuotaSourceFile)
+{
+ static constexpr auto domQuotaSourceFileRelativePath =
+ "dom/quota/ActorsParent.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeSourceTreePath(domQuotaSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domQuotaSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, DomQuotaSourceFile_Exported)
+{
+ static constexpr auto mozillaDomQuotaSourceFileRelativePath =
+ "mozilla/dom/quota/QuotaCommon.h"_ns;
+
+ static constexpr auto domQuotaSourceFileRelativePath =
+ "dom/quota/QuotaCommon.h"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeObjdirDistIncludeTreePath(
+ mozillaDomQuotaSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domQuotaSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, DomIndexedDBSourceFile)
+{
+ static constexpr auto domIndexedDBSourceFileRelativePath =
+ "dom/indexedDB/ActorsParent.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeSourceTreePath(domIndexedDBSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domIndexedDBSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath,
+ DomLocalstorageSourceFile_Exported_Mapped)
+{
+ static constexpr auto mozillaDomSourceFileRelativePath =
+ "mozilla/dom/LocalStorageCommon.h"_ns;
+
+ static constexpr auto domLocalstorageSourceFileRelativePath =
+ "dom/localstorage/LocalStorageCommon.h"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeObjdirDistIncludeTreePath(mozillaDomSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ domLocalstorageSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, NonDomSourceFile)
+{
+ static constexpr auto nonDomSourceFileRelativePath =
+ "storage/mozStorageService.cpp"_ns;
+
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ MakeSourceTreePath(nonDomSourceFileRelativePath))};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(),
+ nonDomSourceFileRelativePath.get());
+}
+
+TEST(QuotaCommon_MakeSourceFileRelativePath, OtherSourceFile)
+{
+ constexpr auto otherSourceFilePath = "/foo/bar/Test.cpp"_ns;
+ const nsCString sourceFileRelativePath{
+ mozilla::dom::quota::detail::MakeSourceFileRelativePath(
+ otherSourceFilePath)};
+
+ EXPECT_STREQ(sourceFileRelativePath.get(), "Test.cpp");
+}
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestQuotaManager.cpp b/dom/quota/test/gtest/TestQuotaManager.cpp
new file mode 100644
index 0000000000..c93f18ca61
--- /dev/null
+++ b/dom/quota/test/gtest/TestQuotaManager.cpp
@@ -0,0 +1,1611 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/dom/quota/DirectoryLock.h"
+#include "mozilla/dom/quota/OriginScope.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "QuotaManagerDependencyFixture.h"
+
+namespace mozilla::dom::quota::test {
+
+class TestQuotaManager : public QuotaManagerDependencyFixture {
+ public:
+ static void SetUpTestCase() { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); }
+
+ static void TearDownTestCase() { ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); }
+};
+
+// Test OpenStorageDirectory when an opening of the storage directory is
+// already ongoing and storage shutdown is scheduled after that.
+TEST_F(TestQuotaManager, OpenStorageDirectory_OngoingWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<UniversalDirectoryLock> directoryLock;
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](
+ UniversalDirectoryLockPromise::ResolveOrRejectValue&&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ [&aValue]() { ASSERT_TRUE(aValue.ResolveValue()); }();
+
+ directoryLock = std::move(aValue.ResolveValue());
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(quotaManager->IOThread(), __func__,
+ [](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ []() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(
+ quotaManager->IsStorageInitializedInternal());
+ }();
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ directoryLock = nullptr;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenStorageDirectory when an opening of the storage directory is
+// already ongoing and an exclusive directory lock is requested after that.
+TEST_F(TestQuotaManager,
+ OpenStorageDirectory_OngoingWithExclusiveDirectoryLock) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<UniversalDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLockInternal(Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ true);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](
+ const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ directoryLock = nullptr;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+ promises.AppendElement(directoryLock->Acquire());
+ promises.AppendElement(
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenStorageDirectory when an opening of the storage directory already
+// finished.
+TEST_F(TestQuotaManager, OpenStorageDirectory_Finished) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ done = false;
+
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenStorageDirectory when an opening of the storage directory already
+// finished but storage shutdown has just been scheduled.
+TEST_F(TestQuotaManager, OpenStorageDirectory_FinishedWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+
+ done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenStorageDirectory when an opening of the storage directory already
+// finished and an exclusive client directory lock for a non-overlapping
+// origin is acquired in between.
+TEST_F(TestQuotaManager,
+ OpenStorageDirectory_FinishedWithExclusiveClientDirectoryLock) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ true);
+
+ done = false;
+
+ directoryLock->Acquire()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ done = false;
+
+ quotaManager
+ ->OpenStorageDirectory(
+ Nullable<PersistenceType>(PERSISTENCE_TYPE_PERSISTENT),
+ OriginScope::FromNull(), Nullable<Client::Type>(),
+ /* aExclusive */ false)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const UniversalDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenClientDirctory when an opening of a client directory is already
+// ongoing and storage shutdown is scheduled after that.
+TEST_F(TestQuotaManager, OpenClientDirectory_OngoingWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<ClientDirectoryLock> directoryLock;
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](
+ ClientDirectoryLockPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ [&aValue]() { ASSERT_TRUE(aValue.ResolveValue()); }();
+
+ directoryLock = std::move(aValue.ResolveValue());
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(quotaManager->IOThread(), __func__,
+ [](const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ []() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(
+ quotaManager->IsStorageInitializedInternal());
+ }();
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ directoryLock = nullptr;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenClientDirectory when an opening of a client directory is already
+// ongoing and an exclusive directory lock is requested after that.
+TEST_F(TestQuotaManager,
+ OpenClientDirectory_OngoingWithExclusiveDirectoryLock) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<UniversalDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLockInternal(Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ true);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](
+ const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ directoryLock = nullptr;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+ promises.AppendElement(directoryLock->Acquire());
+ promises.AppendElement(
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenClientDirectory when an opening of a client directory already
+// finished.
+TEST_F(TestQuotaManager, OpenClientDirectory_Finished) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ done = false;
+
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenClientDirectory when an opening of a client directory already
+// finished but storage shutdown has just been scheduled.
+TEST_F(TestQuotaManager, OpenClientDirectory_FinishedWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+
+ done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test OpenClientDirectory when an opening of a client directory already
+// finished with an exclusive client directory lock for a different origin is
+// acquired in between.
+TEST_F(TestQuotaManager,
+ OpenClientDirectory_FinishedWithOtherExclusiveClientDirectoryLock) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetOtherTestClientMetadata(),
+ /* aExclusive */ true);
+
+ done = false;
+
+ directoryLock->Acquire()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ done = false;
+
+ quotaManager->OpenClientDirectory(GetTestClientMetadata())
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&done](const ClientDirectoryLockPromise::ResolveOrRejectValue&
+ aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test simple InitializeStorage.
+TEST_F(TestQuotaManager, InitializeStorage_Simple) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->InitializeStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization is already ongoing.
+TEST_F(TestQuotaManager, InitializeStorage_Ongoing) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(quotaManager->InitializeStorage());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization is already ongoing and
+// storage shutdown is scheduled after that.
+TEST_F(TestQuotaManager, InitializeStorage_OngoingWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(quotaManager->InitializeStorage());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization is already ongoing and
+// an exclusive directory lock is requested after that.
+TEST_F(TestQuotaManager, InitializeStorage_OngoingWithExclusiveDirectoryLock) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<UniversalDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLockInternal(Nullable<PersistenceType>(),
+ OriginScope::FromNull(),
+ Nullable<Client::Type>(),
+ /* aExclusive */ true);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&directoryLock](const BoolPromise::ResolveOrRejectValue& aValue) {
+ // The exclusive directory lock must be released when the first
+ // storage initialization is finished, otherwise it would endlessly
+ // block the second storage initialization.
+ directoryLock = nullptr;
+
+ if (aValue.IsReject()) {
+ return BoolPromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ }));
+ promises.AppendElement(directoryLock->Acquire());
+ promises.AppendElement(quotaManager->InitializeStorage());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization is already ongoing and
+// shared client directory locks are requested after that.
+// The shared client directory locks don't have to be released in this case.
+TEST_F(TestQuotaManager, InitializeStorage_OngoingWithClientDirectoryLocks) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ RefPtr<ClientDirectoryLock> directoryLock2 =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock->Acquire());
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock2->Acquire());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization is already ongoing and
+// shared client directory locks are requested after that with storage shutdown
+// scheduled in between.
+TEST_F(TestQuotaManager,
+ InitializeStorage_OngoingWithClientDirectoryLocksAndScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ directoryLock->OnInvalidate(
+ [&directoryLock]() { directoryLock = nullptr; });
+
+ RefPtr<ClientDirectoryLock> directoryLock2 =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock->Acquire());
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock2->Acquire());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization already finished.
+TEST_F(TestQuotaManager, InitializeStorage_Finished) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->InitializeStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ done = false;
+
+ quotaManager->InitializeStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization already finished but
+// storage shutdown has just been scheduled.
+TEST_F(TestQuotaManager, InitializeStorage_FinishedWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->InitializeStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(quotaManager->InitializeStorage());
+
+ done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization already finished and
+// shared client directory locks are requested immediately after requesting
+// storage initialization.
+TEST_F(TestQuotaManager, InitializeStorage_FinishedWithClientDirectoryLocks) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock->Acquire());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ RefPtr<ClientDirectoryLock> directoryLock2 =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ promises.Clear();
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock2->Acquire());
+
+ done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test InitializeStorage when a storage initialization already finished and
+// shared client directory locks are requested immediatelly after requesting
+// storage initialization with storage shutdown performed in between.
+// The shared client directory lock is released when it gets invalidated by
+// storage shutdown which then unblocks the shutdown.
+TEST_F(TestQuotaManager,
+ InitializeStorage_FinishedWithClientDirectoryLocksAndScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ directoryLock->OnInvalidate(
+ [&directoryLock]() { directoryLock = nullptr; });
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock->Acquire());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ done = false;
+
+ quotaManager->ShutdownStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_FALSE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ RefPtr<ClientDirectoryLock> directoryLock2 =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ promises.Clear();
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(directoryLock2->Acquire());
+
+ done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+TEST_F(TestQuotaManager,
+ InitializeTemporaryStorage_FinishedWithScheduledShutdown) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ PerformOnBackgroundThread([]() {
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(quotaManager->InitializeTemporaryStorage());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+ ASSERT_TRUE(quotaManager->IsTemporaryStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+
+ promises.Clear();
+
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(quotaManager->InitializeTemporaryStorage());
+
+ done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_TRUE(quotaManager->IsStorageInitialized());
+ ASSERT_TRUE(quotaManager->IsTemporaryStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test simple ShutdownStorage.
+TEST_F(TestQuotaManager, ShutdownStorage_Simple) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(InitializeStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ bool done = false;
+
+ quotaManager->ShutdownStorage()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const BoolPromise::ResolveOrRejectValue& aValue) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_FALSE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test ShutdownStorage when a storage shutdown is already ongoing.
+TEST_F(TestQuotaManager, ShutdownStorage_Ongoing) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(InitializeStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(quotaManager->ShutdownStorage());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_FALSE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test ShutdownStorage when a storage shutdown is already ongoing and storage
+// initialization is scheduled after that.
+TEST_F(TestQuotaManager, ShutdownStorage_OngoingWithScheduledInitialization) {
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(InitializeStorage());
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageInitialized());
+
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ promises.AppendElement(quotaManager->ShutdownStorage());
+ promises.AppendElement(quotaManager->InitializeStorage());
+ promises.AppendElement(quotaManager->ShutdownStorage());
+
+ bool done = false;
+
+ BoolPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](const CopyableTArray<bool>& aResolveValues) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ ASSERT_FALSE(quotaManager->IsStorageInitialized());
+
+ done = true;
+ },
+ [&done](nsresult aRejectValue) {
+ ASSERT_TRUE(false);
+
+ done = true;
+ });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+
+ ASSERT_NO_FATAL_FAILURE(AssertStorageNotInitialized());
+
+ ASSERT_NO_FATAL_FAILURE(ShutdownStorage());
+}
+
+// Test ShutdownStorage when a storage shutdown is already ongoing and a shared
+// client directory lock is requested after that.
+// The shared client directory lock doesn't have to be explicitly released
+// because it gets invalidated while it's still pending which causes that any
+// directory locks that were blocked by the shared client directory lock become
+// unblocked.
+TEST_F(TestQuotaManager, ShutdownStorage_OngoingWithClientDirectoryLock) {
+ PerformOnBackgroundThread([]() {
+ QuotaManager* quotaManager = QuotaManager::Get();
+ ASSERT_TRUE(quotaManager);
+
+ RefPtr<ClientDirectoryLock> directoryLock =
+ quotaManager->CreateDirectoryLock(GetTestClientMetadata(),
+ /* aExclusive */ false);
+
+ nsTArray<RefPtr<BoolPromise>> promises;
+
+ // This creates an exclusive directory lock internally.
+ promises.AppendElement(quotaManager->ShutdownStorage());
+
+ // This directory lock can't be acquired yet because a storage shutdown
+ // (which uses an exclusive diretory lock internall) is ongoing.
+ promises.AppendElement(directoryLock->Acquire());
+
+ // This second ShutdownStorage invalidates the directoryLock, so that
+ // directory lock can't ever be successfully acquired, the promise for it
+ // will be rejected when the first ShutdownStorage is finished (it releases
+ // its exclusive directory lock);
+ promises.AppendElement(quotaManager->ShutdownStorage());
+
+ bool done = false;
+
+ BoolPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [&done](
+ const BoolPromise::AllSettledPromiseType::ResolveOrRejectValue&
+ aValues) { done = true; });
+
+ SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; });
+ });
+}
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/TestResultExtensions.cpp b/dom/quota/test/gtest/TestResultExtensions.cpp
new file mode 100644
index 0000000000..137acc1423
--- /dev/null
+++ b/dom/quota/test/gtest/TestResultExtensions.cpp
@@ -0,0 +1,333 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "Common.h"
+#include "gtest/gtest.h"
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+namespace {
+class TestClass {
+ public:
+ static constexpr int kTestValue = 42;
+
+ nsresult NonOverloadedNoInputComplex(std::pair<int, int>* aOut) {
+ *aOut = std::pair{kTestValue, kTestValue};
+ return NS_OK;
+ }
+ nsresult NonOverloadedNoInputFailsComplex(std::pair<int, int>* aOut) {
+ return NS_ERROR_FAILURE;
+ }
+};
+} // namespace
+
+class DOM_Quota_ResultExtensions_ToResult : public DOM_Quota_Test {};
+class DOM_Quota_ResultExtensions_GenericErrorResult : public DOM_Quota_Test {};
+
+TEST_F(DOM_Quota_ResultExtensions_ToResult, FromBool) {
+ // success
+ {
+ auto res = ToResult(true);
+ static_assert(std::is_same_v<decltype(res), Result<Ok, nsresult>>);
+ EXPECT_TRUE(res.isOk());
+ }
+
+ // failure
+ {
+ auto res = ToResult(false);
+ static_assert(std::is_same_v<decltype(res), Result<Ok, nsresult>>);
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+ }
+}
+
+TEST_F(DOM_Quota_ResultExtensions_ToResult, FromQMResult_Failure) {
+ // copy
+ {
+ const auto res = ToQMResult(NS_ERROR_FAILURE);
+ auto okOrErr = ToResult<QMResult>(res);
+ static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
+ ASSERT_TRUE(okOrErr.isErr());
+ auto err = okOrErr.unwrapErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err.StackId(), ExpectedStackId());
+ ASSERT_EQ(err.FrameId(), 1u);
+ ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err, NS_ERROR_FAILURE);
+#endif
+ }
+
+ // move
+ {
+ auto res = ToQMResult(NS_ERROR_FAILURE);
+ auto okOrErr = ToResult<QMResult>(std::move(res));
+ static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
+ ASSERT_TRUE(okOrErr.isErr());
+ auto err = okOrErr.unwrapErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err.StackId(), ExpectedStackId());
+ ASSERT_EQ(err.FrameId(), 1u);
+ ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err, NS_ERROR_FAILURE);
+#endif
+ }
+}
+
+TEST_F(DOM_Quota_ResultExtensions_ToResult, FromNSResult_Failure_Macro) {
+ auto okOrErr = QM_TO_RESULT(NS_ERROR_FAILURE);
+ static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
+ ASSERT_TRUE(okOrErr.isErr());
+ auto err = okOrErr.unwrapErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err.StackId(), ExpectedStackId());
+ ASSERT_EQ(err.FrameId(), 1u);
+ ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err, NS_ERROR_FAILURE);
+#endif
+}
+
+TEST_F(DOM_Quota_ResultExtensions_GenericErrorResult, ErrorPropagation) {
+ OkOrErr okOrErr1 = ToResult<QMResult>(ToQMResult(NS_ERROR_FAILURE));
+ const auto& err1 = okOrErr1.inspectErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ IncreaseExpectedStackId();
+
+ ASSERT_EQ(err1.StackId(), ExpectedStackId());
+ ASSERT_EQ(err1.FrameId(), 1u);
+ ASSERT_EQ(err1.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err1, NS_ERROR_FAILURE);
+#endif
+
+ OkOrErr okOrErr2 = okOrErr1.propagateErr();
+ const auto& err2 = okOrErr2.inspectErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ ASSERT_EQ(err2.StackId(), ExpectedStackId());
+ ASSERT_EQ(err2.FrameId(), 2u);
+ ASSERT_EQ(err2.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err2, NS_ERROR_FAILURE);
+#endif
+
+ OkOrErr okOrErr3 = okOrErr2.propagateErr();
+ const auto& err3 = okOrErr3.inspectErr();
+
+#ifdef QM_ERROR_STACKS_ENABLED
+ ASSERT_EQ(err3.StackId(), ExpectedStackId());
+ ASSERT_EQ(err3.FrameId(), 3u);
+ ASSERT_EQ(err3.NSResult(), NS_ERROR_FAILURE);
+#else
+ ASSERT_EQ(err3, NS_ERROR_FAILURE);
+#endif
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput)
+{
+ auto res = ToResultGet<int32_t>([](nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return 42;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Err)
+{
+ auto res = ToResultGet<int32_t>([](nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput)
+{
+ auto res = ToResultGet<int32_t>(
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return aValue * 2;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 84);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput_Err)
+{
+ auto res = ToResultGet<int32_t>(
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(int32_t, [](nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return 42;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 42);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Macro_Typed_Parens)
+{
+ auto res =
+ MOZ_TO_RESULT_GET_TYPED((std::pair<int32_t, int32_t>),
+ [](nsresult* aRv) -> std::pair<int32_t, int32_t> {
+ *aRv = NS_OK;
+ return std::pair{42, 42};
+ });
+
+ static_assert(std::is_same_v<decltype(res),
+ Result<std::pair<int32_t, int32_t>, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), (std::pair{42, 42}));
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_NoInput_Err_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(int32_t, [](nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ });
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(
+ int32_t,
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_OK;
+ return aValue * 2;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isOk());
+ EXPECT_EQ(res.unwrap(), 84);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultGet, Lambda_WithInput_Err_Macro_Typed)
+{
+ auto res = MOZ_TO_RESULT_GET_TYPED(
+ int32_t,
+ [](int32_t aValue, nsresult* aRv) -> int32_t {
+ *aRv = NS_ERROR_FAILURE;
+ return -1;
+ },
+ 42);
+
+ static_assert(std::is_same_v<decltype(res), Result<int32_t, nsresult>>);
+
+ EXPECT_TRUE(res.isErr());
+ EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultInvoke, Lambda_NoInput_Complex)
+{
+ TestClass foo;
+
+ // success
+ {
+ auto valOrErr =
+ ToResultInvoke<std::pair<int, int>>([&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isOk());
+ ASSERT_EQ((std::pair{TestClass::kTestValue, TestClass::kTestValue}),
+ valOrErr.unwrap());
+ }
+
+ // failure
+ {
+ auto valOrErr =
+ ToResultInvoke<std::pair<int, int>>([&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputFailsComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isErr());
+ ASSERT_EQ(NS_ERROR_FAILURE, valOrErr.unwrapErr());
+ }
+}
+
+TEST(DOM_Quota_ResultExtensions_ToResultInvoke,
+ Lambda_NoInput_Complex_Macro_Typed)
+{
+ TestClass foo;
+
+ // success
+ {
+ auto valOrErr = MOZ_TO_RESULT_INVOKE_TYPED(
+ (std::pair<int, int>), [&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isOk());
+ ASSERT_EQ((std::pair{TestClass::kTestValue, TestClass::kTestValue}),
+ valOrErr.unwrap());
+ }
+
+ // failure
+ {
+ auto valOrErr = MOZ_TO_RESULT_INVOKE_TYPED(
+ (std::pair<int, int>), [&foo](std::pair<int, int>* out) {
+ return foo.NonOverloadedNoInputFailsComplex(out);
+ });
+ static_assert(std::is_same_v<decltype(valOrErr),
+ Result<std::pair<int, int>, nsresult>>);
+ ASSERT_TRUE(valOrErr.isErr());
+ ASSERT_EQ(NS_ERROR_FAILURE, valOrErr.unwrapErr());
+ }
+}
diff --git a/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp b/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp
new file mode 100644
index 0000000000..00a3393844
--- /dev/null
+++ b/dom/quota/test/gtest/TestScopedLogExtraInfo.cpp
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/ScopedLogExtraInfo.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla::dom::quota;
+
+TEST(DOM_Quota_ScopedLogExtraInfo, AddAndRemove)
+{
+ static constexpr auto text = "foo"_ns;
+
+ {
+ const auto extraInfo =
+ ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text};
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+
+ EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery));
+#endif
+ }
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+
+ EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery));
+#endif
+}
+
+TEST(DOM_Quota_ScopedLogExtraInfo, Nested)
+{
+ static constexpr auto text = "foo"_ns;
+ static constexpr auto nestedText = "bar"_ns;
+
+ {
+ const auto extraInfo =
+ ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text};
+
+ {
+ const auto extraInfo =
+ ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, nestedText};
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+ EXPECT_EQ(nestedText, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery));
+#endif
+ }
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+ EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery));
+#endif
+ }
+
+#ifdef QM_SCOPED_LOG_EXTRA_INFO_ENABLED
+ const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap();
+
+ EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery));
+#endif
+}
diff --git a/dom/quota/test/gtest/TestStorageConnection.cpp b/dom/quota/test/gtest/TestStorageConnection.cpp
new file mode 100644
index 0000000000..9ff19ec44f
--- /dev/null
+++ b/dom/quota/test/gtest/TestStorageConnection.cpp
@@ -0,0 +1,283 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "QuotaManagerDependencyFixture.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageService.h"
+#include "mozStorageCID.h"
+#include "mozilla/dom/quota/IPCStreamCipherStrategy.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/QuotaObject.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsIFile.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIFileURL.h"
+#include "nsIURIMutator.h"
+#include "nss.h"
+
+namespace mozilla {
+
+struct NSSInitContextDeleter {
+ void operator()(NSSInitContext* p) { NSS_ShutdownContext(p); }
+};
+
+namespace dom::quota::test {
+
+namespace {
+
+void InitializeClientDirectory(const ClientMetadata& aClientMetadata) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+
+ QM_TRY(MOZ_TO_RESULT(
+ quotaManager->EnsureTemporaryStorageIsInitializedInternal()),
+ QM_TEST_FAIL);
+
+ QM_TRY_INSPECT(const auto& directory,
+ quotaManager
+ ->EnsureTemporaryOriginIsInitialized(
+ aClientMetadata.mPersistenceType, aClientMetadata)
+ .map([](const auto& aPair) { return aPair.first; }),
+ QM_TEST_FAIL);
+
+ QM_TRY(MOZ_TO_RESULT(directory->Append(
+ Client::TypeToString(aClientMetadata.mClientType))),
+ QM_TEST_FAIL);
+
+ QM_TRY_INSPECT(const bool& exists,
+ MOZ_TO_RESULT_INVOKE_MEMBER(directory, Exists), QM_TEST_FAIL);
+
+ if (!exists) {
+ QM_TRY(MOZ_TO_RESULT(directory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
+ QM_TEST_FAIL);
+ }
+}
+
+void CreateStorageConnection(
+ const ClientMetadata& aClientMetadata,
+ const Maybe<int64_t>& aMaybeDirectoryLockId,
+ const Maybe<IPCStreamCipherStrategy::KeyType>& aMaybeKey,
+ nsCOMPtr<mozIStorageConnection>& aConnection) {
+ QuotaManager* quotaManager = QuotaManager::Get();
+
+ QM_TRY_UNWRAP(auto databaseFile,
+ quotaManager->GetOriginDirectory(aClientMetadata),
+ QM_TEST_FAIL);
+
+ QM_TRY(MOZ_TO_RESULT(databaseFile->Append(
+ Client::TypeToString(aClientMetadata.mClientType))),
+ QM_TEST_FAIL);
+
+ QM_TRY(MOZ_TO_RESULT(databaseFile->Append(u"testdatabase.sqlite"_ns)),
+ QM_TEST_FAIL);
+
+ QM_TRY_INSPECT(
+ const auto& protocolHandler,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIProtocolHandler>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"),
+ QM_TEST_FAIL);
+
+ QM_TRY_INSPECT(const auto& fileHandler,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIFileProtocolHandler>,
+ MOZ_SELECT_OVERLOAD(do_QueryInterface),
+ protocolHandler),
+ QM_TEST_FAIL);
+
+ QM_TRY_INSPECT(
+ const auto& mutator,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCOMPtr<nsIURIMutator>, fileHandler,
+ NewFileURIMutator, databaseFile),
+ QM_TEST_FAIL);
+
+ const auto directoryLockIdClause = [&aMaybeDirectoryLockId] {
+ nsAutoCString directoryLockIdClause;
+ if (aMaybeDirectoryLockId) {
+ directoryLockIdClause =
+ "&directoryLockId="_ns + IntToCString(*aMaybeDirectoryLockId);
+ }
+ return directoryLockIdClause;
+ }();
+
+ const auto keyClause = [&aMaybeKey] {
+ nsAutoCString keyClause;
+ if (aMaybeKey) {
+ keyClause.AssignLiteral("&key=");
+ for (uint8_t byte : IPCStreamCipherStrategy::SerializeKey(*aMaybeKey)) {
+ keyClause.AppendPrintf("%02x", byte);
+ }
+ }
+ return keyClause;
+ }();
+
+ QM_TRY_INSPECT(
+ const auto& fileURL,
+ ([&mutator, &directoryLockIdClause,
+ &keyClause]() -> Result<nsCOMPtr<nsIFileURL>, nsresult> {
+ nsCOMPtr<nsIFileURL> result;
+ QM_TRY(MOZ_TO_RESULT(NS_MutateURI(mutator)
+ .SetQuery("cache=private"_ns +
+ directoryLockIdClause + keyClause)
+ .Finalize(result)));
+ return result;
+ }()),
+ QM_TEST_FAIL);
+
+ QM_TRY_INSPECT(const auto& storageService,
+ MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>,
+ MOZ_SELECT_OVERLOAD(do_GetService),
+ MOZ_STORAGE_SERVICE_CONTRACTID),
+ QM_TEST_FAIL);
+
+ QM_TRY_UNWRAP(auto connection,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, storageService,
+ OpenDatabaseWithFileURL, fileURL, EmptyCString(),
+ mozIStorageService::CONNECTION_DEFAULT),
+ QM_TEST_FAIL);
+
+ QM_TRY(MOZ_TO_RESULT(
+ connection->ExecuteSimpleSQL("PRAGMA journal_mode = wal;"_ns)),
+ QM_TEST_FAIL);
+
+ // The connection needs to be re-created, otherwise GetQuotaObjects is not
+ // able to get the quota object for the journal file.
+
+ QM_TRY(MOZ_TO_RESULT(connection->Close()), QM_TEST_FAIL);
+
+ QM_TRY_UNWRAP(connection,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsCOMPtr<mozIStorageConnection>, storageService,
+ OpenDatabaseWithFileURL, fileURL, EmptyCString(),
+ mozIStorageService::CONNECTION_DEFAULT),
+ QM_TEST_FAIL);
+
+ aConnection = std::move(connection);
+}
+
+} // namespace
+
+class TestStorageConnection : public QuotaManagerDependencyFixture {
+ public:
+ static void SetUpTestCase() {
+ // Do this only once, do not tear it down per test case.
+ if (!sNssContext) {
+ sNssContext.reset(
+ NSS_InitContext("", "", "", "", nullptr,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT));
+ }
+
+ ASSERT_NO_FATAL_FAILURE(InitializeFixture());
+ }
+
+ static void TearDownTestCase() {
+ ASSERT_NO_FATAL_FAILURE(ShutdownFixture());
+
+ sNssContext = nullptr;
+ }
+
+ void TearDown() override {
+ ASSERT_NO_FATAL_FAILURE(ClearStoragesForOrigin(GetTestOriginMetadata()));
+ }
+
+ private:
+ struct NSSInitContextDeleter {
+ void operator()(NSSInitContext* p) { NSS_ShutdownContext(p); }
+ };
+ inline static std::unique_ptr<NSSInitContext, NSSInitContextDeleter>
+ sNssContext;
+};
+
+TEST_F(TestStorageConnection, BaseVFS) {
+ // XXX This call can't be wrapped with ASSERT_NO_FATAL_FAILURE because
+ // base-toolchains builds fail with: error: duplicate label
+ // 'gtest_label_testnofatal_214'
+ PerformClientDirectoryTest(GetTestClientMetadata(), [](int64_t) {
+ ASSERT_NO_FATAL_FAILURE(InitializeClientDirectory(GetTestClientMetadata()));
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ ASSERT_NO_FATAL_FAILURE(CreateStorageConnection(
+ GetTestClientMetadata(), Nothing(), Nothing(), connection));
+
+ RefPtr<QuotaObject> quotaObject;
+ RefPtr<QuotaObject> journalQuotaObject;
+
+ nsresult rv = connection->GetQuotaObjects(
+ getter_AddRefs(quotaObject), getter_AddRefs(journalQuotaObject));
+ ASSERT_EQ(rv, NS_ERROR_FAILURE);
+
+ ASSERT_FALSE(quotaObject);
+ ASSERT_FALSE(journalQuotaObject);
+
+ QM_TRY(MOZ_TO_RESULT(connection->Close()), QM_TEST_FAIL);
+ });
+}
+
+TEST_F(TestStorageConnection, QuotaVFS) {
+ // XXX This call can't be wrapped with ASSERT_NO_FATAL_FAILURE because
+ // base-toolchains builds fail with: error: duplicate label
+ // 'gtest_label_testnofatal_214'
+ PerformClientDirectoryTest(
+ GetTestClientMetadata(), [](int64_t aDirectoryLockId) {
+ ASSERT_NO_FATAL_FAILURE(
+ InitializeClientDirectory(GetTestClientMetadata()));
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ ASSERT_NO_FATAL_FAILURE(CreateStorageConnection(GetTestClientMetadata(),
+ Some(aDirectoryLockId),
+ Nothing(), connection));
+
+ RefPtr<QuotaObject> quotaObject;
+ RefPtr<QuotaObject> journalQuotaObject;
+
+ QM_TRY(MOZ_TO_RESULT(connection->GetQuotaObjects(
+ getter_AddRefs(quotaObject),
+ getter_AddRefs(journalQuotaObject))),
+ QM_TEST_FAIL);
+
+ ASSERT_TRUE(quotaObject);
+ ASSERT_TRUE(journalQuotaObject);
+
+ QM_TRY(MOZ_TO_RESULT(connection->Close()), QM_TEST_FAIL);
+ });
+}
+
+TEST_F(TestStorageConnection, ObfuscatingVFS) {
+ // XXX This call can't be wrapped with ASSERT_NO_FATAL_FAILURE because
+ // base-toolchains builds fail with: error: duplicate label
+ // 'gtest_label_testnofatal_214'
+ PerformClientDirectoryTest(
+ GetTestClientMetadata(), [](int64_t aDirectoryLockId) {
+ ASSERT_NO_FATAL_FAILURE(
+ InitializeClientDirectory(GetTestClientMetadata()));
+
+ QM_TRY_INSPECT(const auto& key, IPCStreamCipherStrategy::GenerateKey(),
+ QM_TEST_FAIL);
+
+ nsCOMPtr<mozIStorageConnection> connection;
+ ASSERT_NO_FATAL_FAILURE(CreateStorageConnection(GetTestClientMetadata(),
+ Some(aDirectoryLockId),
+ Some(key), connection));
+
+ RefPtr<QuotaObject> quotaObject;
+ RefPtr<QuotaObject> journalQuotaObject;
+
+ QM_TRY(MOZ_TO_RESULT(connection->GetQuotaObjects(
+ getter_AddRefs(quotaObject),
+ getter_AddRefs(journalQuotaObject))),
+ QM_TEST_FAIL);
+
+ ASSERT_TRUE(quotaObject);
+ ASSERT_TRUE(journalQuotaObject);
+
+ QM_TRY(MOZ_TO_RESULT(connection->Close()), QM_TEST_FAIL);
+ });
+}
+
+} // namespace dom::quota::test
+} // namespace mozilla
diff --git a/dom/quota/test/gtest/TestStorageOriginAttributes.cpp b/dom/quota/test/gtest/TestStorageOriginAttributes.cpp
new file mode 100644
index 0000000000..4529e1d53b
--- /dev/null
+++ b/dom/quota/test/gtest/TestStorageOriginAttributes.cpp
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/StorageOriginAttributes.h"
+
+namespace mozilla::dom::quota::test {
+
+TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_NoOriginAttributes)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin("https://www.example.com"_ns,
+ originNoSuffix);
+
+ ASSERT_TRUE(ok);
+ ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes,
+ PopulateFromOrigin_InvalidOriginAttribute)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^foo=bar"_ns, originNoSuffix);
+
+ ASSERT_FALSE(ok);
+ ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes,
+ PopulateFromOrigin_InIsolatedMozBrowser_Valid)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^inBrowser=1"_ns, originNoSuffix);
+
+ ASSERT_TRUE(ok);
+ ASSERT_TRUE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes,
+ PopulateFromOrigin_InIsolatedMozBrowser_Invalid)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^inBrowser=0"_ns, originNoSuffix);
+
+ ASSERT_FALSE(ok);
+ ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^inBrowser=true"_ns, originNoSuffix);
+
+ ASSERT_FALSE(ok);
+ ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^inBrowser=false"_ns, originNoSuffix);
+
+ ASSERT_FALSE(ok);
+ ASSERT_FALSE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_UserContextId_Valid)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^userContextId=1"_ns, originNoSuffix);
+
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(originAttributes.UserContextId(), 1u);
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^userContextId=42"_ns, originNoSuffix);
+
+ ASSERT_TRUE(ok);
+ ASSERT_EQ(originAttributes.UserContextId(), 42u);
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes,
+ PopulateFromOrigin_UserContextId_Invalid)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^userContextId=foo"_ns, originNoSuffix);
+
+ ASSERT_FALSE(ok);
+ ASSERT_EQ(originAttributes.UserContextId(), 0u);
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_Mixed_Valid)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^inBrowser=1&userContextId=1"_ns,
+ originNoSuffix);
+
+ ASSERT_TRUE(ok);
+ ASSERT_TRUE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_EQ(originAttributes.UserContextId(), 1u);
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+TEST(DOM_Quota_StorageOriginAttributes, PopulateFromOrigin_Mixed_Invalid)
+{
+ {
+ StorageOriginAttributes originAttributes;
+ nsCString originNoSuffix;
+ bool ok = originAttributes.PopulateFromOrigin(
+ "https://www.example.com^inBrowser=1&userContextId=1&foo=bar"_ns,
+ originNoSuffix);
+
+ ASSERT_FALSE(ok);
+ ASSERT_TRUE(originAttributes.InIsolatedMozBrowser());
+ ASSERT_EQ(originAttributes.UserContextId(), 1u);
+ ASSERT_TRUE(originNoSuffix.Equals("https://www.example.com"_ns));
+ }
+}
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/TestStringifyUtils.cpp b/dom/quota/test/gtest/TestStringifyUtils.cpp
new file mode 100644
index 0000000000..5d50b31f14
--- /dev/null
+++ b/dom/quota/test/gtest/TestStringifyUtils.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/StringifyUtils.h"
+
+#include "gtest/gtest.h"
+
+namespace mozilla::dom::quota {
+
+class B;
+class A : public Stringifyable {
+ public:
+ A() : mB(nullptr) {}
+ virtual void DoStringify(nsACString& aData) override;
+ void setB(B* aB) { mB = aB; }
+
+ private:
+ B* mB;
+};
+
+class B : public Stringifyable {
+ public:
+ B() : mA(nullptr) {}
+ virtual void DoStringify(nsACString& aData) override;
+ void setA(A* aA) { mA = aA; }
+
+ private:
+ A* mA;
+};
+
+void A::DoStringify(nsACString& aData) {
+ aData.Append("Class A content."_ns);
+ mB->Stringify(aData);
+}
+
+void B::DoStringify(nsACString& aData) {
+ aData.Append("Class B content.");
+ mA->Stringify(aData);
+}
+
+TEST(DOM_Quota_StringifyUtils, Nested)
+{
+ A a1;
+ A a2;
+ B b;
+ a1.setB(&b);
+ b.setA(&a2);
+ a2.setB(&b);
+ nsCString msg;
+ a1.Stringify(msg);
+
+ EXPECT_EQ(
+ 0, strcmp("{Class A content.{Class B content.{Class A content.(...)}}}",
+ msg.get()));
+}
+
+} // namespace mozilla::dom::quota
diff --git a/dom/quota/test/gtest/TestTelemetry.cpp b/dom/quota/test/gtest/TestTelemetry.cpp
new file mode 100644
index 0000000000..7936a5a253
--- /dev/null
+++ b/dom/quota/test/gtest/TestTelemetry.cpp
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsITelemetry.h"
+#include "QuotaManagerDependencyFixture.h"
+
+namespace mozilla::dom::quota::test {
+
+namespace {
+
+namespace js_helpers {
+
+void CallFunction(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const char* aName, JS::MutableHandle<JS::Value> aResult) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ JS::Rooted<JS::Value> result(aCx);
+ ASSERT_TRUE(JS_CallFunctionName(aCx, obj, aName,
+ JS::HandleValueArray::empty(), &result));
+
+ aResult.set(result);
+}
+
+void HasProperty(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const char* aName, bool* aResult) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ bool result;
+ ASSERT_TRUE(JS_HasProperty(aCx, obj, aName, &result));
+
+ *aResult = result;
+}
+
+void GetProperty(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ const char* aName, JS::MutableHandle<JS::Value> aResult) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ JS::Rooted<JS::Value> result(aCx);
+ ASSERT_TRUE(JS_GetProperty(aCx, obj, aName, &result));
+
+ aResult.set(result);
+}
+
+void Enumerate(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ JS::MutableHandle<JS::IdVector> aResult) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ ASSERT_TRUE(JS_Enumerate(aCx, obj, aResult));
+}
+
+void GetElement(JSContext* aCx, JS::Handle<JS::Value> aValue, uint32_t aIndex,
+ JS::MutableHandle<JS::Value> aResult) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+
+ JS::Rooted<JS::Value> result(aCx);
+ ASSERT_TRUE(JS_GetElement(aCx, obj, aIndex, &result));
+
+ aResult.set(result);
+}
+
+void GetIndexValue(JSContext* aCx, JS::Handle<JS::Value> aValues,
+ JS::PropertyKey aIndexId, uint32_t* aResult) {
+ nsAutoJSString indexId;
+ ASSERT_TRUE(indexId.init(aCx, aIndexId));
+
+ nsresult rv;
+ const auto index = indexId.ToInteger64(&rv);
+ ASSERT_NS_SUCCEEDED(rv);
+
+ JS::Rooted<JS::Value> element(aCx);
+ GetElement(aCx, aValues, index, &element);
+
+ uint32_t value = 0;
+ ASSERT_TRUE(JS::ToUint32(aCx, element, &value));
+
+ *aResult = value;
+}
+
+} // namespace js_helpers
+
+} // namespace
+
+TEST(DOM_Quota_Telemetry, ShutdownTime)
+{
+ nsCOMPtr<nsITelemetry> telemetry =
+ do_GetService("@mozilla.org/base/telemetry;1");
+
+ JSObject* simpleGlobal = dom::SimpleGlobalObject::Create(
+ dom::SimpleGlobalObject::GlobalType::BindingDetail);
+
+ JS::Rooted<JSObject*> global(dom::RootingCx(), simpleGlobal);
+
+ AutoJSAPI jsapi;
+ ASSERT_TRUE(jsapi.Init(global));
+
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JS::Value> histogram(cx);
+ ASSERT_NS_SUCCEEDED(telemetry->GetKeyedHistogramById("QM_SHUTDOWN_TIME_V0"_ns,
+ cx, &histogram));
+
+ JS::Rooted<JS::Value> dummy(cx);
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::CallFunction(cx, histogram, "clear", &dummy));
+
+ ASSERT_NO_FATAL_FAILURE(QuotaManagerDependencyFixture::InitializeFixture());
+ ASSERT_NO_FATAL_FAILURE(QuotaManagerDependencyFixture::ShutdownFixture());
+
+ JS::Rooted<JS::Value> snapshot(cx);
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::CallFunction(cx, histogram, "snapshot", &snapshot));
+
+ {
+ bool hasKey = false;
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::HasProperty(cx, snapshot, "Normal", &hasKey));
+ ASSERT_TRUE(hasKey);
+
+ JS::Rooted<JS::Value> key(cx);
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetProperty(cx, snapshot, "Normal", &key));
+
+ JS::Rooted<JS::Value> values(cx);
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetProperty(cx, key, "values", &values));
+
+ JS::Rooted<JS::IdVector> indexIds(cx, JS::IdVector(cx));
+ ASSERT_NO_FATAL_FAILURE(js_helpers::Enumerate(cx, values, &indexIds));
+ ASSERT_TRUE(indexIds.length() == 2u || indexIds.length() == 3u);
+
+ if (indexIds.length() == 2) {
+ uint32_t value = 0u;
+
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetIndexValue(cx, values, indexIds[0], &value));
+ ASSERT_EQ(value, 1u);
+
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetIndexValue(cx, values, indexIds[1], &value));
+ ASSERT_EQ(value, 0u);
+ } else {
+ uint32_t value = 1u;
+
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetIndexValue(cx, values, indexIds[0], &value));
+ ASSERT_EQ(value, 0u);
+
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetIndexValue(cx, values, indexIds[1], &value));
+ ASSERT_EQ(value, 1u);
+
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::GetIndexValue(cx, values, indexIds[2], &value));
+ ASSERT_EQ(value, 0u);
+ }
+ }
+
+ {
+ bool hasKey = true;
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::HasProperty(cx, snapshot, "WasSuspended", &hasKey));
+ ASSERT_FALSE(hasKey);
+ }
+
+ {
+ bool hasKey = true;
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::HasProperty(cx, snapshot, "TimeStampErr1", &hasKey));
+ ASSERT_FALSE(hasKey);
+ }
+
+ {
+ bool hasKey = true;
+ ASSERT_NO_FATAL_FAILURE(
+ js_helpers::HasProperty(cx, snapshot, "TimeStampErr2", &hasKey));
+ ASSERT_FALSE(hasKey);
+ }
+}
+
+} // namespace mozilla::dom::quota::test
diff --git a/dom/quota/test/gtest/TestUsageInfo.cpp b/dom/quota/test/gtest/TestUsageInfo.cpp
new file mode 100644
index 0000000000..124783b715
--- /dev/null
+++ b/dom/quota/test/gtest/TestUsageInfo.cpp
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "mozilla/dom/quota/UsageInfo.h"
+
+#include "gtest/gtest.h"
+
+#include <cstdint>
+#include <memory>
+#include <ostream>
+#include <utility>
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/fallible.h"
+
+using namespace mozilla;
+using namespace mozilla::dom::quota;
+
+namespace {
+constexpr uint64_t kTestValue = 42;
+constexpr uint64_t kTestValueZero = 0;
+} // namespace
+
+TEST(DOM_Quota_UsageInfo, DefaultConstructed)
+{
+ const UsageInfo usageInfo;
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Nothing(), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, FileOnly)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValue), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValue), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, DatabaseOnly)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValue), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, FileOnly_Zero)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValueZero));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, DatabaseOnly_Zero)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += DatabaseUsageType(Some(kTestValueZero));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, Both)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValue));
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValue), usageInfo.FileUsage());
+ EXPECT_EQ(Some(2 * kTestValue), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, Both_Zero)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(kTestValueZero));
+ usageInfo += DatabaseUsageType(Some(kTestValueZero));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.FileUsage());
+ EXPECT_EQ(Some(kTestValueZero), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, CapCombined)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(UINT64_MAX));
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.FileUsage());
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, CapFileUsage)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += FileUsageType(Some(UINT64_MAX));
+ usageInfo += FileUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.FileUsage());
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.TotalUsage());
+}
+
+TEST(DOM_Quota_UsageInfo, CapDatabaseUsage)
+{
+ const UsageInfo usageInfo = [] {
+ UsageInfo usageInfo;
+ usageInfo += DatabaseUsageType(Some(UINT64_MAX));
+ usageInfo += DatabaseUsageType(Some(kTestValue));
+ return usageInfo;
+ }();
+ EXPECT_EQ(Nothing(), usageInfo.FileUsage());
+ EXPECT_EQ(Some(UINT64_MAX), usageInfo.TotalUsage());
+}
diff --git a/dom/quota/test/gtest/moz.build b/dom/quota/test/gtest/moz.build
new file mode 100644
index 0000000000..d983e3457e
--- /dev/null
+++ b/dom/quota/test/gtest/moz.build
@@ -0,0 +1,50 @@
+# -*- 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/.
+
+EXPORTS.mozilla.dom.quota += [
+ "QuotaTestChild.h",
+ "QuotaTestParent.h",
+]
+
+EXPORTS.mozilla.dom.quota.test += [
+ "QuotaManagerDependencyFixture.h",
+]
+
+UNIFIED_SOURCES = [
+ "Common.cpp",
+ "QuotaManagerDependencyFixture.cpp",
+ "TestCheckedUnsafePtr.cpp",
+ "TestClientUsageArray.cpp",
+ "TestEncryptedStream.cpp",
+ "TestFileOutputStream.cpp",
+ "TestFlatten.cpp",
+ "TestForwardDecls.cpp",
+ "TestOriginParser.cpp",
+ "TestOriginScope.cpp",
+ "TestPersistenceType.cpp",
+ "TestQMResult.cpp",
+ "TestQuotaCommon.cpp",
+ "TestQuotaManager.cpp",
+ "TestResultExtensions.cpp",
+ "TestScopedLogExtraInfo.cpp",
+ "TestStorageConnection.cpp",
+ "TestStorageOriginAttributes.cpp",
+ "TestStringifyUtils.cpp",
+ "TestTelemetry.cpp",
+ "TestUsageInfo.cpp",
+]
+
+IPDL_SOURCES += [
+ "PQuotaTest.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
+
+LOCAL_INCLUDES += [
+ "/dom/quota",
+]