LYTestImpactFramework.cmake 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  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. if(NOT PAL_TRAIT_BUILD_TESTS_SUPPORTED)
  9. return()
  10. endif()
  11. # Name of test impact framework console static library target
  12. set(LY_TEST_IMPACT_CONSOLE_NATIVE_STATIC_TARGET "TestImpact.Frontend.Console.Native.Static")
  13. # Name of test impact framework python coverage gem target
  14. set(LY_TEST_IMPACT_PYTHON_COVERAGE_STATIC_TARGET "PythonCoverage.Editor.Static")
  15. # Name of test impact framework native console target
  16. set(LY_TEST_IMPACT_NATIVE_CONSOLE_TARGET "TestImpact.Frontend.Console.Native")
  17. # Name of test impact framework native console target
  18. set(LY_TEST_IMPACT_PYTHON_CONSOLE_TARGET "TestImpact.Frontend.Console.Python")
  19. # Directory for test impact artifacts and data
  20. set(LY_TEST_IMPACT_WORKING_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TestImpactFramework")
  21. # Directory name for TIAF temp directory
  22. set(LY_TEST_IMPACT_TEMP_DIR "Temp")
  23. # Directory for artifacts generated at runtime
  24. set(LY_TEST_IMPACT_RUNTIME_TEMP_DIR "${LY_TEST_IMPACT_WORKING_DIR}/$<CONFIG>/${LY_TEST_IMPACT_TEMP_DIR}")
  25. # Directory name for TIAF persistent directory
  26. set(LY_TEST_IMPACT_PERSISTENT_DIR "Persistent")
  27. # Directory for files that persist between runtime runs
  28. set(LY_TEST_IMPACT_RUNTIME_PERSISTENT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/$<CONFIG>/${LY_TEST_IMPACT_PERSISTENT_DIR}")
  29. # Directory for static artifacts produced as part of the build system generation process
  30. set(LY_TEST_IMPACT_ARTIFACT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Artifact")
  31. # Directory for source to build target mappings
  32. set(LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/Mapping")
  33. # Directory for build target dependency/depender graphs
  34. set(LY_TEST_IMPACT_TARGET_DEPENDENCY_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/Dependency")
  35. # Main test enumeration file for all test types
  36. set(LY_TEST_IMPACT_TEST_TYPE_FILE "${LY_TEST_IMPACT_ARTIFACT_DIR}/TestType/All.tests")
  37. # Main gem target file for all shared library gems
  38. set(LY_TEST_IMPACT_GEM_TARGET_FILE "${LY_TEST_IMPACT_ARTIFACT_DIR}/BuildType/All.gems")
  39. # File name for TIAF config files.
  40. set(LY_TEST_IMPACT_CONFIG_FILE_NAME "tiaf.json")
  41. # Path to the config file for each build configuration
  42. set(LY_TEST_IMPACT_CONFIG_FILE_PATH "${LY_TEST_IMPACT_RUNTIME_PERSISTENT_DIR}/${LY_TEST_IMPACT_CONFIG_FILE_NAME}")
  43. # Preprocessor directive for the config file path
  44. set(LY_TEST_IMPACT_CONFIG_FILE_PATH_DEFINITION "LY_TEST_IMPACT_DEFAULT_CONFIG_FILE=\"${LY_TEST_IMPACT_CONFIG_FILE_PATH}\"")
  45. # Path to file used to store data required by TIAF tests
  46. set(LY_TEST_IMPACT_PYTEST_FILE_PATH "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>")
  47. # Path to the directory that the result of native runs will be stored in.
  48. set(LY_TEST_IMPACT_NATIVE_TEST_RUN_DIR "${GTEST_XML_OUTPUT_DIR}")
  49. # Path to the directory that the result of python runs will be stored in.
  50. set(LY_TEST_IMPACT_PYTHON_TEST_RUN_DIR "${PYTEST_XML_OUTPUT_DIR}")
  51. #! ly_test_impact_rebase_file_to_repo_root: rebases the relative and/or absolute path to be relative to repo root directory and places the resulting path in quotes.
  52. #
  53. # \arg:INPUT_FILE the file to rebase
  54. # \arg:OUTPUT_FILE the file after rebasing
  55. # \arg:RELATIVE_DIR_ABS the absolute path that the file will be relatively rebased to
  56. function(ly_test_impact_rebase_file_to_repo_root INPUT_FILE OUTPUT_FILE RELATIVE_DIR_ABS)
  57. # Transform the file paths to absolute paths
  58. set(rebased_file ${INPUT_FILE})
  59. if(NOT IS_ABSOLUTE ${rebased_file})
  60. get_filename_component(rebased_file "${rebased_file}"
  61. REALPATH BASE_DIR "${RELATIVE_DIR_ABS}"
  62. )
  63. endif()
  64. # Rebase absolute path to relative path from repo root
  65. file(RELATIVE_PATH rebased_file ${LY_ROOT_FOLDER} ${rebased_file})
  66. set(${OUTPUT_FILE} ${rebased_file} PARENT_SCOPE)
  67. endfunction()
  68. #! ly_test_impact_rebase_files_to_repo_root: rebases the relative and/or absolute paths to be relative to repo root directory and places the resulting paths in quotes.
  69. #
  70. # \arg:INPUT_FILEs the files to rebase
  71. # \arg:OUTPUT_FILEs the files after rebasing
  72. # \arg:RELATIVE_DIR_ABS the absolute path that the files will be relatively rebased to
  73. function(ly_test_impact_rebase_files_to_repo_root INPUT_FILES OUTPUT_FILES RELATIVE_DIR_ABS)
  74. # Rebase all paths in list to repo root
  75. set(rebased_files "")
  76. foreach(in_file IN LISTS INPUT_FILES)
  77. ly_test_impact_rebase_file_to_repo_root(
  78. ${in_file}
  79. out_file
  80. ${RELATIVE_DIR_ABS}
  81. )
  82. list(APPEND rebased_files "\"${out_file}\"")
  83. endforeach()
  84. set(${OUTPUT_FILES} ${rebased_files} PARENT_SCOPE)
  85. endfunction()
  86. #! ly_test_impact_get_test_launch_method: gets the launch method (either standalone or testrunner) for the specified target.
  87. #
  88. # \arg:TARGET_NAME name of the target
  89. # \arg:LAUNCH_METHOD the type string for the specified target
  90. function(ly_test_impact_get_test_launch_method TARGET_NAME LAUNCH_METHOD)
  91. # Get the test impact framework-friendly launch method string
  92. get_target_property(target_type ${TARGET_NAME} TYPE)
  93. if("${target_type}" STREQUAL "SHARED_LIBRARY" OR "${target_type}" STREQUAL "MODULE_LIBRARY")
  94. set(${LAUNCH_METHOD} "test_runner" PARENT_SCOPE)
  95. elseif("${target_type}" STREQUAL "EXECUTABLE")
  96. set(${LAUNCH_METHOD} "stand_alone" PARENT_SCOPE)
  97. else()
  98. message(FATAL_ERROR "Cannot deduce test target launch method for the target ${TARGET_NAME} with type ${target_type}")
  99. endif()
  100. endfunction()
  101. #! ly_test_impact_extract_google_test_name: extracts the google test name from the composite 'namespace::test_name' string
  102. #
  103. # \arg:COMPOSITE_TEST test in the form 'namespace::test'
  104. # \arg:TEST_NAME name of test
  105. function(ly_test_impact_extract_google_test COMPOSITE_TEST TEST_NAMESPACE TEST_NAME)
  106. get_property(test_components GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_TEST_NAME)
  107. # Namespace and test are mandatory
  108. string(REPLACE "::" ";" test_components ${test_components})
  109. list(LENGTH test_components num_test_components)
  110. if(num_test_components LESS 2)
  111. message(FATAL_ERROR "The test ${test_components} appears to have been specified without a namespace, i.e.:\ly_add_googletest/benchmark(NAME ${test_components})\nInstead of (perhaps):\ly_add_googletest/benchmark(NAME Gem::${test_components})\nPlease add the missing namespace before proceeding.")
  112. endif()
  113. list(GET test_components 0 test_namespace)
  114. list(GET test_components 1 test_name)
  115. set(${TEST_NAMESPACE} ${test_namespace} PARENT_SCOPE)
  116. set(${TEST_NAME} ${test_name} PARENT_SCOPE)
  117. endfunction()
  118. #! ly_test_impact_extract_python_test_name: extracts the python test name from the composite 'namespace::test_name' string
  119. #
  120. # \arg:COMPOSITE_TEST test in form 'namespace::test' or 'test'
  121. # \arg:TEST_NAME name of test
  122. function(ly_test_impact_extract_python_test COMPOSITE_TEST TEST_NAME)
  123. get_property(test_components GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_TEST_NAME)
  124. # namespace is optional, in which case this component will be simply the test name
  125. string(REPLACE "::" ";" test_components ${test_components})
  126. list(LENGTH test_components num_test_components)
  127. if(num_test_components GREATER 1)
  128. list(GET test_components 1 test_name)
  129. else()
  130. set(test_name ${test_components})
  131. endif()
  132. set(${TEST_NAME} ${test_name} PARENT_SCOPE)
  133. endfunction()
  134. #! ly_test_impact_extract_google_test_params: extracts the suites for the given google test.
  135. #
  136. # \arg:COMPOSITE_TEST test in the form 'namespace::test'
  137. # \arg:COMPOSITE_SUITES composite list of suites for this target
  138. # \arg:TEST_NAMESPACE namespace of test
  139. # \arg:TEST_NAME name of test
  140. # \arg:TEST_SUITES extracted list of suites for this target in JSON format
  141. function(ly_test_impact_extract_google_test_params COMPOSITE_TEST COMPOSITE_SUITES TEST_NAMESPACE TEST_NAME TEST_SUITES)
  142. # Namespace and test are mandatory
  143. string(REPLACE "::" ";" test_components ${COMPOSITE_TEST})
  144. list(LENGTH test_components num_test_components)
  145. if(num_test_components LESS 2)
  146. message(FATAL_ERROR "The test ${test_components} appears to have been specified without a namespace, i.e.:\ly_add_googletest/benchmark(NAME ${test_components})\nInstead of (perhaps):\ly_add_googletest/benchmark(NAME Gem::${test_components})\nPlease add the missing namespace before proceeding.")
  147. endif()
  148. list(GET test_components 0 test_namespace)
  149. list(GET test_components 1 test_name)
  150. set(${TEST_NAMESPACE} ${test_namespace} PARENT_SCOPE)
  151. set(${TEST_NAME} ${test_name} PARENT_SCOPE)
  152. set(test_suites "")
  153. foreach(composite_suite ${COMPOSITE_SUITES})
  154. # Command, suite, timeout, labels
  155. string(REPLACE "#" ";" suite_components ${composite_suite})
  156. list(LENGTH suite_components num_suite_components)
  157. if(num_suite_components LESS 4)
  158. message(FATAL_ERROR "Test ${test_components} suite components ${composite_suite} are required to be in the following format: command#suite#timeout#labels.")
  159. endif()
  160. list(GET suite_components 0 test_command)
  161. list(GET suite_components 1 test_suite)
  162. list(GET suite_components 2 test_timeout)
  163. list(GET suite_components 3 test_labels)
  164. string(REPLACE "," "\",\"" test_labels "${test_labels}")
  165. set(suite_params "{ \"suite\": \"${test_suite}\", \"command\": \"${test_command}\", \"timeout\": ${test_timeout}, \"labels\": [\"${test_labels}\"] }")
  166. list(APPEND test_suites "${suite_params}")
  167. endforeach()
  168. string(REPLACE ";" ", " test_suites "${test_suites}")
  169. set(${TEST_SUITES} ${test_suites} PARENT_SCOPE)
  170. endfunction()
  171. #! ly_test_impact_extract_python_test_params: extracts the python test name and relative script path parameters.
  172. #
  173. # \arg:COMPOSITE_TEST test in form 'namespace::test' or 'test'
  174. # \arg:COMPOSITE_SUITES composite list of suites for this target
  175. # \arg:TEST_NAMESPACE namespace of test
  176. # \arg:TEST_NAME name of test
  177. # \arg:TEST_SUITES extracted list of suites for this target in JSON format
  178. function(ly_test_impact_extract_python_test_params COMPOSITE_TEST COMPOSITE_SUITES TEST_NAMESPACE TEST_NAME TEST_SUITES)
  179. get_property(script_path GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_SCRIPT_PATH)
  180. get_property(test_command GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_TEST_COMMAND)
  181. # Strip the separating semicolons to yield the executable command string for this test
  182. string(REPLACE ";" " " test_command "${test_command}")
  183. # namespace is optional, in which case this component will be simply the test name
  184. string(REPLACE "::" ";" test_components ${COMPOSITE_TEST})
  185. list(LENGTH test_components num_test_components)
  186. if(num_test_components GREATER 1)
  187. list(GET test_components 0 test_namespace)
  188. list(GET test_components 1 test_name)
  189. else()
  190. set(test_namespace "")
  191. set(test_name ${test_components})
  192. endif()
  193. set(${TEST_NAMESPACE} ${test_namespace} PARENT_SCOPE)
  194. set(${TEST_NAME} ${test_name} PARENT_SCOPE)
  195. set(test_suites "")
  196. foreach(composite_suite ${COMPOSITE_SUITES})
  197. # Script path, suite, timeout, labels
  198. string(REPLACE "#" ";" suite_components ${composite_suite})
  199. list(LENGTH suite_components num_suite_components)
  200. if(num_suite_components LESS 4)
  201. message(FATAL_ERROR "Test ${test_components} suite components ${composite_suite} are required to be in the following format: script_path#suite#timeout#labels.")
  202. endif()
  203. list(GET suite_components 0 script_path)
  204. list(GET suite_components 1 test_suite)
  205. list(GET suite_components 2 test_timeout)
  206. list(GET suite_components 3 test_labels)
  207. # Get python script path relative to repo root
  208. ly_test_impact_rebase_file_to_repo_root(
  209. "${script_path}"
  210. script_path
  211. "${LY_ROOT_FOLDER}"
  212. )
  213. string(REPLACE "," "\",\"" test_labels "${test_labels}")
  214. set(suite_params "{ \"suite\": \"${test_suite}\", \"script\": \"${script_path}\", \"timeout\": ${test_timeout}, \"command\": \"${test_command}\", \"labels\": [\"${test_labels}\"] }")
  215. list(APPEND test_suites "${suite_params}")
  216. endforeach()
  217. string(REPLACE ";" ", " test_suites "${test_suites}")
  218. set(${TEST_SUITES} ${test_suites} PARENT_SCOPE)
  219. endfunction()
  220. #! ly_test_impact_write_test_enumeration_file: exports the main test list to file.
  221. #
  222. # \arg:TEST_ENUMERATION_TEMPLATE_FILE path to test enumeration template file
  223. function(ly_test_impact_write_test_enumeration_file TEST_ENUMERATION_TEMPLATE_FILE)
  224. get_property(LY_ALL_TESTS GLOBAL PROPERTY LY_ALL_TESTS)
  225. # Enumerated tests for each type
  226. set(google_tests "")
  227. set(google_benchmarks "")
  228. set(python_tests "")
  229. set(python_editor_tests "")
  230. set(unknown_tests "")
  231. # Walk the test list
  232. foreach(test ${LY_ALL_TESTS})
  233. message(TRACE "Parsing ${test}")
  234. get_property(test_params GLOBAL PROPERTY LY_ALL_TESTS_${test}_PARAMS)
  235. get_property(test_type GLOBAL PROPERTY LY_ALL_TESTS_${test}_TEST_LIBRARY)
  236. if("${test_type}" STREQUAL "pytest")
  237. # Python tests
  238. ly_test_impact_extract_python_test_params(${test} "${test_params}" test_namespace test_name test_suites)
  239. list(APPEND python_tests " { \"namespace\": \"${test_namespace}\", \"name\": \"${test_name}\", \"suites\": [${test_suites}] }")
  240. elseif("${test_type}" STREQUAL "pytest_editor")
  241. # Python editor tests
  242. ly_test_impact_extract_python_test_params(${test} "${test_params}" test_namespace test_name test_suites)
  243. list(APPEND python_editor_tests " { \"namespace\": \"${test_namespace}\", \"name\": \"${test_name}\", \"suites\": [${test_suites}] }")
  244. elseif("${test_type}" STREQUAL "googletest")
  245. # Google tests
  246. ly_test_impact_extract_google_test_params(${test} "${test_params}" test_namespace test_name test_suites)
  247. ly_test_impact_get_test_launch_method(${test} launch_method)
  248. list(APPEND google_tests " { \"namespace\": \"${test_namespace}\", \"name\": \"${test_name}\", \"launch_method\": \"${launch_method}\", \"suites\": [${test_suites}] }")
  249. elseif("${test_type}" STREQUAL "googlebenchmark")
  250. # Google benchmarks
  251. ly_test_impact_extract_google_test_params(${test} "${test_params}" test_namespace test_name test_suites)
  252. list(APPEND google_benchmarks " { \"namespace\": \"${test_namespace}\", \"name\": \"${test_name}\", \"launch_method\": \"${launch_method}\", \"suites\": [${test_suites}] }")
  253. else()
  254. message("${test_name} is of unknown type (TEST_LIBRARY property is \"${test_type}\")")
  255. list(APPEND unknown_tests " { \"name\": \"${test}\" }")
  256. endif()
  257. endforeach()
  258. string(REPLACE ";" ",\n" google_tests "${google_tests}")
  259. string(REPLACE ";" ",\n" google_benchmarks "${google_benchmarks}")
  260. string(REPLACE ";" ",\n" python_editor_tests "${python_editor_tests}")
  261. string(REPLACE ";" ",\n" python_tests "${python_tests}")
  262. string(REPLACE ";" ",\n" unknown_tests "${unknown_tests}")
  263. # Write out the test enumeration file
  264. configure_file(${TEST_ENUMERATION_TEMPLATE_FILE} ${LY_TEST_IMPACT_TEST_TYPE_FILE})
  265. endfunction()
  266. #! ly_test_impact_write_gem_target_enumeration_file: exports the main gem target list to file.
  267. #
  268. # \arg:GEM_TARGET_TEMPLATE_FILE path to source to gem target template file
  269. function(ly_test_impact_write_gem_target_enumeration_file GEM_TARGET_TEMPLATE_FILE)
  270. get_property(LY_ALL_TARGETS GLOBAL PROPERTY LY_ALL_TARGETS)
  271. set(enumerated_gem_targets "")
  272. # Walk the build targets
  273. foreach(aliased_target ${LY_ALL_TARGETS})
  274. unset(target)
  275. ly_de_alias_target(${aliased_target} target)
  276. get_target_property(gem_module ${target} GEM_MODULE)
  277. get_target_property(target_type ${target} TYPE)
  278. if("${gem_module}" STREQUAL "TRUE")
  279. if("${target_type}" STREQUAL "SHARED_LIBRARY" OR "${target_type}" STREQUAL "MODULE_LIBRARY")
  280. list(APPEND enumerated_gem_targets " \"${target}\"")
  281. endif()
  282. endif()
  283. endforeach()
  284. string (REPLACE ";" ",\n" enumerated_gem_targets "${enumerated_gem_targets}")
  285. # Write out source to target mapping file
  286. set(mapping_path "${LY_TEST_IMPACT_GEM_TARGET_FILE}")
  287. configure_file(${GEM_TARGET_TEMPLATE_FILE} ${mapping_path})
  288. endfunction()
  289. #! ly_extract_aliased_target_dependencies: recursively extracts the aliases of a target to retrieve the true de-aliased target.
  290. #
  291. # \arg:TARGET target to de-alias
  292. function(ly_extract_aliased_target_dependencies TARGET DE_ALIASED_TARGETS)
  293. if(NOT ARGN)
  294. # Entry point of recursive call, set the parent target and clear any existing aliases
  295. set(PARENT_TARGET ${TARGET})
  296. set_property(GLOBAL PROPERTY LY_EXTRACT_ALIASED_TARGET_DEPENDENCIES_DE_ALIASED_TARGETS_${PARENT_TARGET} "")
  297. endif()
  298. # Check for aliases of this target
  299. get_property(aliased_targets GLOBAL PROPERTY O3DE_ALIASED_TARGETS_${TARGET} SET)
  300. if(${aliased_targets})
  301. # One or more aliases for this target has been found
  302. get_property(aliased_targets GLOBAL PROPERTY O3DE_ALIASED_TARGETS_${TARGET})
  303. foreach(aliased_target ${aliased_targets})
  304. # Recursively extract any aliases of the alias of this target
  305. ly_extract_aliased_target_dependencies(${aliased_target} empty ${PARENT_TARGET})
  306. endforeach()
  307. else()
  308. # No more aliases found for this target, add this target as an alias for the parent target
  309. set_property(GLOBAL APPEND PROPERTY LY_EXTRACT_ALIASED_TARGET_DEPENDENCIES_DE_ALIASED_TARGETS_${PARENT_TARGET} ${TARGET})
  310. endif()
  311. if(NOT ARGN)
  312. # Exit point of recursive call
  313. get_property(de_aliased_targets GLOBAL PROPERTY LY_EXTRACT_ALIASED_TARGET_DEPENDENCIES_DE_ALIASED_TARGETS_${TARGET})
  314. set(${DE_ALIASED_TARGETS} ${de_aliased_targets} PARENT_SCOPE)
  315. endif()
  316. endfunction()
  317. #! ly_extract_target_dependencies: extracts the target dependencies for the specified target as a comma separated list.
  318. function(ly_extract_target_dependencies INPUT_DEPENDENCY_LIST OUTPUT_DEPENDENCY_LIST)
  319. set(dependencies "")
  320. # Walk the dependency list
  321. foreach(qualified_target_name ${INPUT_DEPENDENCY_LIST})
  322. # Extract just the target name, ignoring the namespace
  323. if(TARGET ${qualified_target_name})
  324. set(target_to_add "")
  325. get_target_property(is_imported ${qualified_target_name} IMPORTED)
  326. get_target_property(is_gem_module ${qualified_target_name} GEM_MODULE)
  327. # Skip third party dependencies
  328. if(NOT is_imported OR is_gem_module)
  329. string(REPLACE "::" ";" target_name_components ${qualified_target_name})
  330. list(LENGTH target_name_components num_name_components)
  331. if(num_name_components GREATER 1)
  332. list(GET target_name_components 1 target_name)
  333. set(target_to_add ${target_name})
  334. else()
  335. set(target_to_add ${qualified_target_name})
  336. endif()
  337. # Extract the targets this target may alias
  338. ly_extract_aliased_target_dependencies(${target_to_add} de_aliased_targets)
  339. foreach(de_aliased_target ${de_aliased_targets})
  340. list(APPEND dependencies "\"${de_aliased_target}\"")
  341. endforeach()
  342. endif()
  343. endif()
  344. endforeach()
  345. # Convert to a comma separated list
  346. string (REPLACE ";" ",\n" dependencies "${dependencies}")
  347. set(${OUTPUT_DEPENDENCY_LIST} ${dependencies} PARENT_SCOPE)
  348. endfunction()
  349. #! ly_get_target_type: retrieves the type of target (either production or test target).
  350. #
  351. # \arg:TARGET target to get the type for
  352. # \arg:TARGET_TYPE the retrieved target type
  353. function(ly_get_target_type TARGET TARGET_TYPE)
  354. get_property(O3DE_ALL_TESTS_DE_NAMESPACED GLOBAL PROPERTY O3DE_ALL_TESTS_DE_NAMESPACED)
  355. if(${target_name} IN_LIST O3DE_ALL_TESTS_DE_NAMESPACED)
  356. set(target_type "test")
  357. else()
  358. set(target_type "production")
  359. endif()
  360. set(${TARGET_TYPE} ${target_type} PARENT_SCOPE)
  361. endfunction()
  362. #! ly_test_impact_export_source_target_mappings: exports the static source to target mappings to file.
  363. #
  364. # \arg:MAPPING_TEMPLATE_FILE path to source to target template file
  365. function(ly_test_impact_export_source_target_mappings MAPPING_TEMPLATE_FILE)
  366. get_property(LY_ALL_TARGETS GLOBAL PROPERTY LY_ALL_TARGETS)
  367. # Walk the build targets
  368. foreach(aliased_target ${LY_ALL_TARGETS})
  369. unset(target)
  370. ly_de_alias_target(${aliased_target} target)
  371. message(TRACE "Exporting static source file mappings for ${target}")
  372. # Target name and path relative to root
  373. set(target_name ${target})
  374. get_target_property(target_path_abs ${target} SOURCE_DIR)
  375. file(RELATIVE_PATH target_path ${LY_ROOT_FOLDER} ${target_path_abs})
  376. # Target type
  377. ly_get_target_type(${target_name} target_type)
  378. # Output name
  379. get_target_property(target_output_name ${target} OUTPUT_NAME)
  380. if (target_output_name STREQUAL "target_output_name-NOTFOUND")
  381. # No custom output name was specified so use the target name
  382. set(target_output_name "${target}")
  383. endif()
  384. # Dependencies
  385. get_property(build_dependencies GLOBAL PROPERTY LY_ALL_TARGETS_${target}_BUILD_DEPENDENCIES)
  386. get_property(runtime_dependencies GLOBAL PROPERTY LY_ALL_TARGETS_${target}_RUNTIME_DEPENDENCIES)
  387. ly_extract_target_dependencies("${build_dependencies}" build_dependencies)
  388. ly_extract_target_dependencies("${runtime_dependencies}" runtime_dependencies)
  389. # Autogen source file mappings
  390. get_target_property(autogen_input_files ${target} AUTOGEN_INPUT_FILES)
  391. get_target_property(autogen_output_files ${target} AUTOGEN_OUTPUT_FILES)
  392. if(DEFINED autogen_input_files AND autogen_output_files)
  393. # Rebase input source file paths to repo root
  394. ly_test_impact_rebase_files_to_repo_root(
  395. "${autogen_input_files}"
  396. autogen_input_files
  397. ${target_path_abs}
  398. )
  399. # Rebase output source file paths to repo root
  400. ly_test_impact_rebase_files_to_repo_root(
  401. "${autogen_output_files}"
  402. autogen_output_files
  403. ${target_path_abs}
  404. )
  405. else()
  406. set(autogen_input_files "")
  407. set(autogen_output_files "")
  408. endif()
  409. # Static source file mappings
  410. get_target_property(static_sources ${target} SOURCES)
  411. # Rebase static source files to repo root
  412. ly_test_impact_rebase_files_to_repo_root(
  413. "${static_sources}"
  414. static_sources
  415. ${target_path_abs}
  416. )
  417. # Add the static source file mappings to the contents
  418. string (REPLACE ";" ",\n" autogen_input_files "${autogen_input_files}")
  419. string (REPLACE ";" ",\n" autogen_output_files "${autogen_output_files}")
  420. string (REPLACE ";" ",\n" static_sources "${static_sources}")
  421. # Write out source to target mapping file
  422. set(mapping_path "${LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR}/${target}.target")
  423. configure_file(${MAPPING_TEMPLATE_FILE} ${mapping_path})
  424. endforeach()
  425. endfunction()
  426. #! ly_test_impact_write_config_file: writes out the test impact framework config file using the data derived from the build generation process.
  427. #
  428. # \arg:CONFIG_TEMPLATE_FILE path to the runtime configuration template file
  429. # \arg:BIN_DIR path to repo binary output directory
  430. function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE BIN_DIR)
  431. # Platform this config file is being generated for
  432. set(platform ${PAL_PLATFORM_NAME})
  433. # Timestamp this config file was generated at
  434. string(TIMESTAMP timestamp "%Y-%m-%d %H:%M:%S")
  435. # Build configuration this config file is being generated for
  436. set(build_config "$<CONFIG>")
  437. # Instrumentation binary
  438. if(NOT O3DE_TEST_IMPACT_INSTRUMENTATION_BIN)
  439. # No binary specified is not an error, it just means that the test impact analysis part of the framework is disabled for native tests
  440. message(DEBUG "No test impact framework instrumentation binary was specified, test impact analysis framework will fall back to regular test sequences instead")
  441. set(native_use_test_impact_analysis false)
  442. set(instrumentation_bin "")
  443. else()
  444. set(native_use_test_impact_analysis true)
  445. file(TO_CMAKE_PATH ${O3DE_TEST_IMPACT_INSTRUMENTATION_BIN} instrumentation_bin)
  446. endif()
  447. if(O3DE_TEST_IMPACT_NATIVE_TEST_TARGETS_ENABLED)
  448. set(native_test_targets_enabled true)
  449. # Testrunner binary
  450. set(native_test_runner_bin $<TARGET_FILE:AzTestRunner>)
  451. else()
  452. set(native_test_targets_enabled false)
  453. endif()
  454. if(O3DE_TEST_IMPACT_PYTHON_TEST_TARGETS_ENABLED)
  455. set(python_test_targets_enabled true)
  456. else()
  457. set(python_test_targets_enabled false)
  458. endif()
  459. # Python command
  460. set(python_cmd "${LY_ROOT_FOLDER}/python/python.cmd")
  461. # Repository root
  462. set(repo_dir ${LY_ROOT_FOLDER})
  463. # Test impact framework output binary dir
  464. set(bin_dir ${BIN_DIR})
  465. # Native temp dir
  466. set(native_temp_dir "${LY_TEST_IMPACT_RUNTIME_TEMP_DIR}/Native")
  467. # Native active persistent data dir
  468. set(native_active_dir "${LY_TEST_IMPACT_RUNTIME_PERSISTENT_DIR}/Native/active")
  469. # Native historic persistent data dir
  470. set(native_historic_dir "${LY_TEST_IMPACT_RUNTIME_PERSISTENT_DIR}/Native/historic")
  471. # Python temp dir
  472. set(python_temp_dir "${LY_TEST_IMPACT_RUNTIME_TEMP_DIR}/Python")
  473. # Python active persistent data dir
  474. set(python_active_dir "${LY_TEST_IMPACT_RUNTIME_PERSISTENT_DIR}/Python/active")
  475. # Python historic persistent data dir
  476. set(python_historic_dir "${LY_TEST_IMPACT_RUNTIME_PERSISTENT_DIR}/Python/historic")
  477. # Source to target mappings dir
  478. set(source_target_mapping_dir "${LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR}")
  479. # Test type artifact file
  480. set(test_target_type_file "${LY_TEST_IMPACT_TEST_TYPE_FILE}")
  481. # Gem target file
  482. set(gem_target_file "${LY_TEST_IMPACT_GEM_TARGET_FILE}")
  483. # Build dependency artifact dir
  484. set(target_dependency_dir "${LY_TEST_IMPACT_TARGET_DEPENDENCY_DIR}")
  485. # Test impact analysis framework native runtime binary
  486. if(O3DE_TEST_IMPACT_NATIVE_TEST_TARGETS_ENABLED)
  487. set(native_runtime_bin "$<TARGET_FILE:${LY_TEST_IMPACT_NATIVE_CONSOLE_TARGET}>")
  488. endif()
  489. # Test impact analysis framework python runtime binary
  490. if(O3DE_TEST_IMPACT_PYTHON_TEST_TARGETS_ENABLED)
  491. set(python_runtime_bin "$<TARGET_FILE:${LY_TEST_IMPACT_PYTHON_CONSOLE_TARGET}>")
  492. endif()
  493. # Substitute config file template with above vars
  494. ly_file_read("${CONFIG_TEMPLATE_FILE}" config_file)
  495. string(CONFIGURE ${config_file} config_file)
  496. # Write out entire config contents to a file in the build directory of the test impact framework console target
  497. file(GENERATE
  498. OUTPUT "${LY_TEST_IMPACT_CONFIG_FILE_PATH}"
  499. CONTENT "${config_file}"
  500. )
  501. message(DEBUG "Test impact framework post steps complete")
  502. endfunction()
  503. #! ly_test_impact_write_pytest_file: writes out the test information utilised by our TIAF testing tools, using the data derived from the build generation process.
  504. #
  505. # \arg:CONFIGURATION_FILE path to the test data template file
  506. function(ly_test_impact_write_pytest_file CONFIGURATION_FILE)
  507. # For each configuration type, compile the build info we need and add it to our array
  508. set(build_configs "")
  509. foreach(config_type ${LY_CONFIGURATION_TYPES})
  510. set(config_path "${LY_TEST_IMPACT_WORKING_DIR}/${config_type}/${LY_TEST_IMPACT_PERSISTENT_DIR}/${LY_TEST_IMPACT_CONFIG_FILE_NAME}")
  511. list(APPEND build_configs "\"${config_type}\" : { \"config\" : \"${config_path}\"}")
  512. endforeach()
  513. # Configure our list of entries
  514. string(REPLACE ";" ",\n" build_configs "${build_configs}")
  515. # Configure and write out our test data file
  516. ly_file_read("${CONFIGURATION_FILE}" test_file)
  517. string(CONFIGURE ${test_file} test_file)
  518. file(GENERATE
  519. OUTPUT "${LY_TEST_IMPACT_PYTEST_FILE_PATH}/ly_test_impact_test_data.json"
  520. CONTENT "${test_file}"
  521. )
  522. endfunction()
  523. #! ly_test_impact_clean_directories: Removes the artifact directory, test output directory, temp directory
  524. #! and the persistent directories containing TIAF configs for each build configuration.
  525. function(ly_test_impact_clean_directories)
  526. # Clean the output folders of native and python tests to ensure only the most current run is in there.
  527. file(REMOVE_RECURSE ${LY_TEST_IMPACT_NATIVE_TEST_RUN_DIR})
  528. file(REMOVE_RECURSE ${LY_TEST_IMPACT_PYTHON_TEST_RUN_DIR})
  529. # For each build configuration type, delete the persistent and temp folders
  530. foreach(config_type ${LY_CONFIGURATION_TYPES})
  531. file(REMOVE_RECURSE "${LY_TEST_IMPACT_WORKING_DIR}/${config_type}/${LY_TEST_IMPACT_PERSISTENT_DIR}")
  532. file(REMOVE_RECURSE "${LY_TEST_IMPACT_WORKING_DIR}/${config_type}/${LY_TEST_IMPACT_TEMP_DIR}")
  533. endforeach()
  534. # Erase any existing artifact and non-persistent data to avoid getting test impact framework out of sync with current repo state
  535. file(REMOVE_RECURSE "${LY_TEST_IMPACT_ARTIFACT_DIR}")
  536. endfunction()
  537. #! ly_test_impact_post_step: runs the post steps to be executed after all other cmake scripts have been executed.
  538. function(ly_test_impact_post_step)
  539. # TIAF not supported for monolithic games
  540. if(LY_MONOLITHIC_GAME)
  541. return()
  542. endif()
  543. # Clean temporary and persistent directories
  544. ly_test_impact_clean_directories()
  545. # Directory for binaries built for this profile
  546. set(bin_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIG>")
  547. # Export the soruce to target mapping files
  548. ly_test_impact_export_source_target_mappings(
  549. "cmake/TestImpactFramework/SourceToTargetMapping.in"
  550. )
  551. # Export the enumerated tests
  552. ly_test_impact_write_test_enumeration_file(
  553. "cmake/TestImpactFramework/EnumeratedTests.in"
  554. )
  555. # Export the enumerated gems
  556. ly_test_impact_write_gem_target_enumeration_file(
  557. "cmake/TestImpactFramework/EnumeratedGemTargets.in"
  558. )
  559. # Write out the configuration file
  560. ly_test_impact_write_config_file(
  561. "cmake/TestImpactFramework/ConsoleFrontendConfig.in"
  562. ${bin_dir}
  563. )
  564. # Write out required test data into config file.
  565. ly_test_impact_write_pytest_file(
  566. "cmake/TestImpactFramework/LYTestImpactTestData.in"
  567. )
  568. # Copy over the graphviz options file for the build dependency graphs
  569. message(DEBUG "Test impact framework config file written")
  570. file(COPY "cmake/TestImpactFramework/CMakeGraphVizOptions.cmake" DESTINATION ${CMAKE_BINARY_DIR})
  571. endfunction()