UnitTestUtilities.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  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 <native/tests/UnitTestUtilities.h>
  9. namespace UnitTests
  10. {
  11. MockComponentApplication::MockComponentApplication()
  12. {
  13. AZ::ComponentApplicationBus::Handler::BusConnect();
  14. AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
  15. }
  16. MockComponentApplication::~MockComponentApplication()
  17. {
  18. AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
  19. AZ::ComponentApplicationBus::Handler::BusDisconnect();
  20. }
  21. MockPathConversion::MockPathConversion(const char* scanfolder /*= "c:/somepath" */)
  22. {
  23. m_scanFolderInfo = AssetProcessor::ScanFolderInfo{ scanfolder, "scanfolder", "scanfolder", true, true, { AssetBuilderSDK::PlatformInfo{ "pc", {} } }, 0, 1 };
  24. }
  25. void MockPathConversion::SetScanFolder(const AssetProcessor::ScanFolderInfo& scanFolderInfo)
  26. {
  27. m_scanFolderInfo = scanFolderInfo;
  28. }
  29. bool MockPathConversion::ConvertToRelativePath(QString fullFileName, QString& databaseSourceName, QString& scanFolderName) const
  30. {
  31. EXPECT_TRUE(fullFileName.startsWith(m_scanFolderInfo.ScanPath(), Qt::CaseInsensitive));
  32. scanFolderName = m_scanFolderInfo.ScanPath();
  33. databaseSourceName = fullFileName.mid(scanFolderName.size() + 1);
  34. return true;
  35. }
  36. const AssetProcessor::ScanFolderInfo* MockPathConversion::GetScanFolderForFile(const QString& /*fullFileName*/) const
  37. {
  38. return &m_scanFolderInfo;
  39. }
  40. const AssetProcessor::ScanFolderInfo* MockPathConversion::GetScanFolderById(AZ::s64 /*id*/) const
  41. {
  42. return &m_scanFolderInfo;
  43. }
  44. bool MockMultiPathConversion::ConvertToRelativePath(QString fullFileName, QString& databaseSourceName, QString& scanFolderName) const
  45. {
  46. auto scanfolder = GetScanFolderForFile(fullFileName);
  47. EXPECT_TRUE(scanfolder);
  48. scanFolderName = scanfolder->ScanPath();
  49. databaseSourceName = fullFileName.mid(scanFolderName.size() + 1);
  50. return true;
  51. }
  52. const AssetProcessor::ScanFolderInfo* MockMultiPathConversion::GetScanFolderForFile(const QString& fullFileName) const
  53. {
  54. for (const auto& scanfolder : m_scanFolderInfo)
  55. {
  56. if (AZ::IO::PathView(fullFileName.toUtf8().constData())
  57. .IsRelativeTo(AZ::IO::PathView(scanfolder.ScanPath().toUtf8().constData())))
  58. {
  59. return &scanfolder;
  60. }
  61. }
  62. return nullptr;
  63. }
  64. const AssetProcessor::ScanFolderInfo* MockMultiPathConversion::GetScanFolderById(AZ::s64 id) const
  65. {
  66. return &m_scanFolderInfo[id - 1];
  67. }
  68. void MockMultiPathConversion::AddScanfolder(QString path, QString name)
  69. {
  70. m_scanFolderInfo.push_back(
  71. AssetProcessor::ScanFolderInfo(path, name, name, false, true, { AssetBuilderSDK::PlatformInfo{ "pc", {} } }, 0, m_scanFolderInfo.size() + 1));
  72. }
  73. constexpr AZ::u32 MockVirtualFileIO::ComputeHandle(AZ::IO::PathView path)
  74. {
  75. return AZ::u32(AZStd::hash<AZ::IO::PathView>{}(path));
  76. }
  77. MockMultiBuilderInfoHandler::~MockMultiBuilderInfoHandler()
  78. {
  79. BusDisconnect();
  80. }
  81. void MockMultiBuilderInfoHandler::GetMatchingBuildersInfo(
  82. const AZStd::string& assetPath, AssetProcessor::BuilderInfoList& builderInfoList)
  83. {
  84. AZStd::set<AZ::Uuid> uniqueBuilderDescIDs;
  85. for (AssetUtilities::BuilderFilePatternMatcher& matcherPair : m_matcherBuilderPatterns)
  86. {
  87. if (uniqueBuilderDescIDs.find(matcherPair.GetBuilderDescID()) != uniqueBuilderDescIDs.end())
  88. {
  89. continue;
  90. }
  91. if (matcherPair.MatchesPath(assetPath))
  92. {
  93. const AssetBuilderSDK::AssetBuilderDesc& builderDesc = m_builderDescMap[matcherPair.GetBuilderDescID()];
  94. uniqueBuilderDescIDs.insert(matcherPair.GetBuilderDescID());
  95. builderInfoList.push_back(builderDesc);
  96. }
  97. }
  98. }
  99. void MockMultiBuilderInfoHandler::GetAllBuildersInfo([[maybe_unused]] AssetProcessor::BuilderInfoList& builderInfoList)
  100. {
  101. for (const auto& builder : m_builderDescMap)
  102. {
  103. builderInfoList.push_back(builder.second);
  104. }
  105. }
  106. void MockMultiBuilderInfoHandler::CreateJobs(
  107. AssetBuilderExtraInfo extraInfo, const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
  108. {
  109. response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
  110. for (const auto& platform : request.m_enabledPlatforms)
  111. {
  112. AssetBuilderSDK::JobDescriptor jobDescriptor;
  113. jobDescriptor.m_priority = 0;
  114. jobDescriptor.m_critical = true;
  115. jobDescriptor.m_jobKey = "Mock Job";
  116. jobDescriptor.SetPlatformIdentifier(platform.m_identifier.c_str());
  117. jobDescriptor.m_additionalFingerprintInfo = extraInfo.m_jobFingerprint.toUtf8().constData();
  118. if (!extraInfo.m_jobDependencyFilePath.isEmpty())
  119. {
  120. auto jobDependency = AssetBuilderSDK::JobDependency(
  121. "Mock Job", "pc", AssetBuilderSDK::JobDependencyType::Order,
  122. AssetBuilderSDK::SourceFileDependency(extraInfo.m_jobDependencyFilePath.toUtf8().constData(), AZ::Uuid::CreateNull()));
  123. if (!extraInfo.m_subIdDependencies.empty())
  124. {
  125. jobDependency.m_productSubIds = extraInfo.m_subIdDependencies;
  126. }
  127. jobDescriptor.m_jobDependencyList.push_back(jobDependency);
  128. }
  129. if (!extraInfo.m_dependencyFilePath.isEmpty())
  130. {
  131. response.m_sourceFileDependencyList.push_back(
  132. AssetBuilderSDK::SourceFileDependency(extraInfo.m_dependencyFilePath.toUtf8().constData(), AZ::Uuid::CreateNull()));
  133. }
  134. response.m_createJobOutputs.push_back(jobDescriptor);
  135. m_createJobsCount++;
  136. }
  137. }
  138. void MockMultiBuilderInfoHandler::ProcessJob(
  139. AssetBuilderExtraInfo extraInfo,
  140. [[maybe_unused]] const AssetBuilderSDK::ProcessJobRequest& request,
  141. AssetBuilderSDK::ProcessJobResponse& response)
  142. {
  143. response.m_resultCode = AssetBuilderSDK::ProcessJobResultCode::ProcessJobResult_Success;
  144. }
  145. void MockMultiBuilderInfoHandler::CreateBuilderDesc(
  146. const QString& builderName,
  147. const QString& builderId,
  148. const AZStd::vector<AssetBuilderSDK::AssetBuilderPattern>& builderPatterns,
  149. const AssetBuilderExtraInfo& extraInfo)
  150. {
  151. CreateBuilderDesc(
  152. builderName, builderId, builderPatterns,
  153. AZStd::bind(&MockMultiBuilderInfoHandler::CreateJobs, this, extraInfo, AZStd::placeholders::_1, AZStd::placeholders::_2),
  154. AZStd::bind(&MockMultiBuilderInfoHandler::ProcessJob, this, extraInfo, AZStd::placeholders::_1, AZStd::placeholders::_2), extraInfo.m_analysisFingerprint);
  155. }
  156. void MockMultiBuilderInfoHandler::CreateBuilderDescInfoRef(
  157. const QString& builderName,
  158. const QString& builderId,
  159. const AZStd::vector<AssetBuilderSDK::AssetBuilderPattern>& builderPatterns,
  160. const AssetBuilderExtraInfo& extraInfo)
  161. {
  162. CreateBuilderDesc(
  163. builderName, builderId, builderPatterns,
  164. AZStd::bind(&MockMultiBuilderInfoHandler::CreateJobs, this, AZStd::ref(extraInfo), AZStd::placeholders::_1, AZStd::placeholders::_2),
  165. AZStd::bind(&MockMultiBuilderInfoHandler::ProcessJob, this, AZStd::ref(extraInfo), AZStd::placeholders::_1, AZStd::placeholders::_2),
  166. extraInfo.m_analysisFingerprint);
  167. }
  168. void MockMultiBuilderInfoHandler::CreateBuilderDesc(
  169. const QString& builderName,
  170. const QString& builderId,
  171. const AZStd::vector<AssetBuilderSDK::AssetBuilderPattern>& builderPatterns,
  172. const AssetBuilderSDK::CreateJobFunction& createJobsFunction,
  173. const AssetBuilderSDK::ProcessJobFunction& processJobFunction,
  174. AZStd::optional<QString> analysisFingerprint)
  175. {
  176. AssetBuilderSDK::AssetBuilderDesc builderDesc;
  177. builderDesc.m_name = builderName.toUtf8().data();
  178. builderDesc.m_patterns = builderPatterns;
  179. builderDesc.m_busId = AZ::Uuid::CreateString(builderId.toUtf8().data());
  180. builderDesc.m_builderType = AssetBuilderSDK::AssetBuilderDesc::AssetBuilderType::Internal;
  181. builderDesc.m_createJobFunction = createJobsFunction;
  182. builderDesc.m_processJobFunction = processJobFunction;
  183. if (analysisFingerprint && !analysisFingerprint->isEmpty())
  184. {
  185. builderDesc.m_analysisFingerprint = analysisFingerprint.value().toUtf8().constData();
  186. }
  187. m_builderDescMap[builderDesc.m_busId] = builderDesc;
  188. for (const AssetBuilderSDK::AssetBuilderPattern& pattern : builderDesc.m_patterns)
  189. {
  190. AssetUtilities::BuilderFilePatternMatcher patternMatcher(pattern, builderDesc.m_busId);
  191. m_matcherBuilderPatterns.push_back(patternMatcher);
  192. }
  193. }
  194. TraceBusErrorChecker::TraceBusErrorChecker()
  195. {
  196. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  197. }
  198. TraceBusErrorChecker::~TraceBusErrorChecker()
  199. {
  200. EXPECT_FALSE(m_expectingFailure);
  201. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  202. }
  203. void TraceBusErrorChecker::Begin()
  204. {
  205. m_expectingFailure = true;
  206. m_suppressedMessages.clear();
  207. }
  208. void TraceBusErrorChecker::End(int expectedCount)
  209. {
  210. m_expectingFailure = false;
  211. if (expectedCount != m_suppressedMessages.size())
  212. {
  213. for (const auto& message : m_suppressedMessages)
  214. {
  215. AZ::Debug::Trace::Instance().Output("", message.c_str());
  216. }
  217. EXPECT_EQ(expectedCount, m_suppressedMessages.size());
  218. }
  219. }
  220. bool TraceBusErrorChecker::OnPreAssert(
  221. const char* fileName,
  222. int line,
  223. const char* func,
  224. const char* message)
  225. {
  226. RecordError(fileName, line, func, message);
  227. return m_expectingFailure;
  228. }
  229. bool TraceBusErrorChecker::OnPreError(
  230. [[maybe_unused]] const char* window,
  231. const char* fileName,
  232. int line,
  233. const char* func,
  234. const char* message)
  235. {
  236. RecordError(fileName, line, func, message);
  237. return m_expectingFailure;
  238. }
  239. bool TraceBusErrorChecker::OnPreWarning(
  240. [[maybe_unused]] const char* window,
  241. const char* fileName,
  242. int line,
  243. const char* func,
  244. const char* message)
  245. {
  246. RecordError(fileName, line, func, message);
  247. return m_expectingFailure;
  248. }
  249. void TraceBusErrorChecker::RecordError(const char* fileName, int line, const char* func, const char* message)
  250. {
  251. AZStd::string errorMessage = AZStd::string::format("%s(%d): %s - %s\n", fileName, line, func, message);
  252. if (!m_expectingFailure)
  253. {
  254. AZ::Debug::Trace::Instance().Output("", errorMessage.c_str());
  255. GTEST_NONFATAL_FAILURE_("Unexpected failure occurred");
  256. }
  257. m_suppressedMessages.push_back(errorMessage);
  258. }
  259. MockVirtualFileIO::MockVirtualFileIO()
  260. {
  261. // Cache the existing file io instance and build our mock file io
  262. m_priorFileIO = AZ::IO::FileIOBase::GetInstance();
  263. m_fileIOMock = AZStd::make_unique<testing::NiceMock<AZ::IO::MockFileIOBase>>();
  264. // Swap out current file io instance for our mock
  265. AZ::IO::FileIOBase::SetInstance(nullptr);
  266. AZ::IO::FileIOBase::SetInstance(m_fileIOMock.get());
  267. // Setup the default returns for our mock file io calls
  268. AZ::IO::MockFileIOBase::InstallDefaultReturns(*m_fileIOMock.get());
  269. using namespace ::testing;
  270. using namespace AZ;
  271. ON_CALL(*m_fileIOMock, Open(_, _, _))
  272. .WillByDefault(Invoke(
  273. [this](auto filePath, IO::OpenMode mode, IO::HandleType& handle)
  274. {
  275. AZStd::string normalizedPath(filePath);
  276. AzFramework::StringFunc::Path::Normalize(normalizedPath);
  277. handle = ComputeHandle(AZ::IO::PathView(normalizedPath));
  278. int systemMode = AZ::IO::TranslateOpenModeToSystemFileMode(normalizedPath.c_str(), mode);
  279. // Any mode besides OPEN_READ_ONLY creates a file
  280. if ((systemMode & ~int(IO::SystemFile::SF_OPEN_READ_ONLY)) > 0)
  281. {
  282. m_mockFiles[handle] = { normalizedPath, "" };
  283. }
  284. return AZ::IO::Result(AZ::IO::ResultCode::Success);
  285. }));
  286. ON_CALL(*m_fileIOMock, Tell(_, _))
  287. .WillByDefault(Invoke(
  288. [](auto, auto& offset)
  289. {
  290. offset = 0;
  291. return AZ::IO::ResultCode::Success;
  292. }));
  293. ON_CALL(*m_fileIOMock, Size(An<AZ::IO::HandleType>(), _))
  294. .WillByDefault(Invoke(
  295. [this](auto handle, AZ::u64& size)
  296. {
  297. auto itr = m_mockFiles.find(handle);
  298. size = itr != m_mockFiles.end() ? itr->second.second.size() : 0;
  299. return AZ::IO::ResultCode::Success;
  300. }));
  301. ON_CALL(*m_fileIOMock, Size(An<const char*>(), _))
  302. .WillByDefault(Invoke(
  303. [this](const char* filePath, AZ::u64& size)
  304. {
  305. AZStd::string normalizedPath(filePath);
  306. AzFramework::StringFunc::Path::Normalize(normalizedPath);
  307. auto handle = ComputeHandle(AZ::IO::PathView(normalizedPath));
  308. auto itr = m_mockFiles.find(handle);
  309. size = itr != m_mockFiles.end() ? itr->second.second.size() : 0;
  310. return AZ::IO::ResultCode::Success;
  311. }));
  312. ON_CALL(*m_fileIOMock, Exists(_))
  313. .WillByDefault(Invoke(
  314. [this](const char* filePath)
  315. {
  316. AZStd::string normalizedPath(filePath);
  317. AzFramework::StringFunc::Path::Normalize(normalizedPath);
  318. auto handle = ComputeHandle(AZ::IO::PathView(normalizedPath));
  319. auto itr = m_mockFiles.find(handle);
  320. return itr != m_mockFiles.end();
  321. }));
  322. ON_CALL(*m_fileIOMock, Rename(_, _))
  323. .WillByDefault(Invoke(
  324. [this](const char* originalPath, const char* newPath)
  325. {
  326. AZStd::string normalizedOriginalPath(originalPath);
  327. AzFramework::StringFunc::Path::Normalize(normalizedOriginalPath);
  328. auto originalHandle = ComputeHandle(AZ::IO::PathView(normalizedOriginalPath));
  329. AZStd::string normalizedNewPath(newPath);
  330. AzFramework::StringFunc::Path::Normalize(normalizedNewPath);
  331. auto newHandle = ComputeHandle(AZ::IO::PathView(normalizedNewPath));
  332. auto itr = m_mockFiles.find(originalHandle);
  333. if (itr != m_mockFiles.end())
  334. {
  335. auto& [path, contents] = itr->second;
  336. path = normalizedNewPath;
  337. if (originalHandle != newHandle)
  338. {
  339. m_mockFiles[newHandle] = itr->second;
  340. m_mockFiles.erase(itr);
  341. }
  342. return AZ::IO::ResultCode::Success;
  343. }
  344. return AZ::IO::ResultCode::Error;
  345. }));
  346. ON_CALL(*m_fileIOMock, Remove(_))
  347. .WillByDefault(Invoke(
  348. [this](const char* filePath)
  349. {
  350. AZStd::string normalizedPath(filePath);
  351. AzFramework::StringFunc::Path::Normalize(normalizedPath);
  352. auto handle = ComputeHandle(AZ::IO::PathView(normalizedPath));
  353. m_mockFiles.erase(handle);
  354. return AZ::IO::ResultCode::Success;
  355. }));
  356. ON_CALL(*m_fileIOMock, Read(_, _, _, _, _))
  357. .WillByDefault(Invoke(
  358. [this](auto handle, void* buffer, auto, auto, AZ::u64* bytesRead)
  359. {
  360. auto itr = m_mockFiles.find(handle);
  361. if (itr == m_mockFiles.end())
  362. {
  363. return AZ::IO::ResultCode::Error;
  364. }
  365. memcpy(buffer, itr->second.second.c_str(), itr->second.second.size());
  366. *bytesRead = itr->second.second.size();
  367. return AZ::IO::ResultCode::Success;
  368. }));
  369. ON_CALL(*m_fileIOMock, Write(_, _, _, _))
  370. .WillByDefault(Invoke(
  371. [this](IO::HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten)
  372. {
  373. auto& pair = m_mockFiles[fileHandle];
  374. pair.second.resize(size);
  375. memcpy((void*)pair.second.c_str(), buffer, size);
  376. if (bytesWritten)
  377. {
  378. *bytesWritten = size;
  379. }
  380. return AZ::IO::ResultCode::Success;
  381. }));
  382. ON_CALL(*m_fileIOMock, FindFiles(_, _, _))
  383. .WillByDefault(Invoke(
  384. [this](const char* filePath, const char* filter, auto callback)
  385. {
  386. if ((!filePath)||(filePath[0] == 0))
  387. {
  388. return AZ::IO::ResultCode::Error;
  389. }
  390. AZStd::string normalizedPath(filePath);
  391. AzFramework::StringFunc::Path::Normalize(normalizedPath);
  392. size_t filePathLen = normalizedPath.length();
  393. // there is unfortunately an extra special case here
  394. // This function could be called with filePath being something like "c:/" for the root of the file system
  395. // so the wildcard search term has to be "c:/{filter}"
  396. // but could also be called without a trailing slash for all other folders
  397. // like "c:/somepath", and thus the formatting string to combine them must have a trailing slash.
  398. // We are specifically AVOIDING using AZ::IO::Path here because these are mock paths that might
  399. // be invalid paths on posix (for example, a unit test could ask for "c:/whatever" - the file system
  400. // is also a mock file system.)
  401. const char endingChar = normalizedPath[filePathLen - 1];
  402. bool filePathHasTrailingSlash = endingChar == AZ_CORRECT_FILESYSTEM_SEPARATOR;
  403. AZStd::string formatter = filePathHasTrailingSlash ? "%s%s" : "%s" AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING "%s";
  404. // mockFiles contains only files, but this function is expected to output directories as well
  405. // we will emit any directory that is a substring of a stored file path.
  406. // this will cause it to emit the same one multiple times, but this is enough for emulation.
  407. for (const auto& [hash, pair] : m_mockFiles)
  408. {
  409. const auto& [path, contents] = pair;
  410. if (AZStd::wildcard_match(AZStd::string::format(formatter.c_str(), normalizedPath.c_str(), filter), path))
  411. {
  412. // path is a full path to a file, but we need to emulate directory traversal
  413. // e.g. normalizedPath is a path like "c:/" and the path in the cache might be something like
  414. // "c:/somepath/somefile.txt". For this to function correctly, we must behave as if
  415. // we return "c:/somepath" here, indicating that the contents of "c:/" is "somepath"
  416. // and not "c:/somepath/somefile.txt" as this is NOT a recursive call.
  417. AZStd::string pathWithoutRoot = path.substr(filePathHasTrailingSlash ? filePathLen : filePathLen + 1);
  418. size_t slashPos = pathWithoutRoot.find_first_of(AZ_CORRECT_FILESYSTEM_SEPARATOR);
  419. // eg, input: "c:/", we found "c:/somepath/somefile.txt" in our cache,
  420. // so the pathWithoutRoot is "somepath/somefile.txt".
  421. if (slashPos != AZStd::string::npos)
  422. {
  423. // if we get here, it means that the path we found in our hash is deeper
  424. // in the virtual file tree than the local we are virtually traversing,
  425. // so we return just the folder name after adding the root back in the front:
  426. AZStd::string reassembled = AZStd::string::format(formatter.c_str(), filePath, pathWithoutRoot.substr(0, slashPos).c_str());
  427. if (!callback(reassembled.c_str()))
  428. {
  429. return AZ::IO::ResultCode::Success;
  430. }
  431. }
  432. else
  433. {
  434. if(!callback(path.c_str()))
  435. {
  436. return AZ::IO::ResultCode::Success;
  437. }
  438. }
  439. }
  440. }
  441. return AZ::IO::ResultCode::Success;
  442. }));
  443. ON_CALL(*m_fileIOMock, IsDirectory(_))
  444. .WillByDefault(Invoke(
  445. [this](const char* filePath)
  446. {
  447. AZStd::string normalizedPath(filePath);
  448. AzFramework::StringFunc::Path::Normalize(normalizedPath);
  449. for (const auto& [hash, pair] : m_mockFiles)
  450. {
  451. const auto& [path, contents] = pair;
  452. // if the given path is a prefix of the file path, it could be a directory
  453. if (path.find(normalizedPath.c_str()) == 0)
  454. {
  455. // its not a directory if the path is the exact same as the file path
  456. if (path.compare(normalizedPath.c_str()) == 0)
  457. {
  458. return false; // we found an exact match, so its not a directory, its a file.
  459. }
  460. // if we get here, path starts with filePath must logically be longer than filePath
  461. // since it did not match exactly but started with it.
  462. // It is a directory if there is a slash immediately after filePath inside path
  463. if (path[normalizedPath.length()] == AZ_CORRECT_FILESYSTEM_SEPARATOR)
  464. {
  465. // if we get here, we have positively identified a file path in the cache that
  466. // has the given filePath as a substring of it and the character
  467. // after the substring in the cache is a slash... it must be a directory.
  468. return true;
  469. }
  470. }
  471. }
  472. return false;
  473. }));
  474. }
  475. MockVirtualFileIO::~MockVirtualFileIO()
  476. {
  477. AZ::IO::FileIOBase::SetInstance(nullptr);
  478. AZ::IO::FileIOBase::SetInstance(m_priorFileIO);
  479. }
  480. void MockFileStateCache::RegisterForDeleteEvent(AZ::Event<AssetProcessor::FileStateInfo>::Handler& handler)
  481. {
  482. handler.Connect(m_deleteEvent);
  483. }
  484. bool MockFileStateCache::GetHash(const QString& absolutePath, FileHash* foundHash)
  485. {
  486. if (!Exists(absolutePath))
  487. {
  488. return false;
  489. }
  490. *foundHash = AssetUtilities::GetFileHash(absolutePath.toUtf8().constData(), true);
  491. return true;
  492. }
  493. // this MockFileStateCache has to be permissive for the case where on posix systems the unit tests
  494. // still ask for non-posix paths like "c:/whatever" and expect the rootpath to be "c:/". Calling Path::RootPath
  495. // actually fails on those operating systems (correctly!) because "c:/something" on those systems
  496. // is a relative path, representing '${PWD}/c:/something' with c: being just another directory name.
  497. // To get around this we have to manually split the path here.
  498. static void MockAbsoluteSplit(const QString& absolutePath, AZStd::string& rootPath, AZStd::string& relPathFromRoot)
  499. {
  500. int firstSlash = absolutePath.indexOf("/"); // we assume normalized forward slashes.
  501. if (firstSlash == -1)
  502. {
  503. rootPath = AZStd::string();
  504. relPathFromRoot = absolutePath.toUtf8().constData();
  505. return;
  506. }
  507. rootPath = absolutePath.left(firstSlash + 1).toUtf8().constData();
  508. relPathFromRoot = absolutePath.mid(firstSlash + 1).toUtf8().constData();
  509. }
  510. bool MockFileStateCache::GetFileInfo(const QString& absolutePath, AssetProcessor::FileStateInfo* foundFileInfo) const
  511. {
  512. if (Exists(absolutePath))
  513. {
  514. auto* io = AZ::IO::FileIOBase::GetInstance();
  515. AZ::u64 size;
  516. io->Size(absolutePath.toUtf8().constData(), size);
  517. AZStd::string rootPath;
  518. AZStd::string relPathFromRoot;
  519. MockAbsoluteSplit(absolutePath, rootPath, relPathFromRoot);
  520. // convert the path to the correct case (to emulate GetFileInfo actual).
  521. // Note that calling AssetUtilities::UpdateToCorrectCase would
  522. // cause a stack overflow since it would call this function again.
  523. // Instead, call the underlying AzToolsFramework function.
  524. AzToolsFramework::AssetUtils::UpdateFilePathToCorrectCase(rootPath.c_str(), relPathFromRoot);
  525. AZ::IO::FixedMaxPath correctedPath{rootPath};
  526. correctedPath /= relPathFromRoot;
  527. *foundFileInfo = AssetProcessor::FileStateInfo(
  528. correctedPath.c_str(),
  529. QDateTime::fromMSecsSinceEpoch(io->ModificationTime(absolutePath.toUtf8().constData())),
  530. size,
  531. io->IsDirectory(absolutePath.toUtf8().constData()));
  532. return true;
  533. }
  534. return false;
  535. }
  536. bool MockFileStateCache::Exists(const QString& absolutePath) const
  537. {
  538. // this API needs to be case insensitive to be satisfied, so on case sensitive file systems, we should
  539. // double check. Note that UpdateFilePathToCorrectCase is very expensive, so we only use it as a fallback
  540. // and prefer if the initial if statement passes and returns true
  541. if (AZ::IO::FileIOBase::GetInstance()->Exists(absolutePath.toUtf8().constData()))
  542. {
  543. return true;
  544. }
  545. // note that during Mock unit test operations, the above FileIOBase might be a mock file io base,
  546. // which uses a cache of files and is itself case sensitive. So even on case-insensitive file systems
  547. // this mock still has to do the below de-sensitizing.
  548. AZStd::string rootPath;
  549. AZStd::string relPathFromRoot;
  550. MockAbsoluteSplit(absolutePath, rootPath, relPathFromRoot);
  551. if (AzToolsFramework::AssetUtils::UpdateFilePathToCorrectCase(rootPath, relPathFromRoot))
  552. {
  553. return true;
  554. }
  555. return false;
  556. }
  557. } // namespace UnitTests