xref: /aosp_15_r20/external/skia/tools/testrunners/common/android/adb_test.bzl (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1"""This module defines the adb_test rule."""
2
3load("@local_config_platform//:constraints.bzl", "HOST_CONSTRAINTS")
4load("//bazel:remove_indentation.bzl", "remove_indentation")
5
6def _adb_test_runner_transition_impl(settings, attr):  # buildifier: disable=unused-variable
7    platform = settings["//tools/testrunners/common/android/adb_test_runner:adb_platform"]
8
9    # If no platform was specified via --adb_platform, use the host platform. This allows us to
10    # "bazel test" an adb_test target on a developer workstation without passing said flag to
11    # Bazel.
12    if platform == "":
13        # The HOST_CONSTRAINTS list should always be of the form [cpu, os], e.g.:
14        #
15        #     HOST_CONSTRAINTS = ["@platforms//cpu:x86_64", "@platforms//os:linux"]
16        #
17        # The CPU and OS constraints will be omitted from HOST_CONSTRAINTS in the rare case that
18        # they cannot be determined.
19        #
20        # Reference:
21        # https://github.com/bazelbuild/bazel/blob/30ca122db02d953068ebb2b036b015e6b375c9ce/src/main/java/com/google/devtools/build/lib/bazel/repository/LocalConfigPlatformFunction.java#L186
22        if len(HOST_CONSTRAINTS) != 2 or \
23           not HOST_CONSTRAINTS[0].startswith("@platforms//cpu:") or \
24           not HOST_CONSTRAINTS[1].startswith("@platforms//os:"):
25            fail(
26                "Expected HOST_CONSTRAINTS to be of the form " +
27                """["@platforms//cpu:<cpu>", "@platforms//os:<os>"], got""",
28                HOST_CONSTRAINTS,
29            )
30
31        # Map the Bazel constants to GOARCH constants. More can be added as needed. See
32        # https://github.com/bazelbuild/rules_go/blob/5933b6ed063488472fc14ceca232b3115e8bc39f/go/private/platforms.bzl#LL30C9-L30C9.
33        cpu = HOST_CONSTRAINTS[0].removeprefix("@platforms//cpu:")
34        os = HOST_CONSTRAINTS[1].removeprefix("@platforms//os:")
35        cpu = {
36            "x86_64": "amd64",
37            "aarch64": "arm64",
38        }.get(cpu, cpu)  # Defaults to the original CPU if not in the dictionary.
39        os = {
40            "osx": "darwin",
41        }.get(os, os)  # Default to the original OS if not in the dictionary.
42
43        platform = os + "_" + cpu
44    else:
45        # Map the --adb_platform CPU part to GOARCH style, which we differ from for readability.
46        platform = platform.replace("x86", "amd64")
47
48    return {"//command_line_option:platforms": "@io_bazel_rules_go//go/toolchain:" + platform}
49
50# This transition allows us to cross-compile the Go test runner (i.e. the program that issues adb
51# commands) for a different platform, for example when "bazel build"-ing on an x86 GCE machine and
52# running the compiled artifact on a Raspberry Pi in a subsequent CI task.
53adb_test_runner_transition = transition(
54    implementation = _adb_test_runner_transition_impl,
55    inputs = ["//tools/testrunners/common/android/adb_test_runner:adb_platform"],
56    outputs = ["//command_line_option:platforms"],
57)
58
59def _adb_test_impl(ctx):
60    test_undeclared_outputs_dir_env_var_check = ""
61    output_dir_flag = ""
62    if ctx.attr.save_output_files:
63        test_undeclared_outputs_dir_env_var_check = remove_indentation("""
64            if [[ -z "${TEST_UNDECLARED_OUTPUTS_DIR}" ]]; then
65                echo "FAILED: Environment variable TEST_UNDECLARED_OUTPUTS_DIR is unset. If you"
66                echo "        are running this test outside of Bazel, set said variable to the"
67                echo "        directory where you wish to store any files produced by this test."
68
69                exit 1
70            fi
71        """)
72        output_dir_flag = "--output-dir $TEST_UNDECLARED_OUTPUTS_DIR"
73
74    template = remove_indentation("""
75        #!/bin/bash
76
77        {test_undeclared_outputs_dir_env_var_check}
78
79        # Print commands and expand variables for easier debugging.
80        set -x
81
82        # List the test runner binary for debugging purposes.
83        ls -l $(rootpath {adb_test_runner})
84
85        # Determine the adb_test_runner's --test-runner-extra-args flag value, which is a
86        # space-separated string with any additional arguments to pass to the C++ binary. These can
87        # be set via Bazel's --test_arg flag, or can be passed directly to the binary produced by
88        # "bazel build".
89        #
90        # Note that if this space-separated string includes a
91        # "--device-specific-bazel-config=<config name>" substring, the adb_test_runner will parse
92        # it out as if it were its own flag. This is easier than trying to parse flags in this
93        # script.
94        TEST_RUNNER_EXTRA_ARGS="$@"
95
96        $(rootpath {adb_test_runner}) \
97            {benchmark} \
98            --archive $(rootpath {archive}) \
99            --test-runner $(rootpath {test_runner}) \
100            --test-runner-extra-args "$TEST_RUNNER_EXTRA_ARGS" \
101            {output_dir_flag}
102    """)
103
104    # Expand variables.
105    script = ctx.expand_location(template.format(
106        benchmark = "--benchmark" if ctx.attr.benchmark else "",
107        archive = ctx.attr.archive.label,
108        test_runner = ctx.attr.test_runner.label,
109        adb_test_runner = ctx.attr._adb_test_runner[0].label,
110        test_undeclared_outputs_dir_env_var_check = test_undeclared_outputs_dir_env_var_check,
111        output_dir_flag = output_dir_flag,
112    ), targets = [
113        ctx.attr.archive,
114        ctx.attr.test_runner,
115        ctx.attr._adb_test_runner[0],
116    ])
117
118    output_file = ctx.actions.declare_file(ctx.attr.name)
119    ctx.actions.write(output_file, script, is_executable = True)
120
121    runfiles = ctx.runfiles(files = [ctx.file.archive])
122    runfiles = runfiles.merge(ctx.attr._adb_test_runner[0][DefaultInfo].default_runfiles)
123
124    return [DefaultInfo(
125        executable = output_file,
126        runfiles = runfiles,
127    )]
128
129adb_test = rule(
130    doc = """Runs an Android test on device via `adb`.
131
132    Note: This rule is not intended to be used directly in BUILD files. Instead, please use macros
133    android_unit_test, android_gm_test, android_benchmark_test, etc.
134
135    This test rule produces a wrapper shell script that invokes a Go program that issues adb
136    commands to interact with the device under test.
137
138    When building a test that should run on a different host (e.g. a Skolo Raspberry Pi), invoke
139    Bazel with flag --adb_platform to set the adb_test_runner target platform accordingly, for
140    example --adb_platform=linux_arm64.
141    """,
142    implementation = _adb_test_impl,
143    attrs = {
144        "benchmark": attr.bool(
145            doc = (
146                "Set up the device for benchmark tests. This might affect e.g. CPU and GPU " +
147                "settings specific to the Android device under test."
148            ),
149            mandatory = False,
150            default = False,
151        ),
152        "test_runner": attr.label(
153            doc = (
154                "Test runner script that calls the compiled C++ binary with any necessary " +
155                "command-line arguments. This script will be executed on the Android device."
156            ),
157            allow_single_file = True,
158            mandatory = True,
159        ),
160        "archive": attr.label(
161            doc = (
162                "Tarball containing the test runner script, the compiled C++ binary and any" +
163                "necessary static resources such as fonts, images, etc."
164            ),
165            allow_single_file = [".tar.gz"],
166            mandatory = True,
167        ),
168        "save_output_files": attr.bool(
169            doc = (
170                "If true, save any files produced by this test (e.g. PNG and JSON files in the " +
171                "case of GM tests) as undeclared outputs (see documentation for the " +
172                "TEST_UNDECLARED_OUTPUTS_DIR environment variable at " +
173                "https://bazel.build/reference/test-encyclopedia#initial-conditions)."
174            ),
175        ),
176        "_adb_test_runner": attr.label(
177            default = Label("//tools/testrunners/common/android/adb_test_runner"),
178            allow_single_file = True,
179            executable = True,
180            cfg = adb_test_runner_transition,
181        ),
182        "_allowlist_function_transition": attr.label(
183            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
184        ),
185    },
186    test = True,
187)
188