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