diff options
Diffstat (limited to 'dom/quota/test/gtest')
-rw-r--r-- | dom/quota/test/gtest/TestCheckedUnsafePtr.cpp | 142 | ||||
-rw-r--r-- | dom/quota/test/gtest/TestEncryptedStream.cpp | 751 | ||||
-rw-r--r-- | dom/quota/test/gtest/TestQuotaCommon.cpp | 1465 | ||||
-rw-r--r-- | dom/quota/test/gtest/TestQuotaManager.cpp | 181 | ||||
-rw-r--r-- | dom/quota/test/gtest/TestUsageInfo.cpp | 136 | ||||
-rw-r--r-- | dom/quota/test/gtest/moz.build | 21 |
6 files changed, 2696 insertions, 0 deletions
diff --git a/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp b/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp new file mode 100644 index 0000000000..298eed84cd --- /dev/null +++ b/dom/quota/test/gtest/TestCheckedUnsafePtr.cpp @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#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> {}; + +static_assert(std::is_literal_type_v<CheckedUnsafePtr<NoCheckTestType>>); +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_CASE_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_CASE_P(TypedCheckedUnsafePtrTest, + PointeeWithOneCheckedUnsafePtr, + CheckedUnsafePtrCopyConstructed, + CheckedUnsafePtrCopyAssigned, + PointeeWithOneDanglingCheckedUnsafePtr, + PointeeWithOneCopiedDanglingCheckedUnsafePtr, + PointeeWithOneCopyAssignedDanglingCheckedUnsafePtr); + +using BothTypes = ::testing::Types<BasePointee, DerivedPointee>; +INSTANTIATE_TYPED_TEST_CASE_P(InstantiationOf, TypedCheckedUnsafePtrTest, + BothTypes); diff --git a/dom/quota/test/gtest/TestEncryptedStream.cpp b/dom/quota/test/gtest/TestEncryptedStream.cpp new file mode 100644 index 0000000000..651307e9f3 --- /dev/null +++ b/dom/quota/test/gtest/TestEncryptedStream.cpp @@ -0,0 +1,751 @@ +/* -*- 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/NotNull.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/SafeRefPtr.h" +#include "mozilla/dom/quota/DecryptingInputStream_impl.h" +#include "mozilla/dom/quota/DummyCipherStrategy.h" +#include "mozilla/dom/quota/EncryptedBlock.h" +#include "mozilla/dom/quota/EncryptingOutputStream_impl.h" +#include "mozilla/dom/quota/MemoryOutputStream.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" + +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::Read(char* aBuf, uint32_t aCount, + uint32_t* aReadCount) { + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount); +} + +NS_IMETHODIMP +ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t aCount, uint32_t* result) { + MOZ_ASSERT(result, "null ptr"); + MOZ_ASSERT(mBufferLength >= mPos, "bad stream state"); + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), + "stream inited incorrectly"); + + *result = 0; + while (mPos < mBufferLength) { + uint32_t remaining = mBufferLength - mPos; + MOZ_ASSERT(mArrayBuffer); + + uint32_t count = std::min(aCount, remaining); + if (count == 0) { + break; + } + + uint32_t written; + nsresult rv = writer(this, closure, &mArrayBuffer[0] + mPos, *result, count, + &written); + if (NS_FAILED(rv)) { + // InputStreams do not propagate errors to caller. + return NS_OK; + } + + MOZ_ASSERT(written <= count, + "writer should not write more than we asked it to write"); + mPos += written; + *result += written; + aCount -= written; + } + + return NS_OK; +} + +NS_IMETHODIMP +ArrayBufferInputStream::IsNonBlocking(bool* aNonBlocking) { + // Actually, the stream never blocks, but we lie about it because of the + // assumptions in DecryptingInputStream. + *aNonBlocking = false; + return NS_OK; +} + +NS_IMETHODIMP ArrayBufferInputStream::Tell(int64_t* const aRetval) { + MOZ_ASSERT(aRetval); + + *aRetval = mPos; + + return NS_OK; +} + +NS_IMETHODIMP ArrayBufferInputStream::Seek(const int32_t aWhence, + const int64_t aOffset) { + // XXX This is not safe. it's hard to use CheckedInt here, though. As long as + // the class is only used for testing purposes, that's probably fine. + + int32_t newPos = mPos; + switch (aWhence) { + case NS_SEEK_SET: + newPos = aOffset; + break; + case NS_SEEK_CUR: + newPos += aOffset; + break; + case NS_SEEK_END: + newPos = mBufferLength; + newPos += aOffset; + break; + default: + return NS_ERROR_ILLEGAL_VALUE; + } + if (newPos < 0 || static_cast<uint32_t>(newPos) > mBufferLength) { + return NS_ERROR_ILLEGAL_VALUE; + } + mPos = newPos; + + return NS_OK; +} + +NS_IMETHODIMP ArrayBufferInputStream::SetEOF() { + // Truncating is not supported on a read-only stream. + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP ArrayBufferInputStream::GetCloneable(bool* aCloneable) { + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP ArrayBufferInputStream::Clone(nsIInputStream** _retval) { + *_retval = MakeAndAddRef<ArrayBufferInputStream>( + AsBytes(Span{mArrayBuffer.get(), mBufferLength})) + .take(); + + return NS_OK; +} +} // namespace mozilla::dom::quota + +using namespace mozilla; +using namespace mozilla::dom::quota; + +class DOM_Quota_EncryptedStream : public ::testing::Test { + public: +}; + +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<dom::quota::MemoryOutputStream> 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<dom::quota::MemoryOutputStream>{ + dom::quota::MemoryOutputStream::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->Data()); + + ReadTestData<CipherStrategy>( + WrapNotNull(nsCOMPtr<nsIInputStream>{baseInputStream}), Span{data}, + aReadChunkSize, aBlockSize, aKey, aExtraChecks); + + return baseOutputStream; +} + +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 encryptedDataSpan = AsBytes(Span(encryptedDataStream->Data())); + + 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<dom::quota::MemoryOutputStream>{ + dom::quota::MemoryOutputStream::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->Data()); + + 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<dom::quota::MemoryOutputStream>{ + dom::quota::MemoryOutputStream::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->Data()); + + 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_CASE_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_CASE_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/TestQuotaCommon.cpp b/dom/quota/test/gtest/TestQuotaCommon.cpp new file mode 100644 index 0000000000..b93d9bf86f --- /dev/null +++ b/dom/quota/test/gtest/TestQuotaCommon.cpp @@ -0,0 +1,1465 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsLiteralString.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTLiteralString.h" + +class nsISupports; + +using namespace mozilla; +using namespace mozilla::dom::quota; + +#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(NS_OK); + + tryDidNotReturn = true; + + return NS_OK; + }(); + + EXPECT_TRUE(tryDidNotReturn); + EXPECT_EQ(rv, NS_OK); +} + +#ifdef DEBUG +TEST(QuotaCommon_Try, Success_CustomErr_AssertUnreachable) +{ + bool tryDidNotReturn = false; + + nsresult rv = [&tryDidNotReturn]() -> nsresult { + QM_TRY(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(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(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(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_WithCleanup) +{ + bool tryCleanupRan = false; + bool tryDidNotReturn = false; + + nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult { + QM_TRY(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(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(NS_ERROR_FAILURE, NS_ERROR_UNEXPECTED); + + tryDidNotReturn = true; + + return NS_OK; + }(); + + EXPECT_FALSE(tryDidNotReturn); + EXPECT_EQ(rv, NS_ERROR_UNEXPECTED); +} + +TEST(QuotaCommon_Try, Failure_NoErr) +{ + bool tryDidNotReturn = false; + + [&tryDidNotReturn]() -> void { + QM_TRY(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(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(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(NS_OK, QM_VOID); QM_TRY(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(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(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(NS_OK); + + nestedTry1DidNotReturn = true; + + QM_TRY(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(NS_ERROR_FAILURE); + + nestedTry1DidNotReturn = true; + + QM_TRY(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(NS_OK); + + nestedTry1DidNotReturn = true; + + QM_TRY(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); +} + +#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_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_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); +} + +#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_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_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_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_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); +} + +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_CASE_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"))); + +TEST(QuotaCommon_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(QuotaCommon_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(QuotaCommon_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(QuotaCommon_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); +} + +// 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_ScopedLogExtraInfo, AddAndRemove) +{ + static constexpr auto text = "foo"_ns; + + { + const auto extraInfo = + ScopedLogExtraInfo{ScopedLogExtraInfo::kTagQuery, text}; + +#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO + const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); + + EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery)); +#endif + } + +#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO + const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); + + EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery)); +#endif +} + +TEST(QuotaCommon_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_ENABLE_SCOPED_LOG_EXTRA_INFO + const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); + EXPECT_EQ(nestedText, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery)); +#endif + } + +#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO + const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); + EXPECT_EQ(text, *extraInfoMap.at(ScopedLogExtraInfo::kTagQuery)); +#endif + } + +#ifdef QM_ENABLE_SCOPED_LOG_EXTRA_INFO + const auto& extraInfoMap = ScopedLogExtraInfo::GetExtraInfoMap(); + + EXPECT_EQ(0u, extraInfoMap.count(ScopedLogExtraInfo::kTagQuery)); +#endif +} + +#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/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..d79c106512 --- /dev/null +++ b/dom/quota/test/gtest/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES = [ + "TestCheckedUnsafePtr.cpp", + "TestEncryptedStream.cpp", + "TestQuotaCommon.cpp", + "TestQuotaManager.cpp", + "TestUsageInfo.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/quota", +] |