diff options
Diffstat (limited to 'dom/fs/test/gtest/parent/datamodel')
3 files changed, 1125 insertions, 0 deletions
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", +] |