ArchiveCompressionTests.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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 <AzTest/AzTest.h>
  9. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  10. #include <AzCore/UnitTest/TestTypes.h>
  11. #include <AzCore/UnitTest/UnitTest.h>
  12. #include <AzCore/UserSettings/UserSettingsComponent.h>
  13. #include <AzFramework/Application/Application.h>
  14. #include <AzFramework/IO/LocalFileIO.h>
  15. #include <AzFramework/Archive/ArchiveFileIO.h>
  16. #include <AzFramework/Archive/Archive.h>
  17. #include <AzFramework/Archive/INestedArchive.h>
  18. namespace UnitTest
  19. {
  20. using ArchiveCompressionParamInterface = ::testing::WithParamInterface<AZStd::tuple<
  21. AZ::IO::INestedArchive::EPakFlags,
  22. AZ::IO::INestedArchive::ECompressionMethods,
  23. AZ::IO::INestedArchive::ECompressionLevels,
  24. int, int, int>>;
  25. class ArchiveCompressionTestFixture
  26. : public LeakDetectionFixture
  27. , public ArchiveCompressionParamInterface
  28. {
  29. public:
  30. ArchiveCompressionTestFixture()
  31. : m_application { AZStd::make_unique<AzFramework::Application>() }
  32. {
  33. // Create a unique alias to the user cache directory to avoid race conditions between
  34. // concurrent invocations of this test target running these tests
  35. AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
  36. fileIo->SetAlias("@usercache@", m_tempDirectory.GetDirectory());
  37. }
  38. private:
  39. AZStd::unique_ptr<AzFramework::Application> m_application;
  40. AZ::Test::ScopedAutoTempDirectory m_tempDirectory;
  41. };
  42. auto IsPackValid(const char* path)
  43. {
  44. AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
  45. if (!archive)
  46. {
  47. return false;
  48. }
  49. if (!archive->OpenPack(path))
  50. {
  51. return false;
  52. }
  53. archive->ClosePack(path);
  54. return true;
  55. }
  56. TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionEmptyArchiveTest_PackIsValid)
  57. {
  58. // this also coincidentally tests to make sure packs inside aliases work.
  59. AZStd::string testArchivePath = "@usercache@/archivetest.pak";
  60. AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance();
  61. ASSERT_NE(nullptr, fileIo);
  62. AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
  63. ASSERT_NE(nullptr, archive);
  64. // delete test files in case they already exist
  65. archive->ClosePack(testArchivePath.c_str());
  66. fileIo->Remove(testArchivePath.c_str());
  67. // ------------ BASIC TEST: Create and read Empty Archive ------------
  68. AZStd::intrusive_ptr<AZ::IO::INestedArchive> pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
  69. EXPECT_NE(nullptr, pArchive);
  70. pArchive.reset();
  71. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  72. }
  73. TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionFullArchive_PackIsValid)
  74. {
  75. // ------------ BASIC TEST: Create archive full of standard sizes (including 0) ----------------
  76. AZStd::string testArchivePath = "@usercache@/archivetest.pak";
  77. AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
  78. auto openFlags = AZStd::get<0>(GetParam());
  79. auto compressionMethod = AZStd::get<1>(GetParam());
  80. auto compressionLevel = AZStd::get<2>(GetParam());
  81. auto stepSize = AZStd::get<3>(GetParam());
  82. auto numSteps = AZStd::get<4>(GetParam());
  83. auto iterations = AZStd::get<5>(GetParam());
  84. int maxSize = numSteps * stepSize;
  85. AZStd::vector<uint8_t> checkSums;
  86. checkSums.resize_no_construct(maxSize);
  87. for (int pos = 0; pos < maxSize; ++pos)
  88. {
  89. checkSums[pos] = static_cast<uint8_t>(pos % 256);
  90. }
  91. auto pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
  92. EXPECT_NE(nullptr, pArchive);
  93. // the strategy here is to find errors related to file sizes, alignment, overwrites
  94. // so the first test will just repeatedly write files into the pack file with varying lengths (in odd number increments from a couple KB down to 0, including 0)
  95. AZStd::vector<uint8_t> orderedData;
  96. for (int j = 0; j < iterations; ++j)
  97. {
  98. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  99. {
  100. auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
  101. EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), currentSize, compressionMethod, compressionLevel) == 0);
  102. }
  103. }
  104. pArchive.reset();
  105. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  106. // --------------------------------------------- read it back and verify
  107. pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, openFlags);
  108. EXPECT_NE(nullptr, pArchive);
  109. for (int j = 0; j < iterations; ++j)
  110. {
  111. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  112. {
  113. auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
  114. AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
  115. EXPECT_NE(nullptr, hand);
  116. EXPECT_EQ(currentSize, pArchive->GetFileSize(hand));
  117. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  118. for (int pos = 0; pos < currentSize; ++pos)
  119. {
  120. EXPECT_EQ(pos % 256, checkSums[pos]);
  121. }
  122. }
  123. }
  124. pArchive.reset();
  125. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  126. }
  127. TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionWithOverridenArchiveData_PackIsValid)
  128. {
  129. // ---------------- MORE COMPLICATED TEST which involves overwriting elements ----------------
  130. AZStd::string testArchivePath = "@usercache@/archivetest.pak";
  131. AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
  132. auto openFlags = AZStd::get<0>(GetParam());
  133. auto compressionMethod = AZStd::get<1>(GetParam());
  134. auto compressionLevel = AZStd::get<2>(GetParam());
  135. auto stepSize = AZStd::get<3>(GetParam());
  136. auto numSteps = AZStd::get<4>(GetParam());
  137. auto iterations = AZStd::get<5>(GetParam());
  138. int maxSize = numSteps * stepSize;
  139. AZStd::vector<uint8_t> checkSums;
  140. checkSums.resize_no_construct(maxSize);
  141. for (int pos = 0; pos < maxSize; ++pos)
  142. {
  143. checkSums[pos] = static_cast<uint8_t>(pos % 256);
  144. }
  145. auto pArchive = archive->OpenArchive(testArchivePath.c_str());
  146. EXPECT_NE(nullptr, pArchive);
  147. for (int j = 0; j < iterations; ++j)
  148. {
  149. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  150. {
  151. auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
  152. EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), currentSize, compressionMethod, compressionLevel) == 0);
  153. }
  154. }
  155. // overwrite the first and last iterations with files that are half their original size.
  156. for (int j = 0; j < iterations; ++j)
  157. {
  158. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  159. {
  160. int newSize = currentSize; // more will become zero
  161. if (j != 1)
  162. {
  163. newSize = newSize / 2; // the second iteration overwrites files with exactly the same size.
  164. }
  165. auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
  166. // before we overwrite it, ensure that the element is correctly resized:
  167. AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
  168. EXPECT_NE(nullptr, hand);
  169. EXPECT_EQ(currentSize, pArchive->GetFileSize(hand));
  170. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  171. for (int pos = 0; pos < currentSize; ++pos)
  172. {
  173. EXPECT_EQ(pos % 256, checkSums[pos]);
  174. }
  175. // now overwrite it:
  176. EXPECT_EQ(0, pArchive->UpdateFile(fnBuffer, checkSums.data(), newSize, compressionMethod, compressionLevel));
  177. // after overwriting it ensure that the pack contains the updated info:
  178. hand = pArchive->FindFile(fnBuffer);
  179. EXPECT_NE(nullptr, hand);
  180. EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
  181. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  182. for (int pos = 0; pos < newSize; ++pos)
  183. {
  184. EXPECT_EQ(pos % 256, checkSums[pos]);
  185. }
  186. }
  187. }
  188. pArchive.reset();
  189. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  190. // -------------------------------------------------------------------------------------------
  191. // read it back and verify
  192. pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, openFlags);
  193. EXPECT_NE(nullptr, pArchive);
  194. for (int j = 0; j < iterations; ++j)
  195. {
  196. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  197. {
  198. auto fnBuffer = AZ::StringFunc::Path::FixedString::format("file-%i-%i.dat", currentSize, j);
  199. int newSize = currentSize; // more will become zero
  200. if (j != 1)
  201. {
  202. newSize = newSize / 2; // the middle iteration overwrites files with exactly the same size.
  203. }
  204. AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
  205. EXPECT_NE(nullptr, hand);
  206. EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
  207. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  208. for (int pos = 0; pos < newSize; ++pos)
  209. {
  210. EXPECT_EQ(pos % 256, checkSums[pos]);
  211. }
  212. }
  213. }
  214. pArchive.reset();
  215. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  216. }
  217. TEST_P(ArchiveCompressionTestFixture, TestArchivePacking_CompressionWithScatteredUpdatesAndNewFiles_PackIsValid)
  218. {
  219. // ---------- scattered test --------------
  220. // in this next test, we're going to update only some elements, to make sure it reads existing data okay
  221. // we want to make at least one element shrink and one element grow, adjacent to other files
  222. // this will include files that become zero size, and also includes new files that were not there before
  223. AZStd::string testArchivePath = "@usercache@/archivetest.pak";
  224. AZ::IO::IArchive* archive = AZ::Interface<AZ::IO::IArchive>::Get();
  225. auto openFlags = AZStd::get<0>(GetParam());
  226. auto compressionMethod = AZStd::get<1>(GetParam());
  227. auto compressionLevel = AZStd::get<2>(GetParam());
  228. auto stepSize = AZStd::get<3>(GetParam());
  229. auto numSteps = AZStd::get<4>(GetParam());
  230. auto iterations = AZStd::get<5>(GetParam());
  231. int maxSize = numSteps * stepSize;
  232. AZStd::vector<uint8_t> checkSums;
  233. checkSums.resize_no_construct(maxSize);
  234. for (int pos = 0; pos < maxSize; ++pos)
  235. {
  236. checkSums[pos] = static_cast<uint8_t>(pos % 256);
  237. }
  238. // first, reset the pack to the original state:
  239. auto pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, AZ::IO::INestedArchive::FLAGS_CREATE_NEW);
  240. EXPECT_NE(nullptr, pArchive);
  241. for (int j = 0; j < iterations; ++j)
  242. {
  243. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  244. {
  245. char fnBuffer[AZ_MAX_PATH_LEN];
  246. azsnprintf(fnBuffer, AZ_MAX_PATH_LEN, "file-%i-%i.dat", static_cast<int>(currentSize), j);
  247. EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), currentSize, compressionMethod, compressionLevel) == 0);
  248. }
  249. }
  250. pArchive.reset();
  251. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  252. pArchive = archive->OpenArchive(testArchivePath.c_str());
  253. EXPECT_NE(nullptr, pArchive);
  254. // replace a scattering of the files:
  255. int writeCount = 0;
  256. for (int j = 0; j < iterations + 1; ++j) // note: an extra iteration to generate new files
  257. {
  258. char fnBuffer[AZ_MAX_PATH_LEN];
  259. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  260. {
  261. azsnprintf(fnBuffer, AZ_MAX_PATH_LEN, "file-%i-%i.dat", static_cast<int>(currentSize), j);
  262. ++writeCount;
  263. if (writeCount % 4 == 0)
  264. {
  265. if (j != iterations) // the last one wont be there
  266. {
  267. // don't do anything for every fourth file, but we do make sure its there:
  268. AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
  269. EXPECT_NE(nullptr, hand);
  270. EXPECT_EQ(currentSize, pArchive->GetFileSize(hand));
  271. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  272. }
  273. continue;
  274. }
  275. int newSize = currentSize;
  276. if (writeCount % 4 == 1)
  277. {
  278. newSize = newSize * 2;
  279. }
  280. else if (writeCount % 4 == 2)
  281. {
  282. newSize = newSize / 2;
  283. }
  284. else if (writeCount % 4 == 3)
  285. {
  286. newSize = 0;
  287. }
  288. if (newSize > maxSize)
  289. {
  290. newSize = maxSize; // don't blow our buffer!
  291. }
  292. // overwrite it:
  293. EXPECT_TRUE(pArchive->UpdateFile(fnBuffer, checkSums.data(), newSize, compressionMethod, compressionLevel) == 0);
  294. // after overwriting it ensure that the pack contains the updated info:
  295. AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
  296. EXPECT_NE(nullptr, hand);
  297. EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
  298. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  299. for (int pos = 0; pos < newSize; ++pos)
  300. {
  301. EXPECT_EQ(pos % 256, checkSums[pos]);
  302. }
  303. }
  304. }
  305. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  306. // -------------------------------------------------------------------------------------------
  307. // read it back and verify
  308. pArchive = archive->OpenArchive(testArchivePath.c_str(), {}, openFlags);
  309. EXPECT_NE(nullptr, pArchive);
  310. writeCount = 0;
  311. for (int j = 0; j < iterations + 1; ++j) // make sure the extra iteration is there.
  312. {
  313. char fnBuffer[AZ_MAX_PATH_LEN];
  314. for (int currentSize = maxSize; currentSize >= 0; currentSize -= stepSize)
  315. {
  316. ++writeCount;
  317. int newSize = currentSize;
  318. if (writeCount % 4 == 1)
  319. {
  320. newSize = newSize * 2;
  321. }
  322. else if (writeCount % 4 == 2)
  323. {
  324. newSize = newSize / 2;
  325. }
  326. else if (writeCount % 4 == 3)
  327. {
  328. newSize = 0;
  329. }
  330. else if (writeCount % 4 == 0)
  331. {
  332. if (j == iterations) // the last one wont be there
  333. {
  334. continue;
  335. }
  336. }
  337. if (newSize > maxSize)
  338. {
  339. newSize = maxSize; // don't blow our buffer!
  340. }
  341. azsnprintf(fnBuffer, AZ_MAX_PATH_LEN, "file-%i-%i.dat", static_cast<int>(currentSize), j);
  342. // check it:
  343. AZ::IO::INestedArchive::Handle hand = pArchive->FindFile(fnBuffer);
  344. EXPECT_NE(nullptr, hand);
  345. EXPECT_EQ(newSize, pArchive->GetFileSize(hand));
  346. EXPECT_EQ(0, pArchive->ReadFile(hand, checkSums.data()));
  347. for (int pos = 0; pos < newSize; ++pos)
  348. {
  349. EXPECT_TRUE(checkSums[pos] == (pos % 256));
  350. }
  351. }
  352. }
  353. pArchive.reset();
  354. EXPECT_TRUE(IsPackValid(testArchivePath.c_str()));
  355. }
  356. INSTANTIATE_TEST_SUITE_P(
  357. ArchiveCompression,
  358. ArchiveCompressionTestFixture,
  359. ::testing::Values(
  360. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
  361. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
  362. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST, 777, 7, 1),
  363. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTER, 777, 7, 1),
  364. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_NORMAL, 777, 7, 1),
  365. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
  366. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 777, 7, 1),
  367. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTEST, 777, 7, 1),
  368. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_FASTER, 777, 7, 1),
  369. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_NORMAL, 777, 7, 1),
  370. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
  371. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 777, 7, 1),
  372. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_STORE, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
  373. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_STORE, AZ::IO::INestedArchive::LEVEL_BETTER, 777, 7, 1),
  374. std::tuple(AZ::IO::INestedArchive::FLAGS_READ_ONLY, AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 1111, 10, 1),
  375. std::tuple(static_cast<AZ::IO::INestedArchive::EPakFlags>(0), AZ::IO::INestedArchive::METHOD_COMPRESS, AZ::IO::INestedArchive::LEVEL_BEST, 1111, 10, 1)
  376. ));
  377. }