/* -*- 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: struct FileSystemManagerChildShutdown { explicit FileSystemManagerChildShutdown( TestFileSystemManagerChild* aFileSystemManagerChild) : mFileSystemManagerChild(aFileSystemManagerChild) {} void operator()() const { mFileSystemManagerChild->FileSystemManagerChild::Shutdown(); } TestFileSystemManagerChild* mFileSystemManagerChild; }; void SetUp() override { mListener = MakeAndAddRef(); mChild = FileSystemChildMetadata("parent"_ns, u"ChildName"_ns); mEntry = FileSystemEntryMetadata("myid"_ns, u"EntryName"_ns, /* directory */ false); mName = u"testDir"_ns; mFileSystemManagerChild = MakeAndAddRef(); mManager = MakeAndAddRef( mGlobal, nullptr, MakeRefPtr( mFileSystemManagerChild)); } void TearDown() override { if (!mManager->IsShutdown()) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); } EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); } already_AddRefed GetDefaultPromise() { IgnoredErrorResult rv; RefPtr result = Promise::Create(mGlobal, rv); mListener->ClearDone(); result->AppendNativeHandler(mListener->AsHandler()); return result.forget(); } already_AddRefed GetSimplePromise() { IgnoredErrorResult rv; RefPtr result = Promise::Create(mGlobal, rv); return result.forget(); } UniquePtr GetFileSystemRequestHandler() { return MakeUnique(); } nsIGlobalObject* mGlobal = GetGlobal(); RefPtr mListener; FileSystemChildMetadata mChild; FileSystemEntryMetadata mEntry; nsString mName; RefPtr mFileSystemManagerChild; RefPtr 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 = GetDefaultPromise(); auto testable = GetFileSystemRequestHandler(); testable->GetRootHandle(mManager, promise, IgnoredErrorResult()); SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, [this]() { return mListener->IsDone(); }); } TEST_F(TestFileSystemRequestHandler, isGetRootHandleBlockedAfterShutdown) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); 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 = 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) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); 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 = 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) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); 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 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(tmpfile); TimeStamp last_modified_ms = 0; ContentType type = u"txt"_ns; IPCBlob file; IPCBlobUtils::Serialize(blob, file); nsTArray 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 = 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) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); 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) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); 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) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); IgnoredErrorResult error; GetFileSystemRequestHandler()->GetWritable( mManager, mEntry, /* aKeepData */ false, GetSimplePromise(), error); ASSERT_TRUE(error.Failed()); // XXX This should be reverted back to check NS_ERROR_ILLEGAL_DURING_SHUTDOWN // once bug 1798513 is fixed. #if 0 ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); #else ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_NOT_IMPLEMENTED)); #endif } TEST_F(TestFileSystemRequestHandler, isGetEntriesSuccessful) { auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, auto&& /* aReject */) { nsTArray files; nsTArray directories; FileSystemDirectoryListing listing(files, directories); FileSystemGetEntriesResponse response(listing); aResolve(std::move(response)); }; RefPtr listener = MakeAndAddRef(); IgnoredErrorResult rv; listener->ClearDone(); EXPECT_CALL(listener->GetSuccessHandler(), InvokeMe()); RefPtr promise = Promise::Create(mGlobal, rv); promise->AppendNativeHandler(listener); EXPECT_CALL(*mFileSystemManagerChild, SendGetEntries(_, _, _)) .WillOnce(Invoke(fakeResponse)); auto testable = GetFileSystemRequestHandler(); RefPtr 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) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); RefPtr 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 = GetDefaultPromise(); testable->RemoveEntry(mManager, mChild, /* recursive */ true, promise, IgnoredErrorResult()); SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, [this]() { return mListener->IsDone(); }); } TEST_F(TestFileSystemRequestHandler, isRemoveEntryBlockedAfterShutdown) { EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) .WillOnce(FileSystemManagerChildShutdown(mFileSystemManagerChild)); mManager->Shutdown(); 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