ReadSplitterTests.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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/IO/IStreamerTypes.h>
  9. #include <AzCore/IO/Streamer/ReadSplitter.h>
  10. #include <AzCore/Memory/Memory.h>
  11. #include <AzCore/UnitTest/TestTypes.h>
  12. #include <Tests/FileIOBaseTestTypes.h>
  13. #include <Tests/Streamer/StreamStackEntryConformityTests.h>
  14. #include <Tests/Streamer/StreamStackEntryMock.h>
  15. namespace AZ::IO
  16. {
  17. class ReadSplitterTestDescription :
  18. public StreamStackEntryConformityTestsDescriptor<ReadSplitter>
  19. {
  20. public:
  21. ReadSplitter CreateInstance() override
  22. {
  23. return ReadSplitter(64_kib, AZCORE_GLOBAL_NEW_ALIGNMENT, 1, 0, false, true);
  24. }
  25. bool UsesSlots() const override
  26. {
  27. return false;
  28. }
  29. };
  30. class ReadSplitterWithBufferTestDescription :
  31. public StreamStackEntryConformityTestsDescriptor<ReadSplitter>
  32. {
  33. public:
  34. ReadSplitter CreateInstance() override
  35. {
  36. return ReadSplitter(64_kib, 4096, 512, 5_mib, true, true);
  37. }
  38. bool UsesSlots() const override
  39. {
  40. return true;
  41. }
  42. };
  43. using ReadSplitterTestTypes = ::testing::Types<ReadSplitterTestDescription, ReadSplitterWithBufferTestDescription>;
  44. INSTANTIATE_TYPED_TEST_SUITE_P(Streamer_ReadSplitterConformityTests, StreamStackEntryConformityTests, ReadSplitterTestTypes);
  45. class Streamer_ReadSplitterTest
  46. : public UnitTest::LeakDetectionFixture
  47. {
  48. public:
  49. static constexpr u64 SplitSize = 1_kib;
  50. static constexpr size_t MemoryAlignment = 4096;
  51. static constexpr size_t SizeAlignment = 512;
  52. Streamer_ReadSplitterTest()
  53. : m_mock(AZStd::make_shared<StreamStackEntryMock>())
  54. {
  55. }
  56. void SetUp() override
  57. {
  58. m_prevFileIO = AZ::IO::FileIOBase::GetInstance();
  59. AZ::IO::FileIOBase::SetInstance(&m_fileIO);
  60. }
  61. void TearDown() override
  62. {
  63. if (m_readSplitter)
  64. {
  65. delete m_readSplitter;
  66. m_readSplitter = nullptr;
  67. }
  68. AZ::IO::FileIOBase::SetInstance(m_prevFileIO);
  69. }
  70. void CreateReadSplitter(u64 maxReadSize, u32 memoryAlignment, u32 sizeAlignment, size_t bufferSize,
  71. bool adjustOffset, bool splitAlignedRequests)
  72. {
  73. using ::testing::_;
  74. m_readSplitter = new ReadSplitter(maxReadSize, memoryAlignment, sizeAlignment, bufferSize,
  75. adjustOffset, splitAlignedRequests);
  76. m_readSplitter->SetNext(m_mock);
  77. EXPECT_CALL(*m_mock, SetContext(_));
  78. m_readSplitter->SetContext(m_context);
  79. }
  80. void CreateStandardReadSplitter()
  81. {
  82. CreateReadSplitter(SplitSize, AZCORE_GLOBAL_NEW_ALIGNMENT, 1, 0, false, true);
  83. }
  84. void CreateAlignmentAwareReadSplitter(size_t bufferSize, bool adjustOffset)
  85. {
  86. CreateReadSplitter(SplitSize, MemoryAlignment, SizeAlignment, bufferSize, adjustOffset, true);
  87. }
  88. void CreatePassThroughReadSplitter()
  89. {
  90. // By having no buffer all requests are considered aligned. By turning off splitting
  91. // splitting aligned request this configuration effectively records the alignment state
  92. // and passes the request on to the next stack entry.
  93. CreateReadSplitter(SplitSize, AZCORE_GLOBAL_NEW_ALIGNMENT, 1, 0, false, false);
  94. }
  95. protected:
  96. UnitTest::TestFileIOBase m_fileIO;
  97. FileIOBase* m_prevFileIO{};
  98. StreamerContext m_context;
  99. ReadSplitter* m_readSplitter{ nullptr };
  100. AZStd::shared_ptr<StreamStackEntryMock> m_mock;
  101. };
  102. TEST_F(Streamer_ReadSplitterTest, QueueRequest_LessThanSplitSize_RequestIsForwarded)
  103. {
  104. CreateStandardReadSplitter();
  105. FileRequest* readRequest = m_context.GetNewInternalRequest();
  106. RequestPath path("TestPath");
  107. readRequest->CreateRead(nullptr, nullptr, SplitSize / 2, path, 0, SplitSize / 2);
  108. EXPECT_CALL(*m_mock, QueueRequest(readRequest)).Times(1);
  109. m_readSplitter->QueueRequest(readRequest);
  110. m_context.RecycleRequest(readRequest);
  111. }
  112. TEST_F(Streamer_ReadSplitterTest, QueueRequest_TwiceTheSplitSize_TwoSubRequestsCreated)
  113. {
  114. using ::testing::_;
  115. CreateStandardReadSplitter();
  116. char buffer[SplitSize * 2];
  117. FileRequest* readRequest = m_context.GetNewInternalRequest();
  118. RequestPath path("TestPath");
  119. readRequest->CreateRead(nullptr, buffer, SplitSize * 2, path, 0, SplitSize * 2);
  120. AZStd::vector<FileRequest*> subRequests;
  121. EXPECT_CALL(*m_mock, QueueRequest(_))
  122. .Times(2)
  123. .WillRepeatedly([&subRequests](FileRequest* request) {subRequests.push_back(request); });
  124. m_readSplitter->QueueRequest(readRequest);
  125. for (size_t i=0; i<subRequests.size(); ++i)
  126. {
  127. EXPECT_EQ(subRequests[i]->GetParent(), readRequest);
  128. Requests::ReadData* data = AZStd::get_if<Requests::ReadData>(&subRequests[i]->GetCommand());
  129. ASSERT_NE(nullptr, data);
  130. EXPECT_EQ(SplitSize, data->m_size);
  131. EXPECT_EQ(SplitSize * i, data->m_offset);
  132. EXPECT_EQ(buffer + (SplitSize * i), data->m_output);
  133. EXPECT_EQ(path, data->m_path);
  134. m_context.MarkRequestAsCompleted(subRequests[i]);
  135. }
  136. m_context.FinalizeCompletedRequests();
  137. }
  138. TEST_F(Streamer_ReadSplitterTest, QueueRequest_NoSplitOnAlignedEnabled_RequestIsForwardedWithoutChange)
  139. {
  140. CreatePassThroughReadSplitter();
  141. FileRequest* readRequest = m_context.GetNewInternalRequest();
  142. RequestPath path("TestPath");
  143. readRequest->CreateRead(nullptr, nullptr, SplitSize * 2, path, 0, SplitSize * 2);
  144. EXPECT_CALL(*m_mock, QueueRequest(readRequest)).Times(1);
  145. m_readSplitter->QueueRequest(readRequest);
  146. m_context.RecycleRequest(readRequest);
  147. }
  148. TEST_F(Streamer_ReadSplitterTest, QueueRequest_MoreSubRequestsThanDepenencies_AdditionalDependenciesAreDelayed)
  149. {
  150. using ::testing::_;
  151. CreateStandardReadSplitter();
  152. constexpr size_t batchSize = FileRequest::GetMaxNumDependencies();
  153. constexpr size_t numSubReads = batchSize + 2;
  154. constexpr size_t size = numSubReads * SplitSize;
  155. auto buffer = AZStd::unique_ptr<u8[]>(new u8[size]);
  156. FileRequest* readRequest = m_context.GetNewInternalRequest();
  157. RequestPath path("TestPath");
  158. readRequest->CreateRead(nullptr, buffer.get(), size, path, 0, size);
  159. AZStd::vector<FileRequest*> subRequests;
  160. subRequests.reserve(numSubReads);
  161. EXPECT_CALL(*m_mock, QueueRequest(_))
  162. .Times(batchSize)
  163. .WillRepeatedly([&subRequests](FileRequest* request) { subRequests.push_back(request); });
  164. m_readSplitter->QueueRequest(readRequest);
  165. ASSERT_EQ(subRequests.size(), batchSize);
  166. for (size_t i = 0; i < subRequests.size(); ++i)
  167. {
  168. EXPECT_EQ(subRequests[i]->GetParent(), readRequest);
  169. Requests::ReadData* data = AZStd::get_if<Requests::ReadData>(&subRequests[i]->GetCommand());
  170. ASSERT_NE(nullptr, data);
  171. EXPECT_EQ(SplitSize, data->m_size);
  172. EXPECT_EQ(SplitSize * i, data->m_offset);
  173. EXPECT_EQ(buffer.get() + (SplitSize * i), data->m_output);
  174. EXPECT_EQ(path, data->m_path);
  175. m_context.MarkRequestAsCompleted(subRequests[i]);
  176. }
  177. subRequests.clear();
  178. EXPECT_CALL(*m_mock, QueueRequest(_))
  179. .Times(numSubReads - batchSize)
  180. .WillRepeatedly([&subRequests](FileRequest* request) { subRequests.push_back(request); });
  181. m_context.FinalizeCompletedRequests();
  182. for (size_t i = 0; i < subRequests.size(); ++i)
  183. {
  184. EXPECT_EQ(subRequests[i]->GetParent(), readRequest);
  185. Requests::ReadData* data = AZStd::get_if<Requests::ReadData>(&subRequests[i]->GetCommand());
  186. ASSERT_NE(nullptr, data);
  187. EXPECT_EQ(SplitSize, data->m_size);
  188. EXPECT_EQ(SplitSize * (batchSize + i), data->m_offset);
  189. EXPECT_EQ(buffer.get() + (SplitSize * (batchSize + i)), data->m_output);
  190. EXPECT_EQ(path, data->m_path);
  191. m_context.MarkRequestAsCompleted(subRequests[i]);
  192. }
  193. m_context.FinalizeCompletedRequests();
  194. }
  195. TEST_F(Streamer_ReadSplitterTest, QueueRequest_UnalignedMemoryAdjusted_BuffersAreUsedToReadTo)
  196. {
  197. using ::testing::_;
  198. CreateAlignmentAwareReadSplitter(1_mib, false);
  199. constexpr u64 readSize = SplitSize / 2;
  200. u8* memory = reinterpret_cast<u8*>(azmalloc(readSize + 3, MemoryAlignment));
  201. u8* buffer = memory + 3; // Adjust the starting address so it doesn't align
  202. FileRequest* readRequest = m_context.GetNewInternalRequest();
  203. RequestPath path("TestPath");
  204. readRequest->CreateRead(nullptr, buffer, readSize, path, 0, readSize);
  205. FileRequest* subRequest{ nullptr };
  206. EXPECT_CALL(*m_mock, QueueRequest(_))
  207. .Times(1)
  208. .WillRepeatedly([&subRequest](FileRequest* request) { subRequest = request; });
  209. m_readSplitter->QueueRequest(readRequest);
  210. ASSERT_NE(nullptr, subRequest);
  211. Requests::ReadData* data = AZStd::get_if<Requests::ReadData>(&subRequest->GetCommand());
  212. EXPECT_NE(buffer, data->m_output);
  213. EXPECT_EQ(readSize, data->m_size);
  214. EXPECT_EQ(0, data->m_offset);
  215. EXPECT_EQ(path, data->m_path);
  216. u32* subRequestBuffer = reinterpret_cast<u32*>(data->m_output);
  217. for (u64 i = 0; i < data->m_size / sizeof(u32); ++i)
  218. {
  219. subRequestBuffer[i] = aznumeric_caster(i);
  220. }
  221. m_context.MarkRequestAsCompleted(subRequest);
  222. m_context.FinalizeCompletedRequests();
  223. u32* readBuffer = reinterpret_cast<u32*>(buffer);
  224. for (u64 i = 0; i < readSize / sizeof(u32); ++i)
  225. {
  226. ASSERT_EQ(aznumeric_cast<u32>(i), readBuffer[i]);
  227. }
  228. azfree(memory);
  229. }
  230. TEST_F(Streamer_ReadSplitterTest, QueueRequest_UnalignedOffsetAdjusted_BuffersAreUsedToReadTo)
  231. {
  232. using ::testing::_;
  233. CreateAlignmentAwareReadSplitter(1_mib, true);
  234. constexpr u64 offsetAdjustment = sizeof(u32) * 2;
  235. constexpr u64 readSize = SplitSize / 2;
  236. ASSERT_GT(MemoryAlignment, offsetAdjustment);
  237. u8* buffer = reinterpret_cast<u8*>(azmalloc(readSize, MemoryAlignment));
  238. FileRequest* readRequest = m_context.GetNewInternalRequest();
  239. RequestPath path("TestPath");
  240. readRequest->CreateRead(nullptr, buffer, readSize, path, offsetAdjustment, readSize);
  241. FileRequest* subRequest{ nullptr };
  242. EXPECT_CALL(*m_mock, QueueRequest(_))
  243. .Times(1)
  244. .WillRepeatedly([&subRequest](FileRequest* request) { subRequest = request; });
  245. m_readSplitter->QueueRequest(readRequest);
  246. ASSERT_NE(nullptr, subRequest);
  247. Requests::ReadData* data = AZStd::get_if<Requests::ReadData>(&subRequest->GetCommand());
  248. EXPECT_NE(buffer, data->m_output);
  249. EXPECT_EQ(readSize + offsetAdjustment, data->m_size);
  250. EXPECT_EQ(0, data->m_offset);
  251. EXPECT_EQ(path, data->m_path);
  252. u32* subRequestBuffer = reinterpret_cast<u32*>(data->m_output);
  253. for (u64 i = 0; i < data->m_size / sizeof(u32); ++i)
  254. {
  255. subRequestBuffer[i] = aznumeric_caster(i);
  256. }
  257. m_context.MarkRequestAsCompleted(subRequest);
  258. m_context.FinalizeCompletedRequests();
  259. u32* readBuffer = reinterpret_cast<u32*>(buffer);
  260. for (u64 i = 0; i < readSize / sizeof(u32); ++i)
  261. {
  262. ASSERT_EQ(aznumeric_cast<u32>(i) + (offsetAdjustment / sizeof(u32)), readBuffer[i]);
  263. }
  264. azfree(buffer);
  265. }
  266. TEST_F(Streamer_ReadSplitterTest, QueueRequest_ReadMoreThanFitsInTheCache_ReadsAreDelayedAndThenContinued)
  267. {
  268. using ::testing::_;
  269. CreateAlignmentAwareReadSplitter(SplitSize * 4, false);
  270. constexpr u64 readSize = SplitSize * 6;
  271. u8* memory = reinterpret_cast<u8*>(azmalloc(readSize + 3, MemoryAlignment));
  272. u8* buffer = memory + 3; // Adjust the starting address so it doesn't align
  273. FileRequest* readRequest = m_context.GetNewInternalRequest();
  274. RequestPath path("TestPath");
  275. readRequest->CreateRead(nullptr, buffer, readSize, path, 0, readSize);
  276. EXPECT_CALL(*m_mock, QueueRequest(_))
  277. .Times(4)
  278. .WillRepeatedly([this](FileRequest* request) { m_context.MarkRequestAsCompleted(request); });
  279. m_readSplitter->QueueRequest(readRequest);
  280. EXPECT_CALL(*m_mock, QueueRequest(_))
  281. .Times(2)
  282. .WillRepeatedly([this](FileRequest* request) { m_context.MarkRequestAsCompleted(request); });
  283. m_context.FinalizeCompletedRequests();
  284. azfree(memory);
  285. }
  286. TEST_F(Streamer_ReadSplitterTest, QueueRequest_AlignedReadAfterDelayedRead_SecondReadIsDelayedAsWellAndBothComplete)
  287. {
  288. using ::testing::_;
  289. CreateAlignmentAwareReadSplitter(SplitSize * 4, false);
  290. constexpr u64 readSize = SplitSize * 6;
  291. size_t completedRequests{ 0 };
  292. u8* memory0 = reinterpret_cast<u8*>(azmalloc(readSize + 3, MemoryAlignment));
  293. u8* buffer = memory0 + 3; // Adjust the starting address so it doesn't align
  294. FileRequest* readRequestDelayed = m_context.GetNewInternalRequest();
  295. RequestPath path("TestPath");
  296. readRequestDelayed->CreateRead(nullptr, buffer, readSize, path, 0, readSize);
  297. readRequestDelayed->SetCompletionCallback([&completedRequests](FileRequestHandle) { ++completedRequests; });
  298. u8* memory1 = reinterpret_cast<u8*>(azmalloc(readSize, MemoryAlignment));
  299. FileRequest* readRequestAligned = m_context.GetNewInternalRequest();
  300. readRequestAligned->CreateRead(nullptr, memory1, readSize, path, 0, readSize);
  301. readRequestAligned->SetCompletionCallback([&completedRequests](FileRequestHandle) { ++completedRequests; });
  302. EXPECT_CALL(*m_mock, QueueRequest(_))
  303. .Times(4)
  304. .WillRepeatedly([this, parent = readRequestDelayed](FileRequest* request)
  305. {
  306. EXPECT_EQ(request->GetParent(), parent);
  307. m_context.MarkRequestAsCompleted(request);
  308. });
  309. m_readSplitter->QueueRequest(readRequestDelayed);
  310. m_readSplitter->QueueRequest(readRequestAligned);
  311. auto delayedCallback = [this, parent = readRequestDelayed](FileRequest* request)
  312. {
  313. EXPECT_EQ(request->GetParent(), parent);
  314. m_context.MarkRequestAsCompleted(request);
  315. };
  316. auto alignedCallback = [this, parent = readRequestAligned](FileRequest* request)
  317. {
  318. EXPECT_EQ(request->GetParent(), parent);
  319. m_context.MarkRequestAsCompleted(request);
  320. };
  321. EXPECT_CALL(*m_mock, QueueRequest(_))
  322. .Times(2 + 6)
  323. .WillOnce(delayedCallback)
  324. .WillOnce(delayedCallback)
  325. .WillRepeatedly(alignedCallback);
  326. m_context.FinalizeCompletedRequests();
  327. EXPECT_EQ(2, completedRequests);
  328. azfree(memory1);
  329. azfree(memory0);
  330. }
  331. TEST_F(Streamer_ReadSplitterTest, QueueRequest_BufferedReadAfterDelayedRead_SecondReadIsDelayedAsWellAndBothComplete)
  332. {
  333. using ::testing::_;
  334. CreateAlignmentAwareReadSplitter(SplitSize * 4, false);
  335. constexpr u64 readSize = SplitSize * 6;
  336. size_t completedRequests{ 0 };
  337. u8* memory0 = reinterpret_cast<u8*>(azmalloc(readSize + 3, MemoryAlignment));
  338. u8* buffer0 = memory0 + 3; // Adjust the starting address so it doesn't align
  339. FileRequest* readRequestDelayed = m_context.GetNewInternalRequest();
  340. RequestPath path("TestPath");
  341. readRequestDelayed->CreateRead(nullptr, buffer0, readSize, path, 0, readSize);
  342. readRequestDelayed->SetCompletionCallback([&completedRequests](FileRequestHandle) { ++completedRequests; });
  343. u8* memory1 = reinterpret_cast<u8*>(azmalloc(readSize + 3, MemoryAlignment));
  344. u8* buffer1 = memory1 + 3;
  345. FileRequest* readRequestBuffered = m_context.GetNewInternalRequest();
  346. readRequestBuffered->CreateRead(nullptr, buffer1, readSize, path, 0, readSize);
  347. readRequestBuffered->SetCompletionCallback([&completedRequests](FileRequestHandle) { ++completedRequests; });
  348. EXPECT_CALL(*m_mock, QueueRequest(_))
  349. .Times(4)
  350. .WillRepeatedly([this, parent = readRequestDelayed](FileRequest* request)
  351. {
  352. EXPECT_EQ(request->GetParent(), parent);
  353. m_context.MarkRequestAsCompleted(request);
  354. });
  355. m_readSplitter->QueueRequest(readRequestDelayed);
  356. m_readSplitter->QueueRequest(readRequestBuffered);
  357. auto delayedCallback = [this, parent = readRequestDelayed](FileRequest* request)
  358. {
  359. EXPECT_EQ(request->GetParent(), parent);
  360. m_context.MarkRequestAsCompleted(request);
  361. };
  362. auto alignedCallback = [this, parent = readRequestBuffered](FileRequest* request)
  363. {
  364. EXPECT_EQ(request->GetParent(), parent);
  365. m_context.MarkRequestAsCompleted(request);
  366. };
  367. EXPECT_CALL(*m_mock, QueueRequest(_))
  368. .Times(2 + 6)
  369. .WillOnce(delayedCallback)
  370. .WillOnce(delayedCallback)
  371. .WillRepeatedly(alignedCallback);
  372. m_context.FinalizeCompletedRequests();
  373. EXPECT_EQ(2, completedRequests);
  374. azfree(memory1);
  375. azfree(memory0);
  376. }
  377. TEST_F(Streamer_ReadSplitterTest, CollectStatistics_StatsAreReturned_AfterCallStatsAreAdded)
  378. {
  379. using ::testing::_;
  380. CreateStandardReadSplitter();
  381. AZStd::vector<Statistic> statistics;
  382. ASSERT_TRUE(statistics.empty());
  383. EXPECT_CALL(*m_mock, CollectStatistics(_));
  384. m_readSplitter->CollectStatistics(statistics);
  385. EXPECT_GT(statistics.size(), 0);
  386. }
  387. } // namespace AZ::IO