diff options
Diffstat (limited to 'dom/indexedDB/test/gtest')
-rw-r--r-- | dom/indexedDB/test/gtest/TestIDBResult.cpp | 40 | ||||
-rw-r--r-- | dom/indexedDB/test/gtest/TestKey.cpp | 433 | ||||
-rw-r--r-- | dom/indexedDB/test/gtest/TestSafeRefPtr.cpp | 279 | ||||
-rw-r--r-- | dom/indexedDB/test/gtest/TestSimpleFileInfo.cpp | 278 | ||||
-rw-r--r-- | dom/indexedDB/test/gtest/moz.build | 23 |
5 files changed, 1053 insertions, 0 deletions
diff --git a/dom/indexedDB/test/gtest/TestIDBResult.cpp b/dom/indexedDB/test/gtest/TestIDBResult.cpp new file mode 100644 index 0000000000..d20b68d4a6 --- /dev/null +++ b/dom/indexedDB/test/gtest/TestIDBResult.cpp @@ -0,0 +1,40 @@ +/* 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 "IDBResult.h" + +#include "gtest/gtest.h" + +using mozilla::ErrorResult; +using namespace mozilla::dom::indexedDB; + +TEST(IDBResultTest, ConstructWithValue) +{ + IDBResult<int, IDBSpecialValue::Failure> result(0); + EXPECT_FALSE(result.isErr() && + result.inspectErr().Is(SpecialValues::Failure)); + EXPECT_TRUE(result.isOk()); + EXPECT_EQ(result.unwrap(), 0); +} + +TEST(IDBResultTest, Expand) +{ + IDBResult<int, IDBSpecialValue::Failure> narrow{ + mozilla::Err(SpecialValues::Failure)}; + IDBResult<int, IDBSpecialValue::Failure, IDBSpecialValue::Invalid> wide{ + narrow.propagateErr()}; + EXPECT_TRUE(wide.isErr() && wide.inspectErr().Is(SpecialValues::Failure)); +} + +IDBResult<int, IDBSpecialValue::Failure> ThrowException() { + return mozilla::Err(IDBException(NS_ERROR_FAILURE)); +} + +TEST(IDBResultTest, ThrowException) +{ + auto result = ThrowException(); + EXPECT_TRUE(result.isErr() && + result.inspectErr().Is(SpecialValues::Exception)); + result.unwrapErr().AsException().SuppressException(); +} diff --git a/dom/indexedDB/test/gtest/TestKey.cpp b/dom/indexedDB/test/gtest/TestKey.cpp new file mode 100644 index 0000000000..8de788a2fc --- /dev/null +++ b/dom/indexedDB/test/gtest/TestKey.cpp @@ -0,0 +1,433 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/dom/SimpleGlobalObject.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/indexedDB/Key.h" +#include "mozilla/IntegerRange.h" +#include "mozilla/Unused.h" + +#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject +#include "js/ArrayBuffer.h" +#include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement +#include "js/RootingAPI.h" +#include "js/String.h" +#include "js/TypeDecls.h" +#include "js/Value.h" + +// TODO: This PrintTo overload is defined in dom/media/gtest/TestGroupId.cpp. +// However, it is not used, probably because of +// https://stackoverflow.com/a/36941270 +void PrintTo(const nsString& value, std::ostream* os); + +using namespace mozilla; +using namespace mozilla::dom::indexedDB; +using JS::Rooted; + +// DOM_IndexedDB_Key_Ctor tests test the construction of a Key, and check the +// properties of the constructed key with the const methods afterwards. The +// tested ctors include the default ctor, which constructs an unset key, and the +// ctors that accepts an encoded buffer, which is then decoded using the +// Key::To* method corresponding to its type. +// +// So far, only some cases are tested: +// - scalar binary +// -- empty +// -- with 1-byte encoded representation +// - scalar string +// -- empty +// -- with 1-byte encoded representation +// +// TODO More test cases should be added, including +// - empty (?) +// - scalar binary +// -- containing 0 byte(s) +// -- with 2-byte encoded representation +// - scalar string +// -- with 2-byte and 3-byte encoded representation +// - scalar number +// - scalar date +// - arrays, incl. nested arrays, with various combinations of contained types + +TEST(DOM_IndexedDB_Key, Ctor_Default) +{ + auto key = Key{}; + + EXPECT_TRUE(key.IsUnset()); +} + +// TODO does such a helper function already exist? +template <size_t N> +static auto BufferAsCString(const uint8_t (&aBuffer)[N]) { + return nsCString{reinterpret_cast<const char*>( + static_cast<std::decay_t<const uint8_t[]>>(aBuffer)), + N}; +} + +static void ExpectKeyIsBinary(const Key& aKey) { + EXPECT_FALSE(aKey.IsUnset()); + + EXPECT_FALSE(aKey.IsArray()); + EXPECT_TRUE(aKey.IsBinary()); + EXPECT_FALSE(aKey.IsDate()); + EXPECT_FALSE(aKey.IsFloat()); + EXPECT_FALSE(aKey.IsString()); +} + +static void ExpectKeyIsString(const Key& aKey) { + EXPECT_FALSE(aKey.IsUnset()); + + EXPECT_FALSE(aKey.IsArray()); + EXPECT_FALSE(aKey.IsBinary()); + EXPECT_FALSE(aKey.IsDate()); + EXPECT_FALSE(aKey.IsFloat()); + EXPECT_TRUE(aKey.IsString()); +} + +static void ExpectKeyIsArray(const Key& aKey) { + EXPECT_FALSE(aKey.IsUnset()); + + EXPECT_TRUE(aKey.IsArray()); + EXPECT_FALSE(aKey.IsBinary()); + EXPECT_FALSE(aKey.IsDate()); + EXPECT_FALSE(aKey.IsFloat()); + EXPECT_FALSE(aKey.IsString()); +} + +static JSObject* ExpectArrayBufferObject(const JS::Value& aValue) { + EXPECT_TRUE(aValue.isObject()); + auto& object = aValue.toObject(); + EXPECT_TRUE(JS::IsArrayBufferObject(&object)); + return &object; +} + +static JSObject* ExpectArrayObject(JSContext* const aContext, + JS::Handle<JS::Value> aValue) { + EXPECT_TRUE(aValue.isObject()); + bool rv; + EXPECT_TRUE(JS::IsArrayObject(aContext, aValue, &rv)); + EXPECT_TRUE(rv); + return &aValue.toObject(); +} + +static void CheckArrayBuffer(const nsCString& aExpected, + const JS::Value& aActual) { + auto obj = ExpectArrayBufferObject(aActual); + size_t length; + bool isSharedMemory; + uint8_t* data; + JS::GetArrayBufferLengthAndData(obj, &length, &isSharedMemory, &data); + + EXPECT_EQ(aExpected.Length(), length); + EXPECT_EQ(0, memcmp(aExpected.get(), data, length)); +} + +static void CheckString(JSContext* const aContext, const nsString& aExpected, + JS::Handle<JS::Value> aActual) { + EXPECT_TRUE(aActual.isString()); + int32_t rv; + EXPECT_TRUE(JS_CompareStrings(aContext, + JS_NewUCStringCopyZ(aContext, aExpected.get()), + aActual.toString(), &rv)); + EXPECT_EQ(0, rv); +} + +namespace { +// This is modeled after dom/base/test/gtest/TestContentUtils.cpp +struct AutoTestJSContext { + AutoTestJSContext() + : mGlobalObject( + mozilla::dom::RootingCx(), + mozilla::dom::SimpleGlobalObject::Create( + mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail)) { + EXPECT_TRUE(mJsAPI.Init(mGlobalObject)); + mContext = mJsAPI.cx(); + } + + operator JSContext*() const { return mContext; } + + private: + Rooted<JSObject*> mGlobalObject; + mozilla::dom::AutoJSAPI mJsAPI; + JSContext* mContext; +}; + +// The following classes serve as base classes for the parametrized tests below. +// The name of each class reflects the parameter type. + +class TestWithParam_CString_ArrayBuffer_Pair + : public ::testing::TestWithParam<std::pair<nsCString, nsLiteralCString>> { +}; + +class TestWithParam_CString_String_Pair + : public ::testing::TestWithParam<std::pair<nsCString, nsLiteralString>> {}; + +class TestWithParam_LiteralString + : public ::testing::TestWithParam<nsLiteralString> {}; + +class TestWithParam_StringArray + : public ::testing::TestWithParam<std::vector<nsString>> {}; + +class TestWithParam_ArrayBufferArray + : public ::testing::TestWithParam<std::vector<nsCString>> {}; + +} // namespace + +TEST_P(TestWithParam_CString_ArrayBuffer_Pair, Ctor_EncodedBinary) { + const auto key = Key{GetParam().first}; + + ExpectKeyIsBinary(key); + + AutoTestJSContext context; + + Rooted<JS::Value> rv(context); + EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv)); + + CheckArrayBuffer(GetParam().second, rv); +} + +static const uint8_t zeroLengthBinaryEncodedBuffer[] = {Key::eBinary}; +static const uint8_t nonZeroLengthBinaryEncodedBuffer[] = {Key::eBinary, + 'a' + 1, 'b' + 1}; +INSTANTIATE_TEST_SUITE_P( + DOM_IndexedDB_Key, TestWithParam_CString_ArrayBuffer_Pair, + ::testing::Values( + std::make_pair(BufferAsCString(zeroLengthBinaryEncodedBuffer), ""_ns), + std::make_pair(BufferAsCString(nonZeroLengthBinaryEncodedBuffer), + "ab"_ns))); + +TEST_P(TestWithParam_CString_String_Pair, Ctor_EncodedString) { + const auto key = Key{GetParam().first}; + + ExpectKeyIsString(key); + + EXPECT_EQ(GetParam().second, key.ToString()); +} + +static const uint8_t zeroLengthStringEncodedBuffer[] = {Key::eString}; +static const uint8_t nonZeroLengthStringEncodedBuffer[] = {Key::eString, + 'a' + 1, 'b' + 1}; + +INSTANTIATE_TEST_SUITE_P( + DOM_IndexedDB_Key, TestWithParam_CString_String_Pair, + ::testing::Values( + std::make_pair(BufferAsCString(zeroLengthStringEncodedBuffer), u""_ns), + std::make_pair(BufferAsCString(nonZeroLengthStringEncodedBuffer), + u"ab"_ns))); + +TEST_P(TestWithParam_LiteralString, SetFromString) { + auto key = Key{}; + const auto result = key.SetFromString(GetParam()); + EXPECT_TRUE(result.isOk()); + + ExpectKeyIsString(key); + + EXPECT_EQ(GetParam(), key.ToString()); +} + +INSTANTIATE_TEST_SUITE_P(DOM_IndexedDB_Key, TestWithParam_LiteralString, + ::testing::Values(u""_ns, u"abc"_ns, u"\u007f"_ns, + u"\u0080"_ns, u"\u1fff"_ns, + u"\u7fff"_ns, u"\u8000"_ns, + u"\uffff"_ns)); + +static JS::Value CreateArrayBufferValue(JSContext* const aContext, + const size_t aSize, char* const aData) { + Rooted<JSObject*> arrayBuffer{ + aContext, JS::NewArrayBufferWithContents(aContext, aSize, aData)}; + EXPECT_TRUE(arrayBuffer); + return JS::ObjectValue(*arrayBuffer); +} + +// This tests calling SetFromJSVal with an ArrayBuffer scalar of length 0. +// TODO Probably there should be more test cases for SetFromJSVal with other +// ArrayBuffer scalars, which convert this into a parametrized test as well. +TEST(DOM_IndexedDB_Key, SetFromJSVal_ZeroLengthArrayBuffer) +{ + AutoTestJSContext context; + + auto key = Key{}; + Rooted<JS::Value> arrayBuffer(context, + CreateArrayBufferValue(context, 0, nullptr)); + const auto result = key.SetFromJSVal(context, arrayBuffer); + EXPECT_TRUE(result.isOk()); + + ExpectKeyIsBinary(key); + + Rooted<JS::Value> rv2(context); + EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2)); + + CheckArrayBuffer(""_ns, rv2); +} + +template <typename CheckElement> +static void CheckArray(JSContext* const context, + JS::Handle<JS::Value> arrayValue, + const size_t expectedLength, + const CheckElement& checkElement) { + Rooted<JSObject*> actualArray(context, + ExpectArrayObject(context, arrayValue)); + + uint32_t actualLength; + EXPECT_TRUE(JS::GetArrayLength(context, actualArray, &actualLength)); + EXPECT_EQ(expectedLength, actualLength); + for (size_t i = 0; i < expectedLength; ++i) { + Rooted<JS::Value> element(static_cast<JSContext*>(context)); + EXPECT_TRUE(JS_GetElement(context, actualArray, i, &element)); + + checkElement(i, element); + } +} + +static JS::Value CreateArrayBufferArray( + JSContext* const context, const std::vector<nsCString>& elements) { + Rooted<JSObject*> arrayObject(context, + JS::NewArrayObject(context, elements.size())); + EXPECT_TRUE(arrayObject); + + Rooted<JS::Value> arrayBuffer(context); + for (size_t i = 0; i < elements.size(); ++i) { + // TODO strdup only works if the element is actually 0-terminated + arrayBuffer = CreateArrayBufferValue( + context, elements[i].Length(), + elements[i].Length() ? strdup(elements[i].get()) : nullptr); + EXPECT_TRUE(JS_SetElement(context, arrayObject, i, arrayBuffer)); + } + + return JS::ObjectValue(*arrayObject); +} + +TEST_P(TestWithParam_ArrayBufferArray, SetFromJSVal) { + const auto& elements = GetParam(); + + AutoTestJSContext context; + Rooted<JS::Value> arrayValue(context); + arrayValue = CreateArrayBufferArray(context, elements); + + auto key = Key{}; + const auto result = key.SetFromJSVal(context, arrayValue); + EXPECT_TRUE(result.isOk()); + + ExpectKeyIsArray(key); + + Rooted<JS::Value> rv2(context); + EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2)); + + CheckArray(context, rv2, elements.size(), + [&elements](const size_t i, const JS::HandleValue& element) { + CheckArrayBuffer(elements[i], element); + }); +} + +const uint8_t element2[] = "foo"; +INSTANTIATE_TEST_SUITE_P( + DOM_IndexedDB_Key, TestWithParam_ArrayBufferArray, + testing::Values(std::vector<nsCString>{}, std::vector<nsCString>{""_ns}, + std::vector<nsCString>{""_ns, BufferAsCString(element2)})); + +static JS::Value CreateStringValue(JSContext* const context, + const nsString& string) { + JSString* str = JS_NewUCStringCopyZ(context, string.get()); + EXPECT_TRUE(str); + return JS::StringValue(str); +} + +static JS::Value CreateStringArray(JSContext* const context, + const std::vector<nsString>& elements) { + Rooted<JSObject*> array(context, + JS::NewArrayObject(context, elements.size())); + EXPECT_TRUE(array); + + for (size_t i = 0; i < elements.size(); ++i) { + Rooted<JS::Value> string(context, CreateStringValue(context, elements[i])); + EXPECT_TRUE(JS_SetElement(context, array, i, string)); + } + + return JS::ObjectValue(*array); +} + +TEST_P(TestWithParam_StringArray, SetFromJSVal) { + const auto& elements = GetParam(); + + AutoTestJSContext context; + Rooted<JS::Value> arrayValue(context, CreateStringArray(context, elements)); + + auto key = Key{}; + const auto result = key.SetFromJSVal(context, arrayValue); + EXPECT_TRUE(result.isOk()); + + ExpectKeyIsArray(key); + + Rooted<JS::Value> rv2(context); + EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2)); + + CheckArray( + context, rv2, elements.size(), + [&elements, &context](const size_t i, JS::Handle<JS::Value> element) { + CheckString(context, elements[i], element); + }); +} + +INSTANTIATE_TEST_SUITE_P( + DOM_IndexedDB_Key, TestWithParam_StringArray, + testing::Values(std::vector<nsString>{u""_ns, u"abc\u0080\u1fff"_ns}, + std::vector<nsString>{u"abc\u0080\u1fff"_ns, + u"abc\u0080\u1fff"_ns})); + +TEST(DOM_IndexedDB_Key, CompareKeys_NonZeroLengthArrayBuffer) +{ + AutoTestJSContext context; + const char buf[] = "abc\x80"; + + auto first = Key{}; + Rooted<JS::Value> arrayBuffer1( + context, CreateArrayBufferValue(context, sizeof buf, strdup(buf))); + const auto result1 = first.SetFromJSVal(context, arrayBuffer1); + EXPECT_TRUE(result1.isOk()); + + auto second = Key{}; + Rooted<JS::Value> arrayBuffer2( + context, CreateArrayBufferValue(context, sizeof buf, strdup(buf))); + const auto result2 = second.SetFromJSVal(context, arrayBuffer2); + EXPECT_TRUE(result2.isOk()); + + EXPECT_EQ(0, Key::CompareKeys(first, second)); +} + +constexpr auto kTestLocale = "e"_ns; + +TEST(DOM_IndexedDB_Key, ToLocaleAwareKey_Empty) +{ + const auto input = Key{}; + + auto res = input.ToLocaleAwareKey(kTestLocale); + EXPECT_TRUE(res.isOk()); + + EXPECT_TRUE(res.inspect().IsUnset()); +} + +TEST(DOM_IndexedDB_Key, ToLocaleAwareKey_Bug_1641598) +{ + const auto buffer = [] { + nsCString res; + // This is the encoded representation produced by the test case from bug + // 1641598. + res.AppendLiteral("\x90\x01\x01\x01\x01\x00\x40"); + for (const size_t unused : IntegerRange<size_t>(256)) { + Unused << unused; + res.AppendLiteral("\x01\x01\x80\x03\x43"); + } + return res; + }(); + const auto input = Key{buffer}; + + auto res = input.ToLocaleAwareKey(kTestLocale); + EXPECT_TRUE(res.isOk()); + + EXPECT_EQ(input, res.inspect()); +} diff --git a/dom/indexedDB/test/gtest/TestSafeRefPtr.cpp b/dom/indexedDB/test/gtest/TestSafeRefPtr.cpp new file mode 100644 index 0000000000..f3536db278 --- /dev/null +++ b/dom/indexedDB/test/gtest/TestSafeRefPtr.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "mozilla/dom/SafeRefPtr.h" +#include "mozilla/RefCounted.h" + +using namespace mozilla; + +class SafeBase : public SafeRefCounted<SafeBase> { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(SafeBase) + + SafeBase() : mDead(false) {} + + static inline int sNumDestroyed; + + ~SafeBase() { + MOZ_RELEASE_ASSERT(!mDead); + mDead = true; + sNumDestroyed++; + } + + private: + bool mDead; +}; +struct SafeDerived : public SafeBase {}; + +class Base : public RefCounted<Base> { + public: + MOZ_DECLARE_REFCOUNTED_TYPENAME(Base) + + Base() : mDead(false) {} + + static inline int sNumDestroyed; + + ~Base() { + MOZ_RELEASE_ASSERT(!mDead); + mDead = true; + sNumDestroyed++; + } + + private: + bool mDead; +}; + +struct Derived : public Base {}; + +already_AddRefed<Base> NewFoo() { + RefPtr<Base> ptr(new Base()); + return ptr.forget(); +} + +already_AddRefed<Base> NewBar() { + RefPtr<Derived> bar = new Derived(); + return bar.forget(); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_Default) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr; + ASSERT_FALSE(ptr); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(0, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromNullPtr) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = nullptr; + ASSERT_FALSE(ptr); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(0, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromMakeSafeRefPtr_SafeRefCounted) +{ + SafeBase::sNumDestroyed = 0; + { + SafeRefPtr<SafeBase> ptr = MakeSafeRefPtr<SafeBase>(); + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + } + ASSERT_EQ(1, SafeBase::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, + Construct_FromMakeSafeRefPtr_SafeRefCounted_DerivedType) +{ + SafeBase::sNumDestroyed = 0; + { + SafeRefPtr<SafeBase> ptr = MakeSafeRefPtr<SafeDerived>(); + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + } + ASSERT_EQ(1, SafeBase::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromMakeSafeRefPtr_RefCounted) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = MakeSafeRefPtr<Base>(); + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromRefPtr) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = SafeRefPtr{MakeRefPtr<Base>()}; + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromRefPtr_DerivedType) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = SafeRefPtr{MakeRefPtr<Derived>()}; + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromAlreadyAddRefed) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr1 = SafeRefPtr{NewFoo()}; + SafeRefPtr<Base> ptr2(NewFoo()); + ASSERT_TRUE(ptr1); + ASSERT_TRUE(ptr2); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(2, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromAlreadyAddRefed_DerivedType) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = SafeRefPtr{NewBar()}; + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromRawPtr) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = SafeRefPtr{new Base(), AcquireStrongRefFromRawPtr{}}; + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Construct_FromRawPtr_Dervied) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr = + SafeRefPtr{new Derived(), AcquireStrongRefFromRawPtr{}}; + ASSERT_TRUE(ptr); + ASSERT_EQ(1u, ptr->refCount()); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, ClonePtr) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr1; + { + ptr1 = MakeSafeRefPtr<Base>(); + const SafeRefPtr<Base> ptr2 = ptr1.clonePtr(); + SafeRefPtr<Base> f3 = ptr2.clonePtr(); + + ASSERT_EQ(3u, ptr1->refCount()); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(1u, ptr1->refCount()); + ASSERT_EQ(0, Base::sNumDestroyed); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Forget) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr1 = MakeSafeRefPtr<Base>(); + SafeRefPtr<Base> ptr2 = SafeRefPtr{ptr1.forget()}; + + ASSERT_FALSE(ptr1); + ASSERT_TRUE(ptr2); + ASSERT_EQ(1u, ptr2->refCount()); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, Downcast) +{ + Base::sNumDestroyed = 0; + { + SafeRefPtr<Base> ptr1 = MakeSafeRefPtr<Derived>(); + SafeRefPtr<Derived> ptr2 = std::move(ptr1).downcast<Derived>(); + + ASSERT_FALSE(ptr1); + ASSERT_TRUE(ptr2); + ASSERT_EQ(1u, ptr2->refCount()); + } + ASSERT_EQ(1, Base::sNumDestroyed); +} + +struct SafeTest final : SafeBase { + template <typename Func> + explicit SafeTest(Func aCallback) { + aCallback(SafeRefPtrFromThis()); + } +}; + +TEST(DOM_IndexedDB_SafeRefPtr, SafeRefPtrFromThis_StoreFromCtor) +{ + SafeBase::sNumDestroyed = 0; + { + SafeRefPtr<SafeBase> ptr1; + { + SafeRefPtr<SafeTest> ptr2 = + MakeSafeRefPtr<SafeTest>([&ptr1](SafeRefPtr<SafeBase> ptr) { + ptr1 = std::move(ptr); + EXPECT_EQ(2u, ptr1->refCount()); + }); + ASSERT_EQ(2u, ptr2->refCount()); + } + ASSERT_EQ(0, SafeBase::sNumDestroyed); + ASSERT_EQ(1u, ptr1->refCount()); + } + ASSERT_EQ(1, SafeBase::sNumDestroyed); +} + +TEST(DOM_IndexedDB_SafeRefPtr, SafeRefPtrFromThis_DiscardInCtor) +{ + SafeBase::sNumDestroyed = 0; + { + SafeRefPtr<SafeTest> ptr = MakeSafeRefPtr<SafeTest>( + [](SafeRefPtr<SafeBase> ptr) { EXPECT_EQ(2u, ptr->refCount()); }); + ASSERT_EQ(1u, ptr->refCount()); + ASSERT_EQ(0, SafeBase::sNumDestroyed); + } + ASSERT_EQ(1, SafeBase::sNumDestroyed); +} + +static_assert( + std::is_same_v<SafeRefPtr<Base>, + decltype(std::declval<bool>() ? MakeSafeRefPtr<Derived>() + : MakeSafeRefPtr<Base>())>); diff --git a/dom/indexedDB/test/gtest/TestSimpleFileInfo.cpp b/dom/indexedDB/test/gtest/TestSimpleFileInfo.cpp new file mode 100644 index 0000000000..8971ee54fb --- /dev/null +++ b/dom/indexedDB/test/gtest/TestSimpleFileInfo.cpp @@ -0,0 +1,278 @@ +/* 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 "FileInfo.h" +#include "FileInfoImpl.h" +#include "FileInfoManager.h" + +#include "gtest/gtest.h" + +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/StaticMutex.h" +#include "nsTArray.h" + +#include <array> + +using namespace mozilla; +using namespace mozilla::dom::indexedDB; + +class SimpleFileManager; + +using SimpleFileInfo = FileInfo<SimpleFileManager>; + +struct SimpleFileManagerStats final { + // XXX We don't keep track of the specific aFileId parameters here, should we? + + size_t mAsyncDeleteFileCalls = 0; + size_t mSyncDeleteFileCalls = 0; +}; + +class SimpleFileManager final : public FileInfoManager<SimpleFileManager>, + public AtomicSafeRefCounted<SimpleFileManager> { + public: + using FileInfoManager<SimpleFileManager>::MutexType; + + MOZ_DECLARE_REFCOUNTED_TYPENAME(SimpleFileManager) + + // SimpleFileManager functions that are used by SimpleFileInfo + + [[nodiscard]] nsresult AsyncDeleteFile(const int64_t aFileId) { + MOZ_RELEASE_ASSERT(!mFileInfos.Contains(aFileId)); + + if (mStats) { + ++mStats->mAsyncDeleteFileCalls; + } + + return NS_OK; + } + + [[nodiscard]] nsresult SyncDeleteFile(const int64_t aFileId) { + MOZ_RELEASE_ASSERT(!mFileInfos.Contains(aFileId)); + + if (mStats) { + ++mStats->mSyncDeleteFileCalls; + } + return NS_OK; + } + + // Test-specific functions + explicit SimpleFileManager(SimpleFileManagerStats* aStats = nullptr) + : mStats{aStats} {} + + void CreateDBOnlyFileInfos() { + for (const auto id : kDBOnlyFileInfoIds) { + // Copied from within DatabaseFileManager::Init. + + mFileInfos.InsertOrUpdate( + id, MakeNotNull<SimpleFileInfo*>(FileInfoManagerGuard{}, + SafeRefPtrFromThis(), id, + static_cast<nsrefcnt>(1))); + + mLastFileId = std::max(id, mLastFileId); + } + } + + static MutexType& Mutex() { return sMutex; } + + static constexpr auto kDBOnlyFileInfoIds = + std::array<int64_t, 3>{{10, 20, 30}}; + + private: + inline static MutexType sMutex; + + SimpleFileManagerStats* const mStats; +}; + +// These tests test the SimpleFileManager itself, to ensure the SimpleFileInfo +// tests below are valid. + +TEST(DOM_IndexedDB_SimpleFileManager, Invalidate) +{ + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(); + + fileManager->Invalidate(); + + ASSERT_TRUE(fileManager->Invalidated()); +} + +// These tests mainly test SimpleFileInfo, which is a simplified version of +// DatabaseFileInfo (SimpleFileInfo doesn't work with real files stored on +// disk). The actual objects, DatabaseFileInfo and DatabaseFileManager are not +// tested here. + +TEST(DOM_IndexedDB_SimpleFileInfo, Create) +{ + auto stats = SimpleFileManagerStats{}; + + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + auto fileInfo = fileManager->CreateFileInfo(); + + int32_t memRefCnt, dbRefCnt; + fileInfo->GetReferences(&memRefCnt, &dbRefCnt); + + ASSERT_EQ(fileManager, &fileInfo->Manager()); + + ASSERT_EQ(1, memRefCnt); + ASSERT_EQ(0, dbRefCnt); + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + ASSERT_EQ(1u, stats.mAsyncDeleteFileCalls); +} + +TEST(DOM_IndexedDB_SimpleFileInfo, CreateWithInitialDBRefCnt) +{ + auto stats = SimpleFileManagerStats{}; + + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + fileManager->CreateDBOnlyFileInfos(); + + for (const auto id : SimpleFileManager::kDBOnlyFileInfoIds) { + const auto fileInfo = fileManager->GetFileInfo(id); + ASSERT_NE(nullptr, fileInfo); + + int32_t memRefCnt, dbRefCnt; + fileInfo->GetReferences(&memRefCnt, &dbRefCnt); + + ASSERT_EQ(fileManager, &fileInfo->Manager()); + + ASSERT_EQ(1, memRefCnt); // we hold one in fileInfo ourselves + ASSERT_EQ(1, dbRefCnt); + } + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + // Since the files have still non-zero dbRefCnt, nothing must be deleted. + ASSERT_EQ(0u, stats.mAsyncDeleteFileCalls); +} + +TEST(DOM_IndexedDB_SimpleFileInfo, CreateWithInitialDBRefCnt_Invalidate) +{ + auto stats = SimpleFileManagerStats{}; + + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + fileManager->CreateDBOnlyFileInfos(); + + const auto fileInfos = TransformIntoNewArray( + SimpleFileManager::kDBOnlyFileInfoIds, + [&fileManager](const auto id) { return fileManager->GetFileInfo(id); }); + + fileManager->Invalidate(); + + for (const auto& fileInfo : fileInfos) { + int32_t memRefCnt, dbRefCnt; + fileInfo->GetReferences(&memRefCnt, &dbRefCnt); + + ASSERT_EQ(1, memRefCnt); // we hold one in fileInfo ourselves + ASSERT_EQ(0, dbRefCnt); // dbRefCnt was cleared by Invalidate + } + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + // Since the files have still non-zero dbRefCnt, nothing must be deleted. + ASSERT_EQ(0u, stats.mAsyncDeleteFileCalls); +} + +TEST(DOM_IndexedDB_SimpleFileInfo, CreateWithInitialDBRefCnt_UpdateDBRefsToZero) +{ + auto stats = SimpleFileManagerStats{}; + + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + fileManager->CreateDBOnlyFileInfos(); + + const auto fileInfo = + fileManager->GetFileInfo(SimpleFileManager::kDBOnlyFileInfoIds[0]); + fileInfo->UpdateDBRefs(-1); + + int32_t memRefCnt, dbRefCnt; + fileInfo->GetReferences(&memRefCnt, &dbRefCnt); + + ASSERT_EQ(1, memRefCnt); // we hold one in fileInfo ourselves + ASSERT_EQ(0, dbRefCnt); + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + ASSERT_EQ(1u, stats.mAsyncDeleteFileCalls); +} + +TEST(DOM_IndexedDB_SimpleFileInfo, ReleaseWithFileManagerCleanup) +{ + auto stats = SimpleFileManagerStats{}; + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + fileManager->CreateDBOnlyFileInfos(); + + auto* fileInfo = fileManager->CreateFileInfo().forget().take(); + fileInfo->Release(/* aSyncDeleteFile */ true); + + // This was the only reference and SimpleFileManager was not invalidated, + // so SimpleFileManager::Cleanup should have been called. + ASSERT_EQ(1u, stats.mSyncDeleteFileCalls); + } + ASSERT_EQ(0u, stats.mAsyncDeleteFileCalls); +} + +#ifndef DEBUG +// These tests cause assertion failures in DEBUG builds. + +TEST(DOM_IndexedDB_SimpleFileInfo, Invalidate_CreateFileInfo) +{ + auto stats = SimpleFileManagerStats{}; + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + + fileManager->Invalidate(); + + const auto fileInfo = fileManager->CreateFileInfo(); + Unused << fileInfo; + + ASSERT_EQ(nullptr, fileInfo); + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + ASSERT_EQ(0u, stats.mAsyncDeleteFileCalls); +} +#endif + +TEST(DOM_IndexedDB_SimpleFileInfo, Invalidate_Release) +{ + auto stats = SimpleFileManagerStats{}; + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + + const auto fileInfo = fileManager->CreateFileInfo(); + Unused << fileInfo; + + fileManager->Invalidate(); + + // SimpleFileManager was invalidated, so Release does not do any cleanup. + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + ASSERT_EQ(0u, stats.mAsyncDeleteFileCalls); +} + +TEST(DOM_IndexedDB_SimpleFileInfo, Invalidate_ReleaseWithFileManagerCleanup) +{ + auto stats = SimpleFileManagerStats{}; + { + const auto fileManager = MakeSafeRefPtr<SimpleFileManager>(&stats); + + auto* fileInfo = fileManager->CreateFileInfo().forget().take(); + + fileManager->Invalidate(); + + // SimpleFileManager was invalidated, so Release does not do any cleanup. + fileInfo->Release(/* aSyncDeleteFile */ true); + } + + ASSERT_EQ(0u, stats.mSyncDeleteFileCalls); + ASSERT_EQ(0u, stats.mAsyncDeleteFileCalls); +} + +// XXX Add test for GetFileForFileInfo diff --git a/dom/indexedDB/test/gtest/moz.build b/dom/indexedDB/test/gtest/moz.build new file mode 100644 index 0000000000..2005a813f3 --- /dev/null +++ b/dom/indexedDB/test/gtest/moz.build @@ -0,0 +1,23 @@ +# 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 = [ + "TestIDBResult.cpp", +] + +# not UNIFIED_SOURCES because TestKey.cpp has classes in an anonymous namespace +# which result in a GCC error when used in tests, cf. gfx/tests/gtest/moz.build +SOURCES = [ + "TestKey.cpp", + "TestSafeRefPtr.cpp", + "TestSimpleFileInfo.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/indexedDB", +] |