xref: /aosp_15_r20/external/bazel-skylib/rules/build_test.bzl (revision bcb5dc7965af6ee42bf2f21341a2ec00233a8c8a)
1# Copyright 2019 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://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,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""A test verifying other targets build as part of a `bazel test`"""
16
17load("//lib:new_sets.bzl", "sets")
18
19def _empty_test_impl(ctx):
20    extension = ".bat" if ctx.attr.is_windows else ".sh"
21    content = "exit 0" if ctx.attr.is_windows else "#!/usr/bin/env bash\nexit 0"
22    executable = ctx.actions.declare_file(ctx.label.name + extension)
23    ctx.actions.write(
24        output = executable,
25        is_executable = True,
26        content = content,
27    )
28
29    return [DefaultInfo(
30        files = depset([executable]),
31        executable = executable,
32        runfiles = ctx.runfiles(files = ctx.files.data),
33    )]
34
35_empty_test = rule(
36    implementation = _empty_test_impl,
37    attrs = {
38        "data": attr.label_list(allow_files = True),
39        "is_windows": attr.bool(mandatory = True),
40    },
41    test = True,
42)
43
44_GENRULE_ATTRS = [
45    "compatible_with",
46    "exec_compatible_with",
47    "restricted_to",
48    "tags",
49    "target_compatible_with",
50]
51
52def build_test(name, targets, **kwargs):
53    """Test rule checking that other targets build.
54
55    This works not by an instance of this test failing, but instead by
56    the targets it depends on failing to build, and hence failing
57    the attempt to run this test.
58
59    Typical usage:
60
61    ```
62      load("@bazel_skylib//rules:build_test.bzl", "build_test")
63      build_test(
64          name = "my_build_test",
65          targets = [
66              "//some/package:rule",
67          ],
68      )
69    ```
70
71    Args:
72      name: The name of the test rule.
73      targets: A list of targets to ensure build.
74      **kwargs: The [common attributes for tests](https://bazel.build/reference/be/common-definitions#common-attributes-tests).
75    """
76    if len(targets) == 0:
77        fail("targets must be non-empty", "targets")
78    if kwargs.get("data", None):
79        fail("data is not supported on a build_test()", "data")
80
81    # Remove any duplicate test targets.
82    targets = sets.to_list(sets.make(targets))
83
84    # Use a genrule to ensure the targets are built (works because it forces
85    # the outputs of the other rules on as data for the genrule)
86
87    # Split into batches to hopefully avoid things becoming so large they are
88    # too much for a remote execution set up.
89    batch_size = max(1, len(targets) // 100)
90
91    # Pull a few args over from the test to the genrule.
92    genrule_args = {k: kwargs.get(k) for k in _GENRULE_ATTRS if k in kwargs}
93
94    # Only the test target should be used to determine whether or not the deps
95    # are built. Tagging the genrule targets as manual accomplishes this by
96    # preventing them from being picked up by recursive build patterns (`//...`).
97    genrule_tags = genrule_args.pop("tags", [])
98    if "manual" not in genrule_tags:
99        genrule_tags = genrule_tags + ["manual"]
100
101    # Pass an output from the genrules as data to a shell test to bundle
102    # it all up in a test.
103    test_data = []
104
105    for idx, batch in enumerate([targets[i:i + batch_size] for i in range(0, len(targets), batch_size)]):
106        full_name = "{name}_{idx}__deps".format(name = name, idx = idx)
107        test_data.append(full_name)
108        native.genrule(
109            name = full_name,
110            srcs = batch,
111            outs = [full_name + ".out"],
112            testonly = 1,
113            visibility = ["//visibility:private"],
114            cmd = "touch $@",
115            cmd_bat = "type nul > $@",
116            tags = genrule_tags,
117            **genrule_args
118        )
119
120    _empty_test(
121        name = name,
122        data = test_data,
123        size = kwargs.pop("size", "small"),  # Default to small for test size
124        is_windows = select({
125            "@bazel_tools//src/conditions:host_windows": True,
126            "//conditions:default": False,
127        }),
128        **kwargs
129    )
130