[bazel] Add skia_android_unit_test macro.
This CL defines a generic (i.e. non Android specific) skia_test macro that compiles a Skia C++ unit test with cc_binary_with_flags, and a wrapper test runner script that passes any necessary command-line arguments to the C++ binary. The test wrapper script is exposed to Bazel as a sh_test.
Then, this CL defines a skia_android_unit_test macro that compiles one or more C++ unit tests into a single Android binary and produces a script that runs the test on an attached Android device via adb. See the docstring for details.
Note to reviewer: The skia_test macro is similar enough to the existing skia_cpu_tests and skia_ganesh_tests macros that we could rewrite them as wrappers around skia_test, but that's out of scope for this CL.
Suggested review order:
- //tests/BUILD.bazel
- //tests/android.bzl
Bug: skia:14227
Change-Id: I43c38d530c4b2562673f319ea9dc87d3734cbb10
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/668876
Commit-Queue: Leandro Lovisolo <lovisolo@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/BUILD.bazel b/BUILD.bazel
index b1732f4..70eb45c 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,5 +1,6 @@
load("//:defines.bzl", "DEFAULT_DEFINES", "DEFAULT_LOCAL_DEFINES")
load("@skia_user_config//:copts.bzl", "DEFAULT_OBJC_COPTS")
+load("//bazel:flags.bzl", "selects")
load("//bazel:skia_rules.bzl", "exports_files_legacy", "skia_cc_library", "skia_objc_library")
licenses(["notice"])
@@ -114,3 +115,27 @@
actual = "//infra:gazelle",
visibility = ["//visibility:public"],
)
+
+# Convenience condition that is always true. This condition is satisfied if an arbitrarily chosen
+# boolean built-in flag (https://bazel.build/docs/user-manual#stamp) is either true or false.
+#
+# Inspired by
+# https://github.com/bazelbuild/bazel-skylib/blob/2f0bb4cec0297bb38f830a72fa8961bee057c3cd/lib/selects.bzl#L227.
+selects.config_setting_group(
+ name = "always_true",
+ match_any = [
+ ":always_true_0",
+ ":always_true_1",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+config_setting(
+ name = "always_true_0",
+ values = {"stamp": "0"},
+)
+
+config_setting(
+ name = "always_true_1",
+ values = {"stamp": "1"},
+)
diff --git a/bazel/buildrc b/bazel/buildrc
index 13fb3d0..9060f9e 100644
--- a/bazel/buildrc
+++ b/bazel/buildrc
@@ -52,6 +52,12 @@
build:for_android_arm64 --platforms=//bazel/platform:android_arm64 --cc_output_directory_tag=android_arm64
build:for_android_arm64_with_rbe --config=for_android_arm64 --config=linux_rbe
+# Android device-specific configurations.
+build:pixel_5 --platforms=//bazel/platform:pixel_5 --cc_output_directory_tag=pixel_5
+build:pixel_5_with_rbe --config=pixel_5 --config=linux_rbe
+build:pixel_7 --platforms=//bazel/platform:pixel_7 --cc_output_directory_tag=pixel_7
+build:pixel_7_with_rbe --config=pixel_7 --config=linux_rbe
+
# =============================================================================
# Configurations (what features we want on)
# =============================================================================
diff --git a/bazel/devices/BUILD.bazel b/bazel/devices/BUILD.bazel
new file mode 100644
index 0000000..d127900
--- /dev/null
+++ b/bazel/devices/BUILD.bazel
@@ -0,0 +1,29 @@
+load("//bazel:flags.bzl", "selects")
+load("//bazel:skia_rules.bzl", "exports_files_legacy")
+
+exports_files_legacy()
+
+licenses(["notice"])
+
+constraint_setting(name = "android_device")
+
+constraint_value(
+ name = "pixel_5",
+ constraint_setting = ":android_device",
+ visibility = ["//visibility:public"],
+)
+
+constraint_value(
+ name = "pixel_7",
+ constraint_setting = ":android_device",
+ visibility = ["//visibility:public"],
+)
+
+selects.config_setting_group(
+ name = "has_android_device",
+ match_any = [
+ ":pixel_5",
+ ":pixel_7",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/bazel/platform/BUILD.bazel b/bazel/platform/BUILD.bazel
index 978c08b..d44d108 100644
--- a/bazel/platform/BUILD.bazel
+++ b/bazel/platform/BUILD.bazel
@@ -69,6 +69,24 @@
],
)
+platform(
+ name = "pixel_5",
+ constraint_values = [
+ "@platforms//os:android",
+ "@platforms//cpu:arm64",
+ "//bazel/devices:pixel_5",
+ ],
+)
+
+platform(
+ name = "pixel_7",
+ constraint_values = [
+ "@platforms//os:android",
+ "@platforms//cpu:arm64",
+ "//bazel/devices:pixel_7",
+ ],
+)
+
# This constraint allows us to force Bazel to resolve our hermetic toolchain to build
# the target and not a default one (e.g. on the Linux RBE instance). We do this by
# adding the constraint to our platforms that describe the target we want Bazel to build for.
diff --git a/bazel/remove_indentation.bzl b/bazel/remove_indentation.bzl
new file mode 100644
index 0000000..1c827c4
--- /dev/null
+++ b/bazel/remove_indentation.bzl
@@ -0,0 +1,69 @@
+"""This module defines the remove_indentation macro."""
+
+def remove_indentation(string):
+ """Removes indentation from a multiline string.
+
+ This utility function allows us to write multiline templates in a context that requires
+ indentation, for example inside a macro. It discards the first and last lines if they only
+ contain spaces or tabs. Then, it computes an indentation prefix based on the first remaining
+ line and removes that prefix from all lines.
+
+ Example:
+
+ ```
+ def greeter_script():
+ return remove_indentation('''
+ #!/bin/bash
+ echo "Hello, {name}!"
+ ''').format(name = "world")
+ ```
+
+ This is equivalent to:
+
+ ```
+ TEMPLATE = '''#!/bin/bash
+ echo "Hello, {name}!"
+ '''
+
+ def greeter_script():
+ return TEMPLATE.format(name = "world")
+ ```
+
+ This macro is similar to
+ https://github.com/bazelbuild/rules_rust/blob/937e63399b111a6d7ee53b187e4d113300b089e9/rust/private/utils.bzl#L386.
+
+ Args:
+ string: A multiline string.
+ Returns:
+ The input string minus any indentation.
+ """
+
+ def get_indentation(line):
+ indentation = ""
+ for char in line.elems():
+ if char in [" ", "\t"]:
+ indentation += char
+ else:
+ break
+ return indentation
+
+ lines = string.split("\n")
+
+ # Skip first line if empty.
+ if get_indentation(lines[0]) == lines[0]:
+ lines = lines[1:]
+
+ # Compute indentation based on the first remaining line, and remove indentation from all lines.
+ indentation = get_indentation(lines[0])
+ lines = [line.removeprefix(indentation) for line in lines]
+
+ # Skip last line if empty.
+ if get_indentation(lines[len(lines) - 1]) == lines[len(lines) - 1]:
+ lines = lines[:-1]
+
+ result = "\n".join(lines)
+ if result[:-1] != "\n":
+ # Ensure we always end with a newline.
+ result += "\n"
+
+ return result
diff --git a/resources/BUILD.bazel b/resources/BUILD.bazel
index 560ea49..ecb8d92 100644
--- a/resources/BUILD.bazel
+++ b/resources/BUILD.bazel
@@ -1,5 +1,19 @@
load("//bazel:skia_rules.bzl", "skia_filegroup")
+# We export a known file inside the resources directory so that we can compute a path to said
+# directory from places that support "Make" variables[1], such as the "cmd" attribute[2] of a
+# genrule. For example, a genrule can compute the path to the resources directory from its "cmd"
+# attribute as follows:
+#
+# $$(dirname $$(rootpath //resources:README))
+#
+# [1] https://bazel.build/reference/be/make-variables
+# [2] https://bazel.build/reference/be/general#genrule.cmd
+exports_files(
+ ["README"],
+ visibility = ["//tests:__pkg__"],
+)
+
skia_filegroup(
name = "resources",
srcs = [
diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel
index 860bfcc..2cc003d 100644
--- a/tests/BUILD.bazel
+++ b/tests/BUILD.bazel
@@ -11,6 +11,7 @@
"PATHOPS_TESTS",
"PDF_TESTS",
)
+load(":android.bzl", "skia_android_unit_test")
skia_cc_library(
name = "tests_base",
@@ -280,24 +281,95 @@
],
)
-# This target is known to build successfully for Android with the following Bazel invocations:
+# These targets can be run from a gLinux workstation. Example:
#
-# # For 64-bit ARM.
-# $ bazel build //tests:known_good_android_ganesh_test \
-# --config=for_android_arm64_with_rbe \
-# --gpu_backend=gl_backend \
-# --enable_gpu_test_utils
+# 1. Port-forward the ADB server from a Raspberry Pi that is connected to a Pixel 5:
#
-# # For 32-bit ARM.
-# $ bazel build //tests:known_good_android_ganesh_test \
-# --config=for_android_arm32_with_rbe \
-# --gpu_backend=gl_backend \
-# --enable_gpu_test_utils
+# $ ssh -L 5037:localhost:5037 skia-rpi2-rack1-shelf1-036
#
-# lovisolo@ has yet to test the resulting binaries on a physical Android device.
+# 2. Invoke Bazel as follows:
#
-# Building this alias is equivalent to building //tests:text_blob_cache_test.
-alias(
- name = "known_good_android_ganesh_test",
- actual = ":TextBlobCacheTest",
+# $ bazel test //tests:android_codec_test --config=pixel_5 --config=linux_rbe --test_output=streamed
+
+skia_android_unit_test(
+ name = "android_codec_test",
+ srcs = CODEC_TESTS,
+ flags = {
+ "include_decoder": [
+ "avif_decode_codec",
+ "gif_decode_codec",
+ "jpeg_decode_codec",
+ "jxl_decode_codec",
+ "png_decode_codec",
+ "raw_decode_codec",
+ "webp_decode_codec",
+ ],
+ "include_encoder": [
+ "jpeg_encode_codec",
+ "png_encode_codec",
+ "webp_encode_codec",
+ ],
+ },
+ requires_resources_dir = True,
+ deps = [
+ ":tests_base",
+ "//:skia_internal",
+ ],
+)
+
+skia_android_unit_test(
+ name = "android_ganesh_test",
+ srcs = GANESH_TESTS,
+ requires_condition = "//src/gpu:has_gpu_backend",
+ requires_resources_dir = True,
+ deps = [
+ ":tests_base",
+ "//:skia_internal",
+ ],
+)
+
+skia_android_unit_test(
+ name = "android_pathops_test",
+ srcs = PATHOPS_TESTS,
+ requires_resources_dir = True,
+ deps = [
+ ":pathops_tests_base",
+ "//:skia_internal",
+ ],
+)
+
+# Some test cases fail with --config=pixel_7.
+skia_android_unit_test(
+ name = "android_cpu_only_test",
+ srcs = CPU_ONLY_TESTS,
+ flags = {
+ "fontmgr_factory": ["custom_directory_fontmgr_factory"],
+ "enable_sksl": ["True"],
+ "enable_sksl_tracing": ["True"],
+ "include_decoder": [
+ "jpeg_decode_codec",
+ "png_decode_codec",
+ ],
+ "include_encoder": [
+ "png_encode_codec",
+ ],
+ },
+ requires_resources_dir = True,
+ deps = [
+ ":tests_base",
+ "//:skia_internal",
+ ],
+)
+
+skia_android_unit_test(
+ name = "android_discardable_memory_test",
+ srcs = DISCARDABLE_MEMORY_POOL_TESTS,
+ flags = {
+ "enable_discardable_memory": ["True"],
+ "use_default_global_memory_pool": ["True"],
+ },
+ deps = [
+ ":tests_base",
+ "//:skia_internal",
+ ],
)
diff --git a/tests/adb_test.bzl b/tests/adb_test.bzl
new file mode 100644
index 0000000..51ee98d
--- /dev/null
+++ b/tests/adb_test.bzl
@@ -0,0 +1,113 @@
+"""This module defines the adb_test rule."""
+
+load("//bazel:remove_indentation.bzl", "remove_indentation")
+
+def _adb_test_impl(ctx):
+ # TODO(lovisolo): Add device-specific (via ctx.attr.device) setup steps such as turning cores
+ # on/off and setting the CPU/GPU frequencies.
+
+ # TODO(lovisolo): Replace this with a Go program.
+ template = remove_indentation("""
+ #!/bin/bash
+
+ # Runner script for device "{device}".
+
+ # TODO(lovisolo): Should we check that the machine is attached to the expected device type?
+ # E.g. run "adb devices -l" and check that the output contains
+ # "model:Pixel_5".
+
+ # Note: this script was only tested on Pixel devices.
+ #
+ # The /sdcard/revenge_of_the_skiabot directory is writable for non-root users, but files in
+ # this directory cannot be executed. For this reason, we extract the archive in a directory
+ # under /data, which allows executing files but requires root privileges.
+ #
+ # TODO(lovisolo): Can we do this without "su"-ing as root? Does this work on non-rooted
+ # devices?
+ # TODO(lovisolo): Test on more devices.
+
+ ARCHIVE_ON_DEVICE=/sdcard/revenge_of_the_skiabot/bazel-adb-test.tar.gz
+ DIRECTORY_ON_DEVICE=/data/bazel-adb-test
+
+ # Print commands and expand variables for easier debugging.
+ set -x
+
+ # Ensure that we clean up the device on exit, even in the case of failures.
+ # TODO(lovisolo): Also clean up before running the test, as the device might be in a dirty
+ # state if the previous task did not finish correctly (e.g. device reboot).
+ trap "adb shell su root rm -rf ${{ARCHIVE_ON_DEVICE}} ${{DIRECTORY_ON_DEVICE}}" EXIT
+
+ # Upload archive.
+ adb push $(rootpath {archive}) ${{ARCHIVE_ON_DEVICE}}
+
+ # Extract archive.
+ adb shell su root mkdir ${{DIRECTORY_ON_DEVICE}}
+ adb shell su root tar xzvf ${{ARCHIVE_ON_DEVICE}} -C ${{DIRECTORY_ON_DEVICE}}
+
+ # Run test inside the directory where the archive was extracted. We set the working
+ # directory to the root of the archive, which emulates the directory structure expected by
+ # the test when invoked with "bazel test". See
+ # https://bazel.build/reference/test-encyclopedia#initial-conditions.
+ echo "cd ${{DIRECTORY_ON_DEVICE}} && $(rootpath {test_runner})" | adb shell su root
+ """)
+
+ if ctx.attr.device == "unknown":
+ template = remove_indentation("""
+ #!/bin/bash
+
+ echo "FAILED: No Android device was specified. Try re-running with a Bazel flag that"
+ echo " specifies an Android device under test, such as --config=pixel_5."
+
+ exit 1
+ """)
+
+ # Expand variables.
+ template = ctx.expand_location(template.format(
+ device = ctx.attr.device,
+ archive = ctx.attr.archive.label,
+ test_runner = ctx.attr.test_runner.label,
+ ), targets = [
+ ctx.attr.archive,
+ ctx.attr.test_runner,
+ ])
+
+ output_file = ctx.actions.declare_file(ctx.attr.name)
+ ctx.actions.write(output_file, template, is_executable = True)
+
+ return [DefaultInfo(
+ executable = output_file,
+ runfiles = ctx.runfiles(files = [ctx.file.archive]),
+ )]
+
+adb_test = rule(
+ doc = """Runs an Android test on device via `adb`.""",
+ implementation = _adb_test_impl,
+ attrs = {
+ "device": attr.string(
+ doc = "Device under test.",
+ mandatory = True,
+ values = [
+ "pixel_5",
+ "pixel_7",
+ "unknown",
+ ],
+ ),
+ "test_runner": attr.label(
+ doc = (
+ "Test runner script that calls the compiled C++ binary with any necessary " +
+ "command-line arguments. This script will be executed on the Android device."
+ ),
+ allow_single_file = [".sh"],
+ mandatory = True,
+ ),
+ "archive": attr.label(
+ doc = (
+ "Tarball containing the test runner script, the compiled C++ binary and any" +
+ "necessary static resources such as fonts, images, etc."
+ ),
+ allow_single_file = [".tar.gz"],
+ mandatory = True,
+ ),
+ },
+ test = True,
+)
diff --git a/tests/android.bzl b/tests/android.bzl
new file mode 100644
index 0000000..4eb8494
--- /dev/null
+++ b/tests/android.bzl
@@ -0,0 +1,272 @@
+"""This module defines the skia_android_unit_test macro."""
+
+load("//bazel:cc_binary_with_flags.bzl", "cc_binary_with_flags")
+load("//bazel:remove_indentation.bzl", "remove_indentation")
+load(":adb_test.bzl", "adb_test")
+
+def skia_test(
+ name,
+ srcs,
+ deps,
+ requires_resources_dir = False,
+ extra_args = [],
+ flags = {},
+ limit_to = [],
+ tags = [],
+ size = None):
+ """Defines a generic Skia C++ unit test.
+
+ This macro produces a <name>_binary C++ binary and a <name>.sh wrapper script that runs the
+ binary with the desired command-line arguments (see the extra_args and requires_resources_dir
+ arguments). The <name>.sh wrapper script is exposed as a Bazel test target via the sh_target
+ rule.
+
+ The reason why we place command-line arguments in a wrapper script is that it makes it easier
+ to run a Bazel-built skia_test outside of Bazel. This is useful e.g. for CI jobs where we want
+ to perform test compilation and execution as different steps on different hardware (e.g.
+ compile on a GCE machine, run tests on a Skolo device). In this scenario, the test could be
+ executed outside of Bazel by simply running the <name>.sh script without any arguments. See the
+ skia_android_unit_test macro for an example.
+
+ Note: The srcs attribute must explicitly include a test runner (e.g.
+ //tests/BazelTestRunner.cpp).
+
+ Args:
+ name: The name of the test.
+ srcs: C++ source files.
+ deps: C++ library dependencies.
+ requires_resources_dir: Indicates whether this test requires any files under //resources,
+ such as images, fonts, etc. If so, the compiled C++ binary will be invoked with flag
+ --resourcePath set to the path to the //resources directory under the runfiles tree.
+ Note that this implies the test runner must recognize the --resourcePath flag for this
+ to work.
+ extra_args: Any additional command-line arguments to pass to the compiled C++ binary.
+ 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.
+ limit_to: A list of platform labels (e.g. @platform//os:foo; @platform//cpu:bar) which
+ restrict where this test will be compiled and ran. If the list is empty, it will run
+ anywhere. If it is non-empty, it will only run on platforms which match the entire set
+ of constraints. See https://github.com/bazelbuild/platforms for these.
+ tags: A list of tags for the generated test target.
+ size: The size of the test.
+ """
+ test_binary = "%s_binary" % name
+
+ # We compile the test as a cc_binary, rather than as as a cc_test, because we will not
+ # "bazel test" this binary directly. Instead, we will "bazel test" a wrapper script that
+ # invokes this binary with the required command-line parameters.
+ cc_binary_with_flags(
+ name = test_binary,
+ srcs = srcs,
+ deps = deps,
+ data = ["//resources"] if requires_resources_dir else [],
+ set_flags = flags,
+ target_compatible_with = limit_to,
+ testonly = True, # Needed to gain access to test-only files.
+ )
+
+ test_runner = "%s.sh" % name
+
+ test_args = ([
+ "--resourcePath",
+ "$$(realpath $$(dirname $(rootpath //resources:README)))",
+ ] if requires_resources_dir else []) + extra_args
+
+ # This test runner might run on Android devices, which might not have a /bin/bash binary.
+ test_runner_template = remove_indentation("""
+ #!/bin/sh
+ $(rootpath {test_binary}) {test_args}
+ """)
+
+ # TODO(lovisolo): This should be an actual rule. This will allow us to select() the arguments
+ # based on the device (e.g. for device-specific --skip flags to skip tests).
+ native.genrule(
+ name = "%s_runner" % name,
+ srcs = [test_binary] + (
+ # The script template computes the path to //resources under the runfiles tree via
+ # $$(dirname $(rootpath //resources:README)), so we need to list //resources:README
+ # here explicitly. This file was chosen arbitrarily; there is nothing special about it.
+ ["//resources", "//resources:README"] if requires_resources_dir else []
+ ),
+ outs = [test_runner],
+ cmd = "echo '%s' > $@" % test_runner_template.format(
+ test_binary = test_binary,
+ test_args = "\\\n ".join(test_args),
+ ),
+ testonly = True,
+ )
+
+ native.sh_test(
+ name = name,
+ size = size,
+ srcs = [test_runner],
+ data = [test_binary] + (["//resources"] if requires_resources_dir else []),
+ tags = tags,
+ )
+
+def skia_android_unit_test(
+ name,
+ srcs,
+ deps = [],
+ flags = {},
+ extra_args = [],
+ requires_condition = "//:always_true",
+ requires_resources_dir = False):
+ """Defines a Skia Android unit test.
+
+ This macro compiles one or more C++ unit tests into a single Android binary and produces a
+ script that runs the test on an attached Android device via `adb`.
+
+ This macro requires a device-specific Android platform such as //bazel/devices: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 with the necessary command-line arguments, and any static resources
+ needed by the test, such as fonts and images under //resources.
+ - It produces a <name>.sh 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.
+
+ 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>.sh output files to CAS.
+ - A test task running on a Skolo Raspberry Pi downloads <name>.tar.gz and <name>.sh from CAS
+ and executes <name>.sh *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).
+ 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.
+ TODO(lovisolo): Do we need to support skipping tests? IIUC today we only skip DMs, but
+ we don't skip any unit tests.
+ requires_condition: A necessary condition for the test to work. For example, GPU tests
+ should set this argument to "//src/gpu:has_gpu_backend". If the condition is satisfied,
+ //tests:BazelTestRunner.cpp will be appended to the srcs attribute. If the condition is
+ not satisfied, //tests:BazelNoopRunner.cpp will be included instead, 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 test runfiles, and the test binary will be invoked with flag --resourcePath set
+ to the path to said directory.
+ """
+
+ skia_test(
+ name = "%s_cpp_test" % name,
+ srcs = select({
+ requires_condition: srcs + ["//tests:BazelTestRunner.cpp"],
+ "//conditions:default": ["//tests:BazelNoopRunner.cpp"],
+ }),
+ deps = select({
+ requires_condition: deps,
+ "//conditions:default": [],
+ }),
+ flags = flags,
+ extra_args = extra_args,
+ requires_resources_dir = requires_resources_dir,
+ tags = [
+ # Exclude it from wildcards, e.g. "bazel test //...". We never want to run this binary
+ # directly.
+ "manual",
+ "no-remote", # RBE workers cannot run Android tests.
+ ],
+ size = "large", # Can take several minutes.
+ )
+
+ test_binary = "%s_cpp_test_binary" % name
+ test_runner = "%s_cpp_test.sh" % name
+
+ 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 the contained test 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.
+ #
+ # TODO(lovisolo): Replace this unreadable Bash script with a Go program.
+ native.genrule(
+ name = archive,
+ srcs = archive_srcs,
+ outs = ["%s.tar.gz" % name],
+ cmd = remove_indentation("""
+ # Create archive root directory, and ensure it is cleaned up on exit, even in the
+ # case of errors.
+ ARCHIVE_DIR=$$(mktemp -d);
+ trap "rm -rf $$ARCHIVE_DIR" EXIT;
+
+ # execpaths point to physical files generated by Bazel (e.g.
+ # bazel-out/k8-linux_x64-dbg/bin/tests/some_test), whereas rootpaths are the paths
+ # that a binary running via "bazel run" or "bazel test" expects (e.g
+ # tests/some_test). Thus, we must map the former to the latter.
+ #
+ # Reference:
+ # https://bazel.build/reference/be/make-variables#predefined_label_variables
+ EXECPATHS=({execpaths});
+ ROOTPATHS=({rootpaths});
+ if [ $${{#EXECPATHS[*]}} -ne $${{#ROOTPATHS[*]}} ]; then
+ echo EXECPATHS and ROOTPATHS have different lengths;
+ exit 1;
+ fi;
+
+ # Copy each file in EXECPATHS into ARCHIVE_DIR, renamed as its corresponding entry
+ # in ROOTPATHS.
+ for (( i=0; i<$${{#EXECPATHS[*]}}; i++ )); do
+ EXECPATH=$${{EXECPATHS[$$i]}};
+ ROOTPATH=$${{ROOTPATHS[$$i]}};
+ mkdir -p $$ARCHIVE_DIR/$$(dirname $$ROOTPATH);
+ cp $$EXECPATH $$ARCHIVE_DIR/$$ROOTPATH;
+ done;
+
+ # Create archive.
+ tar zcf $@ -C $$ARCHIVE_DIR .;
+ """.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.
+ )
+
+ adb_test(
+ name = name,
+ archive = archive,
+ test_runner = test_runner,
+ device = select(
+ {
+ "//bazel/devices:pixel_5": "pixel_5",
+ "//bazel/devices:pixel_7": "pixel_7",
+ "//conditions:default": "unknown",
+ },
+ ),
+ tags = ["no-remote"], # Incompatible with RBE because it requires an Android device.
+ target_compatible_with = select({
+ "//bazel/devices:has_android_device": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
+ )
diff --git a/tests/tests.bzl b/tests/tests.bzl
index 60014d3..56da36e 100644
--- a/tests/tests.bzl
+++ b/tests/tests.bzl
@@ -1,6 +1,4 @@
-"""
-This file contains macros that generate multiple test targets, one per file.
-"""
+"""This module contains macros to generate C++ test targets."""
load("//bazel:cc_test_with_flags.bzl", "cc_test_with_flags")