# Copyright 2019 The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import("//build_overrides/pigweed.gni") import("$dir_pw_build/python_action.gni") import("$dir_pw_build/target_types.gni") import("$dir_pw_build/test_info.gni") import("$dir_pw_compilation_testing/negative_compilation_test.gni") import("$dir_pw_toolchain/generate_toolchain.gni") import("$dir_pw_toolchain/host_clang/toolchains.gni") declare_args() { # The unit test framework implementation. Defaults to # pw_unit_test:light, which implements a subset of GoogleTest safe to run on # device. Set to //pw_unit_test:googletest when using GoogleTest. # # Type: string (GN path to a source set) # Usage: toolchain-controlled only pw_unit_test_BACKEND = "$dir_pw_unit_test:light" # The GoogleTest library target. This is not a pw_unit_test backend (anymore). # Use this to depend on the GoogleTest library directly *WITHOUT* using the # pw_unit_test facade. The //pw_unit_test:googletest backend depends on this # library target. # Defaults to //third_party/googletest. # # Type: string (GN path to a source set) # Usage: toolchain-controlled only pw_unit_test_GOOGLETEST_BACKEND = "$dir_pw_third_party/googletest" # Implementation of a main function for ``pw_test`` unit test binaries. Must # be set to an appropriate target for the pw_unit_test backend. # # Type: string (GN path to a source set) # Usage: toolchain-controlled only pw_unit_test_MAIN = "$dir_pw_unit_test:simple_printing_main" # Path to a test runner to automatically run unit tests after they are built. # # If set, a ``pw_test`` target's ``.run`` action will invoke the # test runner specified by this argument, passing the path to the unit test to # run. If this is unset, the ``pw_test`` target's ``.run`` step # will do nothing. # # Targets that don't support parallelized execution of tests (e.g. a on-device # test runner that must flash a device and run the test in serial) should # set pw_unit_test_POOL_DEPTH to 1. # # Type: string (name of an executable on the PATH, or path to an executable) # Usage: toolchain-controlled only pw_unit_test_AUTOMATIC_RUNNER = "" # Optional list of arguments to forward to the automatic runner. # # Type: list of strings (args to pass to pw_unit_test_AUTOMATIC_RUNNER) # Usage: toolchain-controlled only pw_unit_test_AUTOMATIC_RUNNER_ARGS = [] # Optional timeout to apply when running tests via the automatic runner. # Timeout is in seconds. Defaults to empty which means no timeout. pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT = "" # The maximum number of unit tests that may be run concurrently for the # current toolchain. Setting this to 0 disables usage of a pool, allowing # unlimited parallelization. # # Note: A single target with two toolchain configurations (e.g. release/debug) # will use two separate test runner pools by default. Set # pw_unit_test_POOL_TOOLCHAIN to the same toolchain for both targets to # merge the pools and force serialization. # # Type: integer # Usage: toolchain-controlled only pw_unit_test_POOL_DEPTH = 0 # The toolchain to use when referring to the pw_unit_test runner pool. When # this is disabled, the current toolchain is used. This means that every # toolchain will use its own pool definition. If two toolchains should share # the same pool, this argument should be by one of the toolchains to the GN # path of the other toolchain. # # Type: string (GN path to a toolchain) # Usage: toolchain-controlled only pw_unit_test_POOL_TOOLCHAIN = "" # The name of the GN target type used to build pw_unit_test executables. # # Type: string (name of a GN template) # Usage: toolchain-controlled only pw_unit_test_EXECUTABLE_TARGET_TYPE = "pw_executable" # The path to the .gni file that defines pw_unit_test_EXECUTABLE_TARGET_TYPE. # # If pw_unit_test_EXECUTABLE_TARGET_TYPE is not the default of # `pw_executable`, this .gni file is imported to provide the template # definition. # # Type: string (path to a .gni file) # Usage: toolchain-controlled only pw_unit_test_EXECUTABLE_TARGET_TYPE_FILE = "" # If true, the pw_unit_test target, pw_test targets, and pw_test_group targets # will define `testonly = true`. This is false by default for backwards # compatibility. pw_unit_test_TESTONLY = false } if (pw_unit_test_EXECUTABLE_TARGET_TYPE != "pw_executable" && pw_unit_test_EXECUTABLE_TARGET_TYPE_FILE != "") { import(pw_unit_test_EXECUTABLE_TARGET_TYPE_FILE) } # Defines a target if enable_if is true. Otherwise, it defines that target as # .DISABLED and creates an empty group. This can be # used to conditionally create targets without having to conditionally add them # to groups. This results in simpler BUILD.gn files. template("pw_internal_disableable_target") { assert(defined(invoker.enable_if), "`enable_if` is required for pw_internal_disableable_target") assert(defined(invoker.target_type), "`target_type` is required for pw_internal_disableable_target") if (invoker.enable_if) { _actual_target_name = target_name } else { _actual_target_name = target_name + ".DISABLED" # If the target is disabled, create an empty target in its place. Use an # action with the original target's sources as inputs to ensure that # the source files exist (even if they don't compile). pw_python_action(target_name) { script = "$dir_pw_build/py/pw_build/nop.py" stamp = true inputs = [] if (defined(invoker.sources)) { inputs += invoker.sources } if (defined(invoker.public)) { inputs += invoker.public } if (defined(invoker.source_gen_deps)) { deps = invoker.source_gen_deps } } } target(invoker.target_type, _actual_target_name) { sources = [] public_deps = [] deps = [] forward_variables_from(invoker, "*", [ "enable_if", "negative_compilation_tests", "source_gen_deps", "target_type", "test_automatic_runner_args", ]) # Remove "" from dependencies. This allows disabling targets if a variable # (e.g. a backend) is empty. public_deps += [ "" ] public_deps -= [ "" ] deps += [ "" ] deps -= [ "" ] if (defined(invoker.source_gen_deps)) { deps += invoker.source_gen_deps foreach(source_gen_dep, invoker.source_gen_deps) { sources += get_target_outputs(source_gen_dep) } } } } # Creates a library and an executable target for a unit test with pw_unit_test. # # .lib contains the provided test sources as a library, which can # then be linked into a test executable. # is a standalone executable which contains only the test sources # specified in the pw_unit_test_template. # # If the pw_unit_test_AUTOMATIC_RUNNER variable is set, this template also # creates a "${test_name}.run" target which runs the unit test executable after # building it. # # Targets defined using this template will produce test metadata with a # `test_type` of "unit_test" and an additional `test_directory` value describing # the location of the test binary within the build output. # # Args: # - enable_if: (optional) Conditionally enables or disables this test. The # test target and *.run target do nothing when the test is disabled. The # disabled test can still be built and run with the # .DISABLED and .DISABLED.run targets. # Defaults to true (enable_if). # - envvars: (optional) A list of `var_name=value` strings to set as # environment variables when running the target exectuable. # - tags: (optional) List of strings to include in the test metadata. These # have no effect on the build, but may be used by external tools to # distinguish between tests. For example, a tool may want to skip tests # tagged as "slow". # - extra_metadata: (optional) Extra metadata to include in test group # metadata output. This can be used to pass information about this test # to later build tasks. # - source_gen_deps: (optional) List of targets which generate `sources` for # this test. These `deps` will be included even if the test is disabled. # `get_target_outputs` is used to add the generated `sources` to the # test's `sources` list, so these targets must appear earlier in the # same build file. # - All of the regular "executable" target args are accepted. # template("pw_test") { # This is required in order to reference the pw_test template's target name # within the test_metadata of the metadata group below. The group() definition # creates a new scope where the "target_name" variable is set to its target, # shadowing the one in this scope. _test_target_name = target_name _test_is_enabled = !defined(invoker.enable_if) || invoker.enable_if if (pw_toolchain_COVERAGE_ENABLED) { _profraw_path = "$target_out_dir/test/$_test_target_name.profraw" } # Always set the output_dir as pigweed is not compatible with shared # bin directories for tests. _test_output_dir = "${target_out_dir}/test" if (defined(invoker.output_dir)) { _test_output_dir = invoker.output_dir } _test_main = pw_unit_test_MAIN if (defined(invoker.test_main)) { _test_main = invoker.test_main } # The unit test code as a source_set. pw_internal_disableable_target("$target_name.lib") { target_type = "pw_source_set" enable_if = _test_is_enabled testonly = pw_unit_test_TESTONLY # It is possible that the executable target type has been overriden by # pw_unit_test_EXECUTABLE_TARGET_TYPE, which may allow for additional # variables to be specified on the executable template. As such, we cannot # forward all variables ("*") from the invoker to source_set library, as # those additional variables would not be used and GN gen would error. _source_set_relevant_variables = [ # GN source_set variables # https://gn.googlesource.com/gn/+/main/docs/reference.md#target-declarations-source_set_declare-a-source-set-target-variables "asmflags", "cflags", "cflags_c", "cflags_cc", "cflags_objc", "cflags_objcc", "defines", "include_dirs", "inputs", "ldflags", "lib_dirs", "libs", "precompiled_header", "precompiled_source", "rustenv", "rustflags", "swiftflags", "testonly", "assert_no_deps", "data_deps", "deps", "public_deps", "runtime_deps", "write_runtime_deps", "all_dependent_configs", "public_configs", "check_includes", "configs", "data", "friend", "inputs", "metadata", "output_extension", "output_name", "public", "sources", "source_gen_deps", "visibility", # pw_source_set variables # https://pigweed.dev/pw_build/?highlight=pw_executable#target-types "remove_configs", "remove_public_deps", ] forward_variables_from(invoker, _source_set_relevant_variables) if (!defined(deps)) { deps = [] } deps += [ dir_pw_unit_test ] if (defined(invoker.negative_compilation_tests) && invoker.negative_compilation_tests) { deps += [ ":$_test_target_name.nc_test", "$dir_pw_compilation_testing:internal_pigweed_use_only", ] } } # Metadata for this test when used as part of a pw_test_group target. _test_metadata = "${target_name}.metadata" _extra_metadata = { forward_variables_from(invoker, [ "extra_metadata" ]) test_directory = rebase_path(_test_output_dir, root_build_dir) } pw_test_info(_test_metadata) { test_type = "unit_test" test_name = _test_target_name forward_variables_from(invoker, [ "tags" ]) extra_metadata = _extra_metadata } pw_internal_disableable_target(_test_target_name) { target_type = pw_unit_test_EXECUTABLE_TARGET_TYPE enable_if = _test_is_enabled testonly = pw_unit_test_TESTONLY # Include configs, deps, etc. from the pw_test in the executable as well as # the library to ensure that linker flags propagate to the executable. deps = [] forward_variables_from(invoker, "*", [ "extra_metadata", "metadata", "sources", "source_gen_deps", "public", ]) deps += [ ":$_test_metadata", ":$_test_target_name.lib", ] if (_test_main != "") { deps += [ _test_main ] } output_dir = _test_output_dir metadata = { # N.B.: This is placed here instead of in $_test_target_name._run because # pw_test_group only forwards the metadata from _test_target_name and not # _test_target_name._run or _test_target_name.run. if (pw_toolchain_COVERAGE_ENABLED) { profraws = [ { type = "profraw" path = rebase_path(_profraw_path, root_build_dir) }, ] } # Only collect test metadata for the test itself. test_barrier = [ ":$_test_metadata" ] } } if (defined(invoker.negative_compilation_tests) && invoker.negative_compilation_tests) { pw_cc_negative_compilation_test("$target_name.nc_test") { forward_variables_from(invoker, "*") testonly = pw_unit_test_TESTONLY # Add a dependency on pw_unit_test since it is implied for pw_unit_test # targets. if (!defined(deps)) { deps = [] } deps += [ dir_pw_unit_test ] } } if (pw_unit_test_AUTOMATIC_RUNNER != "") { # When the automatic runner is set, create an action which runs the unit # test executable using the test runner script. if (_test_is_enabled) { _test_to_run = _test_target_name } else { # Create a run target for the .DISABLED version of the test. _test_to_run = _test_target_name + ".DISABLED" # Create a placeholder .run target for the regular version of the test. group(_test_target_name + ".run") { testonly = pw_unit_test_TESTONLY deps = [ ":$_test_target_name" ] } } _test_automatic_runner_args = pw_unit_test_AUTOMATIC_RUNNER_ARGS if (defined(invoker.test_automatic_runner_args)) { _test_automatic_runner_args = [] _test_automatic_runner_args += invoker.test_automatic_runner_args } pw_python_action(_test_to_run + "._run") { # Optionally limit max test runner concurrency. if (pw_unit_test_POOL_DEPTH != 0) { _pool_toolchain = current_toolchain if (pw_unit_test_POOL_TOOLCHAIN != "") { _pool_toolchain = pw_unit_test_POOL_TOOLCHAIN } pool = "$dir_pw_unit_test:unit_test_pool($_pool_toolchain)" } deps = [ ":$_test_target_name" ] inputs = [ pw_unit_test_AUTOMATIC_RUNNER ] module = "pw_unit_test.test_runner" python_deps = [ "$dir_pw_cli/py", "$dir_pw_unit_test/py", ] args = [ "--runner", rebase_path(pw_unit_test_AUTOMATIC_RUNNER, root_build_dir), "--test", "", ] if (defined(invoker.envvars)) { foreach(envvars, envvars) { args += [ "--env", envvar, ] } } if (pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT != "") { args += [ "--timeout", pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT, ] } if (pw_toolchain_COVERAGE_ENABLED) { _llvm_profile_file = rebase_path(_profraw_path, root_build_dir) args += [ "--env", "LLVM_PROFILE_FILE=" + _llvm_profile_file, ] } if (_test_automatic_runner_args != []) { args += [ "--" ] + _test_automatic_runner_args } outputs = [] if (pw_toolchain_COVERAGE_ENABLED) { outputs += [ _profraw_path ] } stamp = true } group(_test_to_run + ".run") { testonly = pw_unit_test_TESTONLY public_deps = [ ":$_test_to_run._run" ] } } else { group(_test_target_name + ".run") { testonly = pw_unit_test_TESTONLY public_deps = [ ":$_test_target_name" ] } } } # Defines a related collection of unit tests. # # Targets defined using this template will produce test metadata with a # `test_type` of "test_group" and an additional `deps` list describing the tests # collected by this target. # # Args: # - tests: List of pw_test targets for each of the tests in the group. # - group_deps: (optional) pw_test_group targets on which this group depends. # - enable_if: (optional) Conditionally enables or disables this test group. # If false, an empty group is created. Defaults to true. # - output_metadata: (optional) If true, generates a JSON file containing the # test metadata for this group and all of its dependencies. Defaults to # false. # template("pw_test_group") { _group_target = target_name if (defined(invoker.tests)) { _deps = invoker.tests } else { _deps = [] } # Allow empty pw_test_groups with no tests or group_deps. if (!defined(invoker.tests) && !defined(invoker.group_deps)) { not_needed("*") } _group_is_enabled = !defined(invoker.enable_if) || invoker.enable_if if (_group_is_enabled) { if (defined(invoker.group_deps)) { _deps += invoker.group_deps } group(_group_target + ".lib") { testonly = pw_unit_test_TESTONLY deps = [] foreach(_target, _deps) { _dep_target = get_label_info(_target, "label_no_toolchain") _dep_toolchain = get_label_info(_target, "toolchain") deps += [ "$_dep_target.lib($_dep_toolchain)" ] } } # Create a manifest entry to indicate which tests are a part of this group. _test_group_metadata = "${target_name}_pw_test_group_metadata" _extra_metadata = { forward_variables_from(invoker, [ "extra_metadata" ]) if (_deps != []) { deps = [] foreach(dep, _deps) { deps += [ get_label_info(dep, "label_no_toolchain") ] } } } pw_test_info(_test_group_metadata) { testonly = pw_unit_test_TESTONLY build_label = _group_target test_type = "test_group" test_name = rebase_path(get_label_info(_group_target, "dir"), "//") extra_metadata = _extra_metadata deps = _deps } if (defined(invoker.output_metadata) && invoker.output_metadata) { generated_file(_group_target) { testonly = pw_unit_test_TESTONLY outputs = [ "$target_out_dir/$target_name.testinfo.json" ] data_keys = [ "test_groups", "unit_tests", "action_tests", "perf_tests", "fuzz_tests", ] walk_keys = [ "test_barrier" ] output_conversion = "json" deps = [ ":$_test_group_metadata" ] } } else { group(_group_target) { testonly = pw_unit_test_TESTONLY deps = [ ":$_test_group_metadata" ] } } # If automatic test running is enabled, create a *.run group that collects # all of the individual *.run targets and groups. if (pw_unit_test_AUTOMATIC_RUNNER != "") { group(_group_target + ".run") { testonly = pw_unit_test_TESTONLY deps = [ ":$_group_target" ] foreach(_target, _deps) { _dep_target = get_label_info(_target, "label_no_toolchain") _dep_toolchain = get_label_info(_target, "toolchain") deps += [ "$_dep_target.run($_dep_toolchain)" ] } } } } else { # _group_is_enabled # Create empty groups for the tests to avoid pulling in any dependencies. group(_group_target) { } group(_group_target + ".lib") { } if (pw_unit_test_AUTOMATIC_RUNNER != "") { group(_group_target + ".run") { } } not_needed("*") not_needed(invoker, "*") } # All of the tests in this group and its dependencies bundled into a single # test binary. pw_test(_group_target + ".bundle") { deps = [ ":$_group_target.lib" ] enable_if = _group_is_enabled } }