• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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