SettingsRegistryMergeUtilsTests.cpp 45 KB

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