SettingsRegistryMergeUtilsTests.cpp 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  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/Casting/numeric_cast.h>
  9. #include <AzCore/IO/ByteContainerStream.h>
  10. #include <AzCore/IO/Path/Path.h>
  11. #include <AzCore/Settings/CommandLine.h>
  12. #include <AzCore/Settings/SettingsRegistryImpl.h>
  13. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  14. #include <AzCore/Settings/SettingsRegistryVisitorUtils.h>
  15. #include <AzCore/std/containers/vector.h>
  16. #include <AzCore/std/smart_ptr/unique_ptr.h>
  17. #include <AzCore/std/string/string.h>
  18. #include <AzCore/std/containers/variant.h>
  19. #include <AzCore/UnitTest/TestTypes.h>
  20. #include <AzCore/Utils/Utils.h>
  21. #include <AzCore/Serialization/Json/JsonUtils.h>
  22. namespace SettingsRegistryMergeUtilsTests
  23. {
  24. struct DumpSettingsRegistryParams
  25. {
  26. AZ::SettingsRegistryInterface::Format m_jsonFormat{ AZ::SettingsRegistryInterface::Format::JsonMergePatch };
  27. const char* m_inputJsonDocument{ "" };
  28. const char* m_expectedDumpString{ "" };
  29. AZ::SettingsRegistryMergeUtils::DumperSettings m_dumperSettings;
  30. AZStd::string_view m_jsonPointerPath;
  31. };
  32. class SettingsRegistryMergeUtilsParamFixture
  33. : public UnitTest::LeakDetectionFixture
  34. , public ::testing::WithParamInterface<DumpSettingsRegistryParams>
  35. {
  36. public:
  37. void SetUp() override
  38. {
  39. m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
  40. }
  41. void TearDown() override
  42. {
  43. m_registry.reset();
  44. }
  45. AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
  46. };
  47. TEST_P(SettingsRegistryMergeUtilsParamFixture, DumpSettingsToByteContainerStream_ReturnsExpected)
  48. {
  49. const DumpSettingsRegistryParams& param = GetParam();
  50. ASSERT_TRUE(m_registry->MergeSettings(param.m_inputJsonDocument, param.m_jsonFormat));
  51. AZStd::string dumpString;
  52. AZ::IO::ByteContainerStream stringStream(&dumpString);
  53. EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(*m_registry, param.m_jsonPointerPath, stringStream,
  54. param.m_dumperSettings));
  55. EXPECT_FALSE(dumpString.empty());
  56. EXPECT_STREQ(param.m_expectedDumpString, dumpString.c_str());
  57. }
  58. TEST_P(SettingsRegistryMergeUtilsParamFixture, DumpSettingsStdout_ReturnsExpected)
  59. {
  60. const DumpSettingsRegistryParams& param = GetParam();
  61. ASSERT_TRUE(m_registry->MergeSettings(param.m_inputJsonDocument, param.m_jsonFormat));
  62. AZ::IO::StdoutStream stdoutStream;
  63. EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(*m_registry, param.m_jsonPointerPath, stdoutStream,
  64. param.m_dumperSettings));
  65. }
  66. INSTANTIATE_TEST_CASE_P(
  67. DumpSettings,
  68. SettingsRegistryMergeUtilsParamFixture,
  69. ::testing::Values(
  70. DumpSettingsRegistryParams
  71. {
  72. AZ::SettingsRegistryInterface::Format::JsonPatch,
  73. R"([)" "\n"
  74. R"( { "op": "add", "path": "/Test", "value": { "Object": {} } },)" "\n"
  75. R"( { "op": "add", "path": "/Test/Object/NullType", "value": null },)" "\n"
  76. R"( { "op": "add", "path": "/Test/Object/TrueType", "value": true },)" "\n"
  77. R"( { "op": "add", "path": "/Test/Object/FalseType", "value": false },)" "\n"
  78. R"( { "op": "add", "path": "/Test/Object/IntType", "value": -42 },)" "\n"
  79. R"( { "op": "add", "path": "/Test/Object/UIntType", "value": 42 },)" "\n"
  80. R"( { "op": "add", "path": "/Test/Object/DoubleType", "value": 42.0 },)" "\n"
  81. R"( { "op": "add", "path": "/Test/Object/StringType", "value": "Hello world" },)" "\n"
  82. R"( { "op": "add", "path": "/Test/Array", "value": [ null, true, false, -42, 42, 42.0, "Hello world" ] })" "\n"
  83. R"(])" "\n",
  84. R"({"Test":{"Object":{"NullType":null,"TrueType":true,"FalseType":false,"IntType":-42,"UIntType":42)"
  85. R"(,"DoubleType":42.0,"StringType":"Hello world"},"Array":[null,true,false,-42,42,42.0,"Hello world"]}})",
  86. AZ::SettingsRegistryMergeUtils::DumperSettings
  87. {
  88. false,
  89. [](AZStd::string_view path)
  90. {
  91. AZStd::string_view prefixPath("/Test");
  92. return prefixPath.starts_with(path.substr(0, prefixPath.size()));
  93. }
  94. }
  95. },
  96. DumpSettingsRegistryParams
  97. {
  98. AZ::SettingsRegistryInterface::Format::JsonMergePatch,
  99. R"({)" "\n"
  100. R"( "Test":)" "\n"
  101. R"( {)" "\n"
  102. R"( "Array0": [ 142, 188 ], )" "\n"
  103. R"( "Array1": [ 242, 288 ], )" "\n"
  104. R"( "Array2": [ 342, 388 ] )" "\n"
  105. R"( })" "\n"
  106. R"(})" "\n",
  107. R"({"Test":{"Array0":[142,188],"Array1":[242,288],"Array2":[342,388]}})",
  108. AZ::SettingsRegistryMergeUtils::DumperSettings{ false,
  109. [](AZStd::string_view path)
  110. {
  111. AZStd::string_view prefixPath("/Test");
  112. return prefixPath.starts_with(path.substr(0, prefixPath.size()));
  113. }
  114. }
  115. },
  116. DumpSettingsRegistryParams
  117. {
  118. AZ::SettingsRegistryInterface::Format::JsonMergePatch,
  119. R"({
  120. "Test":
  121. {
  122. "Array0": [ 142, 188 ],
  123. "Array1": [ 242, 288 ],
  124. "Array2": [ 342, 388 ]
  125. }
  126. })",
  127. R"({)""\n"
  128. R"( "Test": {)""\n"
  129. R"( "Array0": [)""\n"
  130. R"( 142,)""\n"
  131. R"( 188)""\n"
  132. R"( ],)""\n"
  133. R"( "Array1": [)""\n"
  134. R"( 242,)""\n"
  135. R"( 288)""\n"
  136. R"( ],)""\n"
  137. R"( "Array2": [)""\n"
  138. R"( 342,)""\n"
  139. R"( 388)""\n"
  140. R"( ])""\n"
  141. R"( })""\n"
  142. R"(})",
  143. AZ::SettingsRegistryMergeUtils::DumperSettings
  144. {
  145. true,
  146. [](AZStd::string_view path)
  147. {
  148. AZStd::string_view prefixPath("/Test");
  149. return prefixPath.starts_with(path.substr(0, prefixPath.size()));
  150. }
  151. }
  152. },
  153. DumpSettingsRegistryParams
  154. {
  155. AZ::SettingsRegistryInterface::Format::JsonMergePatch,
  156. R"({
  157. "Test":
  158. {
  159. "Array0": [ 142, 188 ],
  160. "Array1": [ 242, 288 ],
  161. "Array2": [ 342, 388 ]
  162. }
  163. })",
  164. R"({)""\n"
  165. R"( "Array0": [)""\n"
  166. R"( 142,)""\n"
  167. R"( 188)""\n"
  168. R"( ],)""\n"
  169. R"( "Array1": [)""\n"
  170. R"( 242,)""\n"
  171. R"( 288)""\n"
  172. R"( ],)""\n"
  173. R"( "Array2": [)""\n"
  174. R"( 342,)""\n"
  175. R"( 388)""\n"
  176. R"( ])""\n"
  177. R"(})",
  178. AZ::SettingsRegistryMergeUtils::DumperSettings
  179. {
  180. true,
  181. [](AZStd::string_view path)
  182. {
  183. AZStd::string_view prefixPath("/Test");
  184. return prefixPath.starts_with(path.substr(0, prefixPath.size()));
  185. }
  186. },
  187. "/Test"
  188. },
  189. DumpSettingsRegistryParams{
  190. AZ::SettingsRegistryInterface::Format::JsonMergePatch,
  191. R"({)" "\n"
  192. R"( "Test":)" "\n"
  193. R"( {)" "\n"
  194. R"( "Array0": [ 142, 188 ])" "\n"
  195. R"( })" "\n"
  196. R"(})",
  197. R"({"Root":{"Path":{"Test":{"Array0":[142,188]}}}})",
  198. AZ::SettingsRegistryMergeUtils::DumperSettings{ false, {}, "/Root/Path/Test" },
  199. "/Test"
  200. })
  201. );
  202. static bool CreateTestFile(const AZ::IO::FixedMaxPath& testPath, AZStd::string_view content)
  203. {
  204. AZ::IO::SystemFile file;
  205. if (!file.Open(testPath.c_str(), AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE
  206. | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY))
  207. {
  208. AZ_Assert(false, "Unable to open test file for writing: %s", testPath.c_str());
  209. return false;
  210. }
  211. if (file.Write(content.data(), content.size()) != content.size())
  212. {
  213. AZ_Assert(false, "Unable to write content to test file: %s", testPath.c_str());
  214. return false;
  215. }
  216. return true;
  217. }
  218. //! ConfigFile MergeUtils Test
  219. struct ConfigFileParams
  220. {
  221. AZStd::string_view m_testConfigFileName;
  222. AZStd::string_view m_testConfigContents;
  223. using SettingsValueVariant = AZStd::variant<AZ::s64, bool, double, AZStd::string_view>;
  224. using SettingsKeyValuePair = AZStd::pair<AZStd::string_view, SettingsValueVariant>;
  225. // The following test below will not have more than 32 settings in their config files
  226. AZStd::fixed_vector<SettingsKeyValuePair, 20> m_expectedSettings;
  227. };
  228. class SettingsRegistryMergeUtilsConfigFileFixture
  229. : public UnitTest::LeakDetectionFixture
  230. , public ::testing::WithParamInterface<ConfigFileParams>
  231. {
  232. public:
  233. void SetUp() override
  234. {
  235. m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
  236. auto configFileParam = GetParam();
  237. auto testPath = AZ::IO::FixedMaxPath(m_testFolder.GetDirectory()) / configFileParam.m_testConfigFileName;
  238. // Create the test config file
  239. ASSERT_TRUE(CreateTestFile(testPath, configFileParam.m_testConfigContents));
  240. }
  241. void TearDown() override
  242. {
  243. m_registry.reset();
  244. }
  245. protected:
  246. AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
  247. AZ::Test::ScopedAutoTempDirectory m_testFolder;
  248. };
  249. TEST_P(SettingsRegistryMergeUtilsConfigFileFixture, MergeSettingsToRegistry_ConfigFile_ParseContents_Successfully)
  250. {
  251. auto configFileParam = GetParam();
  252. // Merge Config File to Settings Registry
  253. AZ::SettingsRegistryMergeUtils::ConfigParserSettings parserSettings;
  254. parserSettings.m_commentPrefixFunc = [](AZStd::string_view line) -> AZStd::string_view
  255. {
  256. constexpr AZStd::string_view commentPrefixes[]{ "--", ";","#" };
  257. for (AZStd::string_view commentPrefix : commentPrefixes)
  258. {
  259. if (size_t commentOffset = line.find(commentPrefix); commentOffset != AZStd::string_view::npos)
  260. {
  261. line = line.substr(0, commentOffset);
  262. }
  263. }
  264. return line;
  265. };
  266. auto testPath = AZ::IO::FixedMaxPath(m_testFolder.GetDirectory()) / configFileParam.m_testConfigFileName;
  267. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ConfigFile(*m_registry, testPath.Native(), parserSettings);
  268. // Validate that Settings Registry contains expected settings
  269. for (auto&& expectedSettingPair : configFileParam.m_expectedSettings)
  270. {
  271. auto ValidateExpectedSettings = [this, settingsKey = expectedSettingPair.first](auto&& settingsValue)
  272. {
  273. using SettingsValueType = AZStd::remove_cvref_t<decltype(settingsValue)>;
  274. if constexpr (AZStd::is_same_v<SettingsValueType, AZ::s64>
  275. || AZStd::is_same_v<SettingsValueType, double>
  276. || AZStd::is_same_v<SettingsValueType, bool>)
  277. {
  278. SettingsValueType registryValue{};
  279. EXPECT_TRUE(m_registry->Get(registryValue, settingsKey));
  280. EXPECT_EQ(settingsValue, registryValue);
  281. }
  282. else if constexpr (AZStd::is_same_v<SettingsValueType, AZStd::string_view>)
  283. {
  284. AZ::SettingsRegistryInterface::FixedValueString registryValue;
  285. EXPECT_TRUE(m_registry->Get(registryValue, settingsKey));
  286. EXPECT_EQ(settingsValue, registryValue);
  287. }
  288. };
  289. AZStd::visit(ValidateExpectedSettings, expectedSettingPair.second);
  290. }
  291. }
  292. INSTANTIATE_TEST_CASE_P(
  293. ReadConfigFile,
  294. SettingsRegistryMergeUtilsConfigFileFixture,
  295. ::testing::Values(
  296. // Processes a fake bootstrap.cfg file which contains no section headers
  297. // and properly terminates the file with a newline
  298. ConfigFileParams{ "fake_bootstrap.cfg", R"(
  299. -- When you see an option that does not have a platform preceding it, that is the default
  300. -- value for anything not specifically set per platform. So if remote_filesystem=0 and you have
  301. -- ios_remote_file_system=1 then remote filesystem will be off for all platforms except ios
  302. -- Any of the settings in this file can be prefixed with a platform name:
  303. -- android, ios, mac, linux, windows, etc...
  304. -- or left unprefixed, to set all platforms not specified. The rules apply in the order they're declared
  305. project_path=TestProject
  306. -- remote_filesystem - enable Virtual File System (VFS)
  307. -- This feature allows a remote instance of the game to run off assets
  308. -- on the asset processor computers cache instead of deploying them the remote device
  309. -- By default it is off and can be overridden for any platform
  310. remote_filesystem=0
  311. android_remote_filesystem=0
  312. ios_remote_filesystem=0
  313. mac_remote_filesystem=0
  314. -- What type of assets are we going to load?
  315. -- We need to know this before we establish VFS because different platform assets
  316. -- are stored in different root folders in the cache. These correspond to the names
  317. -- In the asset processor config file. This value also controls what config file is read
  318. -- when you read system_xxxx_xxxx.cfg (for example, system_windows_pc.cfg or system_android_android.cfg)
  319. -- by default, pc assets (in the 'pc' folder) are used, with RC being fed 'pc' as the platform
  320. -- by default on console we use the default assets=pc for better iteration times
  321. -- we should turn on console specific assets only when in release and/or testing assets and/or loading performance
  322. -- that way most people will not need to have 3 different caches taking up disk space
  323. assets = pc
  324. android_assets = android
  325. ios_assets = ios
  326. mac_assets = mac
  327. -- Add the IP address of your console to the white list that will connect to the asset processor here
  328. -- You can list addresses or CIDR's. CIDR's are helpful if you are using DHCP. A CIDR looks like an ip address with
  329. -- a /n on the end means how many bits are significant. 8bits.8bits.8bits.8bits = /32
  330. -- Example: 192.168.1.3
  331. -- Example: 192.168.1.3, 192.168.1.15
  332. -- Example: 192.168.1.0/24 will allow any address starting with 192.168.1.
  333. -- Example: 192.168.0.0/16 will allow any address starting with 192.168.
  334. -- Example: 192.168.0.0/8 will allow any address starting with 192.
  335. -- allowed_list =
  336. -- IP address and optionally port of the asset processor.
  337. -- Set your PC IP here: (and uncomment the next line)
  338. -- If you are running your asset processor on a windows machine you
  339. -- can find out your ip address by opening a cmd prompt and typing in ipconfig
  340. -- remote_ip = 127.0.0.1
  341. -- remote_port = 45643
  342. -- Which way do you want to connect the asset processor to the game: 1=game connects to AP "connect", 0=AP connects to game "listen"
  343. -- Note: android and IOS over USB port forwarding may need to listen instead of connect
  344. connect_to_remote=0
  345. windows_connect_to_remote=1
  346. android_connect_to_remote=0
  347. ios_connect_to_remote=0
  348. mac_connect_to_remote=0
  349. -- Should we tell the game to wait and not proceed unless we have a connection to the AP or
  350. -- do we allow it to continue to try to connect in the background without waiting
  351. -- Note: Certain options REQUIRE that we do not proceed unless we have a connection, and will override this option to 1 when set
  352. -- Since remote_filesystem=1 requires a connection to proceed it will override our option to 1
  353. wait_for_connect=0
  354. windows_wait_for_connect=1
  355. android_wait_for_connect=0
  356. ios_wait_for_connect=0
  357. mac_wait_for_connect=0
  358. -- How long applications should wait while attempting to connect to an already launched AP(in seconds)
  359. -- connect_ap_timeout=3
  360. -- How long application should wait when launching the AP and wait for the AP to connect back to it(in seconds)
  361. -- This time is dependent on Machine load as well as how long it takes for the new AP instance to initialize
  362. -- A debug AP takes longer to start up than a profile AP
  363. -- launch_ap_timeout=15
  364. -- How long to wait for the AssetProcessor to be ready(i.e have all critical assets processed)
  365. -- wait_ap_ready_timeout = 1200
  366. # Commented out line using a number sign character
  367. ; Commented out line using a semicolon
  368. )"
  369. , AZStd::fixed_vector<ConfigFileParams::SettingsKeyValuePair, 20>{
  370. ConfigFileParams::SettingsKeyValuePair{"/project_path", AZStd::string_view{"TestProject"}},
  371. ConfigFileParams::SettingsKeyValuePair{"/remote_filesystem", AZ::s64{0}},
  372. ConfigFileParams::SettingsKeyValuePair{"/android_remote_filesystem", AZ::s64{0}},
  373. ConfigFileParams::SettingsKeyValuePair{"/ios_remote_filesystem", AZ::s64{0}},
  374. ConfigFileParams::SettingsKeyValuePair{"/mac_remote_filesystem", AZ::s64{0}},
  375. ConfigFileParams::SettingsKeyValuePair{"/assets", AZStd::string_view{"pc"}},
  376. ConfigFileParams::SettingsKeyValuePair{"/android_assets", AZStd::string_view{"android"}},
  377. ConfigFileParams::SettingsKeyValuePair{"/ios_assets", AZStd::string_view{"ios"}},
  378. ConfigFileParams::SettingsKeyValuePair{"/mac_assets", AZStd::string_view{"mac"}},
  379. ConfigFileParams::SettingsKeyValuePair{"/connect_to_remote", AZ::s64{0}},
  380. ConfigFileParams::SettingsKeyValuePair{"/windows_connect_to_remote", AZ::s64{1}},
  381. ConfigFileParams::SettingsKeyValuePair{"/android_connect_to_remote", AZ::s64{0}},
  382. ConfigFileParams::SettingsKeyValuePair{"/ios_connect_to_remote", AZ::s64{0}},
  383. ConfigFileParams::SettingsKeyValuePair{"/mac_connect_to_remote", AZ::s64{0}},
  384. ConfigFileParams::SettingsKeyValuePair{"/wait_for_connect", AZ::s64{0}},
  385. ConfigFileParams::SettingsKeyValuePair{"/windows_wait_for_connect", AZ::s64{1}},
  386. ConfigFileParams::SettingsKeyValuePair{"/android_wait_for_connect", AZ::s64{0}},
  387. ConfigFileParams::SettingsKeyValuePair{"/ios_wait_for_connect", AZ::s64{0}},
  388. ConfigFileParams::SettingsKeyValuePair{"/mac_wait_for_connect", AZ::s64{0}},
  389. }},
  390. // Parses a fake AssetProcessorPlatformConfig file which contains sections headers
  391. // and does not end with a newline
  392. ConfigFileParams{ "fake_AssetProcessorPlatformConfig.ini", R"(
  393. ; ---- Enable/Disable platforms for the entire project. AssetProcessor will automatically add the current platform by default.
  394. ; PLATFORM DEFINITIONS
  395. ; [Platform (unique identifier)]
  396. ; tags=(comma-seperated-tags)
  397. ;
  398. ; note: the 'identifier' of a platform is the word(s) following the "Platform" keyword (so [Platform pc] means identifier
  399. ; is 'pc' for example. This is used to name its assets folder in the cache and should be used in your bootstrap.cfg
  400. ; or your main.cpp to choose what assets to load for that particular platform.
  401. ; Its primary use is to enable additional non-host platforms (Ios, android...) that are not the current platform.
  402. ; note: 'tags' is a comma-seperated list of tags to tag the platform with that builders can inspect to decide what to do.
  403. ; while builders can accept any tags you add in order to make decisions, common tags are
  404. ; tools - this platform can host the tools and editor and such
  405. ; renderer - this platform runs the client engine and renders on a GPU. If missing we could be on a server-only platform
  406. ; mobile - a mobile platform such as a set top box or phone with limited resources
  407. ; console - a console platform
  408. ; server - a server platform of some kind, usually headless, no renderer.
  409. test_asset_processor_tag = test_value
  410. [Platform pc]
  411. tags=tools,renderer,dx12,vulkan
  412. [Platform android]
  413. tags=android,mobile,renderer,vulkan ; With Comments at the end
  414. [Platform ios]
  415. tags=mobile,renderer,metal
  416. [Platform mac]
  417. tags=tools,renderer,metal)"
  418. , AZStd::fixed_vector<ConfigFileParams::SettingsKeyValuePair, 20>{
  419. ConfigFileParams::SettingsKeyValuePair{"/test_asset_processor_tag", AZStd::string_view{"test_value"}},
  420. ConfigFileParams::SettingsKeyValuePair{"/Platform pc/tags", AZStd::string_view{"tools,renderer,dx12,vulkan"}},
  421. ConfigFileParams::SettingsKeyValuePair{"/Platform android/tags", AZStd::string_view{"android,mobile,renderer,vulkan"}},
  422. ConfigFileParams::SettingsKeyValuePair{"/Platform ios/tags", AZStd::string_view{"mobile,renderer,metal"}},
  423. ConfigFileParams::SettingsKeyValuePair{"/Platform mac/tags", AZStd::string_view{"tools,renderer,metal"}},
  424. }}
  425. )
  426. );
  427. struct SettingsRegistryGemVisitParams
  428. {
  429. const char* m_o3deManifestJson{ "" };
  430. const char* m_engineManifestJson{ "" };
  431. const char* m_projectManifestJson{ "" };
  432. AZStd::fixed_vector<AZStd::tuple<const char*, const char*>, 8> m_gemManifestJsons;
  433. const char* m_activeGemJson{ "" };
  434. AZStd::fixed_vector<const char*, 8> m_expectedActiveGemPaths;
  435. AZStd::fixed_vector<const char*, 8> m_expectedManifestGemPaths;
  436. };
  437. class SettingsRegistryGemVisitFixture
  438. : public UnitTest::LeakDetectionFixture
  439. , public ::testing::WithParamInterface<SettingsRegistryGemVisitParams>
  440. {
  441. public:
  442. void SetUp() override
  443. {
  444. m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
  445. // Create the manifest json files
  446. const auto& gemVisitParams = GetParam();
  447. auto tempRootFolder = AZ::IO::FixedMaxPath(m_testFolder.GetDirectory());
  448. AZ::IO::FixedMaxPath o3deManifestFilePath = tempRootFolder / "o3de" / "o3de_manifest.json";
  449. AZ::IO::FixedMaxPath engineManifestPath = tempRootFolder / "engine" / "engine.json";
  450. AZ::IO::FixedMaxPath projectManifestPath = tempRootFolder / "project" / "project.json";
  451. ASSERT_TRUE(CreateTestFile(o3deManifestFilePath, gemVisitParams.m_o3deManifestJson));
  452. ASSERT_TRUE(CreateTestFile(engineManifestPath, gemVisitParams.m_engineManifestJson));
  453. ASSERT_TRUE(CreateTestFile(projectManifestPath, gemVisitParams.m_projectManifestJson));
  454. for (const auto& [gemRelativePath, gemManifestJson] : gemVisitParams.m_gemManifestJsons)
  455. {
  456. AZ::IO::FixedMaxPath gemManifestPath = tempRootFolder / gemRelativePath / "gem.json";
  457. ASSERT_TRUE(CreateTestFile(gemManifestPath, gemManifestJson));
  458. }
  459. // Set the FilePathKeys for the o3de root, engine root and project root directories
  460. m_registry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_O3deManifestRootFolder,
  461. (tempRootFolder / "o3de").Native());
  462. m_registry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder,
  463. (tempRootFolder / "engine").Native());
  464. m_registry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath,
  465. (tempRootFolder / "project").Native());
  466. // Merge the Active Gem Json data to the Settings Registry
  467. EXPECT_TRUE(m_registry->MergeSettings(gemVisitParams.m_activeGemJson, AZ::SettingsRegistryInterface::Format::JsonMergePatch));
  468. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ManifestGemsPaths(*m_registry);
  469. }
  470. void TearDown() override
  471. {
  472. m_registry.reset();
  473. }
  474. protected:
  475. AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
  476. AZ::Test::ScopedAutoTempDirectory m_testFolder;
  477. };
  478. TEST_P(SettingsRegistryGemVisitFixture, SettingsRegistryMergeUtils_AllManifestGems_AreInSettingsRegistry)
  479. {
  480. AZStd::vector<AZ::IO::Path> manifestGemPaths;
  481. auto GetManifestGemPaths = [&manifestGemPaths, this](const AZ::SettingsRegistryInterface::VisitArgs& visitArgs)
  482. {
  483. using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
  484. AZ::IO::Path gemPath;
  485. EXPECT_TRUE(m_registry->Get(gemPath.Native(), FixedValueString(visitArgs.m_jsonKeyPath) + "/Path"));
  486. manifestGemPaths.push_back(gemPath.LexicallyRelative(m_testFolder.GetDirectory()));
  487. return AZ::SettingsRegistryInterface::VisitResponse::Skip;
  488. };
  489. EXPECT_TRUE(AZ::SettingsRegistryVisitorUtils::VisitObject(*m_registry,
  490. GetManifestGemPaths, AZ::SettingsRegistryMergeUtils::ManifestGemsRootKey));
  491. const auto& gemVisitParams = GetParam();
  492. EXPECT_THAT(manifestGemPaths, ::testing::UnorderedElementsAreArray(
  493. gemVisitParams.m_expectedManifestGemPaths.begin(), gemVisitParams.m_expectedManifestGemPaths.end()));
  494. }
  495. TEST_P(SettingsRegistryGemVisitFixture, SettingsRegistryMergeUtils_VisitActiveGems_OnlyReturnsActiveGemPaths)
  496. {
  497. AZStd::vector<AZ::IO::Path> activeGemPaths;
  498. auto GetActiveGems = [&activeGemPaths, this](AZStd::string_view, AZStd::string_view gemPath)
  499. {
  500. activeGemPaths.push_back(AZ::IO::Path(gemPath).LexicallyRelative(m_testFolder.GetDirectory()));
  501. };
  502. AZ::SettingsRegistryMergeUtils::VisitActiveGems(*m_registry, GetActiveGems);
  503. const auto& gemVisitParams = GetParam();
  504. EXPECT_THAT(activeGemPaths, ::testing::UnorderedElementsAreArray(
  505. gemVisitParams.m_expectedActiveGemPaths.begin(), gemVisitParams.m_expectedActiveGemPaths.end()));
  506. }
  507. TEST_P(SettingsRegistryGemVisitFixture, Validate_SettingsRegistryUtilsQueries_WorksWithLocalRegistry)
  508. {
  509. const AZ::IO::FixedMaxPath tempRootFolder(m_testFolder.GetDirectory());
  510. AZ::IO::FixedMaxPath testPath = AZ::Utils::GetO3deManifestDirectory(m_registry.get());
  511. EXPECT_EQ((tempRootFolder / "o3de"), testPath);
  512. testPath = AZ::Utils::GetEnginePath(m_registry.get());
  513. EXPECT_EQ((tempRootFolder / "engine"), testPath);
  514. testPath = AZ::Utils::GetProjectPath(m_registry.get());
  515. EXPECT_EQ((tempRootFolder / "project"), testPath);
  516. testPath = AZ::Utils::GetGemPath("outerGem1", m_registry.get());
  517. EXPECT_EQ((tempRootFolder / "o3de/outerGem1"), testPath);
  518. testPath = AZ::Utils::GetGemPath("outerGem2", m_registry.get());
  519. EXPECT_EQ((tempRootFolder / "o3de/outerGem2"), testPath);
  520. testPath = AZ::Utils::GetGemPath("innerGem1", m_registry.get());
  521. EXPECT_EQ((tempRootFolder / "o3de/outerGem2/innerGem1"), testPath);
  522. testPath = AZ::Utils::GetGemPath("engineGem1", m_registry.get());
  523. EXPECT_EQ((tempRootFolder / "engine/engineGem1"), testPath);
  524. testPath = AZ::Utils::GetGemPath("projectGem1", m_registry.get());
  525. EXPECT_EQ((tempRootFolder / "project/projectGem1"), testPath);
  526. testPath = AZ::Utils::GetGemPath("outsideGem1", m_registry.get());
  527. EXPECT_EQ((tempRootFolder / "outsideGem1"), testPath);
  528. }
  529. static auto MakeGemVisitTestingValues()
  530. {
  531. return AZStd::array{
  532. SettingsRegistryGemVisitParams{
  533. R"({
  534. "o3de_manifest_name": "testuser",
  535. "external_subdirectories": [
  536. "outerGem1",
  537. "outerGem2"
  538. ]
  539. })",
  540. R"({
  541. "engine_name": "o3de",
  542. "external_subdirectories": [
  543. "engineGem1"
  544. ]
  545. })",
  546. R"({
  547. "project_name": "TestProject",
  548. "external_subdirectories": [
  549. "projectGem1",
  550. "../outsideGem1"
  551. ]
  552. })",
  553. {
  554. AZStd::make_tuple("o3de/outerGem1",
  555. R"({
  556. "gem_name": "outerGem1",
  557. })"
  558. ),
  559. AZStd::make_tuple("o3de/outerGem2",
  560. R"({
  561. "gem_name": "outerGem2",
  562. "external_subdirectories": [
  563. "innerGem1"
  564. ]
  565. })"
  566. ),
  567. AZStd::make_tuple("o3de/outerGem2/innerGem1",
  568. R"({
  569. "gem_name": "innerGem1",
  570. })"
  571. ),
  572. AZStd::make_tuple("engine/engineGem1",
  573. R"({
  574. "gem_name": "engineGem1",
  575. })"
  576. ),
  577. AZStd::make_tuple("project/projectGem1",
  578. R"({
  579. "gem_name": "projectGem1",
  580. })"
  581. ),
  582. AZStd::make_tuple("outsideGem1",
  583. R"({
  584. "gem_name": "outsideGem1",
  585. })"
  586. ),
  587. },
  588. R"({
  589. "O3DE": {
  590. "Gems": {
  591. "outerGem1" : {},
  592. "innerGem1" : {},
  593. "engineGem1" : {},
  594. "projectGem1" : {},
  595. }
  596. }
  597. })",
  598. {
  599. "o3de/outerGem1",
  600. "o3de/outerGem2/innerGem1",
  601. "engine/engineGem1",
  602. "project/projectGem1"
  603. },
  604. {
  605. "o3de/outerGem1",
  606. "o3de/outerGem2",
  607. "o3de/outerGem2/innerGem1",
  608. "engine/engineGem1",
  609. "project/projectGem1",
  610. "outsideGem1"
  611. }
  612. } };
  613. }
  614. INSTANTIATE_TEST_CASE_P(
  615. MergeManifestJson,
  616. SettingsRegistryGemVisitFixture,
  617. ::testing::ValuesIn(MakeGemVisitTestingValues()));
  618. class SettingsRegistryMergeUtilsCommandLineFixture
  619. : public UnitTest::LeakDetectionFixture
  620. {
  621. public:
  622. void SetUp() override
  623. {
  624. m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
  625. }
  626. void TearDown() override
  627. {
  628. m_registry.reset();
  629. }
  630. AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
  631. };
  632. TEST_F(SettingsRegistryMergeUtilsCommandLineFixture, CommandLineArguments_MergeToSettingsRegistry_Success)
  633. {
  634. AZ::CommandLine commandLine;
  635. commandLine.Parse({ "programname.exe", "--project-path", "--RemoteIp", "10.0.0.1", "--ScanFolders", R"(\a\b\c,\d\e\f)", "Foo", "Bat" });
  636. AZ::SettingsRegistryMergeUtils::StoreCommandLineToRegistry(*m_registry, commandLine);
  637. // Clear the CommandLine instance
  638. commandLine = {};
  639. EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(*m_registry, commandLine));
  640. ASSERT_TRUE(commandLine.HasSwitch("project-path"));
  641. EXPECT_EQ(1, commandLine.GetNumSwitchValues("project-path"));
  642. EXPECT_STREQ("", commandLine.GetSwitchValue("project-path", 0).c_str());
  643. ASSERT_TRUE(commandLine.HasSwitch("remoteip"));
  644. ASSERT_EQ(1, commandLine.GetNumSwitchValues("remoteip"));
  645. EXPECT_STREQ("10.0.0.1", commandLine.GetSwitchValue("remoteip", 0).c_str());
  646. ASSERT_TRUE(commandLine.HasSwitch("scanfolders"));
  647. ASSERT_EQ(2, commandLine.GetNumSwitchValues("scanfolders"));
  648. EXPECT_STREQ(R"(\a\b\c)", commandLine.GetSwitchValue("scanfolders", 0).c_str());
  649. EXPECT_STREQ(R"(\d\e\f)", commandLine.GetSwitchValue("scanfolders", 1).c_str());
  650. ASSERT_EQ(3, commandLine.GetNumMiscValues());
  651. EXPECT_STREQ("programname.exe", commandLine.GetMiscValue(0).c_str());
  652. EXPECT_STREQ("Foo", commandLine.GetMiscValue(1).c_str());
  653. EXPECT_STREQ("Bat", commandLine.GetMiscValue(2).c_str());
  654. }
  655. TEST_F(SettingsRegistryMergeUtilsCommandLineFixture, RegsetFileArgument_DoesNotMergeNUL)
  656. {
  657. AZStd::string regsetFile = AZ::IO::SystemFile::GetNullFilename();
  658. AZ::CommandLine commandLine;
  659. commandLine.Parse({ "--regset-file", regsetFile });
  660. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_registry, commandLine, false);
  661. // Add a settings path to anchor loaded settings underneath
  662. regsetFile = AZStd::string::format("%s::/AnchorPath/Of/Settings", AZ::IO::SystemFile::GetNullFilename());
  663. commandLine.Parse({ "--regset-file", regsetFile });
  664. AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_registry, commandLine, false);
  665. EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/AnchorPath/Of/Settings"));
  666. }
  667. using SettingsRegistryAncestorDescendantOrEqualPathFixture = SettingsRegistryMergeUtilsCommandLineFixture;
  668. TEST_F(SettingsRegistryAncestorDescendantOrEqualPathFixture, ValidateThatAncestorOrDescendantOrPathWithTheSameValue_Succeeds)
  669. {
  670. EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual("/Amazon/AzCore/Bootstrap", "/Amazon/AzCore"));
  671. EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual("/Amazon/AzCore/Bootstrap", "/Amazon/AzCore/Bootstrap"));
  672. EXPECT_TRUE(AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual("/Amazon/AzCore/Bootstrap", "/Amazon/AzCore/Bootstrap/project_path"));
  673. EXPECT_FALSE(AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual("/Amazon/AzCore/Bootstrap", "/Amazon/Project/Settings/project_name"));
  674. }
  675. struct SettingsRegistryFindEngineRootParams
  676. {
  677. AZStd::fixed_vector<const char*, 2> m_engineManifestsJson;
  678. const char* m_projectManifestJson{ "" };
  679. const char* m_userProjectManifestJson{ "" };
  680. const int m_expectedEnginePathIndex; // negative means not found
  681. const char* m_scanUpEngineRoot{ "" };
  682. };
  683. static auto MakeFindEngineRootTestingValues()
  684. {
  685. return AZStd::array{
  686. // Selects correct engine based on name
  687. SettingsRegistryFindEngineRootParams{
  688. // engine.json files
  689. {
  690. R"({ "engine_name": "o3de1", "version": "1.0.0"})",
  691. R"({ "engine_name": "o3de2", "version": "1.2.3"})",
  692. },
  693. // project/project.json
  694. R"({ "project_name": "TestProject", "engine":"o3de1" })",
  695. // project/user/project.json
  696. R"({})",
  697. 0, // expect o3de1
  698. ""
  699. },
  700. // Selects engine with highest version when multiple of same name found
  701. SettingsRegistryFindEngineRootParams{
  702. // engine.json files
  703. {
  704. R"({ "engine_name": "o3de", "version": "1.2.3"})",
  705. R"({ "engine_name": "o3de", "version": "2.3.4"})",
  706. },
  707. // project/project.json
  708. R"({ "project_name": "TestProject", "engine":"o3de" })",
  709. // project/user/project.json
  710. R"({})",
  711. 1, // expect second engine with higher version number
  712. ""
  713. },
  714. // Fails to find engine with name that isn't registered
  715. SettingsRegistryFindEngineRootParams{
  716. // engine.json files
  717. {
  718. R"({ "engine_name": "o3de1", "version": "1.0.0"})",
  719. R"({ "engine_name": "o3de2", "version": "1.2.3"})",
  720. },
  721. // project/project.json
  722. R"({ "project_name": "TestProject", "engine":"o3de-not-found" })",
  723. // project/user/project.json
  724. R"({})",
  725. -1, // not found
  726. ""
  727. },
  728. // Fails to find engine with version that isn't registered
  729. SettingsRegistryFindEngineRootParams{
  730. // engine.json files
  731. {
  732. R"({ "engine_name": "o3de1", "version": "1.0.0"})",
  733. R"({ "engine_name": "o3de2", "version": "1.2.3"})",
  734. },
  735. // project/project.json
  736. R"({ "project_name": "TestProject", "engine":"o3de==1.1.1" })",
  737. // project/user/project.json
  738. R"({})",
  739. -1, // not found
  740. ""
  741. },
  742. // Selects engine that has a legacy version field
  743. SettingsRegistryFindEngineRootParams{
  744. // engine.json files
  745. {
  746. R"({ "engine_name": "o3de", "O3DEVersion": "0.1.0.0"})",
  747. R"({ "engine_name": "o3de-new", "O3DEVersion": "0.1.0.0", "version": "1.2.3"})",
  748. },
  749. // project/project.json
  750. R"({ "project_name": "TestProject", "engine":"o3de" })",
  751. // project/user/project.json
  752. R"({})",
  753. 0, // o3de
  754. ""
  755. },
  756. // Selects first engine when multiple engines found with ambiguous project engine
  757. SettingsRegistryFindEngineRootParams{
  758. // engine.json files
  759. {
  760. R"({ "engine_name": "o3de", "O3DEVersion": "0.1.0.0"})",
  761. R"({ "engine_name": "o3de", "O3DEVersion": "0.1.0.0"})",
  762. },
  763. // project/project.json
  764. R"({ "project_name": "TestProject", "engine":"o3de" })",
  765. // project/user/project.json
  766. R"({})",
  767. 0, // first engine
  768. ""
  769. },
  770. // Selects correct engine when a version specifier is used
  771. SettingsRegistryFindEngineRootParams{
  772. // engine.json files
  773. {
  774. R"({ "engine_name": "o3de1", "version": "1.2.3"})",
  775. R"({ "engine_name": "o3de2", "version": "2.3.4"})",
  776. },
  777. // project/project.json
  778. R"({ "project_name": "TestProject", "engine":"o3de2==2.3.4" })",
  779. // project/user/project.json
  780. R"({})",
  781. 1, // o3de2
  782. ""
  783. },
  784. // Selects the engine specified by name in project/user/project.json
  785. SettingsRegistryFindEngineRootParams{
  786. // engine.json files
  787. {
  788. R"({ "engine_name": "o3de1", "version": "1.2.3"})",
  789. R"({ "engine_name": "o3de2", "version": "2.3.4"})",
  790. },
  791. // project/project.json
  792. R"({ "project_name": "TestProject", "engine":"o3de2==2.3.4" })",
  793. // project/user/project.json
  794. R"({ "engine":"o3de1==1.2.3" })",
  795. 0, // o3de1
  796. ""
  797. },
  798. // Selects the engine specified by path in project/user/project.json
  799. SettingsRegistryFindEngineRootParams{
  800. // engine.json files
  801. {
  802. R"({ "engine_name": "o3de", "version": "1.2.3"})",
  803. R"({ "engine_name": "o3de", "version": "1.2.3"})",
  804. },
  805. // project/project.json
  806. R"({ "project_name": "TestProject", "engine":"o3de" })",
  807. // project/user/project.json
  808. R"({ "engine_path":"<engine_path1>" })",
  809. 1, // 2nd engine, even though both have same name & version
  810. ""
  811. },
  812. // Fails if invalid engine specified in project/user/project.json
  813. SettingsRegistryFindEngineRootParams{
  814. // engine.json files
  815. {
  816. R"({ "engine_name": "o3de", "version": "1.2.3"})",
  817. R"({ "engine_name": "o3de-other", "version": "1.2.3"})",
  818. },
  819. // project/project.json
  820. R"({ "project_name": "TestProject", "engine":"o3de" })",
  821. // project/user/project.json
  822. R"({ "engine_path":"c:/path/not/found" })",
  823. -1, // not found
  824. ""
  825. },
  826. // Uses scan up engine if all other methods fail
  827. SettingsRegistryFindEngineRootParams{
  828. // engine.json files
  829. {
  830. R"({ "engine_name": "o3de", "version": "1.2.3"})",
  831. R"({ "engine_name": "o3de-other", "version": "1.2.3"})",
  832. },
  833. // project/project.json
  834. R"({ "project_name": "TestProject", "engine":"o3de-blah" })",
  835. // project/user/project.json
  836. R"({})",
  837. -1,
  838. "engine"
  839. },
  840. };
  841. }
  842. class SettingsRegistryMergeUtilsFindEngineRootFixture
  843. : public UnitTest::LeakDetectionFixture
  844. , public ::testing::WithParamInterface<SettingsRegistryFindEngineRootParams>
  845. {
  846. public:
  847. void SetUp() override
  848. {
  849. constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" };
  850. m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
  851. // Create the manifest json files
  852. const auto& params = GetParam();
  853. auto tempRootFolder = AZ::IO::FixedMaxPath(m_testFolder.GetDirectory());
  854. AZ::IO::FixedMaxPath o3deManifestFilePath = tempRootFolder / "o3de" / "o3de_manifest.json";
  855. AZ::IO::FixedMaxPath projectPath = tempRootFolder / "project" ;
  856. AZ::IO::FixedMaxPath projectManifestPath = projectPath / "project.json";
  857. AZ::IO::FixedMaxPath projectUserPath = tempRootFolder / "project" / "user";
  858. AZ::IO::FixedMaxPath userProjectManifestPath = projectUserPath / "project.json";
  859. for (size_t i = 0; i < params.m_engineManifestsJson.size(); ++i)
  860. {
  861. const AZ::IO::FixedMaxPath enginePath = tempRootFolder / AZStd::string::format("engine%zu", i);
  862. ASSERT_TRUE(CreateTestFile(enginePath / "engine.json", params.m_engineManifestsJson[i]));
  863. m_enginePaths.emplace_back(AZStd::move(enginePath));
  864. }
  865. m_registry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_O3deManifestRootFolder,
  866. (tempRootFolder / "o3de").Native());
  867. m_registry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath,
  868. projectPath.Native());
  869. m_registry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath,
  870. projectUserPath.Native());
  871. if(!AZStd::string_view(params.m_scanUpEngineRoot).empty())
  872. {
  873. // use an absolute path because that is what should be returned from FindEngineRoot
  874. AZ::IO::FixedMaxPath scanUpEngineRootPath = tempRootFolder / params.m_scanUpEngineRoot;
  875. m_registry->Set(InternalScanUpEngineRootKey, scanUpEngineRootPath.Native());
  876. }
  877. else
  878. {
  879. // set to an empty value to simulate no scan up engine root found
  880. m_registry->Set(InternalScanUpEngineRootKey, "");
  881. }
  882. const char* o3deManifest = R"({ "o3de_manifest_name": "testmanifest", "engines":["<engine_path0>","<engine_path1>"] })";
  883. ASSERT_TRUE(CreateTestFileWithSubstitutions(o3deManifestFilePath, o3deManifest));
  884. ASSERT_TRUE(CreateTestFileWithSubstitutions(projectManifestPath, params.m_projectManifestJson));
  885. ASSERT_TRUE(CreateTestFileWithSubstitutions(userProjectManifestPath, params.m_userProjectManifestJson));
  886. }
  887. bool CreateTestFileWithSubstitutions(const AZ::IO::FixedMaxPath& testPath, AZStd::string_view content)
  888. {
  889. // replace instances of <engine0>, <engine1> etc. with actual engine paths
  890. AZStd::string contentString{ content };
  891. for (size_t i = 0; i < m_enginePaths.size(); ++i)
  892. {
  893. AZStd::string enginePath{ m_enginePaths[i].Native().c_str() };
  894. AZ::StringFunc::Json::ToEscapedString(enginePath);
  895. AZ::StringFunc::Replace(contentString, AZStd::string::format("<engine_path%zu>", i).c_str(), enginePath.c_str());
  896. }
  897. return CreateTestFile(testPath, contentString.c_str());
  898. }
  899. void TearDown() override
  900. {
  901. m_registry.reset();
  902. }
  903. protected:
  904. AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
  905. AZ::Test::ScopedAutoTempDirectory m_testFolder;
  906. AZStd::vector<AZ::IO::FixedMaxPath> m_enginePaths;
  907. };
  908. TEST_P(SettingsRegistryMergeUtilsFindEngineRootFixture, SettingsRegistryMergeUtils_FindEngineRoot_DetectsCorrectPath)
  909. {
  910. const auto& params = GetParam();
  911. const AZ::IO::FixedMaxPath engineRoot = AZ::SettingsRegistryMergeUtils::FindEngineRoot(*m_registry).Native();
  912. if (!AZStd::string_view(params.m_scanUpEngineRoot).empty())
  913. {
  914. auto tempRootFolder = AZ::IO::FixedMaxPath(m_testFolder.GetDirectory());
  915. AZ::IO::FixedMaxPath scanUpEngineRootPath = tempRootFolder / params.m_scanUpEngineRoot;
  916. EXPECT_EQ(engineRoot, scanUpEngineRootPath);
  917. }
  918. else if (params.m_expectedEnginePathIndex < 0 || params.m_expectedEnginePathIndex >= m_enginePaths.size())
  919. {
  920. EXPECT_TRUE(engineRoot.empty());
  921. }
  922. else
  923. {
  924. EXPECT_EQ(engineRoot, m_enginePaths[params.m_expectedEnginePathIndex]);
  925. }
  926. }
  927. INSTANTIATE_TEST_CASE_P(
  928. FindEngineRoot,
  929. SettingsRegistryMergeUtilsFindEngineRootFixture,
  930. ::testing::ValuesIn(MakeFindEngineRootTestingValues()));
  931. }