123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <Tests/Asset/BaseAssetManagerTest.h>
- #include <AzCore/Asset/AssetSerializer.h>
- #include <AzCore/Console/IConsole.h>
- #include <AzCore/Interface/Interface.h>
- #include <AzCore/IO/SystemFile.h>
- #include <AzCore/IO/Streamer/Streamer.h>
- #include <AzCore/IO/FileIO.h>
- #include <AzCore/IO/GenericStreams.h>
- #include <AzCore/Math/Crc.h>
- #include <AzCore/Jobs/JobManager.h>
- #include <AzCore/Jobs/JobContext.h>
- #include <AzCore/Outcome/Outcome.h>
- #include <AzCore/Serialization/SerializeContext.h>
- #include <AzCore/Serialization/ObjectStream.h>
- #include <AzCore/Serialization/Utils.h>
- #include <AzCore/std/parallel/thread.h>
- #include <AzCore/std/functional.h>
- #include <AzCore/std/parallel/condition_variable.h>
- #include <AzCore/UnitTest/TestTypes.h>
- #include <AZTestShared/Utils/Utils.h>
- #include <Streamer/IStreamerMock.h>
- namespace UnitTest
- {
- /**
- * Find the current status of the reload
- */
- AZ::Data::AssetData::AssetStatus TestAssetManager::GetReloadStatus(const AssetId& assetId)
- {
- AZStd::lock_guard<AZStd::recursive_mutex> assetLock(m_assetMutex);
- auto reloadInfo = m_reloads.find(assetId);
- if (reloadInfo != m_reloads.end())
- {
- return reloadInfo->second.GetStatus();
- }
- return AZ::Data::AssetData::AssetStatus::NotLoaded;
- }
- const AZ::Data::AssetManager::OwnedAssetContainerMap& TestAssetManager::GetAssetContainers() const
- {
- return m_ownedAssetContainers;
- }
- const AssetManager::AssetMap& TestAssetManager::GetAssets() const
- {
- return m_assets;
- }
- void BaseAssetManagerTest::SetUp()
- {
- SerializeContextFixture::SetUp();
- SuppressTraceOutput(false);
- AZ::JobManagerDesc jobDesc;
- AZ::JobManagerThreadDesc threadDesc;
- for (size_t threadCount = 0; threadCount < GetNumJobManagerThreads(); threadCount++)
- {
- jobDesc.m_workerThreads.push_back(threadDesc);
- }
- m_jobManager = aznew AZ::JobManager(jobDesc);
- m_jobContext = aznew AZ::JobContext(*m_jobManager);
- AZ::JobContext::SetGlobalContext(m_jobContext);
- m_prevFileIO = IO::FileIOBase::GetInstance();
- IO::FileIOBase::SetInstance(&m_fileIO);
- m_streamer = CreateStreamer();
- if (m_streamer)
- {
- Interface<IO::IStreamer>::Register(m_streamer);
- }
- }
- void BaseAssetManagerTest::TearDown()
- {
- if (m_streamer)
- {
- Interface<IO::IStreamer>::Unregister(m_streamer);
- }
- DestroyStreamer(m_streamer);
- // Clean up any temporary asset files created during the test.
- for (auto& assetName : m_assetsWritten)
- {
- DeleteAssetFromDisk(assetName);
- }
- // Make sure to clear the memory from the name storage before shutting down the allocator.
- m_assetsWritten.clear();
- m_assetsWritten.shrink_to_fit();
- IO::FileIOBase::SetInstance(m_prevFileIO);
- AZ::JobContext::SetGlobalContext(nullptr);
- delete m_jobContext;
- delete m_jobManager;
- // Reset back to default suppression settings to avoid affecting other tests
- SuppressTraceOutput(true);
- SerializeContextFixture::TearDown();
- }
- void BaseAssetManagerTest::SuppressTraceOutput(bool suppress)
- {
- UnitTest::TestRunner::Instance().m_suppressAsserts = suppress;
- UnitTest::TestRunner::Instance().m_suppressErrors = suppress;
- UnitTest::TestRunner::Instance().m_suppressWarnings = suppress;
- UnitTest::TestRunner::Instance().m_suppressPrintf = suppress;
- UnitTest::TestRunner::Instance().m_suppressOutput = suppress;
- }
- void BaseAssetManagerTest::WriteAssetToDisk(const AZStd::string& assetName, [[maybe_unused]] const AZStd::string& assetIdGuid)
- {
- AZ::IO::Path assetFileName = GetTestFolderPath() / assetName;
- AssetWithCustomData asset;
- EXPECT_TRUE(AZ::Utils::SaveObjectToFile(assetFileName.Native(), AZ::DataStream::ST_XML, &asset, m_serializeContext));
- // Keep track of every asset written so that we can remove it on teardown
- m_assetsWritten.emplace_back(AZStd::move(assetFileName).Native());
- }
- void BaseAssetManagerTest::DeleteAssetFromDisk(const AZStd::string& assetName)
- {
- AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
- if (fileIO->Exists(assetName.c_str()))
- {
- fileIO->Remove(assetName.c_str());
- }
- }
- void BaseAssetManagerTest::BlockUntilAssetJobsAreComplete()
- {
- auto maxTimeout = AZStd::chrono::steady_clock::now() + DefaultTimeoutSeconds;
- while (AssetManager::Instance().HasActiveJobsOrStreamerRequests())
- {
- if (AZStd::chrono::steady_clock::now() > maxTimeout)
- {
- break;
- }
- AZStd::this_thread::yield();
- }
- EXPECT_FALSE(AssetManager::Instance().HasActiveJobsOrStreamerRequests());
- }
- MemoryStreamerWrapper::MemoryStreamerWrapper()
- {
- using ::testing::_;
- using ::testing::NiceMock;
- using ::testing::Return;
- ON_CALL(m_mockStreamer, SuspendProcessing()).WillByDefault([this]()
- {
- m_suspended = true;
- });
- ON_CALL(m_mockStreamer, ResumeProcessing()).WillByDefault([this]()
- {
- AZStd::unique_lock lock(m_mutex);
- m_suspended = false;
- while (!m_processingQueue.empty())
- {
- FileRequestHandle requestHandle = m_processingQueue.front();
- m_processingQueue.pop();
- const auto& onCompleteCallback = GetReadRequest(requestHandle)->m_callback;
- if (onCompleteCallback)
- {
- onCompleteCallback(requestHandle);
- }
- }
- });
- ON_CALL(m_mockStreamer, Read(_, ::testing::An<IStreamerTypes::RequestMemoryAllocator&>(), _, _, _, _))
- .WillByDefault(
- [this](
- [[maybe_unused]] AZStd::string_view relativePath, IStreamerTypes::RequestMemoryAllocator& allocator, size_t size,
- AZStd::chrono::microseconds deadline, IStreamerTypes::Priority priority, [[maybe_unused]] size_t offset)
- {
- AZStd::unique_lock lock(m_mutex);
- ReadRequest request;
- // Save off the requested deadline and priority
- request.m_deadline = deadline;
- request.m_priority = priority;
- request.m_data = allocator.Allocate(size, size, 8);
- const auto* virtualFile = FindFile(relativePath);
- AZ_Assert(
- virtualFile->size() == size, "Streamer read request size did not match size of saved file: %d vs %d (%.*s)",
- virtualFile->size(), size,
- relativePath.size(), relativePath.data());
- AZ_Assert(size > 0, "Size is zero %.*s", relativePath.size(), relativePath.data());
- memcpy(request.m_data.m_address, virtualFile->data(), size);
- // Create a real file request result and return it
- request.m_request = m_context.GetNewExternalRequest();
- m_readRequests.push_back(request);
- return request.m_request;
- });
- ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _))
- .WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr&
- {
- // Save off the callback just so that we can call it when the request is "done"
- AZStd::unique_lock lock(m_mutex);
- ReadRequest* readRequest = GetReadRequest(request);
- readRequest->m_callback = callback;
- return request;
- });
- ON_CALL(m_mockStreamer, QueueRequest(_))
- .WillByDefault([this](const auto& fileRequest)
- {
- if (!m_suspended)
- {
- decltype(ReadRequest::m_callback) onCompleteCallback;
- AZStd::unique_lock lock(m_mutex);
- ReadRequest* readRequest = GetReadRequest(fileRequest);
- onCompleteCallback = readRequest->m_callback;
- if (onCompleteCallback)
- {
- onCompleteCallback(fileRequest);
- m_readRequests.erase(readRequest);
- }
- }
- else
- {
- AZStd::unique_lock lock(m_mutex);
- m_processingQueue.push(fileRequest);
- }
- });
- ON_CALL(m_mockStreamer, GetRequestStatus(_))
- .WillByDefault([]([[maybe_unused]] FileRequestHandle request)
- {
- // Return whatever request status has been set in this class
- return IO::IStreamerTypes::RequestStatus::Completed;
- });
- ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
- .WillByDefault([this](
- [[maybe_unused]] FileRequestHandle request, void*& buffer, AZ::u64& numBytesRead,
- IStreamerTypes::ClaimMemory claimMemory)
- {
- // Make sure the requestor plans to free the data buffer we allocated.
- EXPECT_EQ(claimMemory, IStreamerTypes::ClaimMemory::Yes);
- AZStd::unique_lock lock(m_mutex);
- ReadRequest* readRequest = GetReadRequest(request);
- // Provide valid data buffer results.
- numBytesRead = readRequest->m_data.m_size;
- buffer = readRequest->m_data.m_address;
- return true;
- });
- ON_CALL(m_mockStreamer, RescheduleRequest(_, _, _))
- .WillByDefault([this](IO::FileRequestPtr target, AZStd::chrono::microseconds newDeadline, IO::IStreamerTypes::Priority newPriority)
- {
- AZStd::unique_lock lock(m_mutex);
- ReadRequest* readRequest = GetReadRequest(target);
- readRequest->m_deadline = newDeadline;
- readRequest->m_priority = newPriority;
- return target;
- });
- }
- ReadRequest* MemoryStreamerWrapper::GetReadRequest(FileRequestHandle request)
- {
- auto itr = AZStd::find_if(
- m_readRequests.begin(), m_readRequests.end(),
- [request](const ReadRequest& searchItem) -> bool
- {
- return (searchItem.m_request == request);
- });
- return itr;
- }
- AZStd::vector<char>* MemoryStreamerWrapper::FindFile(AZStd::string_view path)
- {
- auto itr = m_virtualFiles.find(path);
- if (itr == m_virtualFiles.end())
- {
- // Path didn't work as-is, does it have the test folder prefixed? If so try removing it
- AZ::IO::Path testFolderPath = GetTestFolderPath();
- if (AZ::IO::PathView(path).IsRelativeTo(testFolderPath))
- {
- AZ::IO::Path pathWithoutFolder = AZ::IO::PathView(path).LexicallyProximate(testFolderPath).String();
- itr = m_virtualFiles.find(pathWithoutFolder);
- }
- else // Path isn't prefixed, so try adding it
- {
- itr = m_virtualFiles.find(GetTestFolderPath() / path);
- }
- }
- if (itr != m_virtualFiles.end())
- {
- return &itr->second;
- }
- // Currently no test expects a file not to exist so we assert to make it easy to quickly find where something went wrong
- // If we ever need to test for a non-existent file this assert should just be conditionally disabled for that specific test
- AZ_Assert(false, "Failed to find virtual file %.*s", AZ_STRING_ARG(path))
- return nullptr;
- }
- void DisklessAssetManagerBase::SetUp()
- {
- using ::testing::_;
- using ::testing::NiceMock;
- using ::testing::Return;
- BaseAssetManagerTest::SetUp();
- ON_CALL(m_fileIO, Size(::testing::Matcher<const char*>(::testing::_), _))
- .WillByDefault(
- [this](const char* path, u64& size)
- {
- AZStd::scoped_lock lock(m_streamerWrapper->m_mutex);
- const auto* file = m_streamerWrapper->FindFile(path);
- if (file)
- {
- size = file->size();
- return ResultCode::Success;
- }
- AZ_Error("DisklessAssetManagerBase", false, "Failed to find virtual file %.*s", path);
- return ResultCode::Error;
- });
- m_prevFileIO = IO::FileIOBase::GetInstance();
- IO::FileIOBase::SetInstance(nullptr);
- IO::FileIOBase::SetInstance(&m_fileIO);
- }
- void DisklessAssetManagerBase::TearDown()
- {
- IO::FileIOBase::SetInstance(nullptr);
- IO::FileIOBase::SetInstance(m_prevFileIO);
- BaseAssetManagerTest::TearDown();
- }
- IO::IStreamer* DisklessAssetManagerBase::CreateStreamer()
- {
- m_streamerWrapper = AZStd::make_unique<MemoryStreamerWrapper>();
- return &(m_streamerWrapper->m_mockStreamer);
- }
- void DisklessAssetManagerBase::DestroyStreamer(IO::IStreamer*)
- {
- m_streamerWrapper = nullptr;
- }
- void DisklessAssetManagerBase::WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string&)
- {
- AZ::IO::Path assetFileName = GetTestFolderPath() / assetName;
- AssetWithCustomData asset;
- EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile(assetFileName.Native(), &asset, m_serializeContext));
- }
- void DisklessAssetManagerBase::DeleteAssetFromDisk(const AZStd::string&)
- {
- }
- }
|