diff options
Diffstat (limited to 'dom/fs/test/gtest')
20 files changed, 3590 insertions, 0 deletions
diff --git a/dom/fs/test/gtest/FileSystemMocks.cpp b/dom/fs/test/gtest/FileSystemMocks.cpp new file mode 100644 index 0000000000..1ad8e77c56 --- /dev/null +++ b/dom/fs/test/gtest/FileSystemMocks.cpp @@ -0,0 +1,94 @@ +/* -*- 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 "FileSystemMocks.h" + +#include <string> + +#include "ErrorList.h" +#include "gtest/gtest-assertion-result.h" +#include "js/RootingAPI.h" +#include "jsapi.h" +#include "mozilla/dom/FileSystemManager.h" +#include "nsContentUtils.h" +#include "nsISupports.h" + +namespace mozilla::dom::fs::test { + +nsIGlobalObject* GetGlobal() { + AutoJSAPI jsapi; + DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope()); + MOZ_ASSERT(ok); + + JSContext* cx = jsapi.cx(); + mozilla::dom::GlobalObject globalObject(cx, JS::CurrentGlobalOrNull(cx)); + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(globalObject.GetAsSupports()); + MOZ_ASSERT(global); + + return global.get(); +} + +nsresult GetAsString(const RefPtr<Promise>& aPromise, nsAString& aString) { + AutoJSAPI jsapi; + DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope()); + MOZ_ASSERT(ok); + + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSObject*> promiseObj(cx, aPromise->PromiseObj()); + JS::Rooted<JS::Value> vp(cx, JS::GetPromiseResult(promiseObj)); + + switch (aPromise->State()) { + case Promise::PromiseState::Pending: { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + case Promise::PromiseState::Resolved: { + if (nsContentUtils::StringifyJSON(cx, vp, aString, + UndefinedIsNullStringLiteral)) { + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; + } + + case Promise::PromiseState::Rejected: { + if (vp.isInt32()) { + int32_t errorCode = vp.toInt32(); + aString.AppendInt(errorCode); + + return NS_OK; + } + + if (!vp.isObject()) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<Exception> exception; + UNWRAP_OBJECT(Exception, &vp, exception); + if (!exception) { + return NS_ERROR_UNEXPECTED; + } + + aString.Append(NS_ConvertUTF8toUTF16( + GetStaticErrorName(static_cast<nsresult>(exception->Result())))); + + return NS_OK; + } + + default: + break; + } + + return NS_ERROR_FAILURE; +} + +mozilla::ipc::PrincipalInfo GetPrincipalInfo() { + return mozilla::ipc::PrincipalInfo{mozilla::ipc::SystemPrincipalInfo{}}; +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/FileSystemMocks.h b/dom/fs/test/gtest/FileSystemMocks.h new file mode 100644 index 0000000000..867302908b --- /dev/null +++ b/dom/fs/test/gtest/FileSystemMocks.h @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_TEST_GTEST_FILESYSTEMMOCKS_H_ +#define DOM_FS_TEST_GTEST_FILESYSTEMMOCKS_H_ + +#include <memory> // We don't have a mozilla shared pointer for pod types + +#include "TestHelpers.h" +#include "fs/FileSystemChildFactory.h" +#include "fs/FileSystemRequestHandler.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "js/Promise.h" +#include "js/RootingAPI.h" +#include "jsapi.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsIGlobalObject.h" +#include "nsISupports.h" +#include "nsISupportsImpl.h" +#include "nsITimer.h" + +namespace mozilla::dom::fs { + +inline std::ostream& operator<<(std::ostream& aOut, + const FileSystemEntryMetadata& aMetadata) { + return aOut; +} + +namespace test { + +nsIGlobalObject* GetGlobal(); + +nsresult GetAsString(const RefPtr<Promise>& aPromise, nsAString& aString); + +mozilla::ipc::PrincipalInfo GetPrincipalInfo(); + +class MockFileSystemRequestHandler : public FileSystemRequestHandler { + public: + MOCK_METHOD(void, GetRootHandle, + (RefPtr<FileSystemManager> aManager, RefPtr<Promise> aPromise, + ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetDirectoryHandle, + (RefPtr<FileSystemManager> & aManager, + const FileSystemChildMetadata& aDirectory, bool aCreate, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetFileHandle, + (RefPtr<FileSystemManager> & aManager, + const FileSystemChildMetadata& aFile, bool aCreate, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetFile, + (RefPtr<FileSystemManager> & aManager, + const FileSystemEntryMetadata& aFile, RefPtr<Promise> aPromise, + ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetEntries, + (RefPtr<FileSystemManager> & aManager, const EntryId& aDirectory, + PageNumber aPage, RefPtr<Promise> aPromise, + RefPtr<FileSystemEntryMetadataArray>& aSink, + ErrorResult& aError), + (override)); + + MOCK_METHOD(void, RemoveEntry, + (RefPtr<FileSystemManager> & aManager, + const FileSystemChildMetadata& aEntry, bool aRecursive, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, MoveEntry, + (RefPtr<FileSystemManager> & aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, + const FileSystemChildMetadata& aNewEntry, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, RenameEntry, + (RefPtr<FileSystemManager> & aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, const Name& aName, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, Resolve, + (RefPtr<FileSystemManager> & aManager, + const FileSystemEntryPair& aEndpoints, RefPtr<Promise> aPromise, + ErrorResult& aError), + (override)); +}; + +class WaitablePromiseListener { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual void ClearDone() = 0; + + virtual bool IsDone() const = 0; + + virtual PromiseNativeHandler* AsHandler() = 0; + + protected: + virtual ~WaitablePromiseListener() = default; +}; + +template <class SuccessHandler, class ErrorHandler, + uint32_t MilliSeconds = 2000u> +class TestPromiseListener : public PromiseNativeHandler, + public WaitablePromiseListener { + public: + TestPromiseListener() + : mIsDone(std::make_shared<bool>(false)), + mTimer(), + mOnSuccess(), + mOnError() { + ClearDone(); + } + + // nsISupports implementation + + NS_IMETHODIMP QueryInterface(REFNSIID aIID, void** aInstancePtr) override { + nsresult rv = NS_ERROR_UNEXPECTED; + NS_INTERFACE_TABLE0(TestPromiseListener) + + return rv; + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestPromiseListener, override) + + // PromiseNativeHandler implementation + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aError) override { + mozilla::ScopeExit flagAsDone([isDone = mIsDone, timer = mTimer] { + timer->Cancel(); + *isDone = true; + }); + + mOnSuccess(); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aError) override { + mozilla::ScopeExit flagAsDone([isDone = mIsDone, timer = mTimer] { + timer->Cancel(); + *isDone = true; + }); + + if (aValue.isInt32()) { + mOnError(static_cast<nsresult>(aValue.toInt32())); + return; + } + + ASSERT_TRUE(aValue.isObject()); + JS::Rooted<JSObject*> exceptionObject(aCx, &aValue.toObject()); + + RefPtr<Exception> exception; + UNWRAP_OBJECT(Exception, exceptionObject, exception); + if (exception) { + mOnError(static_cast<nsresult>(exception->Result())); + return; + } + } + + // WaitablePromiseListener implementation + + void ClearDone() override { + *mIsDone = false; + if (mTimer) { + mTimer->Cancel(); + } + auto timerCallback = [isDone = mIsDone](nsITimer* aTimer) { + *isDone = true; + FAIL() << "Timed out!"; + }; + const char* timerName = "fs::TestPromiseListener::ClearDone"; + auto res = NS_NewTimerWithCallback(timerCallback, MilliSeconds, + nsITimer::TYPE_ONE_SHOT, timerName); + if (res.isOk()) { + mTimer = res.unwrap(); + } + } + + bool IsDone() const override { return *mIsDone; } + + PromiseNativeHandler* AsHandler() override { return this; } + + SuccessHandler& GetSuccessHandler() { return mOnSuccess; } + + SuccessHandler& GetErrorHandler() { return mOnError; } + + protected: + virtual ~TestPromiseListener() = default; + + std::shared_ptr<bool> mIsDone; // We pass this to a callback + + nsCOMPtr<nsITimer> mTimer; + + SuccessHandler mOnSuccess; + + ErrorHandler mOnError; +}; + +class TestFileSystemManagerChild : public FileSystemManagerChild { + public: + MOCK_METHOD(void, SendGetRootHandle, + (mozilla::ipc::ResolveCallback<FileSystemGetHandleResponse> && + aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetDirectoryHandle, + (const FileSystemGetHandleRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetHandleResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetFileHandle, + (const FileSystemGetHandleRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetHandleResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetAccessHandle, + (const FileSystemGetAccessHandleRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetAccessHandleResponse>&& + aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetWritable, + (const FileSystemGetWritableRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetWritableFileStreamResponse>&& + aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetFile, + (const FileSystemGetFileRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetFileResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendResolve, + (const FileSystemResolveRequest& request, + mozilla::ipc::ResolveCallback<FileSystemResolveResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetEntries, + (const FileSystemGetEntriesRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetEntriesResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendRemoveEntry, + (const FileSystemRemoveEntryRequest& request, + mozilla::ipc::ResolveCallback<FileSystemRemoveEntryResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD(void, Shutdown, (), (override)); + + protected: + virtual ~TestFileSystemManagerChild() = default; +}; + +class TestFileSystemChildFactory final : public FileSystemChildFactory { + public: + explicit TestFileSystemChildFactory(TestFileSystemManagerChild* aChild) + : mChild(aChild) {} + + already_AddRefed<FileSystemManagerChild> Create() const override { + return RefPtr<TestFileSystemManagerChild>(mChild).forget(); + } + + ~TestFileSystemChildFactory() = default; + + private: + TestFileSystemManagerChild* mChild; +}; + +struct MockExpectMe { + MOCK_METHOD0(InvokeMe, void()); + + template <class... Args> + void operator()(Args...) { + InvokeMe(); + } +}; + +template <nsresult Expected> +struct NSErrorMatcher { + void operator()(nsresult aErr) { ASSERT_NSEQ(Expected, aErr); } +}; + +struct FailOnCall { + template <class... Args> + void operator()(Args...) { + FAIL(); + } +}; + +} // namespace test +} // namespace mozilla::dom::fs + +#define MOCK_PROMISE_LISTENER(name, ...) \ + using name = mozilla::dom::fs::test::TestPromiseListener<__VA_ARGS__>; + +MOCK_PROMISE_LISTENER( + ExpectNotImplemented, mozilla::dom::fs::test::FailOnCall, + mozilla::dom::fs::test::NSErrorMatcher<NS_ERROR_NOT_IMPLEMENTED>); + +MOCK_PROMISE_LISTENER(ExpectResolveCalled, mozilla::dom::fs::test::MockExpectMe, + mozilla::dom::fs::test::FailOnCall); + +#endif // DOM_FS_TEST_GTEST_FILESYSTEMMOCKS_H_ diff --git a/dom/fs/test/gtest/TestHelpers.cpp b/dom/fs/test/gtest/TestHelpers.cpp new file mode 100644 index 0000000000..b028530f19 --- /dev/null +++ b/dom/fs/test/gtest/TestHelpers.cpp @@ -0,0 +1,61 @@ +/* -*- 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 "TestHelpers.h" + +#include "gtest/gtest.h" +#include "mozilla/dom/quota/CommonMetadata.h" +#include "nsString.h" + +namespace testing::internal { + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsAString& s1, + const nsAString& s2) { + if (s1.Equals(s2)) { + return ::testing::AssertionSuccess(); + } + + return ::testing::internal::EqFailure( + s1_expression, s2_expression, + std::string(NS_ConvertUTF16toUTF8(s1).get()), + std::string(NS_ConvertUTF16toUTF8(s2).get()), + /* ignore case */ false); +} + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsACString& s1, + const nsACString& s2) { + if (s1.Equals(s2)) { + return ::testing::AssertionSuccess(); + } + + return ::testing::internal::EqFailure(s1_expression, s2_expression, + std::string(s1), std::string(s2), + /* ignore case */ false); +} + +} // namespace testing::internal + +namespace mozilla::dom::fs::test { + +quota::OriginMetadata GetTestOriginMetadata() { + return quota::OriginMetadata{""_ns, + "example.com"_ns, + "http://example.com"_ns, + "http://example.com"_ns, + /* aIsPrivate */ false, + quota::PERSISTENCE_TYPE_DEFAULT}; +} + +const Origin& GetTestOrigin() { + static const Origin origin = "http://example.com"_ns; + return origin; +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/TestHelpers.h b/dom/fs/test/gtest/TestHelpers.h new file mode 100644 index 0000000000..bfbcb9840c --- /dev/null +++ b/dom/fs/test/gtest/TestHelpers.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_TEST_GTEST_TESTHELPERS_H_ +#define DOM_FS_TEST_GTEST_TESTHELPERS_H_ + +#include "ErrorList.h" +#include "gtest/gtest.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/QuotaCommon.h" + +namespace testing::internal { + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsAString& s1, + const nsAString& s2); + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsACString& s1, + const nsACString& s2); + +} // namespace testing::internal + +#define ASSERT_NSEQ(lhs, rhs) \ + ASSERT_STREQ(GetStaticErrorName((lhs)), GetStaticErrorName((rhs))) + +#define TEST_TRY_UNWRAP_META(tempVar, target, expr) \ + auto MOZ_REMOVE_PAREN(tempVar) = (expr); \ + ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isOk()) \ + << GetStaticErrorName( \ + mozilla::ToNSResult(MOZ_REMOVE_PAREN(tempVar).unwrapErr())); \ + MOZ_REMOVE_PAREN(target) = MOZ_REMOVE_PAREN(tempVar).unwrap(); + +#define TEST_TRY_UNWRAP_ERR_META(tempVar, target, expr) \ + auto MOZ_REMOVE_PAREN(tempVar) = (expr); \ + ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isErr()); \ + MOZ_REMOVE_PAREN(target) = \ + mozilla::ToNSResult(MOZ_REMOVE_PAREN(tempVar).unwrapErr()); + +#define TEST_TRY_UNWRAP(target, expr) \ + TEST_TRY_UNWRAP_META(MOZ_UNIQUE_VAR(testVar), target, expr) + +#define TEST_TRY_UNWRAP_ERR(target, expr) \ + TEST_TRY_UNWRAP_ERR_META(MOZ_UNIQUE_VAR(testVar), target, expr) + +namespace mozilla::dom { + +namespace quota { + +struct OriginMetadata; + +} // namespace quota + +namespace fs::test { + +quota::OriginMetadata GetTestOriginMetadata(); + +const Origin& GetTestOrigin(); + +} // namespace fs::test +} // namespace mozilla::dom + +#endif // DOM_FS_TEST_GTEST_TESTHELPERS_H_ diff --git a/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp new file mode 100644 index 0000000000..9114f3bb33 --- /dev/null +++ b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp @@ -0,0 +1,234 @@ +/* -*- 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 "FileSystemMocks.h" +#include "gtest/gtest.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileSystemDirectoryHandle.h" +#include "mozilla/dom/FileSystemDirectoryHandleBinding.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/StorageManager.h" +#include "nsIGlobalObject.h" + +using ::testing::_; + +namespace mozilla::dom::fs::test { + +class TestFileSystemDirectoryHandle : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mRequestHandler = MakeUnique<MockFileSystemRequestHandler>(); + mMetadata = FileSystemEntryMetadata("dir"_ns, u"Directory"_ns, + /* directory */ true); + mName = u"testDir"_ns; + mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + + if (!mManager->IsShutdown()) { + mManager->Shutdown(); + } + } + + nsIGlobalObject* mGlobal = GetGlobal(); + const IterableIteratorBase::IteratorType mIteratorType = + IterableIteratorBase::IteratorType::Keys; + UniquePtr<MockFileSystemRequestHandler> mRequestHandler; + FileSystemEntryMetadata mMetadata; + nsString mName; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemDirectoryHandle, constructDirectoryHandleRefPointer) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); +} + +TEST_F(TestFileSystemDirectoryHandle, initIterator) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + RefPtr<FileSystemDirectoryHandle::iterator_t> iterator = + new FileSystemDirectoryHandle::iterator_t(dirHandle.get(), mIteratorType); + IgnoredErrorResult rv; + dirHandle->InitAsyncIteratorData(iterator->Data(), mIteratorType, rv); + ASSERT_TRUE(iterator->Data().mImpl); +} + +class MockFileSystemDirectoryIteratorImpl final + : public FileSystemDirectoryIterator::Impl { + public: + MOCK_METHOD(already_AddRefed<Promise>, Next, + (nsIGlobalObject * aGlobal, RefPtr<FileSystemManager>& aManager, + ErrorResult& aError), + (override)); +}; + +TEST_F(TestFileSystemDirectoryHandle, isNextPromiseReturned) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + auto mockIter = MakeUnique<MockFileSystemDirectoryIteratorImpl>(); + IgnoredErrorResult error; + EXPECT_CALL(*mockIter, Next(_, _, _)) + .WillOnce(::testing::Return(Promise::Create(mGlobal, error))); + + RefPtr<FileSystemDirectoryHandle::iterator_t> iterator = + MakeAndAddRef<FileSystemDirectoryHandle::iterator_t>(dirHandle.get(), + mIteratorType); + iterator->Data().mImpl = std::move(mockIter); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = + dirHandle->GetNextIterationResult(iterator.get(), rv); + ASSERT_TRUE(promise); +} + +TEST_F(TestFileSystemDirectoryHandle, isHandleKindDirectory) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + ASSERT_EQ(FileSystemHandleKind::Directory, dirHandle->Kind()); +} + +TEST_F(TestFileSystemDirectoryHandle, isFileHandleReturned) { + EXPECT_CALL(*mRequestHandler, GetFileHandle(_, _, _, _, _)) + .WillOnce(::testing::ReturnArg<3>()); + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + FileSystemGetFileOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetFileHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesGetFileHandleFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + FileSystemGetFileOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetFileHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemDirectoryHandle, isDirectoryHandleReturned) { + EXPECT_CALL(*mRequestHandler, GetDirectoryHandle(_, _, _, _, _)) + .WillOnce(::testing::ReturnArg<3>()); + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + FileSystemGetDirectoryOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetDirectoryHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesGetDirectoryHandleFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + FileSystemGetDirectoryOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetDirectoryHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemDirectoryHandle, isRemoveEntrySuccessful) { + EXPECT_CALL(*mRequestHandler, RemoveEntry(_, _, _, _, _)) + .WillOnce(::testing::ReturnArg<3>()); + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + FileSystemRemoveOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->RemoveEntry(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesRemoveEntryFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + FileSystemRemoveOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->RemoveEntry(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemDirectoryHandle, isResolveSuccessful) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->Resolve(*dirHandle, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesResolveFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->Resolve(*dirHandle, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/api/TestFileSystemFileHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemFileHandle.cpp new file mode 100644 index 0000000000..263c1f2ed1 --- /dev/null +++ b/dom/fs/test/gtest/api/TestFileSystemFileHandle.cpp @@ -0,0 +1,144 @@ +/* -*- 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 "FileSystemMocks.h" +#include "fs/FileSystemChildFactory.h" +#include "gtest/gtest.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileSystemFileHandle.h" +#include "mozilla/dom/FileSystemFileHandleBinding.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/StorageManager.h" +#include "nsIGlobalObject.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemFileHandle : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mRequestHandler = MakeUnique<MockFileSystemRequestHandler>(); + mMetadata = + FileSystemEntryMetadata("file"_ns, u"File"_ns, /* directory */ false); + mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + + if (!mManager->IsShutdown()) { + mManager->Shutdown(); + } + } + + nsIGlobalObject* mGlobal = GetGlobal(); + UniquePtr<MockFileSystemRequestHandler> mRequestHandler; + FileSystemEntryMetadata mMetadata; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemFileHandle, constructFileHandleRefPointer) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); +} + +TEST_F(TestFileSystemFileHandle, isHandleKindFile) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + ASSERT_EQ(FileSystemHandleKind::File, fileHandle->Kind()); +} + +TEST_F(TestFileSystemFileHandle, isFileReturned) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->GetFile(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemFileHandle, doesGetFileFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->GetFile(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemFileHandle, isWritableReturned) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + FileSystemCreateWritableOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateWritable(options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemFileHandle, doesCreateWritableFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + FileSystemCreateWritableOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateWritable(options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemFileHandle, isSyncAccessHandleReturned) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateSyncAccessHandle(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemFileHandle, doesCreateSyncAccessHandleFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateSyncAccessHandle(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/api/TestFileSystemHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemHandle.cpp new file mode 100644 index 0000000000..19cdc98a84 --- /dev/null +++ b/dom/fs/test/gtest/api/TestFileSystemHandle.cpp @@ -0,0 +1,131 @@ +/* -*- 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 "FileSystemMocks.h" +#include "fs/FileSystemChildFactory.h" +#include "gtest/gtest.h" +#include "mozilla/dom/FileSystemDirectoryHandle.h" +#include "mozilla/dom/FileSystemFileHandle.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/StorageManager.h" +#include "nsIGlobalObject.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemHandle : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mDirMetadata = FileSystemEntryMetadata("dir"_ns, u"Directory"_ns, + /* directory */ true); + mFileMetadata = + FileSystemEntryMetadata("file"_ns, u"File"_ns, /* directory */ false); + mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + + if (!mManager->IsShutdown()) { + mManager->Shutdown(); + } + } + + nsIGlobalObject* mGlobal = GetGlobal(); + FileSystemEntryMetadata mDirMetadata; + FileSystemEntryMetadata mFileMetadata; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemHandle, createAndDestroyHandles) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + EXPECT_TRUE(dirHandle); + EXPECT_TRUE(fileHandle); +} + +TEST_F(TestFileSystemHandle, areFileNamesAsExpected) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + auto GetEntryName = [](const RefPtr<FileSystemHandle>& aHandle) { + DOMString domName; + aHandle->GetName(domName); + nsString result; + domName.ToString(result); + return result; + }; + + const nsString& dirName = GetEntryName(dirHandle); + EXPECT_TRUE(mDirMetadata.entryName().Equals(dirName)); + + const nsString& fileName = GetEntryName(fileHandle); + EXPECT_TRUE(mFileMetadata.entryName().Equals(fileName)); +} + +TEST_F(TestFileSystemHandle, isParentObjectReturned) { + ASSERT_TRUE(mGlobal); + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + + ASSERT_EQ(mGlobal, dirHandle->GetParentObject()); +} + +TEST_F(TestFileSystemHandle, areHandleKindsAsExpected) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + EXPECT_EQ(FileSystemHandleKind::Directory, dirHandle->Kind()); + EXPECT_EQ(FileSystemHandleKind::File, fileHandle->Kind()); +} + +TEST_F(TestFileSystemHandle, isDifferentEntry) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->IsSameEntry(*fileHandle, rv); + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); + ASSERT_TRUE(promise); + ASSERT_EQ(Promise::PromiseState::Resolved, promise->State()); + + nsString result; + ASSERT_NSEQ(NS_OK, GetAsString(promise, result)); + ASSERT_STREQ(u"false"_ns, result); +} + +TEST_F(TestFileSystemHandle, isSameEntry) { + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->IsSameEntry(*fileHandle, rv); + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); + ASSERT_TRUE(promise); + ASSERT_EQ(Promise::PromiseState::Resolved, promise->State()); + + nsString result; + ASSERT_NSEQ(NS_OK, GetAsString(promise, result)); + ASSERT_STREQ(u"true"_ns, result); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/api/moz.build b/dom/fs/test/gtest/api/moz.build new file mode 100644 index 0000000000..eb8416a3ba --- /dev/null +++ b/dom/fs/test/gtest/api/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 = [ + "TestFileSystemDirectoryHandle.cpp", + "TestFileSystemFileHandle.cpp", + "TestFileSystemHandle.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/api", + "/dom/fs/include", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/gtest/child/TestFileSystemBackgroundRequestHandler.cpp b/dom/fs/test/gtest/child/TestFileSystemBackgroundRequestHandler.cpp new file mode 100644 index 0000000000..48d63cfc36 --- /dev/null +++ b/dom/fs/test/gtest/child/TestFileSystemBackgroundRequestHandler.cpp @@ -0,0 +1,64 @@ +/* -*- 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 "FileSystemBackgroundRequestHandler.h" +#include "FileSystemMocks.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/PFileSystemManager.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemBackgroundRequestHandler : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mFileSystemManagerChild = MakeAndAddRef<TestFileSystemManagerChild>(); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + } + + RefPtr<FileSystemBackgroundRequestHandler> + GetFileSystemBackgroundRequestHandler() { + return MakeRefPtr<FileSystemBackgroundRequestHandler>( + new TestFileSystemChildFactory(mFileSystemManagerChild)); + } + + mozilla::ipc::PrincipalInfo mPrincipalInfo = GetPrincipalInfo(); + RefPtr<TestFileSystemManagerChild> mFileSystemManagerChild; +}; + +TEST_F(TestFileSystemBackgroundRequestHandler, + isCreateFileSystemManagerChildSuccessful) { + EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) + .WillOnce([fileSystemManagerChild = + static_cast<void*>(mFileSystemManagerChild.get())]() { + static_cast<TestFileSystemManagerChild*>(fileSystemManagerChild) + ->FileSystemManagerChild::Shutdown(); + }); + + bool done = false; + auto testable = GetFileSystemBackgroundRequestHandler(); + testable->CreateFileSystemManagerChild(mPrincipalInfo) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](bool) { done = true; }, [&done](nsresult) { done = true; }); + // MozPromise should be rejected + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [&done]() { return done; }); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp b/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp new file mode 100644 index 0000000000..c2832103af --- /dev/null +++ b/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp @@ -0,0 +1,352 @@ +/* -*- 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 "FileSystemBackgroundRequestHandler.h" +#include "FileSystemEntryMetadataArray.h" +#include "FileSystemMocks.h" +#include "fs/FileSystemRequestHandler.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/IPCBlob.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/IPCCore.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" + +using ::testing::_; +using ::testing::ByRef; +using ::testing::Invoke; +using ::testing::Return; + +namespace mozilla::dom::fs::test { + +class TestFileSystemRequestHandler : public ::testing::Test { + protected: + void SetUp() override { + mListener = MakeAndAddRef<ExpectResolveCalled>(); + + mChild = FileSystemChildMetadata("parent"_ns, u"ChildName"_ns); + mEntry = FileSystemEntryMetadata("myid"_ns, u"EntryName"_ns, + /* directory */ false); + mName = u"testDir"_ns; + mFileSystemManagerChild = MakeAndAddRef<TestFileSystemManagerChild>(); + mManager = MakeAndAddRef<FileSystemManager>( + mGlobal, nullptr, + MakeRefPtr<FileSystemBackgroundRequestHandler>( + mFileSystemManagerChild)); + } + + void TearDown() override { + if (!mManager->IsShutdown()) { + EXPECT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + } + } + + already_AddRefed<Promise> GetDefaultPromise() { + IgnoredErrorResult rv; + RefPtr<Promise> result = Promise::Create(mGlobal, rv); + mListener->ClearDone(); + result->AppendNativeHandler(mListener->AsHandler()); + + return result.forget(); + } + + already_AddRefed<Promise> GetSimplePromise() { + IgnoredErrorResult rv; + RefPtr<Promise> result = Promise::Create(mGlobal, rv); + + return result.forget(); + } + + already_AddRefed<Promise> GetShutdownPromise() { + RefPtr<Promise> promise = GetDefaultPromise(); + EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) + .WillOnce(Invoke([promise]() { promise->MaybeResolveWithUndefined(); })) + .WillOnce(Return()); + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + + return promise.forget(); + } + + UniquePtr<FileSystemRequestHandler> GetFileSystemRequestHandler() { + return MakeUnique<FileSystemRequestHandler>(); + } + + void ShutdownFileSystemManager() { + RefPtr<Promise> promise = GetShutdownPromise(); + + mManager->Shutdown(); + + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); + ASSERT_TRUE(mManager->IsShutdown()); + } + + nsIGlobalObject* mGlobal = GetGlobal(); + RefPtr<ExpectResolveCalled> mListener; + + FileSystemChildMetadata mChild; + FileSystemEntryMetadata mEntry; + nsString mName; + RefPtr<TestFileSystemManagerChild> mFileSystemManagerChild; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemRequestHandler, isGetRootHandleSuccessful) { + auto fakeResponse = [](auto&& aResolve, auto&& /* aReject */) { + EntryId expected = "expected"_ns; + FileSystemGetHandleResponse response(expected); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetRootHandle(_, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetRootHandle(mManager, promise, IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetRootHandleBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetRootHandle(mManager, GetSimplePromise(), + error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetDirectoryHandleSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + EntryId expected = "expected"_ns; + FileSystemGetHandleResponse response(expected); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetDirectoryHandle(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetDirectoryHandle(mManager, mChild, + /* create */ true, promise, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetDirectoryHandleBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetDirectoryHandle( + mManager, mChild, /* aCreate */ true, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileHandleSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + EntryId expected = "expected"_ns; + FileSystemGetHandleResponse response(expected); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetFileHandle(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetFileHandle(mManager, mChild, /* create */ true, promise, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileHandleBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetFileHandle( + mManager, mChild, /* aCreate */ true, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + // We have to create a temporary file + nsCOMPtr<nsIFile> tmpfile; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpfile)); + ASSERT_EQ(NS_SUCCEEDED(rv), true); + + rv = tmpfile->AppendNative("GetFileTestBlob"_ns); + ASSERT_EQ(NS_SUCCEEDED(rv), true); + + rv = tmpfile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); + ASSERT_EQ(NS_SUCCEEDED(rv), true); + + auto blob = MakeRefPtr<FileBlobImpl>(tmpfile); + + TimeStamp last_modified_ms = 0; + ContentType type = "txt"_ns; + IPCBlob file; + IPCBlobUtils::Serialize(blob, file); + + nsTArray<Name> path; + path.AppendElement(u"root"_ns); + path.AppendElement(u"Trash"_ns); + + FileSystemFileProperties properties(last_modified_ms, file, type, path); + FileSystemGetFileResponse response(properties); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetFile(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetFile(mManager, mEntry, promise, IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetFile(mManager, mEntry, GetSimplePromise(), + error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetAccessHandleBlockedAfterShutdown) { + RefPtr<Promise> promise = GetShutdownPromise(); + + mManager->Shutdown(); + + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); + ASSERT_TRUE(mManager->IsShutdown()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetAccessHandle(mManager, mEntry, + GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetWritableBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetWritable( + mManager, mEntry, /* aKeepData */ false, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetEntriesSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + nsTArray<FileSystemEntryMetadata> files; + nsTArray<FileSystemEntryMetadata> directories; + FileSystemDirectoryListing listing(files, directories); + FileSystemGetEntriesResponse response(listing); + aResolve(std::move(response)); + }; + + RefPtr<ExpectResolveCalled> listener = MakeAndAddRef<ExpectResolveCalled>(); + IgnoredErrorResult rv; + listener->ClearDone(); + EXPECT_CALL(listener->GetSuccessHandler(), InvokeMe()); + + RefPtr<Promise> promise = Promise::Create(mGlobal, rv); + promise->AppendNativeHandler(listener); + + EXPECT_CALL(*mFileSystemManagerChild, SendGetEntries(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + auto testable = GetFileSystemRequestHandler(); + RefPtr<FileSystemEntryMetadataArray> sink; + + testable->GetEntries(mManager, mEntry.entryId(), /* page */ 0, promise, sink, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [listener]() { return listener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetEntriesBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + RefPtr<FileSystemEntryMetadataArray> sink; + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetEntries(mManager, mEntry.entryId(), + /* aPage */ 0, GetSimplePromise(), + sink, error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isRemoveEntrySuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + FileSystemRemoveEntryResponse response(mozilla::void_t{}); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendRemoveEntry(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + auto testable = GetFileSystemRequestHandler(); + RefPtr<Promise> promise = GetDefaultPromise(); + testable->RemoveEntry(mManager, mChild, /* recursive */ true, promise, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isRemoveEntryBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->RemoveEntry( + mManager, mChild, /* aRecursive */ true, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/child/moz.build b/dom/fs/test/gtest/child/moz.build new file mode 100644 index 0000000000..c305ab1f2e --- /dev/null +++ b/dom/fs/test/gtest/child/moz.build @@ -0,0 +1,20 @@ +# -*- 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 = [ + "TestFileSystemBackgroundRequestHandler.cpp", + "TestFileSystemRequestHandler.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/child", + "/dom/fs/include", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/gtest/moz.build b/dom/fs/test/gtest/moz.build new file mode 100644 index 0000000000..81be2a3d33 --- /dev/null +++ b/dom/fs/test/gtest/moz.build @@ -0,0 +1,25 @@ +# -*- 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/. + +TEST_DIRS += [ + "api", + "child", + "parent", + "shared", +] + +UNIFIED_SOURCES = [ + "FileSystemMocks.cpp", + "TestHelpers.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/include", +] diff --git a/dom/fs/test/gtest/parent/TestFileSystemHashSource.cpp b/dom/fs/test/gtest/parent/TestFileSystemHashSource.cpp new file mode 100644 index 0000000000..2a680838a8 --- /dev/null +++ b/dom/fs/test/gtest/parent/TestFileSystemHashSource.cpp @@ -0,0 +1,179 @@ +/* -*- 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 "FileSystemHashSource.h" +#include "TestHelpers.h" +#include "gtest/gtest.h" +#include "mozilla/Array.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "nsContentUtils.h" +#include "nsLiteralString.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsTHashSet.h" + +namespace mozilla::dom::fs::test { + +using mozilla::dom::fs::data::FileSystemHashSource; + +namespace { + +constexpr size_t sha256ByteLength = 32u; + +constexpr size_t kExpectedLength = 52u; + +std::wstring asWide(const nsString& aStr) { + std::wstring result; + result.reserve(aStr.Length()); + for (const auto* it = aStr.BeginReading(); it != aStr.EndReading(); ++it) { + result.push_back(static_cast<wchar_t>(*it)); + } + return result; +} + +} // namespace + +TEST(TestFileSystemHashSource, isHashLengthAsExpected) +{ + EntryId parent = "a"_ns; + Name name = u"b"_ns; + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(parent, name)); + ASSERT_EQ(sha256ByteLength, result.Length()); +}; + +TEST(TestFileSystemHashSource, areNestedNameHashesValidAndUnequal) +{ + EntryId emptyParent = ""_ns; + Name name = u"a"_ns; + const size_t nestingNumber = 500u; + + nsTHashSet<EntryId> results; + nsTHashSet<Name> names; + + auto previousParent = emptyParent; + for (size_t i = 0; i < nestingNumber; ++i) { + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(previousParent, name)); + + TEST_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(result)); + + // Validity checks + ASSERT_TRUE(mozilla::IsAscii(encoded)) + << encoded; + Name upperCaseVersion; + nsContentUtils::ASCIIToUpper(encoded, upperCaseVersion); + ASSERT_STREQ(asWide(upperCaseVersion).c_str(), asWide(encoded).c_str()); + + // Is the same hash encountered? + ASSERT_FALSE(results.Contains(result)); + ASSERT_TRUE(results.Insert(result, mozilla::fallible)); + + // Is the same name encountered? + ASSERT_FALSE(names.Contains(encoded)); + ASSERT_TRUE(names.Insert(encoded, mozilla::fallible)); + + previousParent = result; + } +}; + +TEST(TestFileSystemHashSource, areNameCombinationHashesUnequal) +{ + EntryId emptyParent = ""_ns; + + mozilla::Array<Name, 2> inputs = {u"a"_ns, u"b"_ns}; + nsTArray<EntryId> results; + nsTArray<Name> names; + + for (const auto& name : inputs) { + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(emptyParent, name)); + TEST_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(result)); + + // Validity checks + ASSERT_TRUE(mozilla::IsAscii(encoded)) + << encoded; + Name upperCaseVersion; + nsContentUtils::ASCIIToUpper(encoded, upperCaseVersion); + ASSERT_STREQ(asWide(upperCaseVersion).c_str(), asWide(encoded).c_str()); + + results.AppendElement(result); + names.AppendElement(encoded); + } + + nsTArray<EntryId> more_results; + nsTArray<Name> more_names; + for (const auto& parent : results) { + for (const auto& name : inputs) { + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(parent, name)); + TEST_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(result)); + + // Validity checks + ASSERT_TRUE(mozilla::IsAscii(encoded)) + << encoded; + Name upperCaseVersion; + nsContentUtils::ASCIIToUpper(encoded, upperCaseVersion); + ASSERT_STREQ(asWide(upperCaseVersion).c_str(), asWide(encoded).c_str()); + + more_results.AppendElement(result); + more_names.AppendElement(encoded); + } + } + + results.AppendElements(more_results); + names.AppendElements(more_names); + + // Is the same hash encountered? + for (size_t i = 0; i < results.Length(); ++i) { + for (size_t j = i + 1; j < results.Length(); ++j) { + ASSERT_STRNE(results[i].get(), results[j].get()); + } + } + + // Is the same name encountered? + for (size_t i = 0; i < names.Length(); ++i) { + for (size_t j = i + 1; j < names.Length(); ++j) { + ASSERT_STRNE(asWide(names[i]).c_str(), asWide(names[j]).c_str()); + } + } +}; + +TEST(TestFileSystemHashSource, encodeGeneratedHash) +{ + Name expected = u"HF6FOFV72G3NMDEJKYMVRIFJO4X5ZNZCF2GM7Q4Y5Q3E7NPQKSLA"_ns; + ASSERT_EQ(kExpectedLength, expected.Length()); + + EntryId parent = "a"_ns; + Name name = u"b"_ns; + TEST_TRY_UNWRAP(EntryId entry, + FileSystemHashSource::GenerateHash(parent, name)); + ASSERT_EQ(sha256ByteLength, entry.Length()); + + TEST_TRY_UNWRAP(Name result, FileSystemHashSource::EncodeHash(entry)); + ASSERT_EQ(kExpectedLength, result.Length()); + ASSERT_STREQ(asWide(expected).c_str(), asWide(result).c_str()); + + // Generate further hashes + TEST_TRY_UNWRAP(entry, FileSystemHashSource::GenerateHash(entry, result)); + ASSERT_EQ(sha256ByteLength, entry.Length()); + + TEST_TRY_UNWRAP(result, FileSystemHashSource::EncodeHash(entry)); + + // Always the same length + ASSERT_EQ(kExpectedLength, result.Length()); + + // Encoded versions should differ + ASSERT_STRNE(asWide(expected).c_str(), asWide(result).c_str()); + + // Padding length should have been stripped + char16_t padding = u"="_ns[0]; + const int32_t paddingStart = result.FindChar(padding); + ASSERT_EQ(-1, paddingStart); +}; + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/TestFileSystemQuotaClient.cpp b/dom/fs/test/gtest/parent/TestFileSystemQuotaClient.cpp new file mode 100644 index 0000000000..ee3347f973 --- /dev/null +++ b/dom/fs/test/gtest/parent/TestFileSystemQuotaClient.cpp @@ -0,0 +1,512 @@ +/* -*- 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 "FileSystemQuotaClient.h" +#include "TestHelpers.h" +#include "datamodel/FileSystemDataManager.h" +#include "datamodel/FileSystemDatabaseManager.h" +#include "datamodel/FileSystemFileManager.h" +#include "gtest/gtest.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/quota/FileStreams.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/dom/quota/UsageInfo.h" +#include "mozilla/dom/quota/test/QuotaManagerDependencyFixture.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIQuotaCallbacks.h" +#include "nsIQuotaRequests.h" +#include "nsNetUtil.h" +#include "nsScriptSecurityManager.h" + +namespace mozilla::dom::fs::test { + +quota::OriginMetadata GetTestQuotaOriginMetadata() { + return quota::OriginMetadata{""_ns, + "quotaexample.com"_ns, + "http://quotaexample.com"_ns, + "http://quotaexample.com"_ns, + /* aIsPrivate */ false, + quota::PERSISTENCE_TYPE_DEFAULT}; +} + +class TestFileSystemQuotaClient + : public quota::test::QuotaManagerDependencyFixture { + public: + // ExceedsPreallocation value may depend on platform and sqlite version! + static const int sExceedsPreallocation = 32 * 1024; + + protected: + void SetUp() override { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); } + + void TearDown() override { + EXPECT_NO_FATAL_FAILURE( + ClearStoragesForOrigin(GetTestQuotaOriginMetadata())); + ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); + } + + static void InitializeStorage() { + auto backgroundTask = []() { + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + NS_DispatchAndSpinEventLoopUntilComplete( + "TestFileSystemQuotaClient"_ns, quotaManager->IOThread(), + NS_NewRunnableFunction("TestFileSystemQuotaClient", []() { + quota::QuotaManager* qm = quota::QuotaManager::Get(); + ASSERT_TRUE(qm); + + ASSERT_NSEQ(NS_OK, qm->EnsureStorageIsInitialized()); + + ASSERT_NSEQ(NS_OK, qm->EnsureTemporaryStorageIsInitialized()); + + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + + auto dirInfoRes = qm->EnsureTemporaryOriginIsInitialized( + quota::PERSISTENCE_TYPE_DEFAULT, testOriginMeta); + if (dirInfoRes.isErr()) { + ASSERT_NSEQ(NS_OK, dirInfoRes.unwrapErr()); + } + + qm->EnsureQuotaForOrigin(testOriginMeta); + })); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); + } + + static const Name& GetTestFileName() { + static Name testFileName = []() { + nsCString testCFileName; + testCFileName.SetLength(sExceedsPreallocation); + std::fill(testCFileName.BeginWriting(), testCFileName.EndWriting(), 'x'); + return NS_ConvertASCIItoUTF16(testCFileName.BeginReading(), + sExceedsPreallocation); + }(); + + return testFileName; + } + + static uint64_t BytesOfName(const Name& aName) { + return static_cast<uint64_t>(aName.Length() * sizeof(Name::char_type)); + } + + static const nsCString& GetTestData() { + static const nsCString sTestData = "There is a way out of every box"_ns; + return sTestData; + } + + static void CreateNewEmptyFile( + data::FileSystemDatabaseManager* const aDatabaseManager, + const FileSystemChildMetadata& aFileSlot, EntryId& aFileId) { + // The file should not exist yet + Result<EntryId, QMResult> existingTestFile = + aDatabaseManager->GetOrCreateFile(aFileSlot, sContentType, + /* create */ false); + ASSERT_TRUE(existingTestFile.isErr()); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, + ToNSResult(existingTestFile.unwrapErr())); + + // Create a new file + TEST_TRY_UNWRAP(aFileId, aDatabaseManager->GetOrCreateFile( + aFileSlot, sContentType, /* create */ true)); + } + + static void WriteDataToFile( + data::FileSystemDatabaseManager* const aDatabaseManager, + const EntryId& aFileId, const nsCString& aData) { + ContentType type; + TimeStamp lastModMilliS = 0; + Path path; + nsCOMPtr<nsIFile> fileObj; + + ASSERT_NSEQ(NS_OK, aDatabaseManager->GetFile(aFileId, type, lastModMilliS, + path, fileObj)); + + uint32_t written = 0; + ASSERT_NE(written, aData.Length()); + + const quota::OriginMetadata& testOriginMeta = GetTestQuotaOriginMetadata(); + + TEST_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileStream, + quota::CreateFileOutputStream( + quota::PERSISTENCE_TYPE_DEFAULT, testOriginMeta, + quota::Client::FILESYSTEM, fileObj)); + + auto finallyClose = MakeScopeExit( + [&fileStream]() { ASSERT_NSEQ(NS_OK, fileStream->Close()); }); + ASSERT_NSEQ(NS_OK, + fileStream->Write(aData.get(), aData.Length(), &written)); + + ASSERT_EQ(aData.Length(), written); + } + + /* Static for use in callbacks */ + static void CreateRegisteredDataManager( + Registered<data::FileSystemDataManager>& aRegisteredDataManager) { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestQuotaOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&aRegisteredDataManager, + &done](Registered<data::FileSystemDataManager> + registeredDataManager) mutable { + auto doneOnReturn = MakeScopeExit([&done]() { done = true; }); + + ASSERT_TRUE(registeredDataManager->IsOpen()); + aRegisteredDataManager = std::move(registeredDataManager); + }, + [&done](nsresult rejectValue) { + auto doneOnReturn = MakeScopeExit([&done]() { done = true; }); + + ASSERT_NSEQ(NS_OK, rejectValue); + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + ASSERT_TRUE(aRegisteredDataManager); + ASSERT_TRUE(aRegisteredDataManager->IsOpen()); + ASSERT_TRUE(aRegisteredDataManager->MutableDatabaseManagerPtr()); + } + + static void CheckUsageEqualTo(const quota::UsageInfo& aUsage, + uint64_t expected) { + EXPECT_TRUE(aUsage.FileUsage().isNothing()); + auto dbUsage = aUsage.DatabaseUsage(); + ASSERT_TRUE(dbUsage.isSome()); + const auto actual = dbUsage.value(); + ASSERT_EQ(actual, expected); + } + + static void CheckUsageGreaterThan(const quota::UsageInfo& aUsage, + uint64_t expected) { + EXPECT_TRUE(aUsage.FileUsage().isNothing()); + auto dbUsage = aUsage.DatabaseUsage(); + ASSERT_TRUE(dbUsage.isSome()); + const auto actual = dbUsage.value(); + ASSERT_GT(actual, expected); + } + + static ContentType sContentType; +}; + +ContentType TestFileSystemQuotaClient::sContentType; + +TEST_F(TestFileSystemQuotaClient, CheckUsageBeforeAnyFilesOnDisk) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + auto ioTask = [&isCanceled](const RefPtr<quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + ASSERT_FALSE(isCanceled); + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + const Origin& testOrigin = testOriginMeta.mOrigin; + + // After initialization, + // * database size is not zero + // * GetUsageForOrigin and InitOrigin should agree + TEST_TRY_UNWRAP(quota::UsageInfo usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageGreaterThan(usageNow, 0u)); + const auto initialDbUsage = usageNow.DatabaseUsage().value(); + + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, initialDbUsage)); + + // Create a new file + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId testFileId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, testFileId)); + + // After a new file has been created (only in the database), + // * database size has increased + // * GetUsageForOrigin and InitOrigin should agree + const auto expectedUse = initialDbUsage + BytesOfName(GetTestFileName()); + + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedUse)); + + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedUse)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // For uninitialized database, file usage is nothing + auto checkTask = + [&isCanceled](const RefPtr<mozilla::dom::quota::Client>& quotaClient) { + TEST_TRY_UNWRAP(quota::UsageInfo usageNow, + quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + GetTestQuotaOriginMetadata(), isCanceled)); + + ASSERT_TRUE(usageNow.DatabaseUsage().isNothing()); + EXPECT_TRUE(usageNow.FileUsage().isNothing()); + }; + + PerformOnIOThread(std::move(checkTask), + RefPtr<mozilla::dom::quota::Client>{quotaClient}); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + // Run tests with an initialized database + PerformOnIOThread(std::move(ioTask), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemQuotaClient, WritesToFilesShouldIncreaseUsage) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + auto ioTask = [&isCanceled]( + const RefPtr<mozilla::dom::quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + const Origin& testOrigin = testOriginMeta.mOrigin; + + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId testFileId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, testFileId)); + // const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + TEST_TRY_UNWRAP( + quota::UsageInfo usageNow, + quotaClient->GetUsageForOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_TRUE(usageNow.DatabaseUsage().isSome()); + const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + + // Fill the file with some content + const nsCString& testData = GetTestData(); + const auto expectedTotalUsage = testFileDbUsage + testData.Length(); + + ASSERT_NO_FATAL_FAILURE(WriteDataToFile(dbm, testFileId, testData)); + + // In this test we don't lock the file -> no rescan is expected + // and InitOrigin should return the previous value + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + + // When data manager unlocks the file, it should call update + // but in this test we call it directly + ASSERT_NSEQ(NS_OK, dbm->UpdateUsage(testFileId)); + + // Disk usage should have increased after writing + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedTotalUsage)); + + // The usage values should now agree + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedTotalUsage)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // Storage initialization + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + // Run tests with an initialized database + PerformOnIOThread(std::move(ioTask), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemQuotaClient, TrackedFilesOnInitOriginShouldCauseRescan) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + EntryId* testFileId = new EntryId(); + auto cleanupFileId = MakeScopeExit([&testFileId] { delete testFileId; }); + + auto fileCreation = [&testFileId](data::FileSystemDatabaseManager* dbm) { + const Origin& testOrigin = GetTestQuotaOriginMetadata().mOrigin; + + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId someId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, someId)); + testFileId->Append(someId); + }; + + auto writingToFile = + [&isCanceled, testFileId]( + const RefPtr<mozilla::dom::quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + TEST_TRY_UNWRAP( + quota::UsageInfo usageNow, + quotaClient->GetUsageForOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_TRUE(usageNow.DatabaseUsage().isSome()); + const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + // Fill the file with some content + const auto& testData = GetTestData(); + const auto expectedTotalUsage = testFileDbUsage + testData.Length(); + + ASSERT_NO_FATAL_FAILURE(WriteDataToFile(dbm, *testFileId, testData)); + + // We don't call update now - because the file is tracked and + // InitOrigin should perform a rescan + TEST_TRY_UNWRAP( + usageNow, quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE( + CheckUsageEqualTo(usageNow, expectedTotalUsage)); + + // As always, the cached and scanned values should agree + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE( + CheckUsageEqualTo(usageNow, expectedTotalUsage)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // Storage initialization + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + PerformOnIOThread(std::move(fileCreation), + rdm->MutableDatabaseManagerPtr()); + + // This should force a rescan + ASSERT_NSEQ(NS_OK, rdm->LockExclusive(*testFileId)); + PerformOnIOThread(std::move(writingToFile), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemQuotaClient, RemovingFileShouldDecreaseUsage) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + auto ioTask = [&isCanceled]( + const RefPtr<mozilla::dom::quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + const Origin& testOrigin = testOriginMeta.mOrigin; + + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId testFileId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, testFileId)); + TEST_TRY_UNWRAP(quota::UsageInfo usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_TRUE(usageNow.DatabaseUsage().isSome()); + const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + // Fill the file with some content + const nsCString& testData = GetTestData(); + const auto expectedTotalUsage = testFileDbUsage + testData.Length(); + + ASSERT_NO_FATAL_FAILURE(WriteDataToFile(dbm, testFileId, testData)); + + // Currently, usage is expected to be updated on unlock by data manager + // but here UpdateUsage() is called directly + ASSERT_NSEQ(NS_OK, dbm->UpdateUsage(testFileId)); + + // At least some file disk usage should have appeared after unlocking + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedTotalUsage)); + + TEST_TRY_UNWRAP(bool wasRemoved, + dbm->RemoveFile({rootId, GetTestFileName()})); + ASSERT_TRUE(wasRemoved); + + // Removes cascade and usage table should be up to date immediately + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + + // GetUsageForOrigin should agree + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // Storage initialization + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + // Run tests with an initialized database + PerformOnIOThread(std::move(ioTask), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManager.cpp b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManager.cpp new file mode 100644 index 0000000000..3082508540 --- /dev/null +++ b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManager.cpp @@ -0,0 +1,182 @@ +/* -*- 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 "FileSystemDataManager.h" +#include "TestHelpers.h" +#include "mozilla/MozPromise.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "mozilla/dom/quota/test/QuotaManagerDependencyFixture.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemDataManager + : public quota::test::QuotaManagerDependencyFixture { + public: + static void SetUpTestCase() { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); } + + static void TearDownTestCase() { ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); } +}; + +TEST_F(TestFileSystemDataManager, GetOrCreateFileSystemDataManager) { + auto backgroundTask = []() { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](Registered<data::FileSystemDataManager> registeredDataManager) { + RefPtr<data::FileSystemDataManager> dataManager = + registeredDataManager.get(); + + registeredDataManager = nullptr; + + return dataManager->OnClose(); + }, + [](nsresult rejectValue) { + return BoolPromise::CreateAndReject(rejectValue, __func__); + }) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue&) { done = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemDataManager, + GetOrCreateFileSystemDataManager_PendingOpen) { + auto backgroundTask = []() { + Registered<data::FileSystemDataManager> rdm1; + + Registered<data::FileSystemDataManager> rdm2; + + { + bool done1 = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&rdm1, &done1](Registered<data::FileSystemDataManager> + registeredDataManager) { + ASSERT_TRUE(registeredDataManager->IsOpen()); + + rdm1 = std::move(registeredDataManager); + + done1 = true; + }, + [&done1](nsresult rejectValue) { done1 = true; }); + + bool done2 = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&rdm2, &done2](Registered<data::FileSystemDataManager> + registeredDataManager) { + ASSERT_TRUE(registeredDataManager->IsOpen()); + + rdm2 = std::move(registeredDataManager); + + done2 = true; + }, + [&done2](nsresult rejectValue) { done2 = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, + [&done1, &done2]() { return done1 && done2; }); + } + + RefPtr<data::FileSystemDataManager> dm1 = rdm1.unwrap(); + + RefPtr<data::FileSystemDataManager> dm2 = rdm2.unwrap(); + + { + bool done1 = false; + + dm1->OnClose()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done1](const BoolPromise::ResolveOrRejectValue&) { done1 = true; }); + + bool done2 = false; + + dm2->OnClose()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done2](const BoolPromise::ResolveOrRejectValue&) { done2 = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, + [&done1, &done2]() { return done1 && done2; }); + } + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemDataManager, + GetOrCreateFileSystemDataManager_PendingClose) { + auto backgroundTask = []() { + Registered<data::FileSystemDataManager> rdm; + + { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&rdm, &done](Registered<data::FileSystemDataManager> + registeredDataManager) { + ASSERT_TRUE(registeredDataManager->IsOpen()); + + rdm = std::move(registeredDataManager); + + done = true; + }, + [&done](nsresult rejectValue) { done = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + } + + RefPtr<data::FileSystemDataManager> dm = rdm.unwrap(); + + Unused << dm; + + { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](Registered<data::FileSystemDataManager> + registeredDataManager) { + RefPtr<data::FileSystemDataManager> dataManager = + registeredDataManager.get(); + + registeredDataManager = nullptr; + + return dataManager->OnClose(); + }, + [](nsresult rejectValue) { + return BoolPromise::CreateAndReject(rejectValue, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue&) { + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + } + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManagerVersion001.cpp b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManagerVersion001.cpp new file mode 100644 index 0000000000..f08132221b --- /dev/null +++ b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManagerVersion001.cpp @@ -0,0 +1,921 @@ +/* -*- 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 <algorithm> + +#include "ErrorList.h" +#include "FileSystemDataManager.h" +#include "FileSystemDatabaseManagerVersion001.h" +#include "FileSystemFileManager.h" +#include "FileSystemHashSource.h" +#include "ResultStatement.h" +#include "SchemaVersion001.h" +#include "TestHelpers.h" +#include "gtest/gtest.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozStorageHelper.h" +#include "mozilla/Array.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Result.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/quota/CommonMetadata.h" +#include "mozilla/dom/quota/test/QuotaManagerDependencyFixture.h" +#include "nsContentUtils.h" +#include "nsIFile.h" +#include "nsLiteralString.h" +#include "nsNetCID.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsTHashSet.h" + +namespace mozilla::dom::fs::test { + +using data::FileSystemDatabaseManagerVersion001; +using data::FileSystemFileManager; + +// This is a minimal mock to allow us to safely call the lock methods +// while avoiding assertions +class MockFileSystemDataManager final : public data::FileSystemDataManager { + public: + MockFileSystemDataManager(const quota::OriginMetadata& aOriginMetadata, + MovingNotNull<nsCOMPtr<nsIEventTarget>> aIOTarget, + MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue) + : FileSystemDataManager(aOriginMetadata, nullptr, std::move(aIOTarget), + std::move(aIOTaskQueue)) {} + + virtual ~MockFileSystemDataManager() { + // Need to avoid assertions + mState = State::Closed; + } +}; + +static void MakeDatabaseManagerVersion001( + RefPtr<MockFileSystemDataManager>& aDataManager, + FileSystemDatabaseManagerVersion001*& aDatabaseManager) { + TEST_TRY_UNWRAP(auto storageService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, + MOZ_SELECT_OVERLOAD(do_GetService), + MOZ_STORAGE_SERVICE_CONTRACTID)); + + const auto flags = mozIStorageService::CONNECTION_DEFAULT; + ResultConnection connection; + + nsresult rv = storageService->OpenSpecialDatabase(kMozStorageMemoryStorageKey, + VoidCString(), flags, + getter_AddRefs(connection)); + ASSERT_NSEQ(NS_OK, rv); + + const Origin& testOrigin = GetTestOrigin(); + + TEST_TRY_UNWRAP( + DatabaseVersion version, + SchemaVersion001::InitializeConnection(connection, testOrigin)); + ASSERT_EQ(1, version); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + auto fmRes = FileSystemFileManager::CreateFileSystemFileManager( + GetTestOriginMetadata()); + ASSERT_FALSE(fmRes.isErr()); + + QM_TRY_UNWRAP(auto streamTransportService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>, + MOZ_SELECT_OVERLOAD(do_GetService), + NS_STREAMTRANSPORTSERVICE_CONTRACTID), + QM_VOID); + + quota::OriginMetadata originMetadata = GetTestOriginMetadata(); + + nsCString taskQueueName("OPFS "_ns + originMetadata.mOrigin); + + RefPtr<TaskQueue> ioTaskQueue = + TaskQueue::Create(do_AddRef(streamTransportService), taskQueueName.get()); + + aDataManager = MakeRefPtr<MockFileSystemDataManager>( + originMetadata, WrapMovingNotNull(streamTransportService), + WrapMovingNotNull(ioTaskQueue)); + + aDatabaseManager = new FileSystemDatabaseManagerVersion001( + aDataManager, std::move(connection), + MakeUnique<FileSystemFileManager>(fmRes.unwrap()), rootId); +} + +class TestFileSystemDatabaseManagerVersion001 + : public quota::test::QuotaManagerDependencyFixture { + public: + void SetUp() override { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); } + + void TearDown() override { + ASSERT_NO_FATAL_FAILURE(ClearStoragesForOrigin(GetTestOriginMetadata())); + ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); + } + + static ContentType sContentType; +}; + +ContentType TestFileSystemDatabaseManagerVersion001::sContentType = "psid"_ns; + +TEST_F(TestFileSystemDatabaseManagerVersion001, + smokeTestCreateRemoveDirectories) { + auto ioTask = []() { + nsresult rv = NS_OK; + // Ensure that FileSystemDataManager lives for the lifetime of the test + RefPtr<MockFileSystemDataManager> dataManager; + FileSystemDatabaseManagerVersion001* rdm = nullptr; + ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(dataManager, rdm)); + UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm); + // if any of these exit early, we have to close + auto autoClose = MakeScopeExit([rdm] { rdm->Close(); }); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns); + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP(EntryId firstChild, dm->GetOrCreateDirectory( + firstChildMeta, /* create */ true)); + + int32_t dbVersion = 0; + TEST_TRY_UNWRAP(FileSystemDirectoryListing entries, + dm->GetDirectoryEntries(rootId, dbVersion)); + ASSERT_EQ(1u, entries.directories().Length()); + ASSERT_EQ(0u, entries.files().Length()); + + const auto& firstItemRef = entries.directories()[0]; + ASSERT_TRUE(u"First"_ns == firstItemRef.entryName()) + << firstItemRef.entryName(); + ASSERT_EQ(firstChild, firstItemRef.entryId()); + + TEST_TRY_UNWRAP( + EntryId firstChildClone, + dm->GetOrCreateDirectory(firstChildMeta, /* create */ true)); + ASSERT_EQ(firstChild, firstChildClone); + + FileSystemChildMetadata secondChildMeta(firstChild, u"Second"_ns); + TEST_TRY_UNWRAP( + EntryId secondChild, + dm->GetOrCreateDirectory(secondChildMeta, /* create */ true)); + + FileSystemEntryPair shortPair(firstChild, secondChild); + TEST_TRY_UNWRAP(Path shortPath, dm->Resolve(shortPair)); + ASSERT_EQ(1u, shortPath.Length()); + ASSERT_EQ(u"Second"_ns, shortPath[0]); + + FileSystemEntryPair longPair(rootId, secondChild); + TEST_TRY_UNWRAP(Path longPath, dm->Resolve(longPair)); + ASSERT_EQ(2u, longPath.Length()); + ASSERT_EQ(u"First"_ns, longPath[0]); + ASSERT_EQ(u"Second"_ns, longPath[1]); + + FileSystemEntryPair wrongPair(secondChild, rootId); + TEST_TRY_UNWRAP(Path emptyPath, dm->Resolve(wrongPair)); + ASSERT_TRUE(emptyPath.IsEmpty()); + + PageNumber page = 0; + TEST_TRY_UNWRAP(FileSystemDirectoryListing fEntries, + dm->GetDirectoryEntries(firstChild, page)); + ASSERT_EQ(1u, fEntries.directories().Length()); + ASSERT_EQ(0u, fEntries.files().Length()); + + const auto& secItemRef = fEntries.directories()[0]; + ASSERT_TRUE(u"Second"_ns == secItemRef.entryName()) + << secItemRef.entryName(); + ASSERT_EQ(secondChild, secItemRef.entryId()); + + TEST_TRY_UNWRAP_ERR( + rv, dm->RemoveDirectory(firstChildMeta, /* recursive */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + + TEST_TRY_UNWRAP(bool isDeleted, + dm->RemoveDirectory(firstChildMeta, /* recursive */ true)); + ASSERT_TRUE(isDeleted); + + FileSystemChildMetadata thirdChildMeta(secondChild, u"Second"_ns); + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(thirdChildMeta, /* create */ true)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error? + + dm->Close(); + }; + + PerformOnIOThread(std::move(ioTask)); +} + +TEST_F(TestFileSystemDatabaseManagerVersion001, smokeTestCreateRemoveFiles) { + auto ioTask = []() { + nsresult rv = NS_OK; + // Ensure that FileSystemDataManager lives for the lifetime of the test + RefPtr<MockFileSystemDataManager> datamanager; + FileSystemDatabaseManagerVersion001* rdm = nullptr; + ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(datamanager, rdm)); + UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns); + // If creating is not allowed, getting a file from empty root fails + TEST_TRY_UNWRAP_ERR(rv, dm->GetOrCreateFile(firstChildMeta, sContentType, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + // Creating a file under empty root succeeds + TEST_TRY_UNWRAP( + EntryId firstChild, + dm->GetOrCreateFile(firstChildMeta, sContentType, /* create */ true)); + + // Second time, the same file is returned + TEST_TRY_UNWRAP( + EntryId firstChildClone, + dm->GetOrCreateFile(firstChildMeta, sContentType, /* create */ true)); + ASSERT_EQ(firstChild, firstChildClone); + + // Directory listing returns the created file + PageNumber page = 0; + TEST_TRY_UNWRAP(FileSystemDirectoryListing entries, + dm->GetDirectoryEntries(rootId, page)); + ASSERT_EQ(0u, entries.directories().Length()); + ASSERT_EQ(1u, entries.files().Length()); + + auto& firstItemRef = entries.files()[0]; + ASSERT_TRUE(u"First"_ns == firstItemRef.entryName()) + << firstItemRef.entryName(); + ASSERT_EQ(firstChild, firstItemRef.entryId()); + + ContentType type; + TimeStamp lastModifiedMilliSeconds; + Path path; + nsCOMPtr<nsIFile> file; + rv = dm->GetFile(firstItemRef.entryId(), type, lastModifiedMilliSeconds, + path, file); + ASSERT_NSEQ(NS_OK, rv); + + ASSERT_STREQ(sContentType, type); + + const int64_t nowMilliSeconds = PR_Now() / 1000; + ASSERT_GE(nowMilliSeconds, lastModifiedMilliSeconds); + const int64_t expectedMaxDelayMilliSeconds = 100; + const int64_t actualDelay = nowMilliSeconds - lastModifiedMilliSeconds; + ASSERT_LT(actualDelay, expectedMaxDelayMilliSeconds); + + ASSERT_EQ(1u, path.Length()); + ASSERT_STREQ(u"First"_ns, path[0]); + + ASSERT_NE(nullptr, file); + + // Getting the file entry as directory fails + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + // Getting or creating the file entry as directory also fails + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ true)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + // Creating a file with non existing parent hash fails + + EntryId notAChildHash = "0123456789abcdef0123456789abcdef"_ns; + FileSystemChildMetadata notAChildMeta(notAChildHash, u"Dummy"_ns); + TEST_TRY_UNWRAP_ERR(rv, dm->GetOrCreateFile(notAChildMeta, sContentType, + /* create */ true)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error? + + // We create a directory under root + FileSystemChildMetadata secondChildMeta(rootId, u"Second"_ns); + TEST_TRY_UNWRAP( + EntryId secondChild, + dm->GetOrCreateDirectory(secondChildMeta, /* create */ true)); + + // The root should now contain the existing file and the new directory + TEST_TRY_UNWRAP(FileSystemDirectoryListing fEntries, + dm->GetDirectoryEntries(rootId, page)); + ASSERT_EQ(1u, fEntries.directories().Length()); + ASSERT_EQ(1u, fEntries.files().Length()); + + const auto& secItemRef = fEntries.directories()[0]; + ASSERT_TRUE(u"Second"_ns == secItemRef.entryName()) + << secItemRef.entryName(); + ASSERT_EQ(secondChild, secItemRef.entryId()); + + // Create a file under the new directory + FileSystemChildMetadata thirdChildMeta(secondChild, u"Third"_ns); + TEST_TRY_UNWRAP( + EntryId thirdChild, + dm->GetOrCreateFile(thirdChildMeta, sContentType, /* create */ true)); + + FileSystemEntryPair entryPair(rootId, thirdChild); + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve(entryPair)); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_EQ(u"Second"_ns, entryPath[0]); + ASSERT_EQ(u"Third"_ns, entryPath[1]); + + // If recursion is not allowed, the non-empty new directory may not be + // removed + TEST_TRY_UNWRAP_ERR( + rv, dm->RemoveDirectory(secondChildMeta, /* recursive */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + + // If recursion is allowed, the new directory goes away. + TEST_TRY_UNWRAP(bool isDeleted, + dm->RemoveDirectory(secondChildMeta, /* recursive */ true)); + ASSERT_TRUE(isDeleted); + + // The file under the removed directory is no longer accessible. + TEST_TRY_UNWRAP_ERR(rv, dm->GetOrCreateFile(thirdChildMeta, sContentType, + /* create */ true)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error? + + // The deletion is reflected by the root directory listing + TEST_TRY_UNWRAP(FileSystemDirectoryListing nEntries, + dm->GetDirectoryEntries(rootId, 0)); + ASSERT_EQ(0u, nEntries.directories().Length()); + ASSERT_EQ(1u, nEntries.files().Length()); + + const auto& fileItemRef = nEntries.files()[0]; + ASSERT_TRUE(u"First"_ns == fileItemRef.entryName()) + << fileItemRef.entryName(); + ASSERT_EQ(firstChild, fileItemRef.entryId()); + + dm->Close(); + }; + + PerformOnIOThread(std::move(ioTask)); +} + +TEST_F(TestFileSystemDatabaseManagerVersion001, + smokeTestCreateMoveDirectories) { + auto ioTask = []() { + // Ensure that FileSystemDataManager lives for the lifetime of the test + RefPtr<MockFileSystemDataManager> datamanager; + FileSystemDatabaseManagerVersion001* rdm = nullptr; + ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(datamanager, rdm)); + UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm); + auto closeAtExit = MakeScopeExit([&dm]() { dm->Close(); }); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + FileSystemEntryMetadata rootMeta{rootId, u"root"_ns, + /* is directory */ true}; + + { + // Sanity check: no existing items should be found + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, /* page */ 0u)); + ASSERT_TRUE(contents.directories().IsEmpty()); + ASSERT_TRUE(contents.files().IsEmpty()); + } + + // Create subdirectory + FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns); + TEST_TRY_UNWRAP( + EntryId firstChildDir, + dm->GetOrCreateDirectory(firstChildMeta, /* create */ true)); + + { + // Check that directory listing is as expected + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, /* page */ 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildMeta.childName(), + contents.directories()[0].entryName()); + } + + { + // Try to move subdirectory to its current location + FileSystemEntryMetadata src{firstChildDir, firstChildMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{rootId, src.entryName()}; + TEST_TRY_UNWRAP(bool moved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(moved); + } + + { + // Try to move subdirectory under itself + FileSystemEntryMetadata src{firstChildDir, firstChildMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{src.entryId(), src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + } + + { + // Try to move root under its subdirectory + FileSystemEntryMetadata src{rootId, rootMeta.entryName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + + // Create subsubdirectory + FileSystemChildMetadata firstChildDescendantMeta(firstChildDir, + u"Descendant"_ns); + TEST_TRY_UNWRAP(EntryId firstChildDescendant, + dm->GetOrCreateDirectory(firstChildDescendantMeta, + /* create */ true)); + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, /* page */ 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildDescendantMeta.childName(), + contents.directories()[0].entryName()); + + TEST_TRY_UNWRAP( + Path subSubDirPath, + dm->Resolve({rootId, contents.directories()[0].entryId()})); + ASSERT_EQ(2u, subSubDirPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), subSubDirPath[0]); + ASSERT_STREQ(firstChildDescendantMeta.childName(), subSubDirPath[1]); + } + + { + // Try to move subsubdirectory to its current location + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, src.entryName()}; + TEST_TRY_UNWRAP(bool moved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(moved); + } + + { + // Try to move subsubdirectory under itself + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{src.entryId(), src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + } + + { + // Try to move subdirectory under its descendant + FileSystemEntryMetadata src{firstChildDir, firstChildMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDescendant, src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + } + + { + // Try to move root under its subsubdirectory + FileSystemEntryMetadata src{rootId, rootMeta.entryName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDescendant, src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + + // Create file in the subdirectory with already existing subsubdirectory + FileSystemChildMetadata testFileMeta(firstChildDir, u"Subfile"_ns); + TEST_TRY_UNWRAP( + EntryId testFile, + dm->GetOrCreateFile(testFileMeta, sContentType, /* create */ true)); + + // Get handles to the original locations of the entries + FileSystemEntryMetadata subSubDir; + FileSystemEntryMetadata subSubFile; + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, /* page */ 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_EQ(1u, contents.directories().Length()); + + subSubDir = contents.directories()[0]; + ASSERT_STREQ(firstChildDescendantMeta.childName(), subSubDir.entryName()); + + subSubFile = contents.files()[0]; + ASSERT_STREQ(testFileMeta.childName(), subSubFile.entryName()); + ASSERT_EQ(testFile, subSubFile.entryId()); + } + + { + TEST_TRY_UNWRAP(Path entryPath, + dm->Resolve({rootId, subSubFile.entryId()})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + } + + { + // Try to move file to its current location + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, src.entryName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Try to move subsubdirectory under a file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{testFile, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); + } + + { + // Silently overwrite a directory with a file using rename + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + const FileSystemChildMetadata& dest = firstChildDescendantMeta; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move file back and recreate the directory + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, testFileMeta)); + ASSERT_TRUE(isMoved); + + TEST_TRY_UNWRAP(EntryId firstChildDescendantCheck, + dm->GetOrCreateDirectory(firstChildDescendantMeta, + /* create */ true)); + ASSERT_EQ(firstChildDescendant, firstChildDescendantCheck); + } + + { + // Try to rename directory to quietly overwrite a file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + const FileSystemChildMetadata& dest = testFileMeta; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move directory back and recreate the file + FileSystemEntryMetadata src{firstChildDescendant, + testFileMeta.childName(), + /* is directory */ true}; + + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + TEST_TRY_UNWRAP( + EntryId testFileCheck, + dm->GetOrCreateFile(testFileMeta, sContentType, /* create */ true)); + ASSERT_EQ(testFile, testFileCheck); + } + + { + // Move file one level up + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, src.entryName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Check that listings are as expected + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildDescendantMeta.childName(), + contents.directories()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_STREQ(testFileMeta.childName(), contents.files()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(Path entryPath, + dm->Resolve({rootId, subSubFile.entryId()})); + ASSERT_EQ(1u, entryPath.Length()); + ASSERT_STREQ(testFileMeta.childName(), entryPath[0]); + } + + { + // Try to get a handle to the old item + TEST_TRY_UNWRAP_ERR( + nsresult rv, + dm->GetOrCreateFile(testFileMeta, sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + + { + // Try to move + rename file one level down to collide with a + // subSubDirectory, silently overwriting it + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Restore filename, move file to its original location and recreate + // the overwritten directory + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, testFileMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + FileSystemChildMetadata oldLocation{firstChildDir, + firstChildDescendantMeta.childName()}; + + // Is there still something out there? + TEST_TRY_UNWRAP_ERR( + nsresult rv, + dm->GetOrCreateFile(oldLocation, sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP(EntryId firstChildDescendantCheck, + dm->GetOrCreateDirectory(oldLocation, /* create */ true)); + ASSERT_EQ(firstChildDescendant, firstChildDescendantCheck); + } + + // Rename file first and then try to move it to collide with + // subSubDirectory, silently overwriting it + { + // Rename + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Try to move one level down + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move the file back and recreate the directory + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + FileSystemChildMetadata oldLocation{firstChildDir, + firstChildDescendantMeta.childName()}; + + // Is there still something out there? + TEST_TRY_UNWRAP_ERR( + nsresult rv, + dm->GetOrCreateFile(oldLocation, sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP(EntryId firstChildDescendantCheck, + dm->GetOrCreateDirectory(oldLocation, /* create */ true)); + ASSERT_EQ(firstChildDescendant, firstChildDescendantCheck); + } + + { + // Try to move subSubDirectory one level up to quietly overwrite a file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{rootId, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move subSubDirectory back one level down and recreate the file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + FileSystemChildMetadata oldLocation{rootId, + firstChildDescendantMeta.childName()}; + + // We should no longer find anything there + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->GetOrCreateDirectory( + oldLocation, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP( + EntryId testFileCheck, + dm->GetOrCreateFile(oldLocation, sContentType, /* create */ true)); + ASSERT_NE(testFile, testFileCheck); + testFile = testFileCheck; + } + + // Create a new file in the subsubdirectory + FileSystemChildMetadata newFileMeta{firstChildDescendant, + testFileMeta.childName()}; + TEST_TRY_UNWRAP( + EntryId newFile, + dm->GetOrCreateFile(newFileMeta, sContentType, /* create */ true)); + + { + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, newFile})); + ASSERT_EQ(3u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(firstChildDescendantMeta.childName(), entryPath[1]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[2]); + } + + { + // Move subSubDirectory one level up and rename it to testFile's old name + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{rootId, testFileMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Try to get handles to the moved items + TEST_TRY_UNWRAP_ERR(nsresult rv, + dm->GetOrCreateDirectory(firstChildDescendantMeta, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + // Still under the same parent + TEST_TRY_UNWRAP( + EntryId handle, + dm->GetOrCreateFile(newFileMeta, sContentType, /* create */ false)); + ASSERT_EQ(handle, newFile); + + TEST_TRY_UNWRAP( + handle, dm->GetOrCreateDirectory({rootId, testFileMeta.childName()}, + /* create */ false)); + ASSERT_EQ(handle, firstChildDescendant); + } + + { + // Check that new file path is as expected + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, newFile})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(testFileMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + } + + // Move first file and subSubDirectory back one level down keeping the names + { + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + + // Flag is ignored + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Then move the directory + FileSystemEntryMetadata src{firstChildDescendant, + testFileMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, testFileMeta.childName()}; + + // Flag is ignored + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + // Check that listings are as expected + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildMeta.childName(), + contents.directories()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildDescendantMeta.childName(), + contents.files()[0].entryName()); + ASSERT_STREQ(testFileMeta.childName(), + contents.directories()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDescendant, 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_TRUE(contents.directories().IsEmpty()); + ASSERT_STREQ(testFileMeta.childName(), contents.files()[0].entryName()); + } + + // Names are swapped + { + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, testFile})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(firstChildDescendantMeta.childName(), entryPath[1]); + } + + { + TEST_TRY_UNWRAP(Path entryPath, + dm->Resolve({rootId, subSubDir.entryId()})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + } + + { + // Check that new file path is also as expected + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, newFile})); + ASSERT_EQ(3u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[2]); + } + + { + // Try to get handles to the old items + TEST_TRY_UNWRAP_ERR( + nsresult rv, dm->GetOrCreateFile({rootId, testFileMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, + dm->GetOrCreateFile({rootId, firstChildDescendantMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory({rootId, testFileMeta.childName()}, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR(rv, + dm->GetOrCreateDirectory( + {rootId, firstChildDescendantMeta.childName()}, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateFile({firstChildDir, testFileMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory( + {firstChildDir, firstChildDescendantMeta.childName()}, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateFile({testFile, newFileMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + }; + + PerformOnIOThread(std::move(ioTask)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/datamodel/moz.build b/dom/fs/test/gtest/parent/datamodel/moz.build new file mode 100644 index 0000000000..a1a75c7b65 --- /dev/null +++ b/dom/fs/test/gtest/parent/datamodel/moz.build @@ -0,0 +1,22 @@ +# -*- 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 = [ + "TestFileSystemDataManager.cpp", + "TestFileSystemDataManagerVersion001.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/include", + "/dom/fs/parent", + "/dom/fs/parent/datamodel", + "/dom/fs/test/gtest", + "/dom/fs/test/gtest/parent", +] diff --git a/dom/fs/test/gtest/parent/moz.build b/dom/fs/test/gtest/parent/moz.build new file mode 100644 index 0000000000..9197c6ade2 --- /dev/null +++ b/dom/fs/test/gtest/parent/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/. + +TEST_DIRS += ["datamodel"] + +UNIFIED_SOURCES = [ + "TestFileSystemHashSource.cpp", + "TestFileSystemQuotaClient.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/parent", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/gtest/shared/TestFileSystemHelpers.cpp b/dom/fs/test/gtest/shared/TestFileSystemHelpers.cpp new file mode 100644 index 0000000000..ed38026634 --- /dev/null +++ b/dom/fs/test/gtest/shared/TestFileSystemHelpers.cpp @@ -0,0 +1,179 @@ +/* -*- 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/RefPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/FileSystemHelpers.h" + +namespace mozilla::dom::fs { + +namespace { + +class TestObject { + public: + explicit TestObject(uint32_t aExpectedAddRefCnt = 0, + uint32_t aExpectedAddRegCnt = 0) + : mExpectedAddRefCnt(aExpectedAddRefCnt), + mExpectedAddRegCnt(aExpectedAddRegCnt), + mAddRefCnt(0), + mAddRegCnt(0), + mRefCnt(0), + mRegCnt(0), + mClosed(false) {} + + uint32_t AddRef() { + mRefCnt++; + mAddRefCnt++; + return mRefCnt; + } + + uint32_t Release() { + EXPECT_TRUE(mRefCnt > 0); + mRefCnt--; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + void Register() { + EXPECT_FALSE(mClosed); + mRegCnt++; + mAddRegCnt++; + } + + void Unregister() { + EXPECT_FALSE(mClosed); + EXPECT_TRUE(mRegCnt > 0); + mRegCnt--; + if (mRegCnt == 0) { + mClosed = true; + } + } + + void Foo() const {} + + private: + ~TestObject() { + if (mExpectedAddRefCnt > 0) { + EXPECT_EQ(mAddRefCnt, mExpectedAddRefCnt); + } + if (mExpectedAddRegCnt > 0) { + EXPECT_EQ(mAddRegCnt, mExpectedAddRegCnt); + } + } + + uint32_t mExpectedAddRefCnt; + uint32_t mExpectedAddRegCnt; + uint32_t mAddRefCnt; + uint32_t mAddRegCnt; + uint32_t mRefCnt; + uint32_t mRegCnt; + bool mClosed; +}; + +} // namespace + +TEST(TestFileSystemHelpers_Registered, Construct_Default) +{ + { Registered<TestObject> testObject; } +} + +TEST(TestFileSystemHelpers_Registered, Construct_Copy) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(2, 2)); + Registered<TestObject> testObject2(testObject1); + testObject2 = nullptr; + } +} + +TEST(TestFileSystemHelpers_Registered, Construct_Move) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + Registered<TestObject> testObject2(std::move(testObject1)); + } +} + +TEST(TestFileSystemHelpers_Registered, Construct_FromRefPtr) +{ + { Registered<TestObject> testObject(MakeRefPtr<TestObject>(1, 1)); } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Assign_FromNullPtr) +{ + { + Registered<TestObject> testObject; + testObject = nullptr; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Assign_Copy) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(2, 2)); + Registered<TestObject> testObject2; + testObject2 = testObject1; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Assign_Move) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + Registered<TestObject> testObject2; + testObject2 = std::move(testObject1); + } +} + +TEST(TestFileSystemHelpers_Registered, Method_Inspect) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + const RefPtr<TestObject>& testObject2 = testObject1.inspect(); + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Method_Unwrap) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + RefPtr<TestObject> testObject2 = testObject1.unwrap(); + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Method_Get) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + TestObject* testObject2 = testObject1.get(); + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Conversion_ToRawPtr) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + TestObject* testObject2 = testObject1; + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Dereference_ToRawPtr) +{ + { + Registered<TestObject> testObject(MakeRefPtr<TestObject>(1, 1)); + testObject->Foo(); + } +} + +} // namespace mozilla::dom::fs diff --git a/dom/fs/test/gtest/shared/moz.build b/dom/fs/test/gtest/shared/moz.build new file mode 100644 index 0000000000..e8feae8c57 --- /dev/null +++ b/dom/fs/test/gtest/shared/moz.build @@ -0,0 +1,16 @@ +# -*- 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 = [ + "TestFileSystemHelpers.cpp", +] + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/shared", + "/dom/fs/test/gtest", +] |