1# Copyright 2019 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14 15import("//build_overrides/pigweed.gni") 16 17import("$dir_pw_build/python_action.gni") 18import("$dir_pw_build/target_types.gni") 19import("$dir_pw_compilation_testing/negative_compilation_test.gni") 20import("$dir_pw_toolchain/host_clang/toolchains.gni") 21 22declare_args() { 23 # The GoogleTest implementation to use for Pigweed unit tests. This library 24 # provides "gtest/gtest.h" and related headers. Defaults to 25 # pw_unit_test:light, which implements a subset of GoogleTest. 26 # 27 # Type: string (GN path to a source set) 28 # Usage: toolchain-controlled only 29 pw_unit_test_GOOGLETEST_BACKEND = "$dir_pw_unit_test:light" 30 31 # Implementation of a main function for ``pw_test`` unit test binaries. Must 32 # be set to an appropriate target for the pw_unit_test backend. 33 # 34 # Type: string (GN path to a source set) 35 # Usage: toolchain-controlled only 36 pw_unit_test_MAIN = "$dir_pw_unit_test:simple_printing_main" 37 38 # Path to a test runner to automatically run unit tests after they are built. 39 # 40 # If set, a ``pw_test`` target's ``<target_name>.run`` action will invoke the 41 # test runner specified by this argument, passing the path to the unit test to 42 # run. If this is unset, the ``pw_test`` target's ``<target_name>.run`` step 43 # will do nothing. 44 # 45 # Targets that don't support parallelized execution of tests (e.g. a on-device 46 # test runner that must flash a device and run the test in serial) should 47 # set pw_unit_test_POOL_DEPTH to 1. 48 # 49 # Type: string (name of an executable on the PATH, or path to an executable) 50 # Usage: toolchain-controlled only 51 pw_unit_test_AUTOMATIC_RUNNER = "" 52 53 # Optional list of arguments to forward to the automatic runner. 54 # 55 # Type: list of strings (args to pass to pw_unit_test_AUTOMATIC_RUNNER) 56 # Usage: toolchain-controlled only 57 pw_unit_test_AUTOMATIC_RUNNER_ARGS = [] 58 59 # Optional timeout to apply when running tests via the automatic runner. 60 # Timeout is in seconds. Defaults to empty which means no timeout. 61 pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT = "" 62 63 # The maximum number of unit tests that may be run concurrently for the 64 # current toolchain. Setting this to 0 disables usage of a pool, allowing 65 # unlimited parallelization. 66 # 67 # Note: A single target with two toolchain configurations (e.g. release/debug) 68 # will use two separate test runner pools by default. Set 69 # pw_unit_test_POOL_TOOLCHAIN to the same toolchain for both targets to 70 # merge the pools and force serialization. 71 # 72 # Type: integer 73 # Usage: toolchain-controlled only 74 pw_unit_test_POOL_DEPTH = 0 75 76 # The toolchain to use when referring to the pw_unit_test runner pool. When 77 # this is disabled, the current toolchain is used. This means that every 78 # toolchain will use its own pool definition. If two toolchains should share 79 # the same pool, this argument should be by one of the toolchains to the GN 80 # path of the other toolchain. 81 # 82 # Type: string (GN path to a toolchain) 83 # Usage: toolchain-controlled only 84 pw_unit_test_POOL_TOOLCHAIN = "" 85 86 # The name of the GN target type used to build pw_unit_test executables. 87 # 88 # Type: string (name of a GN template) 89 # Usage: toolchain-controlled only 90 pw_unit_test_EXECUTABLE_TARGET_TYPE = "pw_executable" 91 92 # The path to the .gni file that defines pw_unit_test_EXECUTABLE_TARGET_TYPE. 93 # 94 # If pw_unit_test_EXECUTABLE_TARGET_TYPE is not the default of 95 # `pw_executable`, this .gni file is imported to provide the template 96 # definition. 97 # 98 # Type: string (path to a .gni file) 99 # Usage: toolchain-controlled only 100 pw_unit_test_EXECUTABLE_TARGET_TYPE_FILE = "" 101} 102 103if (pw_unit_test_EXECUTABLE_TARGET_TYPE != "pw_executable" && 104 pw_unit_test_EXECUTABLE_TARGET_TYPE_FILE != "") { 105 import(pw_unit_test_EXECUTABLE_TARGET_TYPE_FILE) 106} 107 108# Defines a target if enable_if is true. Otherwise, it defines that target as 109# <target_name>.DISABLED and creates an empty <target_name> group. This can be 110# used to conditionally create targets without having to conditionally add them 111# to groups. This results in simpler BUILD.gn files. 112template("pw_internal_disableable_target") { 113 assert(defined(invoker.enable_if), 114 "`enable_if` is required for pw_internal_disableable_target") 115 assert(defined(invoker.target_type), 116 "`target_type` is required for pw_internal_disableable_target") 117 118 if (invoker.enable_if) { 119 _actual_target_name = target_name 120 } else { 121 _actual_target_name = target_name + ".DISABLED" 122 123 # If the target is disabled, create an empty target in its place. Use an 124 # action with the original target's sources as inputs to ensure that 125 # the source files exist (even if they don't compile). 126 pw_python_action(target_name) { 127 script = "$dir_pw_build/py/pw_build/nop.py" 128 stamp = true 129 130 inputs = [] 131 if (defined(invoker.sources)) { 132 inputs += invoker.sources 133 } 134 if (defined(invoker.public)) { 135 inputs += invoker.public 136 } 137 } 138 } 139 140 target(invoker.target_type, _actual_target_name) { 141 forward_variables_from(invoker, 142 "*", 143 [ 144 "negative_compilation_tests", 145 "enable_if", 146 "target_type", 147 "test_automatic_runner_args", 148 ]) 149 150 # Remove "" from dependencies. This allows disabling targets if a variable 151 # (e.g. a backend) is empty. 152 if (defined(public_deps)) { 153 public_deps += [ "" ] 154 public_deps -= [ "" ] 155 } 156 if (defined(deps)) { 157 deps += [ "" ] 158 deps -= [ "" ] 159 } 160 } 161} 162 163# Creates a library and an executable target for a unit test with pw_unit_test. 164# 165# <target_name>.lib contains the provided test sources as a library, which can 166# then be linked into a test executable. 167# <target_name> is a standalone executable which contains only the test sources 168# specified in the pw_unit_test_template. 169# 170# If the pw_unit_test_AUTOMATIC_RUNNER variable is set, this template also 171# creates a "${test_name}.run" target which runs the unit test executable after 172# building it. 173# 174# Args: 175# - enable_if: (optional) Conditionally enables or disables this test. The 176# test target and *.run target do nothing when the test is disabled. The 177# disabled test can still be built and run with the 178# <target_name>.DISABLED and <target_name>.DISABLED.run targets. 179# Defaults to true (enable_if). 180# - extra_metadata: (optional) Extra metadata to include in test group 181# metadata output. This can be used to pass information about this test 182# to later build tasks. 183# - All of the regular "executable" target args are accepted. 184# 185template("pw_test") { 186 # This is required in order to reference the pw_test template's target name 187 # within the test_metadata of the metadata group below. The group() definition 188 # creates a new scope where the "target_name" variable is set to its target, 189 # shadowing the one in this scope. 190 _test_target_name = target_name 191 192 _test_is_enabled = !defined(invoker.enable_if) || invoker.enable_if 193 _is_coverage_run = 194 pw_toolchain_COVERAGE_ENABLED && 195 filter_include(pw_toolchain_SANITIZERS, [ "coverage" ]) == [ "coverage" ] 196 if (_is_coverage_run) { 197 _profraw_path = "$target_out_dir/test/$_test_target_name.profraw" 198 } 199 200 # Always set the output_dir as pigweed is not compatible with shared 201 # bin directories for tests. 202 _test_output_dir = "${target_out_dir}/test" 203 if (defined(invoker.output_dir)) { 204 _test_output_dir = invoker.output_dir 205 } 206 207 _extra_metadata = { 208 } 209 if (defined(invoker.extra_metadata)) { 210 _extra_metadata = invoker.extra_metadata 211 } 212 213 _test_main = pw_unit_test_MAIN 214 if (defined(invoker.test_main)) { 215 _test_main = invoker.test_main 216 } 217 218 # The unit test code as a source_set. 219 pw_internal_disableable_target("$target_name.lib") { 220 target_type = "pw_source_set" 221 enable_if = _test_is_enabled 222 223 # It is possible that the executable target type has been overriden by 224 # pw_unit_test_EXECUTABLE_TARGET_TYPE, which may allow for additional 225 # variables to be specified on the executable template. As such, we cannot 226 # forward all variables ("*") from the invoker to source_set library, as 227 # those additional variables would not be used and GN gen would error. 228 _source_set_relevant_variables = [ 229 # GN source_set variables 230 # https://gn.googlesource.com/gn/+/main/docs/reference.md#target-declarations-source_set_declare-a-source-set-target-variables 231 "asmflags", 232 "cflags", 233 "cflags_c", 234 "cflags_cc", 235 "cflags_objc", 236 "cflags_objcc", 237 "defines", 238 "include_dirs", 239 "inputs", 240 "ldflags", 241 "lib_dirs", 242 "libs", 243 "precompiled_header", 244 "precompiled_source", 245 "rustenv", 246 "rustflags", 247 "swiftflags", 248 "testonly", 249 "assert_no_deps", 250 "data_deps", 251 "deps", 252 "public_deps", 253 "runtime_deps", 254 "write_runtime_deps", 255 "all_dependent_configs", 256 "public_configs", 257 "check_includes", 258 "configs", 259 "data", 260 "friend", 261 "inputs", 262 "metadata", 263 "output_extension", 264 "output_name", 265 "public", 266 "sources", 267 "testonly", 268 "visibility", 269 270 # pw_source_set variables 271 # https://pigweed.dev/pw_build/?highlight=pw_executable#target-types 272 "remove_configs", 273 "remove_public_deps", 274 ] 275 forward_variables_from(invoker, _source_set_relevant_variables) 276 277 if (!defined(deps)) { 278 deps = [] 279 } 280 deps += [ dir_pw_unit_test ] 281 282 if (defined(invoker.negative_compilation_tests) && 283 invoker.negative_compilation_tests) { 284 deps += [ 285 ":$_test_target_name.nc_test", 286 "$dir_pw_compilation_testing:internal_pigweed_use_only", 287 ] 288 } 289 } 290 291 pw_internal_disableable_target(_test_target_name) { 292 target_type = pw_unit_test_EXECUTABLE_TARGET_TYPE 293 enable_if = _test_is_enabled 294 295 # Include configs, deps, etc. from the pw_test in the executable as well as 296 # the library to ensure that linker flags propagate to the executable. 297 forward_variables_from(invoker, 298 "*", 299 [ 300 "extra_metadata", 301 "metadata", 302 "sources", 303 "public", 304 ]) 305 306 # Metadata for this test when used as part of a pw_test_group target. 307 metadata = { 308 tests = [ 309 { 310 type = "test" 311 test_name = _test_target_name 312 test_directory = rebase_path(_test_output_dir, root_build_dir) 313 extra_metadata = _extra_metadata 314 }, 315 ] 316 317 # N.B.: This is placed here instead of in $_test_target_name._run because 318 # pw_test_group only forwards the metadata from _test_target_name and not 319 # _test_target_name._run or _test_target_name.run. 320 if (_is_coverage_run) { 321 profraws = [ 322 { 323 type = "profraw" 324 path = rebase_path(_profraw_path, root_build_dir) 325 }, 326 ] 327 } 328 } 329 330 if (!defined(deps)) { 331 deps = [] 332 } 333 deps += [ ":$_test_target_name.lib" ] 334 if (_test_main != "") { 335 deps += [ _test_main ] 336 } 337 338 output_dir = _test_output_dir 339 } 340 341 if (defined(invoker.negative_compilation_tests) && 342 invoker.negative_compilation_tests) { 343 pw_cc_negative_compilation_test("$target_name.nc_test") { 344 forward_variables_from(invoker, "*") 345 346 # Add a dependency on pw_unit_test since it is implied for pw_unit_test 347 # targets. 348 if (!defined(deps)) { 349 deps = [] 350 } 351 deps += [ dir_pw_unit_test ] 352 } 353 } 354 355 if (pw_unit_test_AUTOMATIC_RUNNER != "") { 356 # When the automatic runner is set, create an action which runs the unit 357 # test executable using the test runner script. 358 if (_test_is_enabled) { 359 _test_to_run = _test_target_name 360 } else { 361 # Create a run target for the .DISABLED version of the test. 362 _test_to_run = _test_target_name + ".DISABLED" 363 364 # Create a placeholder .run target for the regular version of the test. 365 group(_test_target_name + ".run") { 366 deps = [ ":$_test_target_name" ] 367 } 368 } 369 370 _test_automatic_runner_args = pw_unit_test_AUTOMATIC_RUNNER_ARGS 371 if (defined(invoker.test_automatic_runner_args)) { 372 _test_automatic_runner_args = [] 373 _test_automatic_runner_args += invoker.test_automatic_runner_args 374 } 375 376 pw_python_action(_test_to_run + "._run") { 377 # Optionally limit max test runner concurrency. 378 if (pw_unit_test_POOL_DEPTH != 0) { 379 _pool_toolchain = current_toolchain 380 if (pw_unit_test_POOL_TOOLCHAIN != "") { 381 _pool_toolchain = pw_unit_test_POOL_TOOLCHAIN 382 } 383 pool = "$dir_pw_unit_test:unit_test_pool($_pool_toolchain)" 384 } 385 386 deps = [ ":$_test_target_name" ] 387 inputs = [ pw_unit_test_AUTOMATIC_RUNNER ] 388 module = "pw_unit_test.test_runner" 389 python_deps = [ 390 "$dir_pw_cli/py", 391 "$dir_pw_unit_test/py", 392 ] 393 args = [ 394 "--runner", 395 rebase_path(pw_unit_test_AUTOMATIC_RUNNER, root_build_dir), 396 "--test", 397 "<TARGET_FILE(:$_test_to_run)>", 398 ] 399 if (pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT != "") { 400 args += [ 401 "--timeout", 402 pw_unit_test_AUTOMATIC_RUNNER_TIMEOUT, 403 ] 404 } 405 if (_is_coverage_run) { 406 args += [ 407 "--coverage-profraw", 408 rebase_path(_profraw_path, root_build_dir), 409 ] 410 } 411 412 if (_test_automatic_runner_args != []) { 413 args += [ "--" ] + _test_automatic_runner_args 414 } 415 416 outputs = [] 417 if (_is_coverage_run) { 418 outputs += [ _profraw_path ] 419 } 420 stamp = true 421 } 422 423 group(_test_to_run + ".run") { 424 public_deps = [ ":$_test_to_run._run" ] 425 } 426 } else { 427 group(_test_target_name + ".run") { 428 public_deps = [ ":$_test_target_name" ] 429 } 430 } 431} 432 433# Defines a related collection of unit tests. 434# 435# pw_test_group targets output a JSON metadata file for the Pigweed test runner. 436# 437# Args: 438# - tests: List of pw_test targets for each of the tests in the group. 439# - group_deps: (optional) pw_test_group targets on which this group depends. 440# - enable_if: (optional) Conditionally enables or disables this test group. 441# If false, an empty group is created. Defaults to true. 442template("pw_test_group") { 443 _group_target = target_name 444 _group_deps_metadata = [] 445 if (defined(invoker.tests)) { 446 _deps = invoker.tests 447 } else { 448 _deps = [] 449 } 450 451 # Allow empty pw_test_groups with no tests or group_deps. 452 if (!defined(invoker.tests) && !defined(invoker.group_deps)) { 453 not_needed("*") 454 } 455 456 _group_is_enabled = !defined(invoker.enable_if) || invoker.enable_if 457 458 if (_group_is_enabled) { 459 if (defined(invoker.group_deps)) { 460 # If the group specified any other group dependencies, create a metadata 461 # entry for each of them indicating that they are another group and a 462 # group target to collect that metadata. 463 foreach(dep, invoker.group_deps) { 464 _group_deps_metadata += [ 465 { 466 type = "dep" 467 group = get_label_info(dep, "label_no_toolchain") 468 }, 469 ] 470 } 471 472 _deps += invoker.group_deps 473 } 474 475 group(_group_target + ".lib") { 476 deps = [] 477 foreach(_target, _deps) { 478 _dep_target = get_label_info(_target, "label_no_toolchain") 479 _dep_toolchain = get_label_info(_target, "toolchain") 480 deps += [ "$_dep_target.lib($_dep_toolchain)" ] 481 } 482 } 483 484 _metadata_group_target = "${target_name}_pw_test_group_metadata" 485 group(_metadata_group_target) { 486 metadata = { 487 group_deps = _group_deps_metadata 488 self = [ 489 { 490 type = "self" 491 name = get_label_info(":$_group_target", "label_no_toolchain") 492 }, 493 ] 494 495 # Metadata from the group's own unit test targets is forwarded through 496 # the group dependencies group. This entry is listed as a "walk_key" in 497 # the generated file so that only test targets' metadata (not group 498 # targets) appear in the output. 499 if (defined(invoker.tests)) { 500 propagate_metadata_from = invoker.tests 501 } 502 } 503 deps = _deps 504 } 505 506 _test_group_deps = [ ":$_metadata_group_target" ] 507 508 generated_file(_group_target) { 509 outputs = [ "$target_out_dir/$target_name.testinfo.json" ] 510 data_keys = [ 511 "group_deps", 512 "self", 513 "tests", 514 ] 515 walk_keys = [ "propagate_metadata_from" ] 516 output_conversion = "json" 517 deps = _test_group_deps 518 } 519 520 # If automatic test running is enabled, create a *.run group that collects 521 # all of the individual *.run targets and groups. 522 if (pw_unit_test_AUTOMATIC_RUNNER != "") { 523 group(_group_target + ".run") { 524 deps = [ ":$_group_target" ] 525 foreach(_target, _deps) { 526 _dep_target = get_label_info(_target, "label_no_toolchain") 527 _dep_toolchain = get_label_info(_target, "toolchain") 528 deps += [ "$_dep_target.run($_dep_toolchain)" ] 529 } 530 } 531 } 532 } else { # _group_is_enabled 533 # Create empty groups for the tests to avoid pulling in any dependencies. 534 group(_group_target) { 535 } 536 group(_group_target + ".lib") { 537 } 538 539 if (pw_unit_test_AUTOMATIC_RUNNER != "") { 540 group(_group_target + ".run") { 541 } 542 } 543 544 not_needed("*") 545 not_needed(invoker, "*") 546 } 547 548 # All of the tests in this group and its dependencies bundled into a single 549 # test binary. 550 pw_test(_group_target + ".bundle") { 551 deps = [ ":$_group_target.lib" ] 552 enable_if = _group_is_enabled 553 } 554} 555