UtilitiesUnitTests.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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/PlatformIncl.h>
  9. #include <AzCore/Component/Entity.h>
  10. #include <AzCore/Jobs/Job.h>
  11. #include <AzTest/AzTest.h>
  12. #include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
  13. #include <native/utilities/assetUtils.h>
  14. #include <native/utilities/ByteArrayStream.h>
  15. #include <native/tests/MockAssetDatabaseRequestsHandler.h>
  16. #include <native/unittests/UnitTestUtils.h>
  17. #include <native/unittests/AssetProcessorUnitTests.h>
  18. #include <QThread>
  19. #if defined(AZ_PLATFORM_LINUX)
  20. #include <sys/stat.h>
  21. #include <fcntl.h>
  22. #endif
  23. using namespace UnitTestUtils;
  24. using namespace AssetUtilities;
  25. using namespace AssetProcessor;
  26. namespace AssetProcessor
  27. {
  28. // simple utility class to make sure threads join and don't cause asserts
  29. // if the unit test exits early.
  30. class AutoThreadJoiner final
  31. {
  32. public:
  33. explicit AutoThreadJoiner(AZStd::thread* ownershipTransferThread)
  34. {
  35. m_threadToOwn = ownershipTransferThread;
  36. }
  37. ~AutoThreadJoiner()
  38. {
  39. if (m_threadToOwn)
  40. {
  41. m_threadToOwn->join();
  42. delete m_threadToOwn;
  43. }
  44. }
  45. AZStd::thread* m_threadToOwn;
  46. };
  47. }
  48. class UtilitiesUnitTests
  49. : public UnitTest::AssetProcessorUnitTestBase
  50. {
  51. };
  52. TEST_F(UtilitiesUnitTests, NormalizeFilePath_FeedFilePathInDifferentFormats_Succeeds)
  53. {
  54. using namespace AzToolsFramework::AssetDatabase;
  55. // do not change case
  56. // do not chop extension
  57. // do not make full path
  58. EXPECT_EQ(NormalizeFilePath("a/b\\c\\d/E.txt"), "a/b/c/d/E.txt");
  59. // do not erase full path
  60. #if defined(AZ_PLATFORM_WINDOWS)
  61. EXPECT_EQ(NormalizeFilePath("c:\\a/b\\c\\d/E.txt"), "C:/a/b/c/d/E.txt");
  62. #else
  63. EXPECT_EQ(NormalizeFilePath("c:\\a/b\\c\\d/E.txt"), "c:/a/b/c/d/E.txt");
  64. #endif // defined(AZ_PLATFORM_WINDOWS)
  65. // same tests but for directories:
  66. #if defined(AZ_PLATFORM_WINDOWS)
  67. EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d"), "C:/a/b/c/d");
  68. #else
  69. EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d"), "c:/a/b/c/d");
  70. #endif // defined(AZ_PLATFORM_WINDOWS)
  71. EXPECT_EQ(NormalizeDirectoryPath("a/b\\c\\d"), "a/b/c/d");
  72. // directories automatically chop slashes:
  73. #if defined(AZ_PLATFORM_WINDOWS)
  74. EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d\\"), "C:/a/b/c/d");
  75. EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d//"), "C:/a/b/c/d");
  76. #else
  77. EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d\\"), "c:/a/b/c/d");
  78. EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d//"), "c:/a/b/c/d");
  79. #endif // defined(AZ_PLATFORM_WINDOWS)
  80. }
  81. TEST_F(UtilitiesUnitTests, ChangeFileAttributes_MakeFileReadOnlyOrWritable_Succeeds)
  82. {
  83. QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  84. QString fileName(dir.filePath("test.txt"));
  85. CreateDummyFile(fileName);
  86. #if defined WIN32
  87. DWORD fileAttributes = GetFileAttributesA(fileName.toUtf8());
  88. if (!(fileAttributes & FILE_ATTRIBUTE_READONLY))
  89. {
  90. //make the file Readonly
  91. if (!SetFileAttributesA(fileName.toUtf8(), fileAttributes | (FILE_ATTRIBUTE_READONLY)))
  92. {
  93. AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to change file attributes for the file: %s.\n", fileName.toUtf8().data());
  94. }
  95. }
  96. #else
  97. QFileInfo fileInfo(fileName);
  98. if (fileInfo.permission(QFile::WriteUser))
  99. {
  100. //remove write user flag
  101. QFile::Permissions permissions = QFile::permissions(fileName) & ~(QFile::WriteUser);
  102. if (!QFile::setPermissions(fileName, permissions))
  103. {
  104. AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to change file attributes for the file: %s.\n", fileName.toUtf8().data());
  105. }
  106. }
  107. #endif
  108. EXPECT_TRUE(AssetUtilities::MakeFileWritable(fileName));
  109. }
  110. TEST_F(UtilitiesUnitTests, NormalizeAndRemoveAlias_FeedFilePathWithDoubleSlackesAndAlias_Succeeds)
  111. {
  112. EXPECT_EQ(AssetUtilities::NormalizeAndRemoveAlias("@test@\\my\\file.txt"), QString("my/file.txt"));
  113. EXPECT_EQ(AssetUtilities::NormalizeAndRemoveAlias("@test@my\\file.txt"), QString("my/file.txt"));
  114. EXPECT_EQ(AssetUtilities::NormalizeAndRemoveAlias("@TeSt@my\\file.txt"), QString("my/file.txt")); // case sensitivity test!
  115. }
  116. TEST_F(UtilitiesUnitTests, CopyFileWithTimeout_FeedFilesInDifferentStates_Succeeds)
  117. {
  118. QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  119. QString fileName(dir.filePath("test.txt"));
  120. QString outputFileName(dir.filePath("test1.txt"));
  121. QFile inputFile(fileName);
  122. inputFile.open(QFile::WriteOnly);
  123. QFile outputFile(outputFileName);
  124. outputFile.open(QFile::WriteOnly);
  125. const int waitTimeInSeconds = 1; // Timeout for copying files
  126. #if defined(AZ_PLATFORM_WINDOWS)
  127. // this test is intentionally disabled on other platforms
  128. // because in general on other platforms its actually possible to delete and move
  129. // files out of the way even if they are currently opened for writing by a different
  130. // handle.
  131. //Trying to copy when the output file is open for reading should fail.
  132. {
  133. UnitTestUtils::AssertAbsorber absorb;
  134. EXPECT_FALSE(CopyFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
  135. // Expected two warnings for copying files:
  136. // 1) Failed to remove old files
  137. // 2) Copy operation failed for the given timeout
  138. EXPECT_EQ(absorb.m_numWarningsAbsorbed, 1);
  139. // Expected another two warnings for moving files:
  140. // 1) Failed to remove old files
  141. // 2) Move operation failed for the given timeout
  142. EXPECT_FALSE(MoveFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
  143. EXPECT_EQ(absorb.m_numWarningsAbsorbed, 2);
  144. }
  145. #endif // AZ_PLATFORM_WINDOWS ONLY
  146. inputFile.close();
  147. outputFile.close();
  148. //Trying to copy when the output file is not open
  149. EXPECT_TRUE(CopyFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
  150. EXPECT_TRUE(CopyFileWithTimeout(fileName, outputFileName, aznumeric_caster(-1)));//invalid timeout time
  151. // Trying to move when the output file is not open
  152. EXPECT_TRUE(MoveFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
  153. EXPECT_TRUE(MoveFileWithTimeout(outputFileName, fileName, waitTimeInSeconds));
  154. // Open the file and then close it in the near future
  155. AZStd::atomic_bool setupDone{ false };
  156. AssetProcessor::AutoThreadJoiner joiner(new AZStd::thread(
  157. [&]()
  158. {
  159. //opening file
  160. outputFile.open(QFile::WriteOnly);
  161. setupDone = true;
  162. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(500));
  163. //closing file
  164. outputFile.close();
  165. }));
  166. while (!setupDone.load())
  167. {
  168. QThread::msleep(1);
  169. }
  170. EXPECT_TRUE(outputFile.isOpen());
  171. //Trying to copy when the output file is open,but will close before the timeout inputted
  172. {
  173. UnitTestUtils::AssertAbsorber absorb;
  174. EXPECT_TRUE(CopyFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
  175. #if defined(AZ_PLATFORM_WINDOWS)
  176. // only windows has an issue with moving files out that are in use.
  177. // other platforms do so without issue.
  178. EXPECT_EQ(absorb.m_numWarningsAbsorbed, 0);
  179. #endif // windows platform.
  180. }
  181. }
  182. TEST_F(UtilitiesUnitTests, CheckCanLock_FeedFileToLock_GetsLockStatus)
  183. {
  184. QDir lockTestDir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  185. QString lockTestFileName(lockTestDir.filePath("lockTest.txt"));
  186. EXPECT_FALSE(AssetUtilities::CheckCanLock(lockTestFileName));
  187. EXPECT_TRUE(CreateDummyFile(lockTestFileName));
  188. EXPECT_TRUE(AssetUtilities::CheckCanLock(lockTestFileName));
  189. #if defined(AZ_PLATFORM_WINDOWS)
  190. // on windows, opening a file for reading locks it
  191. // but on other platforms, this is not the case.
  192. QFile lockTestFile(lockTestFileName);
  193. lockTestFile.open(QFile::ReadOnly);
  194. #elif defined(AZ_PLATFORM_LINUX)
  195. int handle = open(lockTestFileName.toUtf8().constData(), O_RDONLY | O_EXCL | O_NONBLOCK);
  196. #else
  197. int handle = open(lockTestFileName.toUtf8().constData(), O_RDONLY | O_EXLOCK | O_NONBLOCK);
  198. #endif // AZ_PLATFORM_WINDOWS
  199. #if defined(AZ_PLATFORM_WINDOWS)
  200. EXPECT_FALSE(AssetUtilities::CheckCanLock(lockTestFileName));
  201. lockTestFile.close();
  202. #else
  203. if (handle != -1)
  204. {
  205. close(handle);
  206. }
  207. #endif // windows/other platforms ifdef
  208. }
  209. TEST_F(UtilitiesUnitTests, TestByteArrayStream_WriteToStream_Succeeds)
  210. {
  211. AssetProcessor::ByteArrayStream stream;
  212. EXPECT_TRUE(stream.CanSeek());
  213. EXPECT_TRUE(stream.IsOpen());
  214. EXPECT_EQ(stream.GetLength(), 0);
  215. EXPECT_EQ(stream.GetCurPos(), 0);
  216. char tempReadBuffer[24];
  217. azstrcpy(tempReadBuffer, 24, "This is a Test String");
  218. EXPECT_EQ(stream.Read(100, tempReadBuffer), 0);
  219. // reserving does not alter the length.
  220. stream.Reserve(128);
  221. EXPECT_EQ(stream.GetLength(), 0);
  222. // Write 7 bytes from the buffer to the stream
  223. AZ::IO::SizeType sizeInBytesToWrite = 7;
  224. EXPECT_EQ(stream.Write(sizeInBytesToWrite, tempReadBuffer), sizeInBytesToWrite);
  225. AZ::IO::SizeType expectedPosition = sizeInBytesToWrite;
  226. AZ::IO::SizeType expectedLength = sizeInBytesToWrite;
  227. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  228. EXPECT_EQ(stream.GetLength(), expectedLength);
  229. EXPECT_EQ(memcmp(stream.GetArray().constData(), tempReadBuffer, expectedLength), 0);
  230. // Write 7 bytes from the buffer to the stream again
  231. EXPECT_EQ(stream.Write(sizeInBytesToWrite, tempReadBuffer), sizeInBytesToWrite);
  232. expectedPosition += sizeInBytesToWrite;
  233. expectedLength += sizeInBytesToWrite;
  234. EXPECT_EQ(stream.GetLength(), expectedLength);
  235. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  236. EXPECT_EQ(memcmp(stream.GetArray().constData(), "This isThis is", expectedLength), 0);
  237. sizeInBytesToWrite = 4;
  238. AZ::IO::SizeType offsite = 0;
  239. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_BEGIN); // write at begin, without overrunning
  240. expectedPosition = offsite;
  241. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  242. EXPECT_EQ(stream.Write(sizeInBytesToWrite, "that"), sizeInBytesToWrite);
  243. expectedPosition += sizeInBytesToWrite;
  244. EXPECT_EQ(stream.GetLength(), expectedLength);
  245. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  246. EXPECT_EQ(memcmp(stream.GetArray().constData(), "that isThis is", expectedLength), 0);
  247. offsite = 2;
  248. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_CUR); // write in middle, without overrunning
  249. expectedPosition += offsite;
  250. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  251. EXPECT_EQ(stream.Write(sizeInBytesToWrite, "1234"), sizeInBytesToWrite);
  252. expectedPosition += sizeInBytesToWrite;
  253. EXPECT_EQ(stream.GetLength(), expectedLength);
  254. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  255. EXPECT_EQ(memcmp(stream.GetArray().constData(), "that i1234s is", expectedLength), 0);
  256. offsite = AZ::IO::SizeType(-6);
  257. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_END); // write in end, negative offset, without overrunning
  258. expectedPosition = expectedLength + offsite;
  259. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  260. EXPECT_EQ(stream.Write(sizeInBytesToWrite, "5555"), sizeInBytesToWrite);
  261. expectedPosition += sizeInBytesToWrite;
  262. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  263. EXPECT_EQ(stream.GetLength(), expectedLength);
  264. EXPECT_EQ(memcmp(stream.GetArray().constData(), "that i125555is", expectedLength), 0);
  265. offsite = 2;
  266. sizeInBytesToWrite = 14;
  267. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_BEGIN); // write at begin offset, with overrun:
  268. expectedPosition = offsite;
  269. EXPECT_EQ(stream.GetCurPos(), offsite);
  270. EXPECT_EQ(stream.GetLength(), expectedLength);
  271. EXPECT_EQ(stream.Write(sizeInBytesToWrite, "xxxxxxxxxxxxxx"), sizeInBytesToWrite);
  272. expectedPosition += sizeInBytesToWrite;
  273. expectedLength = expectedPosition;
  274. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  275. EXPECT_EQ(stream.GetLength(), expectedLength);
  276. EXPECT_EQ(memcmp(stream.GetArray().constData(), "thxxxxxxxxxxxxxx", expectedLength), 0);
  277. offsite = 14;
  278. sizeInBytesToWrite = 4;
  279. stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  280. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_CUR); // write in middle, with overrunning:
  281. expectedPosition = offsite;
  282. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  283. EXPECT_EQ(stream.GetLength(), expectedLength);
  284. EXPECT_EQ(stream.Write(sizeInBytesToWrite, "yyyy"), sizeInBytesToWrite);
  285. expectedPosition += sizeInBytesToWrite;
  286. expectedLength = expectedPosition;
  287. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  288. EXPECT_EQ(stream.GetLength(), expectedLength);
  289. EXPECT_EQ(memcmp(stream.GetArray().constData(), "thxxxxxxxxxxxxyyyy", expectedLength), 0);
  290. offsite = AZ::IO::SizeType(-2);
  291. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_END); // write in end, negative offset, with overrunning
  292. expectedPosition = expectedLength + offsite;
  293. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  294. EXPECT_EQ(stream.GetLength(), expectedLength);
  295. EXPECT_EQ(stream.Write(sizeInBytesToWrite, "ZZZZ"), sizeInBytesToWrite);
  296. expectedPosition += sizeInBytesToWrite;
  297. expectedLength = expectedPosition;
  298. EXPECT_EQ(stream.GetCurPos(), expectedPosition);
  299. EXPECT_EQ(stream.GetLength(), expectedLength);
  300. EXPECT_EQ(memcmp(stream.GetArray().constData(), "thxxxxxxxxxxxxyyZZZZ", expectedLength), 0);
  301. // read test.
  302. stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  303. EXPECT_EQ(stream.Read(expectedLength, tempReadBuffer), expectedLength);
  304. EXPECT_EQ(memcmp(tempReadBuffer, "thxxxxxxxxxxxxyyZZZZ", expectedLength), 0);
  305. EXPECT_EQ(stream.Read(expectedLength, tempReadBuffer), 0); // because its already at end.
  306. EXPECT_EQ(memcmp(tempReadBuffer, "thxxxxxxxxxxxxyyZZZZ", expectedLength), 0); // it should not have disturbed the buffer.
  307. offsite = 2;
  308. stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_BEGIN);
  309. EXPECT_EQ(stream.Read(expectedLength, tempReadBuffer), expectedLength - offsite);
  310. EXPECT_EQ(memcmp(tempReadBuffer, "xxxxxxxxxxxxyyZZZZZZ", expectedLength), 0); // it should not have disturbed the buffer bits that it was not asked to touch.
  311. }
  312. TEST_F(UtilitiesUnitTests, MatchFilePattern_FeedDifferentPatternsAndFilePaths_Succeeds)
  313. {
  314. {
  315. AssetBuilderSDK::FilePatternMatcher extensionWildcardTest(AssetBuilderSDK::AssetBuilderPattern("*.cfg", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  316. EXPECT_TRUE(extensionWildcardTest.MatchesPath(AZStd::string("foo.cfg")));
  317. EXPECT_TRUE(extensionWildcardTest.MatchesPath(AZStd::string("abcd/foo.cfg")));
  318. EXPECT_FALSE(extensionWildcardTest.MatchesPath(AZStd::string("abcd/foo.cfd")));
  319. }
  320. {
  321. AssetBuilderSDK::FilePatternMatcher prefixWildcardTest(AssetBuilderSDK::AssetBuilderPattern("abf*.llm", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  322. EXPECT_TRUE(prefixWildcardTest.MatchesPath(AZStd::string("abf.llm")));
  323. EXPECT_TRUE(prefixWildcardTest.MatchesPath(AZStd::string("abf12345.llm")));
  324. EXPECT_FALSE(prefixWildcardTest.MatchesPath(AZStd::string("foo/abf12345.llm")));
  325. EXPECT_FALSE(prefixWildcardTest.MatchesPath(AZStd::string("foo/abf12345.lls")));
  326. EXPECT_FALSE(prefixWildcardTest.MatchesPath(AZStd::string("foo/ab2345.llm")));
  327. }
  328. {
  329. AssetBuilderSDK::FilePatternMatcher extensionPrefixWildcardTest(AssetBuilderSDK::AssetBuilderPattern("sdf.c*", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  330. EXPECT_TRUE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cpp")));
  331. EXPECT_TRUE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cxx")));
  332. EXPECT_TRUE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.c")));
  333. EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cpp")));
  334. EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.cpp")));
  335. EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdc.c")));
  336. EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.hxx")));
  337. EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.hxx")));
  338. }
  339. {
  340. AssetBuilderSDK::FilePatternMatcher prefixExtensionPrefixWildcardTest(AssetBuilderSDK::AssetBuilderPattern("s*.c*", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  341. EXPECT_TRUE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cpp")));
  342. EXPECT_TRUE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cxx")));
  343. EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cpp")));
  344. EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("c:\\asd/abcd/sdf.cpp")));
  345. EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.hxx")));
  346. EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.hxx")));
  347. }
  348. {
  349. AssetBuilderSDK::FilePatternMatcher fixedNameTest(AssetBuilderSDK::AssetBuilderPattern("a.bcd", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  350. EXPECT_TRUE(fixedNameTest.MatchesPath(AZStd::string("a.bcd")));
  351. EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("foo\\a.bcd")));
  352. EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("foo/a.bcd")));
  353. EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("c:/foo/a.bcd")));
  354. EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("c:\\foo/a.bcd")));
  355. EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("sdf.hxx")));
  356. }
  357. {
  358. AssetBuilderSDK::FilePatternMatcher midMatchExtensionPrefixTest(AssetBuilderSDK::AssetBuilderPattern("s*f.c*", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  359. EXPECT_TRUE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sdf.cpp")));
  360. EXPECT_TRUE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sef.cxx")));
  361. EXPECT_TRUE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sf.c")));
  362. EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("c:\\asd/abcd/sdf.cpp")));
  363. EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("abcd/sdf.cpp")));
  364. EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sdc.c")));
  365. EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sdf.hxx")));
  366. EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.hxx")));
  367. }
  368. {
  369. AssetBuilderSDK::FilePatternMatcher subFolderExtensionWildcardTest(AssetBuilderSDK::AssetBuilderPattern("abcd/*.cfg", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
  370. EXPECT_TRUE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cfg")));
  371. EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("c://abcd/sdf.cfg")));
  372. EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("sdf.cfg")));
  373. EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("abcs/sdf.cfg")));
  374. EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cfx")));
  375. }
  376. {
  377. AssetBuilderSDK::FilePatternMatcher subFolderPatternTest(AssetBuilderSDK::AssetBuilderPattern(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex));
  378. EXPECT_TRUE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/savebackup/sdf.cfg")));
  379. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/savebackup")));
  380. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("savebackup/sdf.cfg")));
  381. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("c://abcd/sdf.cfg")));
  382. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("sdf.cfg")));
  383. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcs/sdf.cfg")));
  384. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/sdf.cfx")));
  385. }
  386. {
  387. AssetBuilderSDK::FilePatternMatcher subFolderPatternTest(AssetBuilderSDK::AssetBuilderPattern(".*\\/Presets\\/GeomCache\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex));
  388. EXPECT_TRUE(subFolderPatternTest.MatchesPath(AZStd::string("something/Presets/GeomCache/sdf.cfg")));
  389. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("Presets/GeomCache/sdf.cfg"))); // should not match because it demands that there is a slash
  390. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/savebackup")));
  391. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("savebackup/sdf.cfg")));
  392. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("c://abcd/sdf.cfg")));
  393. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("sdf.cfg")));
  394. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcs/sdf.cfg")));
  395. EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/sdf.cfx")));
  396. }
  397. }
  398. TEST_F(UtilitiesUnitTests, GetFileHashFromStream_NullPath_Returns0)
  399. {
  400. AZ::u64 result = GetFileHash(nullptr);
  401. EXPECT_EQ(result, 0);
  402. }
  403. TEST_F(UtilitiesUnitTests, GetFileHashFromStreamSmallFile_ReturnsExpectedHash)
  404. {
  405. QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  406. QString fileName(dir.filePath("test.txt"));
  407. CreateDummyFile(fileName);
  408. AZ::u64 result = GetFileHash(fileName.toUtf8());
  409. EXPECT_EQ(result, AZ::u64(17241709254077376921ul));
  410. }
  411. TEST_F(UtilitiesUnitTests, GetFileHashFromStream_SmallFileForced_ReturnsExpectedHash)
  412. {
  413. QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  414. QString fileName(dir.filePath("test.txt"));
  415. CreateDummyFile(fileName);
  416. AZ::u64 result = GetFileHash(fileName.toUtf8(), true);
  417. EXPECT_EQ(result, AZ::u64(17241709254077376921ul));
  418. }
  419. // This tests a race condition where one process was writing to a file and the Asset Processor started hashing that file
  420. // in a rare edge case, the end of file check used within the FileIOStream was reporting an incorrect end of file.
  421. // This job runs in a separate thread at the same time the hashing is run, and replicates this edge case.
  422. class FileWriteThrashTestJob : public AZ::Job
  423. {
  424. public:
  425. AZ_CLASS_ALLOCATOR(FileWriteThrashTestJob, AZ::ThreadPoolAllocator);
  426. FileWriteThrashTestJob(bool deleteWhenDone, AZ::JobContext* jobContext, AZ::IO::HandleType fileHandle, AZStd::string_view bufferToWrite)
  427. : Job(deleteWhenDone, jobContext),
  428. m_fileHandle(fileHandle),
  429. m_bufferToWrite(bufferToWrite)
  430. {
  431. for (; m_initialWriteCount >= 0; --m_initialWriteCount)
  432. {
  433. AZ::IO::FileIOBase::GetInstance()->Write(m_fileHandle, m_bufferToWrite.c_str(), m_bufferToWrite.length());
  434. }
  435. }
  436. void Process() override
  437. {
  438. for (; m_writeLoopCount >= 0; --m_writeLoopCount)
  439. {
  440. AZ::IO::FileIOBase::GetInstance()->Write(m_fileHandle, m_bufferToWrite.c_str(), m_bufferToWrite.length());
  441. // Writing this unsigned int triggers the race condition more often.
  442. unsigned int uintToWrite = 10;
  443. AZ::IO::FileIOBase::GetInstance()->Write(m_fileHandle, &uintToWrite, sizeof(uintToWrite));
  444. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1));
  445. }
  446. AZ::IO::FileIOBase::GetInstance()->Close(m_fileHandle);
  447. }
  448. AZStd::string m_bufferToWrite;
  449. AZ::IO::HandleType m_fileHandle;
  450. int m_writeLoopCount = 1023 * 10; // Write enough times to trigger the race condition, a bit more than 10 seconds.
  451. int m_initialWriteCount = 1021 * 10; // Start with a larger file to make sure the hash operation won't finish immediately.
  452. };
  453. // This is a regression test for a bug found with this repro:
  454. // In the editor, export a level, the asset processor shuts down.
  455. // This occured because the asset processor's file hashing picked up the temporary navigation mesh file
  456. // when it was created, and started hashing it. At the same time, the editor was still writing to this file.
  457. // When the file hash called AZ::IO::FileIOStream's read function with a buffer to read in this file,
  458. // and it hit the end of the file and it read less than the buffer, the end of file checked used by the
  459. // AZ::IO::FileIOStream would sometimes incorrectly report that it did not hit the end of the file.
  460. // The hash function was updated to use a more accurate check. Instead of always requesting to read the buffer size,
  461. // it requests to read either the buffer size, or the remaining length of the file.
  462. // This test replicates that behavior by starting a job on another thread that writes to a file,
  463. // and at the same time it runs the file hashing process. With the fix reverted, this test fails.
  464. // This test purposely does not force a failure state to verify the assert would occur because
  465. // it's a race condition, and this test should not fail due to timing issues on different machines.
  466. class MockTraceMessageBusHandler
  467. : public AZ::Debug::TraceMessageBus::Handler
  468. {
  469. public:
  470. bool OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override
  471. {
  472. m_assertTriggered = true;
  473. return true;
  474. }
  475. bool m_assertTriggered = false;
  476. };
  477. TEST_F(UtilitiesUnitTests, GetFileHashFromStream_LargeFileForcedAnotherThreadWritesToFile_ReturnsExpectedHash)
  478. {
  479. MockTraceMessageBusHandler traceMessageBusHandler;
  480. traceMessageBusHandler.BusConnect();
  481. QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
  482. QString fileName(dir.filePath("test.txt"));
  483. CreateDummyFile(fileName);
  484. // Use a small buffer to frequently write a lot of data into the file, to help force the race condition.
  485. char buffer[10];
  486. memset(buffer, 'a', AZ_ARRAY_SIZE(buffer));
  487. AZ::IO::HandleType writeHandle;
  488. // Using a file handle and not a file stream because the navigation mesh system used this same interface for writing the file.
  489. AZ::IO::FileIOBase::GetInstance()->Open(fileName.toUtf8(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, writeHandle);
  490. // The job will close the stream
  491. FileWriteThrashTestJob* job = aznew FileWriteThrashTestJob(true, nullptr, writeHandle, AZStd::string_view(buffer, AZ_ARRAY_SIZE(buffer)));
  492. job->m_writeLoopCount = 100; // compensate for artificial hashing delay below, keeping this test duration similar to others
  493. job->Start();
  494. // Use an artificial delay on hashing to ensure the race condition actually occurs.
  495. AZ::u64 result =
  496. GetFileHash(fileName.toUtf8(), true, nullptr, /*hashMsDelay*/ 20);
  497. // This test will result in different hash results on different machines, because writing to the stream
  498. // and reading from the stream to generate the hash happen at different speeds in different setups.
  499. // Just make sure it returns some result here.
  500. EXPECT_NE(result, 0);
  501. EXPECT_FALSE(traceMessageBusHandler.m_assertTriggered);
  502. traceMessageBusHandler.BusDisconnect();
  503. }