xref: /aosp_15_r20/external/bazel-skylib/lib/unittest.bzl (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
1*bcb5dc79SHONG Yifan# Copyright 2017 The Bazel Authors. All rights reserved.
2*bcb5dc79SHONG Yifan#
3*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License");
4*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License.
5*bcb5dc79SHONG Yifan# You may obtain a copy of the License at
6*bcb5dc79SHONG Yifan#
7*bcb5dc79SHONG Yifan#    http://www.apache.org/licenses/LICENSE-2.0
8*bcb5dc79SHONG Yifan#
9*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software
10*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS,
11*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and
13*bcb5dc79SHONG Yifan# limitations under the License.
14*bcb5dc79SHONG Yifan
15*bcb5dc79SHONG Yifan"""Unit testing support.
16*bcb5dc79SHONG Yifan
17*bcb5dc79SHONG YifanUnlike most Skylib files, this exports four modules:
18*bcb5dc79SHONG Yifan* `unittest` contains functions to declare and define unit tests for ordinary
19*bcb5dc79SHONG Yifan   Starlark functions;
20*bcb5dc79SHONG Yifan* `analysistest` contains functions to declare and define tests for analysis
21*bcb5dc79SHONG Yifan   phase behavior of a rule, such as a given target's providers or registered
22*bcb5dc79SHONG Yifan   actions;
23*bcb5dc79SHONG Yifan* `loadingtest` contains functions to declare and define tests for loading
24*bcb5dc79SHONG Yifan   phase behavior, such as macros and `native.*`;
25*bcb5dc79SHONG Yifan* `asserts` contains the assertions used within tests.
26*bcb5dc79SHONG Yifan
27*bcb5dc79SHONG YifanSee https://bazel.build/extending/concepts for background about macros, rules,
28*bcb5dc79SHONG Yifanand the different phases of a build.
29*bcb5dc79SHONG Yifan"""
30*bcb5dc79SHONG Yifan
31*bcb5dc79SHONG Yifanload(":new_sets.bzl", new_sets = "sets")
32*bcb5dc79SHONG Yifanload(":partial.bzl", "partial")
33*bcb5dc79SHONG Yifanload(":types.bzl", "types")
34*bcb5dc79SHONG Yifan
35*bcb5dc79SHONG Yifan# The following function should only be called from WORKSPACE files and workspace macros.
36*bcb5dc79SHONG Yifan# buildifier: disable=unnamed-macro
37*bcb5dc79SHONG Yifandef register_unittest_toolchains():
38*bcb5dc79SHONG Yifan    """Registers the toolchains for unittest users."""
39*bcb5dc79SHONG Yifan    native.register_toolchains(
40*bcb5dc79SHONG Yifan        "@bazel_skylib//toolchains/unittest:cmd_toolchain",
41*bcb5dc79SHONG Yifan        "@bazel_skylib//toolchains/unittest:bash_toolchain",
42*bcb5dc79SHONG Yifan    )
43*bcb5dc79SHONG Yifan
44*bcb5dc79SHONG YifanTOOLCHAIN_TYPE = "@bazel_skylib//toolchains/unittest:toolchain_type"
45*bcb5dc79SHONG Yifan
46*bcb5dc79SHONG Yifan_UnittestToolchainInfo = provider(
47*bcb5dc79SHONG Yifan    doc = "Execution platform information for rules in the bazel_skylib repository.",
48*bcb5dc79SHONG Yifan    fields = [
49*bcb5dc79SHONG Yifan        "file_ext",
50*bcb5dc79SHONG Yifan        "success_templ",
51*bcb5dc79SHONG Yifan        "failure_templ",
52*bcb5dc79SHONG Yifan        "join_on",
53*bcb5dc79SHONG Yifan        "escape_chars_with",
54*bcb5dc79SHONG Yifan        "escape_other_chars_with",
55*bcb5dc79SHONG Yifan    ],
56*bcb5dc79SHONG Yifan)
57*bcb5dc79SHONG Yifan
58*bcb5dc79SHONG Yifandef _unittest_toolchain_impl(ctx):
59*bcb5dc79SHONG Yifan    return [
60*bcb5dc79SHONG Yifan        platform_common.ToolchainInfo(
61*bcb5dc79SHONG Yifan            unittest_toolchain_info = _UnittestToolchainInfo(
62*bcb5dc79SHONG Yifan                file_ext = ctx.attr.file_ext,
63*bcb5dc79SHONG Yifan                success_templ = ctx.attr.success_templ,
64*bcb5dc79SHONG Yifan                failure_templ = ctx.attr.failure_templ,
65*bcb5dc79SHONG Yifan                join_on = ctx.attr.join_on,
66*bcb5dc79SHONG Yifan                escape_chars_with = ctx.attr.escape_chars_with,
67*bcb5dc79SHONG Yifan                escape_other_chars_with = ctx.attr.escape_other_chars_with,
68*bcb5dc79SHONG Yifan            ),
69*bcb5dc79SHONG Yifan        ),
70*bcb5dc79SHONG Yifan    ]
71*bcb5dc79SHONG Yifan
72*bcb5dc79SHONG Yifanunittest_toolchain = rule(
73*bcb5dc79SHONG Yifan    implementation = _unittest_toolchain_impl,
74*bcb5dc79SHONG Yifan    attrs = {
75*bcb5dc79SHONG Yifan        "failure_templ": attr.string(
76*bcb5dc79SHONG Yifan            mandatory = True,
77*bcb5dc79SHONG Yifan            doc = (
78*bcb5dc79SHONG Yifan                "Test script template with a single `%s`. That " +
79*bcb5dc79SHONG Yifan                "placeholder is replaced with the lines in the " +
80*bcb5dc79SHONG Yifan                "failure message joined with the string " +
81*bcb5dc79SHONG Yifan                "specified in `join_with`. The resulting script " +
82*bcb5dc79SHONG Yifan                "should print the failure message and exit with " +
83*bcb5dc79SHONG Yifan                "non-zero status."
84*bcb5dc79SHONG Yifan            ),
85*bcb5dc79SHONG Yifan        ),
86*bcb5dc79SHONG Yifan        "file_ext": attr.string(
87*bcb5dc79SHONG Yifan            mandatory = True,
88*bcb5dc79SHONG Yifan            doc = (
89*bcb5dc79SHONG Yifan                "File extension for test script, including leading dot."
90*bcb5dc79SHONG Yifan            ),
91*bcb5dc79SHONG Yifan        ),
92*bcb5dc79SHONG Yifan        "join_on": attr.string(
93*bcb5dc79SHONG Yifan            mandatory = True,
94*bcb5dc79SHONG Yifan            doc = (
95*bcb5dc79SHONG Yifan                "String used to join the lines in the failure " +
96*bcb5dc79SHONG Yifan                "message before including the resulting string " +
97*bcb5dc79SHONG Yifan                "in the script specified in `failure_templ`."
98*bcb5dc79SHONG Yifan            ),
99*bcb5dc79SHONG Yifan        ),
100*bcb5dc79SHONG Yifan        "success_templ": attr.string(
101*bcb5dc79SHONG Yifan            mandatory = True,
102*bcb5dc79SHONG Yifan            doc = (
103*bcb5dc79SHONG Yifan                "Test script generated when the test passes. " +
104*bcb5dc79SHONG Yifan                "Should exit with status 0."
105*bcb5dc79SHONG Yifan            ),
106*bcb5dc79SHONG Yifan        ),
107*bcb5dc79SHONG Yifan        "escape_chars_with": attr.string_dict(
108*bcb5dc79SHONG Yifan            doc = (
109*bcb5dc79SHONG Yifan                "Dictionary of characters that need escaping in " +
110*bcb5dc79SHONG Yifan                "test failure message to prefix appended to escape " +
111*bcb5dc79SHONG Yifan                "those characters. For example, " +
112*bcb5dc79SHONG Yifan                '`{"%": "%", ">": "^"}` would replace `%` with ' +
113*bcb5dc79SHONG Yifan                "`%%` and `>` with `^>` in the failure message " +
114*bcb5dc79SHONG Yifan                "before that is included in `success_templ`."
115*bcb5dc79SHONG Yifan            ),
116*bcb5dc79SHONG Yifan        ),
117*bcb5dc79SHONG Yifan        "escape_other_chars_with": attr.string(
118*bcb5dc79SHONG Yifan            default = "",
119*bcb5dc79SHONG Yifan            doc = (
120*bcb5dc79SHONG Yifan                "String to prefix every character in test failure " +
121*bcb5dc79SHONG Yifan                "message which is not a key in `escape_chars_with` " +
122*bcb5dc79SHONG Yifan                "before including that in `success_templ`. For " +
123*bcb5dc79SHONG Yifan                'example, `"\"` would prefix every character in ' +
124*bcb5dc79SHONG Yifan                "the failure message (except those in the keys of " +
125*bcb5dc79SHONG Yifan                "`escape_chars_with`) with `\\`."
126*bcb5dc79SHONG Yifan            ),
127*bcb5dc79SHONG Yifan        ),
128*bcb5dc79SHONG Yifan    },
129*bcb5dc79SHONG Yifan)
130*bcb5dc79SHONG Yifan
131*bcb5dc79SHONG Yifandef _impl_function_name(impl):
132*bcb5dc79SHONG Yifan    """Derives the name of the given rule implementation function.
133*bcb5dc79SHONG Yifan
134*bcb5dc79SHONG Yifan    This can be used for better test feedback.
135*bcb5dc79SHONG Yifan
136*bcb5dc79SHONG Yifan    Args:
137*bcb5dc79SHONG Yifan      impl: the rule implementation function
138*bcb5dc79SHONG Yifan
139*bcb5dc79SHONG Yifan    Returns:
140*bcb5dc79SHONG Yifan      The name of the given function
141*bcb5dc79SHONG Yifan    """
142*bcb5dc79SHONG Yifan
143*bcb5dc79SHONG Yifan    # Starlark currently stringifies a function as "<function NAME>", so we use
144*bcb5dc79SHONG Yifan    # that knowledge to parse the "NAME" portion out. If this behavior ever
145*bcb5dc79SHONG Yifan    # changes, we'll need to update this.
146*bcb5dc79SHONG Yifan    # TODO(bazel-team): Expose a ._name field on functions to avoid this.
147*bcb5dc79SHONG Yifan    impl_name = str(impl)
148*bcb5dc79SHONG Yifan    impl_name = impl_name.partition("<function ")[-1]
149*bcb5dc79SHONG Yifan    return impl_name.rpartition(">")[0]
150*bcb5dc79SHONG Yifan
151*bcb5dc79SHONG Yifandef _make(impl, attrs = {}, doc = "", toolchains = []):
152*bcb5dc79SHONG Yifan    """Creates a unit test rule from its implementation function.
153*bcb5dc79SHONG Yifan
154*bcb5dc79SHONG Yifan    Each unit test is defined in an implementation function that must then be
155*bcb5dc79SHONG Yifan    associated with a rule so that a target can be built. This function handles
156*bcb5dc79SHONG Yifan    the boilerplate to create and return a test rule and captures the
157*bcb5dc79SHONG Yifan    implementation function's name so that it can be printed in test feedback.
158*bcb5dc79SHONG Yifan
159*bcb5dc79SHONG Yifan    The optional `attrs` argument can be used to define dependencies for this
160*bcb5dc79SHONG Yifan    test, in order to form unit tests of rules.
161*bcb5dc79SHONG Yifan
162*bcb5dc79SHONG Yifan    The optional `toolchains` argument can be used to define toolchain
163*bcb5dc79SHONG Yifan    dependencies for this test.
164*bcb5dc79SHONG Yifan
165*bcb5dc79SHONG Yifan    An example of a unit test:
166*bcb5dc79SHONG Yifan
167*bcb5dc79SHONG Yifan    ```
168*bcb5dc79SHONG Yifan    def _your_test(ctx):
169*bcb5dc79SHONG Yifan      env = unittest.begin(ctx)
170*bcb5dc79SHONG Yifan
171*bcb5dc79SHONG Yifan      # Assert statements go here
172*bcb5dc79SHONG Yifan
173*bcb5dc79SHONG Yifan      return unittest.end(env)
174*bcb5dc79SHONG Yifan
175*bcb5dc79SHONG Yifan    your_test = unittest.make(_your_test)
176*bcb5dc79SHONG Yifan    ```
177*bcb5dc79SHONG Yifan
178*bcb5dc79SHONG Yifan    Recall that names of test rules must end in `_test`.
179*bcb5dc79SHONG Yifan
180*bcb5dc79SHONG Yifan    Args:
181*bcb5dc79SHONG Yifan      impl: The implementation function of the unit test.
182*bcb5dc79SHONG Yifan      attrs: An optional dictionary to supplement the attrs passed to the
183*bcb5dc79SHONG Yifan          unit test's `rule()` constructor.
184*bcb5dc79SHONG Yifan      doc: A description of the rule that can be extracted by documentation generating tools.
185*bcb5dc79SHONG Yifan      toolchains: An optional list to supplement the toolchains passed to
186*bcb5dc79SHONG Yifan          the unit test's `rule()` constructor.
187*bcb5dc79SHONG Yifan
188*bcb5dc79SHONG Yifan    Returns:
189*bcb5dc79SHONG Yifan      A rule definition that should be stored in a global whose name ends in
190*bcb5dc79SHONG Yifan      `_test`.
191*bcb5dc79SHONG Yifan    """
192*bcb5dc79SHONG Yifan    attrs = dict(attrs)
193*bcb5dc79SHONG Yifan    attrs["_impl_name"] = attr.string(default = _impl_function_name(impl))
194*bcb5dc79SHONG Yifan
195*bcb5dc79SHONG Yifan    return rule(
196*bcb5dc79SHONG Yifan        impl,
197*bcb5dc79SHONG Yifan        doc = doc,
198*bcb5dc79SHONG Yifan        attrs = attrs,
199*bcb5dc79SHONG Yifan        _skylark_testable = True,
200*bcb5dc79SHONG Yifan        test = True,
201*bcb5dc79SHONG Yifan        toolchains = toolchains + [TOOLCHAIN_TYPE],
202*bcb5dc79SHONG Yifan    )
203*bcb5dc79SHONG Yifan
204*bcb5dc79SHONG Yifan_ActionInfo = provider(
205*bcb5dc79SHONG Yifan    doc = "Information relating to the target under test.",
206*bcb5dc79SHONG Yifan    fields = ["actions", "bin_path"],
207*bcb5dc79SHONG Yifan)
208*bcb5dc79SHONG Yifan
209*bcb5dc79SHONG Yifandef _action_retrieving_aspect_impl(target, ctx):
210*bcb5dc79SHONG Yifan    return [
211*bcb5dc79SHONG Yifan        _ActionInfo(
212*bcb5dc79SHONG Yifan            actions = target.actions,
213*bcb5dc79SHONG Yifan            bin_path = ctx.bin_dir.path,
214*bcb5dc79SHONG Yifan        ),
215*bcb5dc79SHONG Yifan    ]
216*bcb5dc79SHONG Yifan
217*bcb5dc79SHONG Yifan_action_retrieving_aspect = aspect(
218*bcb5dc79SHONG Yifan    attr_aspects = [],
219*bcb5dc79SHONG Yifan    implementation = _action_retrieving_aspect_impl,
220*bcb5dc79SHONG Yifan)
221*bcb5dc79SHONG Yifan
222*bcb5dc79SHONG Yifan# TODO(cparsons): Provide more full documentation on analysis testing in README.
223*bcb5dc79SHONG Yifandef _make_analysis_test(
224*bcb5dc79SHONG Yifan        impl,
225*bcb5dc79SHONG Yifan        expect_failure = False,
226*bcb5dc79SHONG Yifan        attrs = {},
227*bcb5dc79SHONG Yifan        fragments = [],
228*bcb5dc79SHONG Yifan        config_settings = {},
229*bcb5dc79SHONG Yifan        extra_target_under_test_aspects = [],
230*bcb5dc79SHONG Yifan        doc = ""):
231*bcb5dc79SHONG Yifan    """Creates an analysis test rule from its implementation function.
232*bcb5dc79SHONG Yifan
233*bcb5dc79SHONG Yifan    An analysis test verifies the behavior of a "real" rule target by examining
234*bcb5dc79SHONG Yifan    and asserting on the providers given by the real target.
235*bcb5dc79SHONG Yifan
236*bcb5dc79SHONG Yifan    Each analysis test is defined in an implementation function that must then be
237*bcb5dc79SHONG Yifan    associated with a rule so that a target can be built. This function handles
238*bcb5dc79SHONG Yifan    the boilerplate to create and return a test rule and captures the
239*bcb5dc79SHONG Yifan    implementation function's name so that it can be printed in test feedback.
240*bcb5dc79SHONG Yifan
241*bcb5dc79SHONG Yifan    An example of an analysis test:
242*bcb5dc79SHONG Yifan
243*bcb5dc79SHONG Yifan    ```
244*bcb5dc79SHONG Yifan    def _your_test(ctx):
245*bcb5dc79SHONG Yifan      env = analysistest.begin(ctx)
246*bcb5dc79SHONG Yifan
247*bcb5dc79SHONG Yifan      # Assert statements go here
248*bcb5dc79SHONG Yifan
249*bcb5dc79SHONG Yifan      return analysistest.end(env)
250*bcb5dc79SHONG Yifan
251*bcb5dc79SHONG Yifan    your_test = analysistest.make(_your_test)
252*bcb5dc79SHONG Yifan    ```
253*bcb5dc79SHONG Yifan
254*bcb5dc79SHONG Yifan    Recall that names of test rules must end in `_test`.
255*bcb5dc79SHONG Yifan
256*bcb5dc79SHONG Yifan    Args:
257*bcb5dc79SHONG Yifan      impl: The implementation function of the unit test.
258*bcb5dc79SHONG Yifan      expect_failure: If true, the analysis test will expect the target_under_test
259*bcb5dc79SHONG Yifan          to fail. Assertions can be made on the underlying failure using asserts.expect_failure
260*bcb5dc79SHONG Yifan      attrs: An optional dictionary to supplement the attrs passed to the
261*bcb5dc79SHONG Yifan          unit test's `rule()` constructor.
262*bcb5dc79SHONG Yifan      fragments: An optional list of fragment names that can be used to give rules access to
263*bcb5dc79SHONG Yifan          language-specific parts of configuration.
264*bcb5dc79SHONG Yifan      config_settings: A dictionary of configuration settings to change for the target under
265*bcb5dc79SHONG Yifan          test and its dependencies. This may be used to essentially change 'build flags' for
266*bcb5dc79SHONG Yifan          the target under test, and may thus be utilized to test multiple targets with different
267*bcb5dc79SHONG Yifan          flags in a single build
268*bcb5dc79SHONG Yifan      extra_target_under_test_aspects: An optional list of aspects to apply to the target_under_test
269*bcb5dc79SHONG Yifan          in addition to those set up by default for the test harness itself.
270*bcb5dc79SHONG Yifan      doc: A description of the rule that can be extracted by documentation generating tools.
271*bcb5dc79SHONG Yifan
272*bcb5dc79SHONG Yifan    Returns:
273*bcb5dc79SHONG Yifan      A rule definition that should be stored in a global whose name ends in
274*bcb5dc79SHONG Yifan      `_test`.
275*bcb5dc79SHONG Yifan    """
276*bcb5dc79SHONG Yifan    attrs = dict(attrs)
277*bcb5dc79SHONG Yifan    attrs["_impl_name"] = attr.string(default = _impl_function_name(impl))
278*bcb5dc79SHONG Yifan
279*bcb5dc79SHONG Yifan    changed_settings = dict(config_settings)
280*bcb5dc79SHONG Yifan    if expect_failure:
281*bcb5dc79SHONG Yifan        changed_settings["//command_line_option:allow_analysis_failures"] = "True"
282*bcb5dc79SHONG Yifan
283*bcb5dc79SHONG Yifan    target_attr_kwargs = {}
284*bcb5dc79SHONG Yifan    if changed_settings:
285*bcb5dc79SHONG Yifan        test_transition = analysis_test_transition(
286*bcb5dc79SHONG Yifan            settings = changed_settings,
287*bcb5dc79SHONG Yifan        )
288*bcb5dc79SHONG Yifan        target_attr_kwargs["cfg"] = test_transition
289*bcb5dc79SHONG Yifan
290*bcb5dc79SHONG Yifan    attrs["target_under_test"] = attr.label(
291*bcb5dc79SHONG Yifan        aspects = [_action_retrieving_aspect] + extra_target_under_test_aspects,
292*bcb5dc79SHONG Yifan        mandatory = True,
293*bcb5dc79SHONG Yifan        **target_attr_kwargs
294*bcb5dc79SHONG Yifan    )
295*bcb5dc79SHONG Yifan
296*bcb5dc79SHONG Yifan    return rule(
297*bcb5dc79SHONG Yifan        impl,
298*bcb5dc79SHONG Yifan        doc = doc,
299*bcb5dc79SHONG Yifan        attrs = attrs,
300*bcb5dc79SHONG Yifan        fragments = fragments,
301*bcb5dc79SHONG Yifan        test = True,
302*bcb5dc79SHONG Yifan        toolchains = [TOOLCHAIN_TYPE],
303*bcb5dc79SHONG Yifan        analysis_test = True,
304*bcb5dc79SHONG Yifan    )
305*bcb5dc79SHONG Yifan
306*bcb5dc79SHONG Yifandef _suite(name, *test_rules):
307*bcb5dc79SHONG Yifan    """Defines a `test_suite` target that contains multiple tests.
308*bcb5dc79SHONG Yifan
309*bcb5dc79SHONG Yifan    After defining your test rules in a `.bzl` file, you need to create targets
310*bcb5dc79SHONG Yifan    from those rules so that `blaze test` can execute them. Doing this manually
311*bcb5dc79SHONG Yifan    in a BUILD file would consist of listing each test in your `load` statement
312*bcb5dc79SHONG Yifan    and then creating each target one by one. To reduce duplication, we recommend
313*bcb5dc79SHONG Yifan    writing a macro in your `.bzl` file to instantiate all targets, and calling
314*bcb5dc79SHONG Yifan    that macro from your BUILD file so you only have to load one symbol.
315*bcb5dc79SHONG Yifan
316*bcb5dc79SHONG Yifan    You can use this function to create the targets and wrap them in a single
317*bcb5dc79SHONG Yifan    test_suite target. If a test rule requires no arguments, you can simply list
318*bcb5dc79SHONG Yifan    it as an argument. If you wish to supply attributes explicitly, you can do so
319*bcb5dc79SHONG Yifan    using `partial.make()`. For instance, in your `.bzl` file, you could write:
320*bcb5dc79SHONG Yifan
321*bcb5dc79SHONG Yifan    ```
322*bcb5dc79SHONG Yifan    def your_test_suite():
323*bcb5dc79SHONG Yifan      unittest.suite(
324*bcb5dc79SHONG Yifan          "your_test_suite",
325*bcb5dc79SHONG Yifan          your_test,
326*bcb5dc79SHONG Yifan          your_other_test,
327*bcb5dc79SHONG Yifan          partial.make(yet_another_test, timeout = "short"),
328*bcb5dc79SHONG Yifan      )
329*bcb5dc79SHONG Yifan    ```
330*bcb5dc79SHONG Yifan
331*bcb5dc79SHONG Yifan    Then, in your `BUILD` file, simply load the macro and invoke it to have all
332*bcb5dc79SHONG Yifan    of the targets created:
333*bcb5dc79SHONG Yifan
334*bcb5dc79SHONG Yifan    ```
335*bcb5dc79SHONG Yifan    load("//path/to/your/package:tests.bzl", "your_test_suite")
336*bcb5dc79SHONG Yifan    your_test_suite()
337*bcb5dc79SHONG Yifan    ```
338*bcb5dc79SHONG Yifan
339*bcb5dc79SHONG Yifan    If you pass _N_ unit test rules to `unittest.suite`, _N_ + 1 targets will be
340*bcb5dc79SHONG Yifan    created: a `test_suite` target named `${name}` (where `${name}` is the name
341*bcb5dc79SHONG Yifan    argument passed in here) and targets named `${name}_test_${i}`, where `${i}`
342*bcb5dc79SHONG Yifan    is the index of the test in the `test_rules` list, which is used to uniquely
343*bcb5dc79SHONG Yifan    name each target.
344*bcb5dc79SHONG Yifan
345*bcb5dc79SHONG Yifan    Args:
346*bcb5dc79SHONG Yifan      name: The name of the `test_suite` target, and the prefix of all the test
347*bcb5dc79SHONG Yifan          target names.
348*bcb5dc79SHONG Yifan      *test_rules: A list of test rules defines by `unittest.test`.
349*bcb5dc79SHONG Yifan    """
350*bcb5dc79SHONG Yifan    test_names = []
351*bcb5dc79SHONG Yifan    for index, test_rule in enumerate(test_rules):
352*bcb5dc79SHONG Yifan        test_name = "%s_test_%d" % (name, index)
353*bcb5dc79SHONG Yifan        if partial.is_instance(test_rule):
354*bcb5dc79SHONG Yifan            partial.call(test_rule, name = test_name)
355*bcb5dc79SHONG Yifan        else:
356*bcb5dc79SHONG Yifan            test_rule(name = test_name)
357*bcb5dc79SHONG Yifan        test_names.append(test_name)
358*bcb5dc79SHONG Yifan
359*bcb5dc79SHONG Yifan    native.test_suite(
360*bcb5dc79SHONG Yifan        name = name,
361*bcb5dc79SHONG Yifan        tests = [":%s" % t for t in test_names],
362*bcb5dc79SHONG Yifan    )
363*bcb5dc79SHONG Yifan
364*bcb5dc79SHONG Yifandef _begin(ctx):
365*bcb5dc79SHONG Yifan    """Begins a unit test.
366*bcb5dc79SHONG Yifan
367*bcb5dc79SHONG Yifan    This should be the first function called in a unit test implementation
368*bcb5dc79SHONG Yifan    function. It initializes a "test environment" that is used to collect
369*bcb5dc79SHONG Yifan    assertion failures so that they can be reported and logged at the end of the
370*bcb5dc79SHONG Yifan    test.
371*bcb5dc79SHONG Yifan
372*bcb5dc79SHONG Yifan    Args:
373*bcb5dc79SHONG Yifan      ctx: The Starlark context. Pass the implementation function's `ctx` argument
374*bcb5dc79SHONG Yifan          in verbatim.
375*bcb5dc79SHONG Yifan
376*bcb5dc79SHONG Yifan    Returns:
377*bcb5dc79SHONG Yifan      A test environment struct that must be passed to assertions and finally to
378*bcb5dc79SHONG Yifan      `unittest.end`. Do not rely on internal details about the fields in this
379*bcb5dc79SHONG Yifan      struct as it may change.
380*bcb5dc79SHONG Yifan    """
381*bcb5dc79SHONG Yifan    return struct(ctx = ctx, failures = [])
382*bcb5dc79SHONG Yifan
383*bcb5dc79SHONG Yifandef _begin_analysis_test(ctx):
384*bcb5dc79SHONG Yifan    """Begins an analysis test.
385*bcb5dc79SHONG Yifan
386*bcb5dc79SHONG Yifan    This should be the first function called in an analysis test implementation
387*bcb5dc79SHONG Yifan    function. It initializes a "test environment" that is used to collect
388*bcb5dc79SHONG Yifan    assertion failures so that they can be reported and logged at the end of the
389*bcb5dc79SHONG Yifan    test.
390*bcb5dc79SHONG Yifan
391*bcb5dc79SHONG Yifan    Args:
392*bcb5dc79SHONG Yifan      ctx: The Starlark context. Pass the implementation function's `ctx` argument
393*bcb5dc79SHONG Yifan          in verbatim.
394*bcb5dc79SHONG Yifan
395*bcb5dc79SHONG Yifan    Returns:
396*bcb5dc79SHONG Yifan      A test environment struct that must be passed to assertions and finally to
397*bcb5dc79SHONG Yifan      `analysistest.end`. Do not rely on internal details about the fields in this
398*bcb5dc79SHONG Yifan      struct as it may change.
399*bcb5dc79SHONG Yifan    """
400*bcb5dc79SHONG Yifan    return struct(ctx = ctx, failures = [])
401*bcb5dc79SHONG Yifan
402*bcb5dc79SHONG Yifandef _end_analysis_test(env):
403*bcb5dc79SHONG Yifan    """Ends an analysis test and logs the results.
404*bcb5dc79SHONG Yifan
405*bcb5dc79SHONG Yifan    This must be called and returned at the end of an analysis test implementation function so
406*bcb5dc79SHONG Yifan    that the results are reported.
407*bcb5dc79SHONG Yifan
408*bcb5dc79SHONG Yifan    Args:
409*bcb5dc79SHONG Yifan      env: The test environment returned by `analysistest.begin`.
410*bcb5dc79SHONG Yifan
411*bcb5dc79SHONG Yifan    Returns:
412*bcb5dc79SHONG Yifan      A list of providers needed to automatically register the analysis test result.
413*bcb5dc79SHONG Yifan    """
414*bcb5dc79SHONG Yifan    return [AnalysisTestResultInfo(
415*bcb5dc79SHONG Yifan        success = (len(env.failures) == 0),
416*bcb5dc79SHONG Yifan        message = "\n".join(env.failures),
417*bcb5dc79SHONG Yifan    )]
418*bcb5dc79SHONG Yifan
419*bcb5dc79SHONG Yifandef _end(env):
420*bcb5dc79SHONG Yifan    """Ends a unit test and logs the results.
421*bcb5dc79SHONG Yifan
422*bcb5dc79SHONG Yifan    This must be called and returned at the end of a unit test implementation function so
423*bcb5dc79SHONG Yifan    that the results are reported.
424*bcb5dc79SHONG Yifan
425*bcb5dc79SHONG Yifan    Args:
426*bcb5dc79SHONG Yifan      env: The test environment returned by `unittest.begin`.
427*bcb5dc79SHONG Yifan
428*bcb5dc79SHONG Yifan    Returns:
429*bcb5dc79SHONG Yifan      A list of providers needed to automatically register the test result.
430*bcb5dc79SHONG Yifan    """
431*bcb5dc79SHONG Yifan
432*bcb5dc79SHONG Yifan    tc = env.ctx.toolchains[TOOLCHAIN_TYPE].unittest_toolchain_info
433*bcb5dc79SHONG Yifan    testbin = env.ctx.actions.declare_file(env.ctx.label.name + tc.file_ext)
434*bcb5dc79SHONG Yifan    if env.failures:
435*bcb5dc79SHONG Yifan        failure_message_lines = "\n".join(env.failures).split("\n")
436*bcb5dc79SHONG Yifan        escaped_failure_message_lines = [
437*bcb5dc79SHONG Yifan            "".join([
438*bcb5dc79SHONG Yifan                tc.escape_chars_with.get(c, tc.escape_other_chars_with) + c
439*bcb5dc79SHONG Yifan                for c in line.elems()
440*bcb5dc79SHONG Yifan            ])
441*bcb5dc79SHONG Yifan            for line in failure_message_lines
442*bcb5dc79SHONG Yifan        ]
443*bcb5dc79SHONG Yifan        cmd = tc.failure_templ % tc.join_on.join(escaped_failure_message_lines)
444*bcb5dc79SHONG Yifan    else:
445*bcb5dc79SHONG Yifan        cmd = tc.success_templ
446*bcb5dc79SHONG Yifan
447*bcb5dc79SHONG Yifan    env.ctx.actions.write(
448*bcb5dc79SHONG Yifan        output = testbin,
449*bcb5dc79SHONG Yifan        content = cmd,
450*bcb5dc79SHONG Yifan        is_executable = True,
451*bcb5dc79SHONG Yifan    )
452*bcb5dc79SHONG Yifan    return [DefaultInfo(executable = testbin)]
453*bcb5dc79SHONG Yifan
454*bcb5dc79SHONG Yifandef _fail(env, msg):
455*bcb5dc79SHONG Yifan    """Unconditionally causes the current test to fail.
456*bcb5dc79SHONG Yifan
457*bcb5dc79SHONG Yifan    Args:
458*bcb5dc79SHONG Yifan      env: The test environment returned by `unittest.begin`.
459*bcb5dc79SHONG Yifan      msg: The message to log describing the failure.
460*bcb5dc79SHONG Yifan    """
461*bcb5dc79SHONG Yifan    full_msg = "In test %s: %s" % (env.ctx.attr._impl_name, msg)
462*bcb5dc79SHONG Yifan
463*bcb5dc79SHONG Yifan    # There isn't a better way to output the message in Starlark, so use print.
464*bcb5dc79SHONG Yifan    # buildifier: disable=print
465*bcb5dc79SHONG Yifan    print(full_msg)
466*bcb5dc79SHONG Yifan    env.failures.append(full_msg)
467*bcb5dc79SHONG Yifan
468*bcb5dc79SHONG Yifandef _assert_true(
469*bcb5dc79SHONG Yifan        env,
470*bcb5dc79SHONG Yifan        condition,
471*bcb5dc79SHONG Yifan        msg = "Expected condition to be true, but was false."):
472*bcb5dc79SHONG Yifan    """Asserts that the given `condition` is true.
473*bcb5dc79SHONG Yifan
474*bcb5dc79SHONG Yifan    Args:
475*bcb5dc79SHONG Yifan      env: The test environment returned by `unittest.begin`.
476*bcb5dc79SHONG Yifan      condition: A value that will be evaluated in a Boolean context.
477*bcb5dc79SHONG Yifan      msg: An optional message that will be printed that describes the failure.
478*bcb5dc79SHONG Yifan          If omitted, a default will be used.
479*bcb5dc79SHONG Yifan    """
480*bcb5dc79SHONG Yifan    if not condition:
481*bcb5dc79SHONG Yifan        _fail(env, msg)
482*bcb5dc79SHONG Yifan
483*bcb5dc79SHONG Yifandef _assert_false(
484*bcb5dc79SHONG Yifan        env,
485*bcb5dc79SHONG Yifan        condition,
486*bcb5dc79SHONG Yifan        msg = "Expected condition to be false, but was true."):
487*bcb5dc79SHONG Yifan    """Asserts that the given `condition` is false.
488*bcb5dc79SHONG Yifan
489*bcb5dc79SHONG Yifan    Args:
490*bcb5dc79SHONG Yifan      env: The test environment returned by `unittest.begin`.
491*bcb5dc79SHONG Yifan      condition: A value that will be evaluated in a Boolean context.
492*bcb5dc79SHONG Yifan      msg: An optional message that will be printed that describes the failure.
493*bcb5dc79SHONG Yifan          If omitted, a default will be used.
494*bcb5dc79SHONG Yifan    """
495*bcb5dc79SHONG Yifan    if condition:
496*bcb5dc79SHONG Yifan        _fail(env, msg)
497*bcb5dc79SHONG Yifan
498*bcb5dc79SHONG Yifandef _assert_equals(env, expected, actual, msg = None):
499*bcb5dc79SHONG Yifan    """Asserts that the given `expected` and `actual` values are equal.
500*bcb5dc79SHONG Yifan
501*bcb5dc79SHONG Yifan    Args:
502*bcb5dc79SHONG Yifan      env: The test environment returned by `unittest.begin`.
503*bcb5dc79SHONG Yifan      expected: The expected value of some computation.
504*bcb5dc79SHONG Yifan      actual: The actual value returned by some computation.
505*bcb5dc79SHONG Yifan      msg: An optional message that will be printed that describes the failure.
506*bcb5dc79SHONG Yifan          If omitted, a default will be used.
507*bcb5dc79SHONG Yifan    """
508*bcb5dc79SHONG Yifan    if expected != actual:
509*bcb5dc79SHONG Yifan        expectation_msg = 'Expected "%s", but got "%s"' % (expected, actual)
510*bcb5dc79SHONG Yifan        if msg:
511*bcb5dc79SHONG Yifan            full_msg = "%s (%s)" % (msg, expectation_msg)
512*bcb5dc79SHONG Yifan        else:
513*bcb5dc79SHONG Yifan            full_msg = expectation_msg
514*bcb5dc79SHONG Yifan        _fail(env, full_msg)
515*bcb5dc79SHONG Yifan
516*bcb5dc79SHONG Yifandef _assert_set_equals(env, expected, actual, msg = None):
517*bcb5dc79SHONG Yifan    """Asserts that the given `expected` and `actual` sets are equal.
518*bcb5dc79SHONG Yifan
519*bcb5dc79SHONG Yifan    Args:
520*bcb5dc79SHONG Yifan      env: The test environment returned by `unittest.begin`.
521*bcb5dc79SHONG Yifan      expected: The expected set resulting from some computation.
522*bcb5dc79SHONG Yifan      actual: The actual set returned by some computation.
523*bcb5dc79SHONG Yifan      msg: An optional message that will be printed that describes the failure.
524*bcb5dc79SHONG Yifan          If omitted, a default will be used.
525*bcb5dc79SHONG Yifan    """
526*bcb5dc79SHONG Yifan    if not new_sets.is_equal(expected, actual):
527*bcb5dc79SHONG Yifan        missing = new_sets.difference(expected, actual)
528*bcb5dc79SHONG Yifan        unexpected = new_sets.difference(actual, expected)
529*bcb5dc79SHONG Yifan        expectation_msg = "Expected %s, but got %s" % (new_sets.str(expected), new_sets.str(actual))
530*bcb5dc79SHONG Yifan        if new_sets.length(missing) > 0:
531*bcb5dc79SHONG Yifan            expectation_msg += ", missing are %s" % (new_sets.str(missing))
532*bcb5dc79SHONG Yifan        if new_sets.length(unexpected) > 0:
533*bcb5dc79SHONG Yifan            expectation_msg += ", unexpected are %s" % (new_sets.str(unexpected))
534*bcb5dc79SHONG Yifan        if msg:
535*bcb5dc79SHONG Yifan            full_msg = "%s (%s)" % (msg, expectation_msg)
536*bcb5dc79SHONG Yifan        else:
537*bcb5dc79SHONG Yifan            full_msg = expectation_msg
538*bcb5dc79SHONG Yifan        _fail(env, full_msg)
539*bcb5dc79SHONG Yifan
540*bcb5dc79SHONG Yifan_assert_new_set_equals = _assert_set_equals
541*bcb5dc79SHONG Yifan
542*bcb5dc79SHONG Yifandef _expect_failure(env, expected_failure_msg = ""):
543*bcb5dc79SHONG Yifan    """Asserts that the target under test has failed with a given error message.
544*bcb5dc79SHONG Yifan
545*bcb5dc79SHONG Yifan    This requires that the analysis test is created with `analysistest.make()` and
546*bcb5dc79SHONG Yifan    `expect_failures = True` is specified.
547*bcb5dc79SHONG Yifan
548*bcb5dc79SHONG Yifan    Args:
549*bcb5dc79SHONG Yifan      env: The test environment returned by `analysistest.begin`.
550*bcb5dc79SHONG Yifan      expected_failure_msg: The error message to expect as a result of analysis failures.
551*bcb5dc79SHONG Yifan    """
552*bcb5dc79SHONG Yifan    dep = _target_under_test(env)
553*bcb5dc79SHONG Yifan    if AnalysisFailureInfo in dep:
554*bcb5dc79SHONG Yifan        actual_errors = ""
555*bcb5dc79SHONG Yifan        for cause in dep[AnalysisFailureInfo].causes.to_list():
556*bcb5dc79SHONG Yifan            actual_errors += cause.message + "\n"
557*bcb5dc79SHONG Yifan        if actual_errors.find(expected_failure_msg) < 0:
558*bcb5dc79SHONG Yifan            expectation_msg = "Expected errors to contain '%s' but did not. " % expected_failure_msg
559*bcb5dc79SHONG Yifan            expectation_msg += "Actual errors:%s" % actual_errors
560*bcb5dc79SHONG Yifan            _fail(env, expectation_msg)
561*bcb5dc79SHONG Yifan    else:
562*bcb5dc79SHONG Yifan        _fail(env, "Expected failure of target_under_test, but found success")
563*bcb5dc79SHONG Yifan
564*bcb5dc79SHONG Yifandef _target_actions(env):
565*bcb5dc79SHONG Yifan    """Returns a list of actions registered by the target under test.
566*bcb5dc79SHONG Yifan
567*bcb5dc79SHONG Yifan    Args:
568*bcb5dc79SHONG Yifan      env: The test environment returned by `analysistest.begin`.
569*bcb5dc79SHONG Yifan
570*bcb5dc79SHONG Yifan    Returns:
571*bcb5dc79SHONG Yifan      A list of actions registered by the target under test
572*bcb5dc79SHONG Yifan    """
573*bcb5dc79SHONG Yifan
574*bcb5dc79SHONG Yifan    # Validate?
575*bcb5dc79SHONG Yifan    return _target_under_test(env)[_ActionInfo].actions
576*bcb5dc79SHONG Yifan
577*bcb5dc79SHONG Yifandef _target_bin_dir_path(env):
578*bcb5dc79SHONG Yifan    """Returns ctx.bin_dir.path for the target under test.
579*bcb5dc79SHONG Yifan
580*bcb5dc79SHONG Yifan    Args:
581*bcb5dc79SHONG Yifan      env: The test environment returned by `analysistest.begin`.
582*bcb5dc79SHONG Yifan
583*bcb5dc79SHONG Yifan    Returns:
584*bcb5dc79SHONG Yifan      Output bin dir path string.
585*bcb5dc79SHONG Yifan    """
586*bcb5dc79SHONG Yifan    return _target_under_test(env)[_ActionInfo].bin_path
587*bcb5dc79SHONG Yifan
588*bcb5dc79SHONG Yifandef _target_under_test(env):
589*bcb5dc79SHONG Yifan    """Returns the target under test.
590*bcb5dc79SHONG Yifan
591*bcb5dc79SHONG Yifan    Args:
592*bcb5dc79SHONG Yifan      env: The test environment returned by `analysistest.begin`.
593*bcb5dc79SHONG Yifan
594*bcb5dc79SHONG Yifan    Returns:
595*bcb5dc79SHONG Yifan      The target under test.
596*bcb5dc79SHONG Yifan    """
597*bcb5dc79SHONG Yifan    result = getattr(env.ctx.attr, "target_under_test")
598*bcb5dc79SHONG Yifan    if types.is_list(result):
599*bcb5dc79SHONG Yifan        if result:
600*bcb5dc79SHONG Yifan            return result[0]
601*bcb5dc79SHONG Yifan        else:
602*bcb5dc79SHONG Yifan            fail("test rule does not have a target_under_test")
603*bcb5dc79SHONG Yifan    return result
604*bcb5dc79SHONG Yifan
605*bcb5dc79SHONG Yifandef _loading_test_impl(ctx):
606*bcb5dc79SHONG Yifan    tc = ctx.toolchains[TOOLCHAIN_TYPE].unittest_toolchain_info
607*bcb5dc79SHONG Yifan    content = tc.success_templ
608*bcb5dc79SHONG Yifan    if ctx.attr.failure_message:
609*bcb5dc79SHONG Yifan        content = tc.failure_templ % ctx.attr.failure_message
610*bcb5dc79SHONG Yifan
611*bcb5dc79SHONG Yifan    testbin = ctx.actions.declare_file("loading_test_" + ctx.label.name + tc.file_ext)
612*bcb5dc79SHONG Yifan    ctx.actions.write(
613*bcb5dc79SHONG Yifan        output = testbin,
614*bcb5dc79SHONG Yifan        content = content,
615*bcb5dc79SHONG Yifan        is_executable = True,
616*bcb5dc79SHONG Yifan    )
617*bcb5dc79SHONG Yifan    return [DefaultInfo(executable = testbin)]
618*bcb5dc79SHONG Yifan
619*bcb5dc79SHONG Yifan_loading_test = rule(
620*bcb5dc79SHONG Yifan    implementation = _loading_test_impl,
621*bcb5dc79SHONG Yifan    attrs = {
622*bcb5dc79SHONG Yifan        "failure_message": attr.string(),
623*bcb5dc79SHONG Yifan    },
624*bcb5dc79SHONG Yifan    toolchains = [TOOLCHAIN_TYPE],
625*bcb5dc79SHONG Yifan    test = True,
626*bcb5dc79SHONG Yifan)
627*bcb5dc79SHONG Yifan
628*bcb5dc79SHONG Yifandef _loading_make(name):
629*bcb5dc79SHONG Yifan    """Creates a loading phase test environment and test_suite.
630*bcb5dc79SHONG Yifan
631*bcb5dc79SHONG Yifan    Args:
632*bcb5dc79SHONG Yifan       name: name of the suite of tests to create
633*bcb5dc79SHONG Yifan
634*bcb5dc79SHONG Yifan    Returns:
635*bcb5dc79SHONG Yifan       loading phase environment passed to other loadingtest functions
636*bcb5dc79SHONG Yifan    """
637*bcb5dc79SHONG Yifan    native.test_suite(
638*bcb5dc79SHONG Yifan        name = name + "_tests",
639*bcb5dc79SHONG Yifan        tags = [name + "_test_case"],
640*bcb5dc79SHONG Yifan    )
641*bcb5dc79SHONG Yifan    return struct(name = name)
642*bcb5dc79SHONG Yifan
643*bcb5dc79SHONG Yifandef _loading_assert_equals(env, test_case, expected, actual):
644*bcb5dc79SHONG Yifan    """Creates a test case for asserting state at LOADING phase.
645*bcb5dc79SHONG Yifan
646*bcb5dc79SHONG Yifan    Args:
647*bcb5dc79SHONG Yifan      env:       Loading test env created from loadingtest.make
648*bcb5dc79SHONG Yifan      test_case: Name of the test case
649*bcb5dc79SHONG Yifan      expected:  Expected value to test
650*bcb5dc79SHONG Yifan      actual:    Actual value received.
651*bcb5dc79SHONG Yifan
652*bcb5dc79SHONG Yifan    Returns:
653*bcb5dc79SHONG Yifan      None, creates test case
654*bcb5dc79SHONG Yifan    """
655*bcb5dc79SHONG Yifan
656*bcb5dc79SHONG Yifan    msg = None
657*bcb5dc79SHONG Yifan    if expected != actual:
658*bcb5dc79SHONG Yifan        msg = 'Expected "%s", but got "%s"' % (expected, actual)
659*bcb5dc79SHONG Yifan
660*bcb5dc79SHONG Yifan    _loading_test(
661*bcb5dc79SHONG Yifan        name = "%s_%s" % (env.name, test_case),
662*bcb5dc79SHONG Yifan        failure_message = msg,
663*bcb5dc79SHONG Yifan        tags = [env.name + "_test_case"],
664*bcb5dc79SHONG Yifan    )
665*bcb5dc79SHONG Yifan
666*bcb5dc79SHONG Yifanasserts = struct(
667*bcb5dc79SHONG Yifan    expect_failure = _expect_failure,
668*bcb5dc79SHONG Yifan    equals = _assert_equals,
669*bcb5dc79SHONG Yifan    false = _assert_false,
670*bcb5dc79SHONG Yifan    set_equals = _assert_set_equals,
671*bcb5dc79SHONG Yifan    new_set_equals = _assert_new_set_equals,
672*bcb5dc79SHONG Yifan    true = _assert_true,
673*bcb5dc79SHONG Yifan)
674*bcb5dc79SHONG Yifan
675*bcb5dc79SHONG Yifanunittest = struct(
676*bcb5dc79SHONG Yifan    make = _make,
677*bcb5dc79SHONG Yifan    suite = _suite,
678*bcb5dc79SHONG Yifan    begin = _begin,
679*bcb5dc79SHONG Yifan    end = _end,
680*bcb5dc79SHONG Yifan    fail = _fail,
681*bcb5dc79SHONG Yifan)
682*bcb5dc79SHONG Yifan
683*bcb5dc79SHONG Yifananalysistest = struct(
684*bcb5dc79SHONG Yifan    make = _make_analysis_test,
685*bcb5dc79SHONG Yifan    begin = _begin_analysis_test,
686*bcb5dc79SHONG Yifan    end = _end_analysis_test,
687*bcb5dc79SHONG Yifan    fail = _fail,
688*bcb5dc79SHONG Yifan    target_actions = _target_actions,
689*bcb5dc79SHONG Yifan    target_bin_dir_path = _target_bin_dir_path,
690*bcb5dc79SHONG Yifan    target_under_test = _target_under_test,
691*bcb5dc79SHONG Yifan)
692*bcb5dc79SHONG Yifan
693*bcb5dc79SHONG Yifanloadingtest = struct(
694*bcb5dc79SHONG Yifan    make = _loading_make,
695*bcb5dc79SHONG Yifan    equals = _loading_assert_equals,
696*bcb5dc79SHONG Yifan)
697