blob: 868a8052a112fea74030af7f8ec79274a0f791f3 [file] [log] [blame]
# 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")
declare_args() {
# Path to a test runner to automatically run unit tests after they are built.
#
# If set, a ``pw_test`` target's ``<target_name>.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 ``<target_name>.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 = ""
# Additional dependencies required by all unit test targets. (For example, if
# using a different test library like Googletest.)
#
# Type: list of strings (list of dependencies as GN paths)
# Usage: toolchain-controlled only
pw_unit_test_PUBLIC_DEPS = []
# Implementation of a main function for ``pw_test`` unit test binaries.
#
# Type: string (GN path to a source set)
# Usage: toolchain-controlled only
pw_unit_test_MAIN = "$dir_pw_unit_test:simple_printing_main"
# 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 = ""
}
# Defines a target if enable_if is true. Otherwise, it defines that target as
# <target_name>.DISABLED and creates an empty <target_name> 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_disableable_target") {
assert(defined(invoker.enable_if),
"`enable_if` is required for _pw_disableable_target")
assert(defined(invoker.target_type),
"`target_type` is required for _pw_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
}
}
}
target(invoker.target_type, _actual_target_name) {
forward_variables_from(invoker,
"*",
[
"enable_if",
"target_type",
])
# Remove "" from dependencies. This allows disabling targets if a variable
# (e.g. a backend) is empty.
if (defined(public_deps)) {
public_deps += [ "" ]
public_deps -= [ "" ]
}
if (defined(deps)) {
deps += [ "" ]
deps -= [ "" ]
}
}
}
# Creates a library and an executable target for a unit test.
#
# <target_name>.lib contains the provided test sources as a library, which can
# then be linked into a test executable.
# <target_name> 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.
#
# 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
# <target_name>.DISABLED and <target_name>.DISABLED.run targets.
# Defaults to true (enable_if).
# - 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
# 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_disableable_target("$target_name.lib") {
target_type = "pw_source_set"
enable_if = _test_is_enabled
forward_variables_from(invoker, "*", [ "metadata" ])
if (!defined(public_deps)) {
public_deps = []
}
public_deps += pw_unit_test_PUBLIC_DEPS + [ dir_pw_unit_test ]
}
_pw_disableable_target(_test_target_name) {
target_type = "pw_executable"
enable_if = _test_is_enabled
# Metadata for this test when used as part of a pw_test_group target.
metadata = {
tests = [
{
type = "test"
test_name = _test_target_name
test_directory = rebase_path(_test_output_dir, root_build_dir)
},
]
}
deps = [ ":$_test_target_name.lib" ]
if (_test_main != "") {
deps += [ _test_main ]
}
output_dir = _test_output_dir
}
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") {
deps = [ ":$_test_target_name" ]
}
}
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",
"<TARGET_FILE(:$_test_to_run)>",
]
if (pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT != "") {
args += [
"--timeout",
pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT,
]
}
if (pw_unit_test_AUTOMATIC_RUNNER_ARGS != []) {
args += [ "--" ] + pw_unit_test_AUTOMATIC_RUNNER_ARGS
}
stamp = true
}
# TODO(frolv): Alias for the deprecated _run target. Remove when projects
# are migrated.
group(_test_to_run + "_run") {
public_deps = [ ":$_test_to_run.run" ]
}
} else {
group(_test_target_name + ".run") {
}
}
}
# Defines a related collection of unit tests.
#
# pw_test_group targets output a JSON metadata file for the Pigweed test runner.
#
# 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.
template("pw_test_group") {
_group_target = target_name
_group_deps_metadata = []
if (defined(invoker.tests)) {
_deps = invoker.tests
} else {
_deps = []
}
_group_is_enabled = !defined(invoker.enable_if) || invoker.enable_if
if (_group_is_enabled) {
if (defined(invoker.group_deps)) {
# If the group specified any other group dependencies, create a metadata
# entry for each of them indicating that they are another group and a
# group target to collect that metadata.
foreach(dep, invoker.group_deps) {
_group_deps_metadata += [
{
type = "dep"
group = get_label_info(dep, "label_no_toolchain")
},
]
}
_deps += invoker.group_deps
}
group(_group_target + ".lib") {
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)" ]
}
}
_metadata_group_target = "${target_name}_pw_test_group_metadata"
group(_metadata_group_target) {
metadata = {
group_deps = _group_deps_metadata
self = [
{
type = "self"
name = get_label_info(":$_group_target", "label_no_toolchain")
},
]
# Metadata from the group's own unit test targets is forwarded through
# the group dependencies group. This entry is listed as a "walk_key" in
# the generated file so that only test targets' metadata (not group
# targets) appear in the output.
if (defined(invoker.tests)) {
propagate_metadata_from = invoker.tests
}
}
deps = _deps
}
_test_group_deps = [ ":$_metadata_group_target" ]
generated_file(_group_target) {
outputs = [ "$target_out_dir/$target_name.testinfo.json" ]
data_keys = [
"group_deps",
"self",
"tests",
]
walk_keys = [ "propagate_metadata_from" ]
output_conversion = "json"
deps = _test_group_deps
}
# 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") {
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)" ]
}
}
# TODO(frolv): Remove this deprecated alias.
group(_group_target + "_run") {
deps = [ ":$_group_target.run" ]
}
}
} 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") {
}
# TODO(frolv): Remove this deprecated alias.
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
}
}