xref: /aosp_15_r20/external/pigweed/pw_unit_test/test.gni (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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