# # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or # its licensors. # # For complete copyright and license terms please see the LICENSE at the root of this # distribution (the "License"). All use of this software is governed by the License, # or, if provided, by the license below or the license accompanying this file. Do not # remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # # Switch to enable/disable test impact analysis (and related build targets) option(LY_TEST_IMPACT_ACTIVE "Enable test impact framework" OFF) # Path to test instrumentation binary option(LY_TEST_IMPACT_INSTRUMENTATION_BIN "Path to test impact framework instrumentation binary" OFF) # Name of test impact framework console target set(LY_TEST_IMPACT_CONSOLE_TARGET "TestImpact.Frontend.Console") # Directory for non-persistent test impact data trashed with each generation of build system set(LY_TEST_IMPACT_WORKING_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TestImpactFramework") # Directory for temporary files generated at runtime set(LY_TEST_IMPACT_TEMP_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Temp") # Directory for static artifacts produced as part of the build system generation process set(LY_TEST_IMPACT_ARTIFACT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Artefact") # Directory for source to build target mappings set(LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/Mapping") # Directory for build target dependency/depender graphs set(LY_TEST_IMPACT_TARGET_DEPENDENCY_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/Dependency") # Directory for test type enumeration files set(LY_TEST_IMPACT_TEST_TYPE_DIR "${LY_TEST_IMPACT_ARTIFACT_DIR}/TestType") # Master test enumeration file for all test types set(LY_TEST_IMPACT_TEST_TYPE_FILE "${LY_TEST_IMPACT_TEST_TYPE_DIR}/All.tests") #! 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. # # \arg:INPUT_FILE the file to rebase # \arg:OUTPUT_FILE the file after rebasing # \arg:RELATIVE_DIR_ABS the absolute path that the file will be relatively rebased to function(ly_test_impact_rebase_file_to_repo_root INPUT_FILE OUTPUT_FILE RELATIVE_DIR_ABS) # Transform the file paths to absolute paths set(rebased_file ${INPUT_FILE}) if(NOT IS_ABSOLUTE ${rebased_file}) get_filename_component(rebased_file "${rebased_file}" REALPATH BASE_DIR "${RELATIVE_DIR_ABS}" ) endif() # Rebase absolute path to relative path from repo root file(RELATIVE_PATH rebased_file ${LY_ROOT_FOLDER} ${rebased_file}) set(${OUTPUT_FILE} ${rebased_file} PARENT_SCOPE) endfunction() #! 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. # # \arg:INPUT_FILEs the files to rebase # \arg:OUTPUT_FILEs the files after rebasing # \arg:RELATIVE_DIR_ABS the absolute path that the files will be relatively rebased to function(ly_test_impact_rebase_files_to_repo_root INPUT_FILES OUTPUT_FILES RELATIVE_DIR_ABS) # Rebase all paths in list to repo root set(rebased_files "") foreach(in_file IN LISTS INPUT_FILES) ly_test_impact_rebase_file_to_repo_root( ${in_file} out_file ${RELATIVE_DIR_ABS} ) list(APPEND rebased_files "\"${out_file}\"") endforeach() set(${OUTPUT_FILES} ${rebased_files} PARENT_SCOPE) endfunction() #! ly_test_impact_get_target_type_string: gets the target type string (either executable, dynalib or unknown) for the specified target. # # \arg:TARGET_NAME name of the target # \arg:TARGET_TYPE the type string for the specified target function(ly_test_impact_get_target_type_string TARGET_NAME TARGET_TYPE) # Get the test impact framework-friendly target type string get_target_property(target_type ${TARGET_NAME} TYPE) if("${target_type}" STREQUAL "SHARED_LIBRARY" OR "${target_type}" STREQUAL "MODULE_LIBRARY") set(${TARGET_TYPE} "dynlib" PARENT_SCOPE) elseif("${target_type}" STREQUAL "EXECUTABLE") set(${TARGET_TYPE} "executable" PARENT_SCOPE) else() set(${TARGET_TYPE} "unknown" PARENT_SCOPE) endif() endfunction() #! ly_test_impact_extract_google_test: explodes a composite google test string into namespace, test and suite components. # # \arg:COMPOSITE_TEST test in the form 'namespace::test' # \arg:TEST_QUALIFER qualifier for the test (namespace) # \arg:TEST_NAME name of test function(ly_test_impact_extract_google_test COMPOSITE_TEST TEST_QUALIFER TEST_NAME) get_property(test_components GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_TEST_NAME) # Namespace and test are mandetiry string(REPLACE "::" ";" test_components ${test_components}) list(LENGTH test_components num_test_components) if(num_test_components LESS 2) 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.") endif() list(GET test_components 0 test_qualifier) list(GET test_components 1 test_name) set(${TEST_QUALIFER} ${test_qualifier} PARENT_SCOPE) set(${TEST_NAME} ${test_name} PARENT_SCOPE) endfunction() #! ly_test_impact_extract_python_test: explodes a composite python test string into filename, namespace, test and suite components. # # \arg:COMPOSITE_TEST test in form 'namespace::test' or 'test' # \arg:TEST_QUALIFER qualifier for the test (optional) # \arg:TEST_NAME name of test # \arg:TEST_FILE the Python script path for this test function(ly_test_impact_extract_python_test COMPOSITE_TEST TEST_QUALIFER TEST_NAME TEST_FILE) get_property(test_components GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_TEST_NAME) get_property(test_file GLOBAL PROPERTY LY_ALL_TESTS_${COMPOSITE_TEST}_SCRIPT_PATH) # namespace is optional, in which case this component will be simply the test name string(REPLACE "::" ";" test_components ${test_components}) list(LENGTH test_components num_test_components) if(num_test_components GREATER 1) list(GET test_components 0 test_qualifier) list(GET test_components 1 test_name) else() set(test_qualifier "") set(test_name ${test_components}) endif() # Get python script path relative to repo root ly_test_impact_rebase_file_to_repo_root( ${test_file} test_file ${LY_ROOT_FOLDER} ) set(${TEST_QUALIFER} ${test_qualifier} PARENT_SCOPE) set(${TEST_NAME} ${test_name} PARENT_SCOPE) set(${TEST_FILE} ${test_file} PARENT_SCOPE) endfunction() #! ly_test_impact_write_test_enumeration_file: exports the master test lists to file. # # \arg:TEST_ENUMERATION_TEMPLATE_FILE path to test enumeration template file function(ly_test_impact_write_test_enumeration_file TEST_ENUMERATION_TEMPLATE_FILE) get_property(LY_ALL_TESTS GLOBAL PROPERTY LY_ALL_TESTS) # Enumerated tests for each type set(google_tests "") set(google_benchmarks "") set(python_tests "") set(python_editor_tests "") set(unknown_tests "") # Walk the test list foreach(test ${LY_ALL_TESTS}) message(TRACE "Parsing ${test}") get_property(test_type GLOBAL PROPERTY LY_ALL_TESTS_${test}_TEST_LIBRARY) get_property(test_suite GLOBAL PROPERTY LY_ALL_TESTS_${test}_TEST_SUITE) if("${test_type}" STREQUAL "pytest") # Python tests ly_test_impact_extract_python_test(${test} test_qualifier test_name test_file) list(APPEND python_tests "{ name = \"${test_name}\", qualifier = \"${test_qualifier}\", suite = \"${test_suite}\", path = \"${test_file}\" }") elseif("${test_type}" STREQUAL "pytest_editor") # Python editor tests ly_test_impact_extract_python_test(${test} test_qualifier test_name test_file) list(APPEND python_editor_tests "{ name = \"${test_name}\", qualifier = \"${test_qualifier}\", suite = \"${test_suite}\", path = \"${test_file}\" }") elseif("${test_type}" STREQUAL "googletest") # Google tests ly_test_impact_extract_google_test(${test} test_qualifier test_name) ly_test_impact_get_target_type_string(${test_name} target_type) list(APPEND google_tests "{ name = \"${test_name}\", qualifier = \"${test_qualifier}\", suite = \"${test_suite}\", build_type = \"${target_type}\" }") elseif("${test_type}" STREQUAL "googlebenchmark") # Google benchmarks ly_test_impact_extract_google_test(${test} test_qualifier test_name) list(APPEND google_benchmarks "{ name = \"${test_name}\", qualifier = \"${test_qualifier}\", suite = \"${test_suite}\" }") else() message("${test} is of unknown type (TEST_LIBRARY property is empty)") list(APPEND unknown_tests "{ name = \"${test}\" }") endif() endforeach() string(REPLACE ";" ",\n" google_tests "${google_tests}") string(REPLACE ";" ",\n" google_benchmarks "${google_benchmarks}") string(REPLACE ";" ",\n" python_editor_tests "${python_editor_tests}") string(REPLACE ";" ",\n" python_tests "${python_tests}") string(REPLACE ";" ",\n" unknown_tests "${unknown_tests}") # Write out the test enumeration file configure_file(${TEST_ENUMERATION_TEMPLATE_FILE} ${LY_TEST_IMPACT_TEST_TYPE_FILE}) endfunction() #! ly_test_impact_export_source_target_mappings: exports the static source to target mappings to file. # # \arg:MAPPING_TEMPLATE_FILE path to source to target template file function(ly_test_impact_export_source_target_mappings MAPPING_TEMPLATE_FILE) get_property(LY_ALL_TARGETS GLOBAL PROPERTY LY_ALL_TARGETS) # Walk the build targets foreach(aliased_target ${LY_ALL_TARGETS}) unset(target) ly_de_alias_target(${aliased_target} target) message(TRACE "Exporting static source file mappings for ${target}") # Target name and path relative to root set(target_name ${target}) get_target_property(target_path_abs ${target} SOURCE_DIR) file(RELATIVE_PATH target_path ${LY_ROOT_FOLDER} ${target_path_abs}) # Output name get_target_property(target_output_name ${target} OUTPUT_NAME) if (target_output_name STREQUAL "target_output_name-NOTFOUND") # No custom output name was specified so use the target name set(target_output_name "${target}") endif() # Autogen source file mappings get_target_property(autogen_input_files ${target} AUTOGEN_INPUT_FILES) get_target_property(autogen_output_files ${target} AUTOGEN_OUTPUT_FILES) if(DEFINED autogen_input_files AND autogen_output_files) # Rebase input source file paths to repo root ly_test_impact_rebase_files_to_repo_root( "${autogen_input_files}" autogen_input_files ${target_path_abs} ) # Rebase output source file paths to repo root ly_test_impact_rebase_files_to_repo_root( "${autogen_output_files}" autogen_output_files ${target_path_abs} ) else() set(autogen_input_files "") set(autogen_output_files "") endif() # Static source file mappings get_target_property(target_type ${target} TYPE) if("${target_type}" STREQUAL "INTERFACE_LIBRARY") get_target_property(static_sources ${target}_HEADERS SOURCES) else() get_target_property(static_sources ${target} SOURCES) endif() # Rebase static source files to repo root ly_test_impact_rebase_files_to_repo_root( "${static_sources}" static_sources ${target_path_abs} ) # Add the static source file mappings to the contents string (REPLACE ";" ",\n" autogen_input_files "${autogen_input_files}") string (REPLACE ";" ",\n" autogen_output_files "${autogen_output_files}") string (REPLACE ";" ",\n" static_sources "${static_sources}") # Write out source to target mapping file set(mapping_path "${LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR}/${target}.target") configure_file(${MAPPING_TEMPLATE_FILE} ${mapping_path}) endforeach() endfunction() #! ly_test_impact_write_config_file: writes out the test impact framework config file using the data derived from the build generation process. # # \arg:CONFIG_TEMPLATE_FILE path to the runtime configuration template file # \arg:PERSISTENT_DATA_DIR path to the test impact framework persistent data directory # \arg:RUNTIME_BIN_DIR path to repo binary ourput directory function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE PERSISTENT_DATA_DIR RUNTIME_BIN_DIR) set(repo_dir ${LY_ROOT_FOLDER}) # SparTIA instrumentation binary if(NOT LY_TEST_IMPACT_INSTRUMENTATION_BIN) message(FATAL_ERROR "No test impact framework instrumentation binary was specified, please provide the path with option LY_TEST_IMPACT_INSTRUMENTATION_BIN") endif() set(instrumentation_bin ${LY_TEST_IMPACT_INSTRUMENTATION_BIN}) # test impact framework working dir ly_test_impact_rebase_file_to_repo_root( ${LY_TEST_IMPACT_WORKING_DIR} working_dir ${LY_ROOT_FOLDER} ) # test impact framework console binary dir ly_test_impact_rebase_file_to_repo_root( ${RUNTIME_BIN_DIR} runtime_bin_dir ${LY_ROOT_FOLDER} ) # Test dir ly_test_impact_rebase_file_to_repo_root( "${PERSISTENT_DATA_DIR}/Tests" tests_dir ${LY_ROOT_FOLDER} ) # Temp dir ly_test_impact_rebase_file_to_repo_root( "${LY_TEST_IMPACT_TEMP_DIR}" temp_dir ${LY_ROOT_FOLDER} ) # Source to target mappings dir ly_test_impact_rebase_file_to_repo_root( "${LY_TEST_IMPACT_SOURCE_TARGET_MAPPING_DIR}" source_target_mapping_dir ${LY_ROOT_FOLDER} ) # Test type artifact dir ly_test_impact_rebase_file_to_repo_root( "${LY_TEST_IMPACT_TEST_TYPE_DIR}" test_type_dir ${LY_ROOT_FOLDER} ) # Bild dependency artifact dir ly_test_impact_rebase_file_to_repo_root( "${LY_TEST_IMPACT_TARGET_DEPENDENCY_DIR}" target_dependency_dir ${LY_ROOT_FOLDER} ) # Substitute config file template with above vars file(READ "${CONFIG_TEMPLATE_FILE}" config_file) string(CONFIGURE ${config_file} config_file) # Write out entire config contents to a file in the build directory of the test impact framework console target string(TIMESTAMP timestamp "%Y-%m-%d %H:%M:%S") set(header "# Test Impact Framework configuration file for Lumberyard\n# Platform: ${CMAKE_SYSTEM_NAME}\n# Build: $\n# ${timestamp}") file(GENERATE OUTPUT "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$/$.$.cfg" CONTENT "${header}\n\n${config_file}" ) endfunction() #! ly_test_impact_post_step: runs the post steps to be executed after all other cmake scripts have been executed. function(ly_test_impact_post_step) if(NOT ${LY_TEST_IMPACT_ACTIVE}) return() endif() # Directory per build config for persistent test impact data (to be checked in) set(persistent_data_dir "${LY_ROOT_FOLDER}/Tests/test_impact_framework/${CMAKE_SYSTEM_NAME}/$") # Directory for binaries built for this profile set(runtime_bin_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$") # Erase any existing non-persistent data to avoid getting test impact framework out of sync with current repo state file(REMOVE_RECURSE "${LY_TEST_IMPACT_WORKING_DIR}") # Export the soruce to target mapping files ly_test_impact_export_source_target_mappings( "cmake/TestImpactFramework/SourceToTargetMapping.in" ) # Export the enumerated tests ly_test_impact_write_test_enumeration_file( "cmake/TestImpactFramework/EnumeratedTests.in" ) # Write out the configuration file ly_test_impact_write_config_file( "cmake/TestImpactFramework/ConsoleFrontendConfig.in" ${persistent_data_dir} ${runtime_bin_dir} ) # Copy over the graphviz options file for the build dependency graphs message(DEBUG "Test impact framework config file written") file(COPY "cmake/TestImpactFramework/CMakeGraphVizOptions.cmake" DESTINATION ${CMAKE_BINARY_DIR}) # Set the above config file as the default config file to use for the test impact framework console target target_compile_definitions(${LY_TEST_IMPACT_CONSOLE_TARGET} PRIVATE "LY_TEST_IMPACT_DEFAULT_CONFIG_FILE=\"$.$.cfg\"") message(DEBUG "Test impact framework post steps complete") endfunction()