123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <AzCore/PlatformIncl.h>
- #include <AzCore/Component/Entity.h>
- #include <AzCore/Jobs/Job.h>
- #include <AzTest/AzTest.h>
- #include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
- #include <native/utilities/assetUtils.h>
- #include <native/utilities/ByteArrayStream.h>
- #include <native/tests/MockAssetDatabaseRequestsHandler.h>
- #include <native/unittests/UnitTestUtils.h>
- #include <native/unittests/AssetProcessorUnitTests.h>
- #include <QThread>
- #if defined(AZ_PLATFORM_LINUX)
- #include <sys/stat.h>
- #include <fcntl.h>
- #endif
- using namespace UnitTestUtils;
- using namespace AssetUtilities;
- using namespace AssetProcessor;
- namespace AssetProcessor
- {
- // simple utility class to make sure threads join and don't cause asserts
- // if the unit test exits early.
- class AutoThreadJoiner final
- {
- public:
- explicit AutoThreadJoiner(AZStd::thread* ownershipTransferThread)
- {
- m_threadToOwn = ownershipTransferThread;
- }
- ~AutoThreadJoiner()
- {
- if (m_threadToOwn)
- {
- m_threadToOwn->join();
- delete m_threadToOwn;
- }
- }
- AZStd::thread* m_threadToOwn;
- };
- }
- class UtilitiesUnitTests
- : public UnitTest::AssetProcessorUnitTestBase
- {
- };
- TEST_F(UtilitiesUnitTests, NormalizeFilePath_FeedFilePathInDifferentFormats_Succeeds)
- {
- using namespace AzToolsFramework::AssetDatabase;
- // do not change case
- // do not chop extension
- // do not make full path
- EXPECT_EQ(NormalizeFilePath("a/b\\c\\d/E.txt"), "a/b/c/d/E.txt");
- // do not erase full path
- #if defined(AZ_PLATFORM_WINDOWS)
- EXPECT_EQ(NormalizeFilePath("c:\\a/b\\c\\d/E.txt"), "C:/a/b/c/d/E.txt");
- #else
- EXPECT_EQ(NormalizeFilePath("c:\\a/b\\c\\d/E.txt"), "c:/a/b/c/d/E.txt");
- #endif // defined(AZ_PLATFORM_WINDOWS)
- // same tests but for directories:
- #if defined(AZ_PLATFORM_WINDOWS)
- EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d"), "C:/a/b/c/d");
- #else
- EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d"), "c:/a/b/c/d");
- #endif // defined(AZ_PLATFORM_WINDOWS)
- EXPECT_EQ(NormalizeDirectoryPath("a/b\\c\\d"), "a/b/c/d");
- // directories automatically chop slashes:
- #if defined(AZ_PLATFORM_WINDOWS)
- EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d\\"), "C:/a/b/c/d");
- EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d//"), "C:/a/b/c/d");
- #else
- EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d\\"), "c:/a/b/c/d");
- EXPECT_EQ(NormalizeDirectoryPath("c:\\a/b\\c\\d//"), "c:/a/b/c/d");
- #endif // defined(AZ_PLATFORM_WINDOWS)
- }
- TEST_F(UtilitiesUnitTests, ChangeFileAttributes_MakeFileReadOnlyOrWritable_Succeeds)
- {
- QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
- QString fileName(dir.filePath("test.txt"));
- CreateDummyFile(fileName);
- #if defined WIN32
- DWORD fileAttributes = GetFileAttributesA(fileName.toUtf8());
- if (!(fileAttributes & FILE_ATTRIBUTE_READONLY))
- {
- //make the file Readonly
- if (!SetFileAttributesA(fileName.toUtf8(), fileAttributes | (FILE_ATTRIBUTE_READONLY)))
- {
- AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to change file attributes for the file: %s.\n", fileName.toUtf8().data());
- }
- }
- #else
- QFileInfo fileInfo(fileName);
- if (fileInfo.permission(QFile::WriteUser))
- {
- //remove write user flag
- QFile::Permissions permissions = QFile::permissions(fileName) & ~(QFile::WriteUser);
- if (!QFile::setPermissions(fileName, permissions))
- {
- AZ_TracePrintf(AssetProcessor::DebugChannel, "Unable to change file attributes for the file: %s.\n", fileName.toUtf8().data());
- }
- }
- #endif
- EXPECT_TRUE(AssetUtilities::MakeFileWritable(fileName));
- }
- TEST_F(UtilitiesUnitTests, NormalizeAndRemoveAlias_FeedFilePathWithDoubleSlackesAndAlias_Succeeds)
- {
- EXPECT_EQ(AssetUtilities::NormalizeAndRemoveAlias("@test@\\my\\file.txt"), QString("my/file.txt"));
- EXPECT_EQ(AssetUtilities::NormalizeAndRemoveAlias("@test@my\\file.txt"), QString("my/file.txt"));
- EXPECT_EQ(AssetUtilities::NormalizeAndRemoveAlias("@TeSt@my\\file.txt"), QString("my/file.txt")); // case sensitivity test!
- }
- TEST_F(UtilitiesUnitTests, CopyFileWithTimeout_FeedFilesInDifferentStates_Succeeds)
- {
- QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
- QString fileName(dir.filePath("test.txt"));
- QString outputFileName(dir.filePath("test1.txt"));
-
- QFile inputFile(fileName);
- inputFile.open(QFile::WriteOnly);
- QFile outputFile(outputFileName);
- outputFile.open(QFile::WriteOnly);
- const int waitTimeInSeconds = 1; // Timeout for copying files
- #if defined(AZ_PLATFORM_WINDOWS)
- // this test is intentionally disabled on other platforms
- // because in general on other platforms its actually possible to delete and move
- // files out of the way even if they are currently opened for writing by a different
- // handle.
- //Trying to copy when the output file is open for reading should fail.
- {
- UnitTestUtils::AssertAbsorber absorb;
- EXPECT_FALSE(CopyFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
- // Expected two warnings for copying files:
- // 1) Failed to remove old files
- // 2) Copy operation failed for the given timeout
- EXPECT_EQ(absorb.m_numWarningsAbsorbed, 1);
- // Expected another two warnings for moving files:
- // 1) Failed to remove old files
- // 2) Move operation failed for the given timeout
- EXPECT_FALSE(MoveFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
- EXPECT_EQ(absorb.m_numWarningsAbsorbed, 2);
- }
- #endif // AZ_PLATFORM_WINDOWS ONLY
- inputFile.close();
- outputFile.close();
- //Trying to copy when the output file is not open
- EXPECT_TRUE(CopyFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
- EXPECT_TRUE(CopyFileWithTimeout(fileName, outputFileName, aznumeric_caster(-1)));//invalid timeout time
- // Trying to move when the output file is not open
- EXPECT_TRUE(MoveFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
- EXPECT_TRUE(MoveFileWithTimeout(outputFileName, fileName, waitTimeInSeconds));
- // Open the file and then close it in the near future
- AZStd::atomic_bool setupDone{ false };
- AssetProcessor::AutoThreadJoiner joiner(new AZStd::thread(
- [&]()
- {
- //opening file
- outputFile.open(QFile::WriteOnly);
- setupDone = true;
- AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(500));
- //closing file
- outputFile.close();
- }));
- while (!setupDone.load())
- {
- QThread::msleep(1);
- }
- EXPECT_TRUE(outputFile.isOpen());
- //Trying to copy when the output file is open,but will close before the timeout inputted
- {
- UnitTestUtils::AssertAbsorber absorb;
- EXPECT_TRUE(CopyFileWithTimeout(fileName, outputFileName, waitTimeInSeconds));
- #if defined(AZ_PLATFORM_WINDOWS)
- // only windows has an issue with moving files out that are in use.
- // other platforms do so without issue.
- EXPECT_EQ(absorb.m_numWarningsAbsorbed, 0);
- #endif // windows platform.
- }
- }
- TEST_F(UtilitiesUnitTests, CheckCanLock_FeedFileToLock_GetsLockStatus)
- {
- QDir lockTestDir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
- QString lockTestFileName(lockTestDir.filePath("lockTest.txt"));
- EXPECT_FALSE(AssetUtilities::CheckCanLock(lockTestFileName));
- EXPECT_TRUE(CreateDummyFile(lockTestFileName));
- EXPECT_TRUE(AssetUtilities::CheckCanLock(lockTestFileName));
- #if defined(AZ_PLATFORM_WINDOWS)
- // on windows, opening a file for reading locks it
- // but on other platforms, this is not the case.
- QFile lockTestFile(lockTestFileName);
- lockTestFile.open(QFile::ReadOnly);
- #elif defined(AZ_PLATFORM_LINUX)
- int handle = open(lockTestFileName.toUtf8().constData(), O_RDONLY | O_EXCL | O_NONBLOCK);
- #else
- int handle = open(lockTestFileName.toUtf8().constData(), O_RDONLY | O_EXLOCK | O_NONBLOCK);
- #endif // AZ_PLATFORM_WINDOWS
- #if defined(AZ_PLATFORM_WINDOWS)
- EXPECT_FALSE(AssetUtilities::CheckCanLock(lockTestFileName));
- lockTestFile.close();
- #else
- if (handle != -1)
- {
- close(handle);
- }
- #endif // windows/other platforms ifdef
- }
- TEST_F(UtilitiesUnitTests, TestByteArrayStream_WriteToStream_Succeeds)
- {
- AssetProcessor::ByteArrayStream stream;
- EXPECT_TRUE(stream.CanSeek());
- EXPECT_TRUE(stream.IsOpen());
- EXPECT_EQ(stream.GetLength(), 0);
- EXPECT_EQ(stream.GetCurPos(), 0);
- char tempReadBuffer[24];
- azstrcpy(tempReadBuffer, 24, "This is a Test String");
- EXPECT_EQ(stream.Read(100, tempReadBuffer), 0);
- // reserving does not alter the length.
- stream.Reserve(128);
- EXPECT_EQ(stream.GetLength(), 0);
- // Write 7 bytes from the buffer to the stream
- AZ::IO::SizeType sizeInBytesToWrite = 7;
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, tempReadBuffer), sizeInBytesToWrite);
- AZ::IO::SizeType expectedPosition = sizeInBytesToWrite;
- AZ::IO::SizeType expectedLength = sizeInBytesToWrite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), tempReadBuffer, expectedLength), 0);
- // Write 7 bytes from the buffer to the stream again
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, tempReadBuffer), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- expectedLength += sizeInBytesToWrite;
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "This isThis is", expectedLength), 0);
- sizeInBytesToWrite = 4;
- AZ::IO::SizeType offsite = 0;
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_BEGIN); // write at begin, without overrunning
- expectedPosition = offsite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, "that"), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "that isThis is", expectedLength), 0);
- offsite = 2;
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_CUR); // write in middle, without overrunning
- expectedPosition += offsite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, "1234"), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "that i1234s is", expectedLength), 0);
- offsite = AZ::IO::SizeType(-6);
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_END); // write in end, negative offset, without overrunning
- expectedPosition = expectedLength + offsite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, "5555"), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "that i125555is", expectedLength), 0);
- offsite = 2;
- sizeInBytesToWrite = 14;
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_BEGIN); // write at begin offset, with overrun:
- expectedPosition = offsite;
- EXPECT_EQ(stream.GetCurPos(), offsite);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, "xxxxxxxxxxxxxx"), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- expectedLength = expectedPosition;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "thxxxxxxxxxxxxxx", expectedLength), 0);
- offsite = 14;
- sizeInBytesToWrite = 4;
- stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_CUR); // write in middle, with overrunning:
- expectedPosition = offsite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, "yyyy"), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- expectedLength = expectedPosition;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "thxxxxxxxxxxxxyyyy", expectedLength), 0);
- offsite = AZ::IO::SizeType(-2);
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_END); // write in end, negative offset, with overrunning
- expectedPosition = expectedLength + offsite;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(stream.Write(sizeInBytesToWrite, "ZZZZ"), sizeInBytesToWrite);
- expectedPosition += sizeInBytesToWrite;
- expectedLength = expectedPosition;
- EXPECT_EQ(stream.GetCurPos(), expectedPosition);
- EXPECT_EQ(stream.GetLength(), expectedLength);
- EXPECT_EQ(memcmp(stream.GetArray().constData(), "thxxxxxxxxxxxxyyZZZZ", expectedLength), 0);
- // read test.
- stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
- EXPECT_EQ(stream.Read(expectedLength, tempReadBuffer), expectedLength);
- EXPECT_EQ(memcmp(tempReadBuffer, "thxxxxxxxxxxxxyyZZZZ", expectedLength), 0);
- EXPECT_EQ(stream.Read(expectedLength, tempReadBuffer), 0); // because its already at end.
- EXPECT_EQ(memcmp(tempReadBuffer, "thxxxxxxxxxxxxyyZZZZ", expectedLength), 0); // it should not have disturbed the buffer.
- offsite = 2;
- stream.Seek(offsite, AZ::IO::GenericStream::ST_SEEK_BEGIN);
- EXPECT_EQ(stream.Read(expectedLength, tempReadBuffer), expectedLength - offsite);
- EXPECT_EQ(memcmp(tempReadBuffer, "xxxxxxxxxxxxyyZZZZZZ", expectedLength), 0); // it should not have disturbed the buffer bits that it was not asked to touch.
- }
- TEST_F(UtilitiesUnitTests, MatchFilePattern_FeedDifferentPatternsAndFilePaths_Succeeds)
- {
- {
- AssetBuilderSDK::FilePatternMatcher extensionWildcardTest(AssetBuilderSDK::AssetBuilderPattern("*.cfg", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(extensionWildcardTest.MatchesPath(AZStd::string("foo.cfg")));
- EXPECT_TRUE(extensionWildcardTest.MatchesPath(AZStd::string("abcd/foo.cfg")));
- EXPECT_FALSE(extensionWildcardTest.MatchesPath(AZStd::string("abcd/foo.cfd")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher prefixWildcardTest(AssetBuilderSDK::AssetBuilderPattern("abf*.llm", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(prefixWildcardTest.MatchesPath(AZStd::string("abf.llm")));
- EXPECT_TRUE(prefixWildcardTest.MatchesPath(AZStd::string("abf12345.llm")));
- EXPECT_FALSE(prefixWildcardTest.MatchesPath(AZStd::string("foo/abf12345.llm")));
- EXPECT_FALSE(prefixWildcardTest.MatchesPath(AZStd::string("foo/abf12345.lls")));
- EXPECT_FALSE(prefixWildcardTest.MatchesPath(AZStd::string("foo/ab2345.llm")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher extensionPrefixWildcardTest(AssetBuilderSDK::AssetBuilderPattern("sdf.c*", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cpp")));
- EXPECT_TRUE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cxx")));
- EXPECT_TRUE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.c")));
- EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cpp")));
- EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.cpp")));
- EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdc.c")));
- EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.hxx")));
- EXPECT_FALSE(extensionPrefixWildcardTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.hxx")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher prefixExtensionPrefixWildcardTest(AssetBuilderSDK::AssetBuilderPattern("s*.c*", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cpp")));
- EXPECT_TRUE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.cxx")));
- EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cpp")));
- EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("c:\\asd/abcd/sdf.cpp")));
- EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("sdf.hxx")));
- EXPECT_FALSE(prefixExtensionPrefixWildcardTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.hxx")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher fixedNameTest(AssetBuilderSDK::AssetBuilderPattern("a.bcd", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(fixedNameTest.MatchesPath(AZStd::string("a.bcd")));
- EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("foo\\a.bcd")));
- EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("foo/a.bcd")));
- EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("c:/foo/a.bcd")));
- EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("c:\\foo/a.bcd")));
- EXPECT_FALSE(fixedNameTest.MatchesPath(AZStd::string("sdf.hxx")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher midMatchExtensionPrefixTest(AssetBuilderSDK::AssetBuilderPattern("s*f.c*", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sdf.cpp")));
- EXPECT_TRUE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sef.cxx")));
- EXPECT_TRUE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sf.c")));
- EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("c:\\asd/abcd/sdf.cpp")));
- EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("abcd/sdf.cpp")));
- EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sdc.c")));
- EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("sdf.hxx")));
- EXPECT_FALSE(midMatchExtensionPrefixTest.MatchesPath(AZStd::string("s:\\asd/abcd/sdf.hxx")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher subFolderExtensionWildcardTest(AssetBuilderSDK::AssetBuilderPattern("abcd/*.cfg", AssetBuilderSDK::AssetBuilderPattern::Wildcard));
- EXPECT_TRUE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cfg")));
- EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("c://abcd/sdf.cfg")));
- EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("sdf.cfg")));
- EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("abcs/sdf.cfg")));
- EXPECT_FALSE(subFolderExtensionWildcardTest.MatchesPath(AZStd::string("abcd/sdf.cfx")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher subFolderPatternTest(AssetBuilderSDK::AssetBuilderPattern(".*\\/savebackup\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex));
- EXPECT_TRUE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/savebackup/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/savebackup")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("savebackup/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("c://abcd/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcs/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/sdf.cfx")));
- }
- {
- AssetBuilderSDK::FilePatternMatcher subFolderPatternTest(AssetBuilderSDK::AssetBuilderPattern(".*\\/Presets\\/GeomCache\\/.*", AssetBuilderSDK::AssetBuilderPattern::Regex));
- EXPECT_TRUE(subFolderPatternTest.MatchesPath(AZStd::string("something/Presets/GeomCache/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("Presets/GeomCache/sdf.cfg"))); // should not match because it demands that there is a slash
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/savebackup")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("savebackup/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("c://abcd/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcs/sdf.cfg")));
- EXPECT_FALSE(subFolderPatternTest.MatchesPath(AZStd::string("abcd/sdf.cfx")));
- }
- }
- TEST_F(UtilitiesUnitTests, GetFileHashFromStream_NullPath_Returns0)
- {
- AZ::u64 result = GetFileHash(nullptr);
- EXPECT_EQ(result, 0);
- }
- TEST_F(UtilitiesUnitTests, GetFileHashFromStreamSmallFile_ReturnsExpectedHash)
- {
- QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
- QString fileName(dir.filePath("test.txt"));
- CreateDummyFile(fileName);
- AZ::u64 result = GetFileHash(fileName.toUtf8());
- EXPECT_EQ(result, AZ::u64(17241709254077376921ul));
- }
- TEST_F(UtilitiesUnitTests, GetFileHashFromStream_SmallFileForced_ReturnsExpectedHash)
- {
- QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
- QString fileName(dir.filePath("test.txt"));
- CreateDummyFile(fileName);
- AZ::u64 result = GetFileHash(fileName.toUtf8(), true);
- EXPECT_EQ(result, AZ::u64(17241709254077376921ul));
- }
- // This tests a race condition where one process was writing to a file and the Asset Processor started hashing that file
- // in a rare edge case, the end of file check used within the FileIOStream was reporting an incorrect end of file.
- // This job runs in a separate thread at the same time the hashing is run, and replicates this edge case.
- class FileWriteThrashTestJob : public AZ::Job
- {
- public:
- AZ_CLASS_ALLOCATOR(FileWriteThrashTestJob, AZ::ThreadPoolAllocator);
- FileWriteThrashTestJob(bool deleteWhenDone, AZ::JobContext* jobContext, AZ::IO::HandleType fileHandle, AZStd::string_view bufferToWrite)
- : Job(deleteWhenDone, jobContext),
- m_fileHandle(fileHandle),
- m_bufferToWrite(bufferToWrite)
- {
- for (; m_initialWriteCount >= 0; --m_initialWriteCount)
- {
- AZ::IO::FileIOBase::GetInstance()->Write(m_fileHandle, m_bufferToWrite.c_str(), m_bufferToWrite.length());
- }
- }
- void Process() override
- {
- for (; m_writeLoopCount >= 0; --m_writeLoopCount)
- {
- AZ::IO::FileIOBase::GetInstance()->Write(m_fileHandle, m_bufferToWrite.c_str(), m_bufferToWrite.length());
- // Writing this unsigned int triggers the race condition more often.
- unsigned int uintToWrite = 10;
- AZ::IO::FileIOBase::GetInstance()->Write(m_fileHandle, &uintToWrite, sizeof(uintToWrite));
- AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1));
- }
- AZ::IO::FileIOBase::GetInstance()->Close(m_fileHandle);
- }
- AZStd::string m_bufferToWrite;
- AZ::IO::HandleType m_fileHandle;
- int m_writeLoopCount = 1023 * 10; // Write enough times to trigger the race condition, a bit more than 10 seconds.
- int m_initialWriteCount = 1021 * 10; // Start with a larger file to make sure the hash operation won't finish immediately.
- };
- // This is a regression test for a bug found with this repro:
- // In the editor, export a level, the asset processor shuts down.
- // This occured because the asset processor's file hashing picked up the temporary navigation mesh file
- // when it was created, and started hashing it. At the same time, the editor was still writing to this file.
- // When the file hash called AZ::IO::FileIOStream's read function with a buffer to read in this file,
- // and it hit the end of the file and it read less than the buffer, the end of file checked used by the
- // AZ::IO::FileIOStream would sometimes incorrectly report that it did not hit the end of the file.
- // The hash function was updated to use a more accurate check. Instead of always requesting to read the buffer size,
- // it requests to read either the buffer size, or the remaining length of the file.
- // This test replicates that behavior by starting a job on another thread that writes to a file,
- // and at the same time it runs the file hashing process. With the fix reverted, this test fails.
- // This test purposely does not force a failure state to verify the assert would occur because
- // it's a race condition, and this test should not fail due to timing issues on different machines.
- class MockTraceMessageBusHandler
- : public AZ::Debug::TraceMessageBus::Handler
- {
- public:
- bool OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override
- {
- m_assertTriggered = true;
- return true;
- }
- bool m_assertTriggered = false;
- };
- TEST_F(UtilitiesUnitTests, GetFileHashFromStream_LargeFileForcedAnotherThreadWritesToFile_ReturnsExpectedHash)
- {
- MockTraceMessageBusHandler traceMessageBusHandler;
- traceMessageBusHandler.BusConnect();
- QDir dir(m_assetDatabaseRequestsHandler->GetAssetRootDir().c_str());
- QString fileName(dir.filePath("test.txt"));
- CreateDummyFile(fileName);
- // Use a small buffer to frequently write a lot of data into the file, to help force the race condition.
- char buffer[10];
- memset(buffer, 'a', AZ_ARRAY_SIZE(buffer));
- AZ::IO::HandleType writeHandle;
- // Using a file handle and not a file stream because the navigation mesh system used this same interface for writing the file.
- AZ::IO::FileIOBase::GetInstance()->Open(fileName.toUtf8(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, writeHandle);
- // The job will close the stream
- FileWriteThrashTestJob* job = aznew FileWriteThrashTestJob(true, nullptr, writeHandle, AZStd::string_view(buffer, AZ_ARRAY_SIZE(buffer)));
- job->m_writeLoopCount = 100; // compensate for artificial hashing delay below, keeping this test duration similar to others
- job->Start();
- // Use an artificial delay on hashing to ensure the race condition actually occurs.
- AZ::u64 result =
- GetFileHash(fileName.toUtf8(), true, nullptr, /*hashMsDelay*/ 20);
- // This test will result in different hash results on different machines, because writing to the stream
- // and reading from the stream to generate the hash happen at different speeds in different setups.
- // Just make sure it returns some result here.
- EXPECT_NE(result, 0);
- EXPECT_FALSE(traceMessageBusHandler.m_assertTriggered);
- traceMessageBusHandler.BusDisconnect();
- }
|