• 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_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