summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/test/gtest
diff options
context:
space:
mode:
Diffstat (limited to 'dom/indexedDB/test/gtest')
-rw-r--r--dom/indexedDB/test/gtest/TestIDBResult.cpp40
-rw-r--r--dom/indexedDB/test/gtest/TestKey.cpp433
-rw-r--r--dom/indexedDB/test/gtest/TestSafeRefPtr.cpp279
-rw-r--r--dom/indexedDB/test/gtest/TestSimpleFileInfo.cpp278
-rw-r--r--dom/indexedDB/test/gtest/moz.build23
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",
+]