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