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