AssetDataStreamTests.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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/AssetDataStream.h>
  9. #include <AzCore/IO/Streamer/FileRequest.h>
  10. #include <AzCore/UnitTest/TestTypes.h>
  11. #include <AZTestShared/Utils/Utils.h>
  12. #include <Tests/Streamer/IStreamerMock.h>
  13. class AssetDataStreamTest
  14. : public UnitTest::LeakDetectionFixture
  15. {
  16. public:
  17. void SetUp() override
  18. {
  19. using ::testing::_;
  20. using ::testing::NiceMock;
  21. using ::testing::Return;
  22. UnitTest::LeakDetectionFixture::SetUp();
  23. AZ::Interface<AZ::IO::IStreamer>::Register(&m_mockStreamer);
  24. // Reroute enough mock streamer calls to this class to let us validate the input parameters and mock
  25. // out a "functioning" read request.
  26. ON_CALL(m_mockStreamer, Read(_,::testing::An<IStreamerTypes::RequestMemoryAllocator&>(),_,_,_,_))
  27. .WillByDefault([this](
  28. AZStd::string_view relativePath,
  29. IStreamerTypes::RequestMemoryAllocator& allocator,
  30. size_t size,
  31. AZStd::chrono::microseconds deadline,
  32. IStreamerTypes::Priority priority,
  33. size_t offset)
  34. {
  35. // Save off all the input parameters to the read request so that we can validate that they match expectations.
  36. m_allocator = &allocator;
  37. m_relativePath = relativePath;
  38. m_size = size;
  39. m_deadline = deadline;
  40. m_priority = priority;
  41. m_offset = offset;
  42. return nullptr;
  43. });
  44. ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _))
  45. .WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr&
  46. {
  47. // Save off the callback just so that we can call it when the request is "done"
  48. m_callback = callback;
  49. return request;
  50. });
  51. ON_CALL(m_mockStreamer, QueueRequest(_))
  52. .WillByDefault([this](const FileRequestPtr& request)
  53. {
  54. // As soon as the request is queued to run, consider it "done" and call the callback
  55. if (m_callback)
  56. {
  57. FileRequestHandle handle(request);
  58. m_callback(handle);
  59. }
  60. });
  61. ON_CALL(m_mockStreamer, GetRequestStatus(_))
  62. .WillByDefault([this]([[maybe_unused]] FileRequestHandle request)
  63. {
  64. // Return whatever request status has been set in this class
  65. return m_requestStatus;
  66. });
  67. ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
  68. .WillByDefault([this](
  69. [[maybe_unused]] FileRequestHandle request,
  70. void*& buffer,
  71. AZ::u64& numBytesRead,
  72. [[maybe_unused]] IStreamerTypes::ClaimMemory claimMemory)
  73. {
  74. auto result = m_allocator->Allocate(m_size, m_size, AZCORE_GLOBAL_NEW_ALIGNMENT);
  75. m_buffer = static_cast<AZ::u8*>(result.m_address);
  76. memset(m_buffer, m_expectedBufferChar, m_size);
  77. numBytesRead = m_size;
  78. buffer = m_buffer;
  79. return true;
  80. });
  81. }
  82. void TearDown() override
  83. {
  84. AZ::Interface<AZ::IO::IStreamer>::Unregister(&m_mockStreamer);
  85. UnitTest::LeakDetectionFixture::TearDown();
  86. }
  87. protected:
  88. ::testing::NiceMock<StreamerMock> m_mockStreamer;
  89. AZStd::string_view m_relativePath;
  90. size_t m_size{ 0 };
  91. AZStd::chrono::microseconds m_deadline{ 0 };
  92. IStreamerTypes::Priority m_priority{ IStreamerTypes::s_priorityLowest };
  93. size_t m_offset{ 0 };
  94. AZ::u8* m_buffer{ nullptr };
  95. IStreamerTypes::RequestStatus m_requestStatus{ IStreamerTypes::RequestStatus::Completed };
  96. AZ::IO::IStreamer::OnCompleteCallback m_callback{};
  97. IStreamerTypes::RequestMemoryAllocator* m_allocator{ nullptr };
  98. // Define some arbitrary numbers for validating buffer contents
  99. static inline constexpr AZ::u8 m_expectedBufferChar{ 0xFE };
  100. static inline constexpr AZ::u8 m_badBufferChar{ 0xFD };
  101. };
  102. TEST_F(AssetDataStreamTest, Init_CreateTrivialInstance_CreationSuccessful)
  103. {
  104. AZ::Data::AssetDataStream assetDataStream;
  105. // AssetDataStream is a read-only stream
  106. EXPECT_TRUE(assetDataStream.CanRead());
  107. EXPECT_FALSE(assetDataStream.CanWrite());
  108. // AssetDataStream only supports forward seeking, not arbitrary seeking, so CanSeek() should be false.
  109. EXPECT_FALSE(assetDataStream.CanSeek());
  110. }
  111. TEST_F(AssetDataStreamTest, Open_OpenAndCopyBuffer_BufferCopied)
  112. {
  113. // Pick an arbitrary buffer size
  114. constexpr int bufferSize = 100;
  115. // Create a buffer filled with an expected charater
  116. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  117. // Create an assetDataStream and *copy* the buffer into it
  118. AZ::Data::AssetDataStream assetDataStream;
  119. assetDataStream.Open(buffer);
  120. // Assign the buffer to a different, unexpected character
  121. buffer.assign(bufferSize, m_badBufferChar);
  122. // Create a new buffer, fill it with a bad character, and read the data from the assetDataStream into it
  123. AZStd::vector<AZ::u8> outBuffer(bufferSize, m_badBufferChar);
  124. AZ::IO::SizeType bytesRead = assetDataStream.Read(outBuffer.size(), outBuffer.data());
  125. // Validate that we read the correct number of bytes
  126. EXPECT_EQ(bytesRead, outBuffer.size());
  127. // Validate that the data read back does *not* match the invalid data.
  128. // This validates that the buffer got copied and not directly used.
  129. EXPECT_NE(buffer, outBuffer);
  130. // Validate that the data read back *does* match the original valid data.
  131. buffer.assign(bufferSize, m_expectedBufferChar);
  132. EXPECT_EQ(buffer, outBuffer);
  133. assetDataStream.Close();
  134. }
  135. TEST_F(AssetDataStreamTest, Open_OpenAndUseBuffer_BufferUsed)
  136. {
  137. // Pick an arbitrary buffer size
  138. constexpr int bufferSize = 100;
  139. // Create a buffer filled with an expected charater
  140. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  141. // Create an assetDataStream and *move* the buffer into it
  142. AZ::Data::AssetDataStream assetDataStream;
  143. assetDataStream.Open(AZStd::move(buffer));
  144. // The buffer should be moved, so it should no longer contain data.
  145. EXPECT_TRUE(buffer.empty());
  146. // Create a new buffer, fill it with a bad character, and read the data from the assetDataStream into it
  147. AZStd::vector<AZ::u8> outBuffer(bufferSize, m_badBufferChar);
  148. AZ::IO::SizeType bytesRead = assetDataStream.Read(outBuffer.size(), outBuffer.data());
  149. // Validate that we read the correct number of bytes
  150. EXPECT_EQ(bytesRead, outBuffer.size());
  151. // Validate that the data read back matches the original valid data.
  152. buffer.assign(bufferSize, m_expectedBufferChar);
  153. EXPECT_EQ(buffer, outBuffer);
  154. assetDataStream.Close();
  155. }
  156. TEST_F(AssetDataStreamTest, Open_OpenAndReadFile_FileReadSuccessfully)
  157. {
  158. // Choose some non-standard input values to pass in to our Open() request.
  159. const AZStd::string filePath("path/test");
  160. const size_t fileOffset = 100;
  161. const size_t assetSize = 500;
  162. const AZStd::chrono::milliseconds deadline(1000);
  163. const AZ::IO::IStreamerTypes::Priority priority(AZ::IO::IStreamerTypes::s_priorityHigh);
  164. // Keep track of whether or not our callback gets called.
  165. bool callbackCalled = false;
  166. AZ::IO::IStreamerTypes::RequestStatus callbackStatus;
  167. // Create a callback to call on load completion.
  168. AZ::Data::AssetDataStream::OnCompleteCallback loadCallback =
  169. [&callbackCalled, &callbackStatus](AZ::IO::IStreamerTypes::RequestStatus status)
  170. {
  171. callbackCalled = true;
  172. callbackStatus = status;
  173. };
  174. // Create an AssetDataStream, create a file open request, and wait for it to finish.
  175. AZ::Data::AssetDataStream assetDataStream;
  176. assetDataStream.Open(filePath, fileOffset, assetSize, deadline, priority, loadCallback);
  177. assetDataStream.BlockUntilLoadComplete();
  178. // Validate that the AssetDataStream passed our input parameters correctly to the file streamer
  179. EXPECT_EQ(filePath, m_relativePath);
  180. EXPECT_EQ(assetSize, m_size);
  181. EXPECT_EQ(deadline, m_deadline);
  182. EXPECT_EQ(priority, m_priority);
  183. EXPECT_EQ(fileOffset, m_offset);
  184. // Validate that our completion callback got called
  185. EXPECT_TRUE(callbackCalled);
  186. EXPECT_EQ(callbackStatus, m_requestStatus);
  187. // Create a new buffer, fill it with a bad character, and read the data from the assetDataStream into it
  188. AZStd::vector<AZ::u8> outBuffer(assetSize, m_badBufferChar);
  189. AZ::IO::SizeType bytesRead = assetDataStream.Read(outBuffer.size(), outBuffer.data());
  190. // Validate that we read the correct number of bytes
  191. EXPECT_EQ(bytesRead, outBuffer.size());
  192. // Validate that the data read back matches the original valid data.
  193. EXPECT_EQ(memcmp(m_buffer, outBuffer.data(), bytesRead), 0);
  194. assetDataStream.Close();
  195. }
  196. TEST_F(AssetDataStreamTest, IsOpen_OpenAndCloseStream_OnlyTrueWhileOpen)
  197. {
  198. // Pick an arbitrary buffer size
  199. constexpr int bufferSize = 100;
  200. // Create a buffer filled with an expected charater
  201. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  202. AZ::Data::AssetDataStream assetDataStream;
  203. EXPECT_FALSE(assetDataStream.IsOpen());
  204. assetDataStream.Open(AZStd::move(buffer));
  205. EXPECT_TRUE(assetDataStream.IsOpen());
  206. assetDataStream.Close();
  207. EXPECT_FALSE(assetDataStream.IsOpen());
  208. }
  209. TEST_F(AssetDataStreamTest, IsFullyLoaded_OpenStreamFromBuffer_DataIsFullyLoaded)
  210. {
  211. // Pick an arbitrary buffer size
  212. constexpr int bufferSize = 100;
  213. // Create a buffer filled with an expected charater
  214. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  215. AZ::Data::AssetDataStream assetDataStream;
  216. EXPECT_FALSE(assetDataStream.IsFullyLoaded());
  217. EXPECT_EQ(assetDataStream.GetLoadedSize(), 0);
  218. EXPECT_EQ(assetDataStream.GetLength(), 0);
  219. assetDataStream.Open(AZStd::move(buffer));
  220. EXPECT_TRUE(assetDataStream.IsFullyLoaded());
  221. EXPECT_EQ(assetDataStream.GetLoadedSize(), bufferSize);
  222. EXPECT_EQ(assetDataStream.GetLength(), bufferSize);
  223. assetDataStream.Close();
  224. EXPECT_FALSE(assetDataStream.IsFullyLoaded());
  225. EXPECT_EQ(assetDataStream.GetLoadedSize(), 0);
  226. EXPECT_EQ(assetDataStream.GetLength(), 0);
  227. }
  228. TEST_F(AssetDataStreamTest, IsFullyLoaded_FileDoesNotReadAllData_DataIsNotFullyLoaded)
  229. {
  230. // Set up some arbitrary file parameters.
  231. const AZStd::string filePath("path/test");
  232. const size_t fileOffset = 0;
  233. const size_t assetSize = 500;
  234. // Pick a size less than assetSize to represent the amount of data actually loaded
  235. const size_t incompleteAssetSize = 200;
  236. using ::testing::_;
  237. ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
  238. .WillByDefault([this](
  239. [[maybe_unused]] FileRequestHandle request,
  240. void*& buffer,
  241. AZ::u64& numBytesRead,
  242. [[maybe_unused]] IStreamerTypes::ClaimMemory claimMemory)
  243. {
  244. // On the request for the read result, create a size and buffer that's less than the requested amount.
  245. m_size = incompleteAssetSize;
  246. auto result = m_allocator->Allocate(m_size, m_size, AZCORE_GLOBAL_NEW_ALIGNMENT);
  247. m_buffer = static_cast<AZ::u8*>(result.m_address);
  248. memset(m_buffer, m_expectedBufferChar, m_size);
  249. numBytesRead = m_size;
  250. buffer = m_buffer;
  251. return true;
  252. });
  253. // Create an AssetDataStream, create a file open request, and wait for it to finish.
  254. AZ::Data::AssetDataStream assetDataStream;
  255. // We expect one error message during the load due to the incomplete file load.
  256. AZ_TEST_START_TRACE_SUPPRESSION;
  257. assetDataStream.Open(filePath, fileOffset, assetSize);
  258. assetDataStream.BlockUntilLoadComplete();
  259. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  260. // Verify that the data reports back the incomplete size for loaded size, and that it is not fully loaded.
  261. EXPECT_FALSE(assetDataStream.IsFullyLoaded());
  262. EXPECT_EQ(assetDataStream.GetLoadedSize(), incompleteAssetSize);
  263. // GetLength should still report back the expected size instead of the loaded size
  264. EXPECT_EQ(assetDataStream.GetLength(), assetSize);
  265. assetDataStream.Close();
  266. }
  267. TEST_F(AssetDataStreamTest, Write_TryWritingToStream_WritingCausesAsserts)
  268. {
  269. // Create an arbitrary buffer
  270. constexpr int bufferSize = 100;
  271. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  272. // Create a data stream from the buffer
  273. AZ::Data::AssetDataStream assetDataStream;
  274. assetDataStream.Open(buffer);
  275. // We should get an error when trying to write to the stream.
  276. AZ_TEST_START_ASSERTTEST;
  277. assetDataStream.Write(bufferSize, buffer.data());
  278. AZ_TEST_STOP_ASSERTTEST(1);
  279. assetDataStream.Close();
  280. }
  281. TEST_F(AssetDataStreamTest, GetFilename_StreamOpenedWithFile_FileNameMatches)
  282. {
  283. // Set up some arbitrary file parameters.
  284. const AZStd::string filePath("path/test");
  285. const size_t fileOffset = 0;
  286. const size_t assetSize = 500;
  287. // Create an AssetDataStream, create a file open request, and wait for it to finish.
  288. AZ::Data::AssetDataStream assetDataStream;
  289. assetDataStream.Open(filePath, fileOffset, assetSize);
  290. assetDataStream.BlockUntilLoadComplete();
  291. // Verify that the stream has the expected filename
  292. EXPECT_EQ(strcmp(assetDataStream.GetFilename(), filePath.c_str()), 0);
  293. assetDataStream.Close();
  294. }
  295. TEST_F(AssetDataStreamTest, GetFilename_StreamOpenedWithMemoryBuffer_FileNameIsEmpty)
  296. {
  297. // Create an arbitrary buffer
  298. constexpr int bufferSize = 100;
  299. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  300. // Create an AssetDataStream from the memory buffer
  301. AZ::Data::AssetDataStream assetDataStream;
  302. assetDataStream.Open(buffer);
  303. // Verify that the stream has no filename
  304. EXPECT_EQ(strcmp(assetDataStream.GetFilename(), ""), 0);
  305. assetDataStream.Close();
  306. }
  307. TEST_F(AssetDataStreamTest, BlockUntilLoadComplete_BlockWhenOpenedWithMemoryBuffer_BlockReturnsSuccessfully)
  308. {
  309. // Create an arbitrary buffer
  310. constexpr int bufferSize = 100;
  311. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  312. // Create an AssetDataStream from the memory buffer
  313. AZ::Data::AssetDataStream assetDataStream;
  314. assetDataStream.Open(buffer);
  315. // Verify that calling BlockUntilLoadComplete doesn't cause problems when used with a memory buffer instead of a file.
  316. assetDataStream.BlockUntilLoadComplete();
  317. assetDataStream.Close();
  318. }
  319. TEST_F(AssetDataStreamTest, Read_ReadDataIncrementally_PartialDataReadSuccessfully)
  320. {
  321. // Create an arbitrary buffer with different data in every byte
  322. constexpr int bufferSize = 256;
  323. AZStd::vector<AZ::u8> buffer(bufferSize);
  324. for (int offset = 0; offset < bufferSize; offset++)
  325. {
  326. // Use the lowest 8 bits of offset to get a repeating pattern of 00 -> FF
  327. buffer[offset] = offset & 0xFF;
  328. }
  329. // Create an AssetDataStream from the memory buffer
  330. AZ::Data::AssetDataStream assetDataStream;
  331. assetDataStream.Open(buffer);
  332. for (int offset = 0; offset < bufferSize; offset++)
  333. {
  334. // Verify that the current position increments correctly on each read request
  335. EXPECT_EQ(offset, assetDataStream.GetCurPos());
  336. AZ::u8 byte;
  337. auto bytesRead = assetDataStream.Read(1, &byte);
  338. // Verify that when we read one byte at a time, it's incrementing forward through the data set and getting the correct byte.
  339. EXPECT_EQ(bytesRead, 1);
  340. EXPECT_EQ(byte, buffer[offset]);
  341. }
  342. assetDataStream.Close();
  343. }
  344. TEST_F(AssetDataStreamTest, Seek_SeekForward_SeekingForwardWorksSuccessfully)
  345. {
  346. // Create an arbitrary buffer with different data in every byte
  347. constexpr int bufferSize = 256;
  348. AZStd::vector<AZ::u8> buffer(bufferSize);
  349. for (int offset = 0; offset < bufferSize; offset++)
  350. {
  351. // Use the lowest 8 bits of offset to get a repeating pattern of 00 -> FF
  352. buffer[offset] = offset & 0xFF;
  353. }
  354. // Create an AssetDataStream from the memory buffer
  355. AZ::Data::AssetDataStream assetDataStream;
  356. assetDataStream.Open(buffer);
  357. // Pick an arbitrary amount to seek forward on every iteration
  358. constexpr int skipForwardBytes = 3;
  359. // The expected offset should increment by the byte read plus the seek on every iteration
  360. constexpr int offsetIncrement = skipForwardBytes + 1;
  361. for (int offset = 0; offset < bufferSize; offset+=offsetIncrement)
  362. {
  363. // Verify that the current position is correct on each read request, even with the seek
  364. EXPECT_EQ(offset, assetDataStream.GetCurPos());
  365. AZ::u8 byte;
  366. assetDataStream.Read(1, &byte);
  367. // Verify that we're getting the byte we expected even with the seek
  368. EXPECT_EQ(byte, buffer[offset]);
  369. assetDataStream.Seek(skipForwardBytes, AZ::IO::GenericStream::SeekMode::ST_SEEK_CUR);
  370. }
  371. assetDataStream.Close();
  372. }
  373. TEST_F(AssetDataStreamTest, Seek_SeekBackward_SeekingBackwardNotAllowed)
  374. {
  375. // Create an arbitrary buffer
  376. constexpr int bufferSize = 100;
  377. AZStd::vector<AZ::u8> buffer(bufferSize, m_expectedBufferChar);
  378. // Create an AssetDataStream from the memory buffer
  379. AZ::Data::AssetDataStream assetDataStream;
  380. assetDataStream.Open(buffer);
  381. // Moving to the start of the file doesn't move, so this should succeed
  382. assetDataStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
  383. // Read a byte
  384. AZ::u8 byte;
  385. assetDataStream.Read(1, &byte);
  386. // Moving to the start of the file now is moving backwards, so this should fail
  387. AZ_TEST_START_ASSERTTEST;
  388. assetDataStream.Seek(0, AZ::IO::GenericStream::SeekMode::ST_SEEK_BEGIN);
  389. AZ_TEST_STOP_ASSERTTEST(1);
  390. assetDataStream.Close();
  391. }