AssetManagerStreamingTests.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/Asset/AssetManager.h>
  9. #include <AzCore/Asset/AssetSerializer.h>
  10. #include <AzCore/Console/IConsole.h>
  11. #include <AzCore/Interface/Interface.h>
  12. #include <AzCore/IO/SystemFile.h>
  13. #include <AzCore/IO/Streamer/Streamer.h>
  14. #include <AzCore/IO/FileIO.h>
  15. #include <AzCore/IO/GenericStreams.h>
  16. #include <AzCore/Math/Crc.h>
  17. #include <AzCore/Jobs/JobManager.h>
  18. #include <AzCore/Jobs/JobContext.h>
  19. #include <AzCore/Outcome/Outcome.h>
  20. #include <AzCore/Serialization/SerializeContext.h>
  21. #include <AzCore/Serialization/ObjectStream.h>
  22. #include <AzCore/Serialization/Utils.h>
  23. #include <AzCore/std/parallel/thread.h>
  24. #include <AzCore/std/functional.h>
  25. #include <AzCore/std/parallel/condition_variable.h>
  26. #include <AzCore/UnitTest/TestTypes.h>
  27. #include <AZTestShared/Utils/Utils.h>
  28. #include <Streamer/IStreamerMock.h>
  29. #include <Tests/Asset/BaseAssetManagerTest.h>
  30. #include <Tests/Asset/MockLoadAssetCatalogAndHandler.h>
  31. #include <Tests/Asset/TestAssetTypes.h>
  32. #include <Tests/SerializeContextFixture.h>
  33. #include <Tests/TestCatalog.h>
  34. namespace UnitTest
  35. {
  36. using namespace AZ;
  37. using namespace AZ::Data;
  38. struct StreamerWrapper
  39. {
  40. StreamerWrapper()
  41. {
  42. using ::testing::_;
  43. using ::testing::NiceMock;
  44. using ::testing::Return;
  45. ON_CALL(m_mockStreamer, Read(_, ::testing::An<IStreamerTypes::RequestMemoryAllocator&>(), _, _, _, _))
  46. .WillByDefault([this](
  47. [[maybe_unused]] AZStd::string_view relativePath,
  48. IStreamerTypes::RequestMemoryAllocator& allocator,
  49. size_t size,
  50. AZStd::chrono::microseconds deadline,
  51. IStreamerTypes::Priority priority,
  52. [[maybe_unused]] size_t offset)
  53. {
  54. // Save off the requested deadline and priority
  55. m_deadline = deadline;
  56. m_priority = priority;
  57. // Allocate a real data buffer for the supposedly read-in asset
  58. m_data = allocator.Allocate(size, size, 8);
  59. // Create a real file request result and return it
  60. m_request = m_context.GetNewExternalRequest();
  61. return m_request;
  62. });
  63. ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _))
  64. .WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr&
  65. {
  66. // Save off the callback just so that we can call it when the request is "done"
  67. m_callback = callback;
  68. return request;
  69. });
  70. ON_CALL(m_mockStreamer, GetRequestStatus(_))
  71. .WillByDefault([]([[maybe_unused]] FileRequestHandle request)
  72. {
  73. // Return whatever request status has been set in this class
  74. return IO::IStreamerTypes::RequestStatus::Completed;
  75. });
  76. ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
  77. .WillByDefault([this](
  78. [[maybe_unused]] FileRequestHandle request,
  79. void*& buffer,
  80. AZ::u64& numBytesRead,
  81. IStreamerTypes::ClaimMemory claimMemory)
  82. {
  83. // Make sure the requestor plans to free the data buffer we allocated.
  84. EXPECT_EQ(claimMemory, IStreamerTypes::ClaimMemory::Yes);
  85. // Provide valid data buffer results.
  86. numBytesRead = m_data.m_size;
  87. buffer = m_data.m_address;
  88. // Clear out our stored values for this, because we're handing off ownership to the caller.
  89. m_data.m_address = nullptr;
  90. m_data.m_size = 0;
  91. return true;
  92. });
  93. ON_CALL(m_mockStreamer, RescheduleRequest(_, _, _))
  94. .WillByDefault([this](IO::FileRequestPtr target, AZStd::chrono::microseconds newDeadline, IO::IStreamerTypes::Priority newPriority)
  95. {
  96. m_deadline = newDeadline;
  97. m_priority = newPriority;
  98. return target;
  99. });
  100. }
  101. ~StreamerWrapper() = default;
  102. ::testing::NiceMock<StreamerMock> m_mockStreamer;
  103. AZ::IO::IStreamerTypes::Deadline m_deadline;
  104. AZ::IO::IStreamerTypes::Priority m_priority;
  105. IO::StreamerContext m_context;
  106. AZ::IO::IStreamer::OnCompleteCallback m_callback;
  107. IO::FileRequestPtr m_request;
  108. IO::IStreamerTypes::RequestMemoryAllocatorResult m_data{ nullptr, 0, IO::IStreamerTypes::MemoryType::ReadWrite };
  109. };
  110. // Use a mock asset catalog and asset handler to pretend to create and load an asset, since we don't really care about the data
  111. // inside the asset itself for these tests.
  112. // This subclass overrides the asset information to provide non-zero asset sizes so that the asset load makes it all the way to the
  113. // mocked-out streamer class, so that we can track information about streamer deadline and priority changes.
  114. // This subclass also provides facilities for getting/setting the default deadline and priority so that we can test the usage of
  115. // those values as well.
  116. class MockLoadAssetWithNonZeroSizeCatalogAndHandler
  117. : public MockLoadAssetCatalogAndHandler
  118. {
  119. public:
  120. AZ_CLASS_ALLOCATOR(MockLoadAssetWithNonZeroSizeCatalogAndHandler, AZ::SystemAllocator)
  121. MockLoadAssetWithNonZeroSizeCatalogAndHandler(
  122. AZStd::unordered_set<AZ::Data::AssetId> ids
  123. , AZ::Data::AssetType assetType
  124. , AZ::Data::AssetPtr(*createAsset)()
  125. , void(*destroyAsset)(AZ::Data::AssetPtr asset))
  126. : MockLoadAssetCatalogAndHandler(ids, assetType, createAsset, destroyAsset)
  127. {
  128. }
  129. // Overridden to provide a non-zero asset size so that the asset load makes it to the streamer.
  130. AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
  131. {
  132. AZ::Data::AssetInfo result;
  133. if (m_ids.contains(id))
  134. {
  135. result.m_assetType = m_assetType;
  136. result.m_assetId = id;
  137. result.m_sizeBytes = sizeof(EmptyAsset);
  138. }
  139. return result;
  140. }
  141. // Overridden to provide a non-zero size and non-empty name so that the asset load makes it to the streamer.
  142. AZ::Data::AssetStreamInfo GetStreamInfoForLoad([[maybe_unused]] const AZ::Data::AssetId& id,
  143. [[maybe_unused]] const AZ::Data::AssetType& type) override
  144. {
  145. AZ::Data::AssetStreamInfo info;
  146. info.m_dataLen = sizeof(EmptyAsset);
  147. info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
  148. info.m_streamName = "test";
  149. return info;
  150. }
  151. // Provides controllable default values for deadlines and priorities.
  152. void GetDefaultAssetLoadPriority([[maybe_unused]] AssetType type, AZ::IO::IStreamerTypes::Deadline& defaultDeadline,
  153. AZ::IO::IStreamerTypes::Priority& defaultPriority) const override
  154. {
  155. defaultDeadline = GetDefaultDeadline();
  156. defaultPriority = GetDefaultPriority();
  157. }
  158. AZStd::chrono::milliseconds GetDefaultDeadline() const { return m_defaultDeadline; }
  159. AZ::IO::IStreamerTypes::Priority GetDefaultPriority() const { return m_defaultPriority; }
  160. void SetDefaultDeadline(AZStd::chrono::milliseconds deadline) { m_defaultDeadline = deadline; }
  161. void SetDefaultPriority(AZ::IO::IStreamerTypes::Priority priority) { m_defaultPriority = priority; }
  162. protected:
  163. AZStd::chrono::milliseconds m_defaultDeadline{ AZStd::chrono::milliseconds(0) };
  164. AZ::IO::IStreamerTypes::Priority m_defaultPriority{ AZ::IO::IStreamerTypes::s_priorityLowest };
  165. };
  166. // Tests that validate the interaction between AssetManager and the IO Streamer
  167. struct AssetManagerStreamerTests
  168. : BaseAssetManagerTest
  169. {
  170. static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
  171. void SetUp() override
  172. {
  173. BaseAssetManagerTest::SetUp();
  174. // create the database
  175. AssetManager::Descriptor desc;
  176. AssetManager::Create(desc);
  177. }
  178. void TearDown() override
  179. {
  180. // This will also delete m_assetHandlerAndCatalog
  181. AssetManager::Destroy();
  182. BaseAssetManagerTest::TearDown();
  183. }
  184. size_t GetNumJobManagerThreads() const override
  185. {
  186. return 1;
  187. }
  188. // Create a mock streamer instead of a real one.
  189. IO::IStreamer* CreateStreamer() override
  190. {
  191. m_mockStreamer = AZStd::make_unique<StreamerWrapper>();
  192. return &(m_mockStreamer->m_mockStreamer);
  193. }
  194. void DestroyStreamer([[maybe_unused]] IO::IStreamer* streamer) override
  195. {
  196. m_mockStreamer = nullptr;
  197. }
  198. AZStd::unique_ptr<StreamerWrapper> m_mockStreamer;
  199. };
  200. TEST_F(AssetManagerStreamerTests, LoadReschedule)
  201. {
  202. struct DeadlinePriorityTest
  203. {
  204. // The deadline / priority values to request for this test
  205. AZStd::chrono::milliseconds m_requestDeadline;
  206. AZ::IO::IStreamerTypes::Priority m_requestPriority;
  207. // The expected value of the deadline / priority after the request
  208. AZStd::chrono::milliseconds m_resultDeadline;
  209. AZ::IO::IStreamerTypes::Priority m_resultPriority;
  210. };
  211. DeadlinePriorityTest tests[] =
  212. {
  213. // Initial asset request with deadline and priority.
  214. // Results should match what was requested.
  215. {AZStd::chrono::milliseconds(1000), AZ::IO::IStreamerTypes::s_priorityLow,
  216. AZStd::chrono::milliseconds(1000), AZ::IO::IStreamerTypes::s_priorityLow},
  217. // Make the deadline longer and the priority higher.
  218. // Only the priority should change.
  219. {AZStd::chrono::milliseconds(1500), AZ::IO::IStreamerTypes::s_priorityHigh,
  220. AZStd::chrono::milliseconds(1000), AZ::IO::IStreamerTypes::s_priorityHigh},
  221. // Make the deadline shorter and the priority lower.
  222. // Only the deadline should change.
  223. {AZStd::chrono::milliseconds(500), AZ::IO::IStreamerTypes::s_priorityLow,
  224. AZStd::chrono::milliseconds(500), AZ::IO::IStreamerTypes::s_priorityHigh},
  225. // Make the deadline shorter and the priority higher.
  226. // Both the deadline and the priority should change.
  227. {AZStd::chrono::milliseconds(250), AZ::IO::IStreamerTypes::s_priorityHigh + 1,
  228. AZStd::chrono::milliseconds(250), AZ::IO::IStreamerTypes::s_priorityHigh + 1},
  229. };
  230. // The deadline needs to be shorter than the last test scenario, and the priority higher, so that we can
  231. // verify that these values are actually used in our final test scenario.
  232. AZStd::chrono::milliseconds assetHandlerDefaultDeadline = AZStd::chrono::milliseconds(200);
  233. AZ::IO::IStreamerTypes::Priority assetHandlerDefaultPriority = AZ::IO::IStreamerTypes::s_priorityHigh + 2;
  234. UnitTest::MockLoadAssetWithNonZeroSizeCatalogAndHandler testAssetCatalog(
  235. { MyAsset1Id },
  236. azrtti_typeid<EmptyAsset>(),
  237. []() { return AssetPtr(aznew EmptyAsset()); },
  238. [](AssetPtr ptr) { delete ptr; }
  239. );
  240. {
  241. AssetLoadParameters loadParams;
  242. AZ::Data::Asset<EmptyAsset> asset1;
  243. // Run through every test scenario and verify that the results match expectations.
  244. for (auto& test : tests)
  245. {
  246. loadParams.m_deadline = test.m_requestDeadline;
  247. loadParams.m_priority = test.m_requestPriority;
  248. asset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default, loadParams);
  249. ASSERT_TRUE(asset1);
  250. EXPECT_EQ(m_mockStreamer->m_deadline, test.m_resultDeadline);
  251. EXPECT_EQ(m_mockStreamer->m_priority, test.m_resultPriority);
  252. }
  253. // Final scenario: Request another load with no deadline or priority set.
  254. // This should use the defaults from the asset handler.
  255. loadParams.m_deadline = {};
  256. loadParams.m_priority = {};
  257. testAssetCatalog.SetDefaultDeadline(assetHandlerDefaultDeadline);
  258. testAssetCatalog.SetDefaultPriority(assetHandlerDefaultPriority);
  259. // (Verify that we've chosen a shorter deadline and higher priority for our defaults than our current state)
  260. EXPECT_LT(assetHandlerDefaultDeadline, m_mockStreamer->m_deadline);
  261. EXPECT_GT(assetHandlerDefaultPriority, m_mockStreamer->m_priority);
  262. asset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MyAsset1Id, AZ::Data::AssetLoadBehavior::Default, loadParams);
  263. ASSERT_TRUE(asset1);
  264. EXPECT_EQ(m_mockStreamer->m_deadline, assetHandlerDefaultDeadline);
  265. EXPECT_EQ(m_mockStreamer->m_priority, assetHandlerDefaultPriority);
  266. // Run callback to cleanup and wait for the Asset Manager to finish processing the loaded asset.
  267. m_mockStreamer->m_callback(m_mockStreamer->m_request);
  268. asset1.BlockUntilLoadComplete();
  269. // Allow the asset manager to finish processing the "OnAssetReady" event so that it doesn't hold extra
  270. // references to the asset. This allows the asset to get cleaned up correctly at the end of the test.
  271. AssetManager::Instance().DispatchEvents();
  272. // Clear out our pointers so that they clean themselves up and release any references to the loading asset.
  273. // If we didn't do this, the asset might not get cleaned up until the mock streamer is deleted, which happens
  274. // after the asset manager shuts down. This would cause asserts and potentially a crash.
  275. m_mockStreamer->m_callback = nullptr;
  276. m_mockStreamer->m_request = nullptr;
  277. }
  278. }
  279. // The AssetManagerStreamerImmediateCompletionTests class adjusts the asset loading to force it to complete immediately,
  280. // while still within the callstack for GetAsset(). This can be used to test various conditions in which the load thread
  281. // completes more rapidly than expected, and can expose subtle race conditions.
  282. // There are a few key things that this class does to make this work:
  283. // - The file I/O streamer is mocked
  284. // - The asset stream data is mocked to a 0-byte length for the asset so that the stream load will bypass the I/O streamer and
  285. // just immediately return completion.
  286. // - The number of JobManager threads is set to 0, forcing jobs to execute synchronously inline when they are started.
  287. // With these changes, GetAssetInternal() will queue the stream, which will immediately call the callback that creates LoadAssetJob,
  288. // which immediately executes in-place to process the asset due to the synchronous JobManager.
  289. // Note that if we just created the asset in a Ready state, most of the asset loading code is completely bypassed, and so we
  290. // wouldn't be able to test for race conditions in the AssetContainer.
  291. //
  292. // This class also unregisters the catalog and asset handler before shutting down the asset manager. This is done to catch
  293. // any outstanding asset references that exist due to loads not completing and cleaning up successfully.
  294. struct AssetManagerStreamerImmediateCompletionTests : public BaseAssetManagerTest,
  295. public AZ::Data::AssetCatalogRequestBus::Handler,
  296. public AZ::Data::AssetHandler,
  297. public AZ::Data::AssetCatalog
  298. {
  299. static inline const AZ::Uuid TestAssetId{"{E970B177-5F45-44EB-A2C4-9F29D9A0B2A2}"};
  300. static inline const AZ::Uuid MissingAssetId{"{11111111-1111-1111-1111-111111111111}"};
  301. static inline constexpr AZStd::string_view TestAssetPath = "test";
  302. void SetUp() override
  303. {
  304. BaseAssetManagerTest::SetUp();
  305. AssetManager::Descriptor desc;
  306. AssetManager::Create(desc);
  307. // Register the handler and catalog after creation, because we intend to destroy them before AssetManager destruction.
  308. // The specific asset we load is irrelevant, so register EmptyAsset.
  309. AZ::Data::AssetManager::Instance().RegisterHandler(this, AZ::AzTypeInfo<EmptyAsset>::Uuid());
  310. AZ::Data::AssetManager::Instance().RegisterCatalog(this, AZ::AzTypeInfo<EmptyAsset>::Uuid());
  311. // Intercept messages for finding assets by name so that we can mock out the asset we're loading.
  312. AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
  313. }
  314. void TearDown() override
  315. {
  316. // Unregister before destroying AssetManager.
  317. // This will catch any assets that got stuck in a loading state without getting cleaned up.
  318. AZ::Data::AssetManager::Instance().UnregisterCatalog(this);
  319. AZ::Data::AssetManager::Instance().UnregisterHandler(this);
  320. AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
  321. AssetManager::Destroy();
  322. BaseAssetManagerTest::TearDown();
  323. }
  324. size_t GetNumJobManagerThreads() const override
  325. {
  326. // Return 0 threads so that the Job Manager executes jobs synchronously inline. This lets us finish a load while still
  327. // in the callstack that initiates the load.
  328. return 0;
  329. }
  330. // Create a mock streamer instead of a real one, since we don't really want to load an asset.
  331. IO::IStreamer* CreateStreamer() override
  332. {
  333. m_mockStreamer = AZStd::make_unique<StreamerWrapper>();
  334. return &(m_mockStreamer->m_mockStreamer);
  335. }
  336. void DestroyStreamer([[maybe_unused]] IO::IStreamer* streamer) override
  337. {
  338. m_mockStreamer = nullptr;
  339. }
  340. // AssetHandler implementation
  341. // Minimalist mock to create a new EmptyAsset with the desired asset ID.
  342. AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
  343. {
  344. return new EmptyAsset(id);
  345. }
  346. void DestroyAsset(AZ::Data::AssetPtr ptr) override
  347. {
  348. delete ptr;
  349. }
  350. // The mocked-out Asset Catalog handles EmptyAsset types.
  351. void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override
  352. {
  353. assetTypes.push_back(AZ::AzTypeInfo<EmptyAsset>::Uuid());
  354. }
  355. // This is a mocked-out load, so just immediately return completion without doing anything.
  356. AZ::Data::AssetHandler::LoadResult LoadAssetData(
  357. [[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
  358. [[maybe_unused]] AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
  359. [[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
  360. {
  361. return AZ::Data::AssetHandler::LoadResult::LoadComplete;
  362. }
  363. // AssetCatalogRequestBus implementation
  364. // Minimalist mocks to provide our desired asset path or asset id
  365. AZStd::string GetAssetPathById(const AZ::Data::AssetId& id) override
  366. {
  367. if (id == TestAssetId)
  368. {
  369. return TestAssetPath;
  370. }
  371. return "";
  372. }
  373. AZ::Data::AssetId GetAssetIdByPath(
  374. const char* path, [[maybe_unused]] const AZ::Data::AssetType& typeToRegister,
  375. [[maybe_unused]] bool autoRegisterIfNotFound) override
  376. {
  377. if (path == TestAssetPath)
  378. {
  379. return TestAssetId;
  380. }
  381. return AZ::Data::AssetId();
  382. }
  383. // Return the mocked-out information for our test asset
  384. AZ::Data::AssetInfo GetAssetInfoById(const AZ::Data::AssetId& id) override
  385. {
  386. AZ::Data::AssetInfo assetInfo;
  387. if (id == TestAssetId)
  388. {
  389. assetInfo.m_assetId = TestAssetId;
  390. assetInfo.m_assetType = AZ::AzTypeInfo<EmptyAsset>::Uuid();
  391. assetInfo.m_relativePath = TestAssetPath;
  392. }
  393. return assetInfo;
  394. }
  395. // AssetCatalog implementation
  396. // Set the mocked-out asset load to have a 0-byte length so that the load skips I/O and immediately returns success
  397. AZ::Data::AssetStreamInfo GetStreamInfoForLoad(
  398. const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
  399. {
  400. EXPECT_TRUE(type == AZ::AzTypeInfo<EmptyAsset>::Uuid());
  401. AZ::Data::AssetStreamInfo info;
  402. info.m_dataOffset = 0;
  403. info.m_dataLen = 0;
  404. info.m_streamFlags = AZ::IO::OpenMode::ModeRead;
  405. if (id == TestAssetId)
  406. {
  407. info.m_streamName = TestAssetPath;
  408. }
  409. return info;
  410. }
  411. AZStd::unique_ptr<StreamerWrapper> m_mockStreamer;
  412. };
  413. // This test will verify that even if the asset loading stream/job returns immediately, all of the loading
  414. // code works successfully. The test here is fairly simple - it just loads the asset and verifies that it
  415. // loaded successfully. The bulk of the test is really in the setup class above, where the load is forced
  416. // to complete immediately. Also, the true failure condition is caught in the setup class too, which is
  417. // the presence of any assets at the point that the asset handler is unregistered. If they're present, then
  418. // the immediate load wasn't truly successful, as it left around extra references to the asset that haven't
  419. // been cleaned up.
  420. TEST_F(AssetManagerStreamerImmediateCompletionTests, LoadAssetWithImmediateJobCompletion_WorksSuccessfully)
  421. {
  422. AZ::Data::AssetLoadParameters loadParams;
  423. auto testAsset =
  424. AssetManager::Instance().GetAsset<EmptyAsset>(TestAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
  425. AZ::Data::AssetManager::Instance().DispatchEvents();
  426. EXPECT_TRUE(testAsset.IsReady());
  427. }
  428. // This test verifies that even if the asset loading returns immediately with an error, all of the loading code works
  429. // successfully. The test itself loads a missing asset twice - the first time is a non-immediate error, where the error
  430. // isn't reported until the DispatchEvents() call. The second time is an immediate error, because now the asset is already
  431. // registered in an Error state. If the test fails, it will likely get caught in the shutdown of the test class, if any
  432. // assets still exist at the point that the asset handler is unregistered. If they're present, then handling of the immediate
  433. // error didn't work, as it left around extra references to the asset that haven't been cleaned up.
  434. TEST_F(AssetManagerStreamerImmediateCompletionTests, ImmediateAssetError_WorksSuccessfully)
  435. {
  436. AZ::Data::AssetLoadParameters loadParams;
  437. // Attempt to load a missing asset the first time. It will get an error, but not until the DispatchEvents() call happens.
  438. auto testAsset1 = AssetManager::Instance().GetAsset<EmptyAsset>(MissingAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
  439. AZ::Data::AssetManager::Instance().DispatchEvents();
  440. EXPECT_TRUE(testAsset1.IsError());
  441. // While the reference to the missing asset still exists, try to get it again. This will cause a more immediate error in
  442. // the AssetContainer code, which should still get handled correctly. In the failure condition, it will instead leave the
  443. // AssetContainer in a state where it never sends the final OnAssetContainerReady/Canceled message.
  444. auto testAsset2 = AssetManager::Instance().GetAsset<EmptyAsset>(MissingAssetId, AZ::Data::AssetLoadBehavior::Default, loadParams);
  445. AZ::Data::AssetManager::Instance().DispatchEvents();
  446. EXPECT_TRUE(testAsset2.IsError());
  447. }
  448. } // namespace UnitTest