ReadSplitterTests.cpp 19 KB

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