diff options
Diffstat (limited to 'dom/quota/test/gtest')
21 files changed, 4669 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..e4ca37325a --- /dev/null +++ b/dom/quota/test/gtest/PQuotaTest.ipdl @@ -0,0 +1,26 @@ +/* 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 { + +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..2e52b4b69a --- /dev/null +++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.cpp @@ -0,0 +1,160 @@ +/* -*- 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/dom/ScriptSettings.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "mozilla/gtest/MozAssertions.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(StorageInitialized()); + + 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::StorageInitialized(bool* aResult) { + AutoJSAPI jsapi; + + bool ok = jsapi.Init(xpc::PrivilegedJunkScope()); + ASSERT_TRUE(ok); + + nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + ASSERT_TRUE(qms); + + nsCOMPtr<nsIQuotaRequest> request; + nsresult rv = qms->StorageInitialized(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(); }); + + if (aResult) { + nsCOMPtr<nsIVariant> result; + rv = request->GetResult(getter_AddRefs(result)); + ASSERT_NS_SUCCEEDED(rv); + + rv = result->GetAsBool(aResult); + ASSERT_NS_SUCCEEDED(rv); + } +} + +// static +void QuotaManagerDependencyFixture::ClearStoragesForOrigin( + const OriginMetadata& aOriginMetadata) { + nsCOMPtr<nsIQuotaManagerService> qms = QuotaManagerService::GetOrCreate(); + ASSERT_TRUE(qms); + + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + ASSERT_TRUE(ssm); + + nsCOMPtr<nsIPrincipal> principal; + nsresult rv = ssm->CreateContentPrincipalFromOrigin( + aOriginMetadata.mOrigin, getter_AddRefs(principal)); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr<nsIQuotaRequest> request; + rv = qms->ClearStoragesForPrincipal(principal, VoidCString(), VoidString(), + /* aClearAll */ false, + getter_AddRefs(request)); + ASSERT_NS_SUCCEEDED(rv); + + RefPtr<RequestResolver> resolver = new RequestResolver(); + ASSERT_TRUE(resolver); + + 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..4d57952242 --- /dev/null +++ b/dom/quota/test/gtest/QuotaManagerDependencyFixture.h @@ -0,0 +1,87 @@ +/* -*- 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/ForwardDecls.h" +#include "mozilla/dom/quota/QuotaManager.h" + +namespace mozilla::dom::quota::test { + +class QuotaManagerDependencyFixture : public testing::Test { + public: + protected: + static void InitializeFixture(); + + static void ShutdownFixture(); + + static void StorageInitialized(bool* aResult = nullptr); + + 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)] { + 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)]() { + boundTask(); + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue& value) { + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + } + + static const nsCOMPtr<nsISerialEventTarget>& BackgroundTargetStrongRef() { + return sBackgroundTarget; + } + + private: + 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..9eac04a391 --- /dev/null +++ b/dom/quota/test/gtest/TestEncryptedStream.cpp @@ -0,0 +1,791 @@ +/* -*- 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/Scoped.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 + +namespace mozilla { +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedNSSContext, NSSInitContext, + NSS_ShutdownContext); + +} // namespace mozilla + +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 = + 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: + inline static ScopedNSSContext sNssContext = ScopedNSSContext{}; +}; + +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)); +} + +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..d5a6095094 --- /dev/null +++ b/dom/quota/test/gtest/TestFileOutputStream.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/quota/Client.h" +#include "mozilla/dom/quota/CommonMetadata.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 { + +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"); + + ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); + } + + static const int32_t mQuotaLimit = 8192; +}; + +TEST_F(TestFileOutputStream, extendFileStreamWithSetEOF) { + auto ioTask = []() { + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + + auto originMetadata = + quota::OriginMetadata{""_ns, + "example.com"_ns, + "http://example.com"_ns, + "http://example.com"_ns, + /* aIsPrivate */ false, + quota::PERSISTENCE_TYPE_DEFAULT}; + + { + ASSERT_NS_SUCCEEDED(quotaManager->EnsureStorageIsInitialized()); + + ASSERT_NS_SUCCEEDED(quotaManager->EnsureTemporaryStorageIsInitialized()); + + 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); + }; + + PerformOnIOThread(std::move(ioTask)); +} + +} // 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/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..c1a047a533 --- /dev/null +++ b/dom/quota/test/gtest/TestQuotaCommon.cpp @@ -0,0 +1,2171 @@ +/* -*- 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 "nsLiteralString.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTLiteralString.h" + +class nsISupports; + +using namespace mozilla; +using namespace mozilla::dom::quota; + +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, 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 diff --git a/dom/quota/test/gtest/TestQuotaManager.cpp b/dom/quota/test/gtest/TestQuotaManager.cpp new file mode 100644 index 0000000000..7d185df481 --- /dev/null +++ b/dom/quota/test/gtest/TestQuotaManager.cpp @@ -0,0 +1,181 @@ +/* -*- 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/OriginScope.h" + +#include "gtest/gtest.h" + +#include <cstdint> +#include <memory> +#include "ErrorList.h" +#include "mozilla/Result.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/fallible.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" + +using namespace mozilla; +using 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); +} + +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(QuotaManager, OriginScope) +{ + 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 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); + } + } +} + +TEST(QuotaManager, WarnIfUnknownFile) +{ + 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); +} 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/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..30ca99b254 --- /dev/null +++ b/dom/quota/test/gtest/moz.build @@ -0,0 +1,44 @@ +# -*- 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", + "TestPersistenceType.cpp", + "TestQMResult.cpp", + "TestQuotaCommon.cpp", + "TestQuotaManager.cpp", + "TestResultExtensions.cpp", + "TestScopedLogExtraInfo.cpp", + "TestUsageInfo.cpp", +] + +IPDL_SOURCES += [ + "PQuotaTest.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/quota", +] |