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")
19
20declare_args() {
21  # Path to a test runner to automatically run unit tests after they are built.
22  #
23  # If set, the pw_test() template creates an action that invokes the test runner
24  # on each test executable. If unset, the pw_test() template only creates a test
25  # executable target.
26  #
27  # This should only be enabled for targets which support parallelized running of
28  # unit tests, such as desktops with multiple cores.
29  pw_unit_test_AUTOMATIC_RUNNER = ""
30
31  # Additional dependencies required by all unit test targets. (For example, if
32  # using a different test library like Googletest.)
33  pw_unit_test_PUBLIC_DEPS = []
34
35  # Implementation of a main function for "pw_test" unit test binaries.
36  pw_unit_test_MAIN = "$dir_pw_unit_test:simple_printing_main"
37}
38
39# Defines a target if enable_if is true. Otherwise, it defines that target as
40# <target_name>.DISABLED and creates an empty <target_name> group. This can be
41# used to conditionally create targets without having to conditionally add them
42# to groups. This results in simpler BUILD.gn files.
43template("_pw_disableable_target") {
44  assert(defined(invoker.enable_if),
45         "`enable_if` is required for _pw_disableable_target")
46  assert(defined(invoker.target_type),
47         "`target_type` is required for _pw_disableable_target")
48
49  if (invoker.enable_if) {
50    _actual_target_name = target_name
51  } else {
52    _actual_target_name = target_name + ".DISABLED"
53
54    # If the target is disabled, create an empty target in its place. Use an
55    # action with the original target's sources as inputs to ensure that
56    # the source files exist (even if they don't compile).
57    pw_python_action(target_name) {
58      script = "$dir_pw_build/py/pw_build/nop.py"
59      stamp = true
60
61      inputs = []
62      if (defined(invoker.sources)) {
63        inputs += invoker.sources
64      }
65      if (defined(invoker.public)) {
66        inputs += invoker.public
67      }
68    }
69  }
70
71  target(invoker.target_type, _actual_target_name) {
72    forward_variables_from(invoker,
73                           "*",
74                           [
75                             "enable_if",
76                             "target_type",
77                           ])
78
79    # Remove "" from dependencies. This allows disabling targets if a variable
80    # (e.g. a backend) is empty.
81    if (defined(public_deps)) {
82      public_deps += [ "" ]
83      public_deps -= [ "" ]
84    }
85    if (defined(deps)) {
86      deps += [ "" ]
87      deps -= [ "" ]
88    }
89  }
90}
91
92# Creates a library and an executable target for a unit test.
93#
94# <target_name>.lib contains the provided test sources as a library, which can
95# then be linked into a test executable.
96# <target_name> is a standalone executable which contains only the test sources
97# specified in the pw_unit_test_template.
98#
99# If the pw_unit_test_AUTOMATIC_RUNNER variable is set, this template also creates a
100# "${test_name}.run" target which runs the unit test executable after building
101# it.
102#
103# Args:
104#   - enable_if: (optional) Conditionally enables or disables this test. The test
105#         target and *.run target do nothing when the test is disabled. The
106#         disabled test can still be built and run with the
107#         <target_name>.DISABLED and <target_name>.DISABLED.run targets.
108#         Defaults to true (enable_if).
109#   - All of the regular "executable" target args are accepted.
110template("pw_test") {
111  # This is required in order to reference the pw_test template's target name
112  # within the test_metadata of the metadata group below. The group() definition
113  # creates a new scope where the "target_name" variable is set to its target,
114  # shadowing the one in this scope.
115  _test_target_name = target_name
116
117  _test_is_enabled = !defined(invoker.enable_if) || invoker.enable_if
118
119  # Always set the output_dir as pigweed is not compatible with shared
120  # bin directories for tests.
121  _test_output_dir = "${target_out_dir}/test"
122  if (defined(invoker.output_dir)) {
123    _test_output_dir = invoker.output_dir
124  }
125
126  _test_main = pw_unit_test_MAIN
127  if (defined(invoker.test_main)) {
128    _test_main = invoker.test_main
129  }
130
131  # The unit test code as a source_set.
132  _pw_disableable_target("$target_name.lib") {
133    target_type = "pw_source_set"
134    enable_if = _test_is_enabled
135    forward_variables_from(invoker, "*", [ "metadata" ])
136
137    if (!defined(public_deps)) {
138      public_deps = []
139    }
140    public_deps += pw_unit_test_PUBLIC_DEPS + [ dir_pw_unit_test ]
141  }
142
143  _pw_disableable_target(_test_target_name) {
144    target_type = "pw_executable"
145    enable_if = _test_is_enabled
146
147    # Metadata for this test when used as part of a pw_test_group target.
148    metadata = {
149      tests = [
150        {
151          type = "test"
152          test_name = _test_target_name
153          test_directory = rebase_path(_test_output_dir, root_build_dir)
154        },
155      ]
156    }
157
158    deps = [ ":$_test_target_name.lib" ]
159    if (_test_main != "") {
160      deps += [ _test_main ]
161    }
162
163    output_dir = _test_output_dir
164  }
165
166  if (pw_unit_test_AUTOMATIC_RUNNER != "") {
167    # When the automatic runner is set, create an action which runs the unit
168    # test executable using the test runner script.
169    if (_test_is_enabled) {
170      _test_to_run = _test_target_name
171    } else {
172      # Create a run target for the .DISABLED version of the test.
173      _test_to_run = _test_target_name + ".DISABLED"
174
175      # Create a dummy _run target for the regular version of the test.
176      group(_test_target_name + ".run") {
177        deps = [ ":$_test_target_name" ]
178      }
179    }
180
181    pw_python_action(_test_to_run + ".run") {
182      deps = [ ":$_test_target_name" ]
183      inputs = [ pw_unit_test_AUTOMATIC_RUNNER ]
184      script = "$dir_pw_unit_test/py/pw_unit_test/test_runner.py"
185      python_deps = [ "$dir_pw_cli/py" ]
186      args = [
187        "--runner",
188        rebase_path(pw_unit_test_AUTOMATIC_RUNNER),
189        "--test",
190        "<TARGET_FILE(:$_test_to_run)>",
191      ]
192      stamp = true
193    }
194
195    # TODO(frolv): Alias for the deprecated _run target. Remove when projects
196    # are migrated.
197    group(_test_to_run + "_run") {
198      public_deps = [ ":$_test_to_run.run" ]
199    }
200  } else {
201    group(_test_target_name + ".run") {
202    }
203  }
204}
205
206# Defines a related collection of unit tests.
207#
208# pw_test_group targets output a JSON metadata file for the Pigweed test runner.
209#
210# Args:
211#   - tests: List of pw_test targets for each of the tests in the group.
212#   - group_deps: (optional) pw_test_group targets on which this group depends.
213#   - enable_if: (optional) Conditionally enables or disables this test group.
214#         If false, an empty group is created. Defaults to true.
215template("pw_test_group") {
216  _group_target = target_name
217  _group_deps_metadata = []
218  if (defined(invoker.tests)) {
219    _deps = invoker.tests
220  } else {
221    _deps = []
222  }
223
224  _group_is_enabled = !defined(invoker.enable_if) || invoker.enable_if
225
226  if (_group_is_enabled) {
227    if (defined(invoker.group_deps)) {
228      # If the group specified any other group dependencies, create a metadata
229      # entry for each of them indicating that they are another group and a
230      # group target to collect that metadata.
231      foreach(dep, invoker.group_deps) {
232        _group_deps_metadata += [
233          {
234            type = "dep"
235            group = get_label_info(dep, "label_no_toolchain")
236          },
237        ]
238      }
239
240      _deps += invoker.group_deps
241    }
242
243    group(_group_target + ".lib") {
244      deps = []
245      foreach(_target, _deps) {
246        _dep_target = get_label_info(_target, "label_no_toolchain")
247        _dep_toolchain = get_label_info(_target, "toolchain")
248        deps += [ "$_dep_target.lib($_dep_toolchain)" ]
249      }
250    }
251
252    _metadata_group_target = "${target_name}_pw_test_group_metadata"
253    group(_metadata_group_target) {
254      metadata = {
255        group_deps = _group_deps_metadata
256        self = [
257          {
258            type = "self"
259            name = get_label_info(":$_group_target", "label_no_toolchain")
260          },
261        ]
262
263        # Metadata from the group's own unit test targets is forwarded through
264        # the group dependencies group. This entry is listed as a "walk_key" in
265        # the generated file so that only test targets' metadata (not group
266        # targets) appear in the output.
267        if (defined(invoker.tests)) {
268          propagate_metadata_from = invoker.tests
269        }
270      }
271      deps = _deps
272    }
273
274    _test_group_deps = [ ":$_metadata_group_target" ]
275
276    generated_file(_group_target) {
277      outputs = [ "$target_out_dir/$target_name.testinfo.json" ]
278      data_keys = [
279        "group_deps",
280        "self",
281        "tests",
282      ]
283      walk_keys = [ "propagate_metadata_from" ]
284      output_conversion = "json"
285      deps = _test_group_deps
286    }
287
288    # If automatic test running is enabled, create a *.run group that collects
289    # all of the individual *.run targets and groups.
290    if (pw_unit_test_AUTOMATIC_RUNNER != "") {
291      group(_group_target + ".run") {
292        deps = [ ":$_group_target" ]
293        foreach(_target, _deps) {
294          _dep_target = get_label_info(_target, "label_no_toolchain")
295          _dep_toolchain = get_label_info(_target, "toolchain")
296          deps += [ "$_dep_target.run($_dep_toolchain)" ]
297        }
298      }
299
300      # TODO(frolv): Remove this deprecated alias.
301      group(_group_target + "_run") {
302        deps = [ ":$_group_target.run" ]
303      }
304    }
305  } else {  # _group_is_enabled
306    # Create empty groups for the tests to avoid pulling in any dependencies.
307    group(_group_target) {
308    }
309    group(_group_target + ".lib") {
310    }
311
312    if (pw_unit_test_AUTOMATIC_RUNNER != "") {
313      group(_group_target + ".run") {
314      }
315
316      # TODO(frolv): Remove this deprecated alias.
317      group(_group_target + "_run") {
318      }
319    }
320
321    not_needed("*")
322    not_needed(invoker, "*")
323  }
324
325  # All of the tests in this group and its dependencies bundled into a single
326  # test binary.
327  pw_test(_group_target + ".bundle") {
328    deps = [ ":$_group_target.lib" ]
329    enable_if = _group_is_enabled
330  }
331}
332