blob: 0dd646c9b1e612c094163631525db3d394803a1f [file] [log] [blame]
"""This module defines the android_test macro."""
load("//bazel:cc_binary_with_flags.bzl", "cc_binary_with_flags")
load("//bazel/devices:android_devices.bzl", "ANDROID_DEVICES")
load("//bazel:adb_test.bzl", "adb_test")
load("//bazel:binary_wrapper_script_with_cmdline_flags.bzl", "binary_wrapper_script_with_cmdline_flags")
def android_test(
name,
srcs,
test_runner_if_required_condition_is_satisfied,
test_runner_if_required_condition_is_not_satisfied,
deps = [],
flags = {},
extra_args = [],
requires_condition = "//:always_true",
requires_resources_dir = False,
save_output_files = False):
"""Defines an Android test.
Note: This macro is not intended to be used directly in BUILD files. Instead, please use macros
android_unit_test, android_gm_test, etc.
This macro compiles one or more C++ tests into a single Android binary and produces a script
that runs the test on an attached Android device via `adb`. This macro is compatible with unit,
GM and perf tests.
This macro requires a device-specific Android platform such as //bazel/platform:pixel_5. This is
used to decide what device-specific set-up steps to apply, such as setting CPU/GPU frequencies.
The test target produced by this macro can be executed on a machine attached to an Android
device. This can be either via USB, or by port-forwarding a remote ADB server (TCP port 5037)
running on a machine attached to the target device, such as a Skolo Raspberry Pi.
High-level overview of how this rule works:
- It produces a <name>.tar.gz archive containing the Android binary, a minimal launcher script
that invokes the binary on the device under test with any necessary command-line arguments,
and any static resources needed by the C++ tests, such as fonts and images under //resources.
- It produces a <name> test runner script that extracts the tarball into the device via `adb`,
sets up the device, runs the test, cleans up and pipes through the test's exit code.
- Optionally, the <name> test runner script can be configured to download from the device any
files produced by the C++ tests (such as PNG and JSON files produced by GM tests). These
files will be available as undeclared test outputs (see documentation for the
TEST_UNDECLARED_OUTPUTS_DIR environment variable at
https://bazel.build/reference/test-encyclopedia#initial-conditions).
For CI jobs, rather than invoking "bazel test" on a Raspberry Pi attached to the Android device
under test, we compile and run the test in two separate tasks:
- A build task running on a GCE machine compiles the test on RBE with Bazel and stores the
<name>.tar.gz and <name> output files to CAS.
- A test task running on a Skolo Raspberry Pi downloads <name>.tar.gz and <name> from CAS and
executes <name> *outside of Bazel*.
The reason why we don't want to run Bazel on a Raspberry Pi is due to its constrained
resources.
Note: Although not currently supported, we could use a similar approach for Apple devices in
in the future.
Args:
name: The name of the test.
srcs: A list of C++ source files. This list should not include a main() function (see the
requires_condition argument).
test_runner_if_required_condition_is_satisfied: A C++ source file with a main() function to
be appended to the srcs attribute if requires_condition is satisfied. The resulting
program should return exit code 0 if all tests pass, or a non-zero exit code in the
case of failures. See the requires_condition argument.
test_runner_if_required_condition_is_not_satisfied: A C++ source file with a main()
function to be appended to the srcs attribute if requires_condition is *not* satisfied.
The main() function in this source file should do nothing, and the resulting program
should always return exit code 0 to indicate that the test was successful. See the
requires_condition argument.
deps: Any dependencies needed by the srcs. This list should not include a main() function
(see the requires_condition argument).
flags: A map of strings to lists of strings to specify features that must be compiled in
for these tests to work. For example, tests targeting our codec logic will want the
various codecs included, but most tests won't need that.
extra_args: Additional command-line arguments to pass to the test, for example, any
device-specific --skip flags to skip incompatible or buggy test cases.
requires_condition: A necessary condition for the test to work. For example, Ganesh tests
should set this argument to "//src/gpu:has_ganesh_backend". If the condition is
satisfied, test_runner_if_required_condition_is_satisfied will be appended to the srcs
attribute.
If the condition is not satisfied, test_runner_if_required_condition_is_not_satisfied
will be included as the only source file, and no deps will be included. This prevents
spurious build failures when using wildcard expressions (e.g.
"bazel build //tests/...") with a configuration that is incompatible with this test.
requires_resources_dir: If set, the contents of the //resources directory will be included
in the tarball that is pushed to the device via `adb push`, and the test binary will be
invoked with flag --resourcePath set to the path to said directory.
save_output_files: If true, save any files produced by this test (e.g. PNG and JSON files
in the case of GM tests) as undeclared outputs (see documentation for the
TEST_UNDECLARED_OUTPUTS_DIR environment variable at
https://bazel.build/reference/test-encyclopedia#initial-conditions).
"""
test_binary = "%s_binary" % name
cc_binary_with_flags(
name = test_binary,
srcs = select({
requires_condition: srcs + [test_runner_if_required_condition_is_satisfied],
"//conditions:default": [test_runner_if_required_condition_is_not_satisfied],
}),
deps = select({
requires_condition: deps,
"//conditions:default": [],
}),
set_flags = flags,
testonly = True, # Needed to gain access to test-only files.
)
test_runner = "%s_runner" % name
binary_wrapper_script_with_cmdline_flags(
name = test_runner,
binary = test_binary,
extra_args = extra_args,
requires_resources_dir = requires_resources_dir,
testonly = True, # Needed to gain access to test-only files.
)
archive = "%s_archive" % name
archive_srcs = [test_runner, test_binary] + (
["//resources"] if requires_resources_dir else []
)
# Create an archive containing the test and its resources, with a structure that emulates
# the environment expected by the test when executed via "bazel test". This archive can be
# pushed to an Android device via "adb push", and once extracted, the test binary can be
# executed on the device via "adb shell" as long as the working directory is set to the
# directory where the archive is extracted.
#
# See https://bazel.build/reference/test-encyclopedia#initial-conditions.
native.genrule(
name = archive,
srcs = archive_srcs,
outs = ["%s.tar.gz" % name],
cmd = """
$(location //bazel/make_tarball) \
--execpaths "{execpaths}" \
--rootpaths "{rootpaths}" \
--output-file $@
""".format(
execpaths = " ".join(["$(execpaths %s)" % src for src in archive_srcs]),
rootpaths = " ".join(["$(rootpaths %s)" % src for src in archive_srcs]),
),
testonly = True, # Needed to gain access to test-only files.
# Tools are always built for the exec platform
# (https://bazel.build/reference/be/general#genrule.tools), e.g. Linux on x86_64 when
# running on a gLinux workstation or on a Linux GCE machine.
tools = ["//bazel/make_tarball"],
)
adb_test(
name = name,
archive = archive,
test_runner = test_runner,
device = select(dict(
[
("//bazel/devices:%s" % device_name, device_name)
for device_name in ANDROID_DEVICES
] + [
("//conditions:default", "unknown"),
],
)),
save_output_files = save_output_files,
tags = ["no-remote"], # Incompatible with RBE because it requires an Android device.
target_compatible_with = select({
"//bazel/devices:has_android_device": [], # Compatible with everything.
"//conditions:default": ["@platforms//:incompatible"],
}),
)