Skip to main content
Glama
android_instrumentation_test.bzl13 kB
# Copyright (c) Meta Platforms, Inc. and affiliates. # # This source code is licensed under both the MIT license found in the # LICENSE-MIT file in the root directory of this source tree and the Apache # License, Version 2.0 found in the LICENSE-APACHE file in the root directory # of this source tree. load("@prelude//android:android_providers.bzl", "AndroidApkInfo", "AndroidInstrumentationApkInfo") load("@prelude//android:android_toolchain.bzl", "AndroidToolchainInfo") load("@prelude//java:class_to_srcs.bzl", "JavaClassToSourceMapInfo") load("@prelude//java:java_providers.bzl", "JavaPackagingInfo", "get_all_java_packaging_deps_tset") load("@prelude//java:java_toolchain.bzl", "JavaToolchainInfo") load("@prelude//java/utils:java_more_utils.bzl", "get_path_separator_for_exec_os") load( "@prelude//linking:shared_libraries.bzl", "SharedLibraryInfo", "create_shlib_symlink_tree", "merge_shared_libraries", "traverse_shared_library_info", ) load("@prelude//test:inject_test_run_info.bzl", "inject_test_run_info") load("@prelude//utils:argfile.bzl", "at_argfile") load("@prelude//utils:expect.bzl", "expect") ANDROID_EMULATOR_ABI_LABEL_PREFIX = "tpx-re-config::" DEFAULT_ANDROID_SUBPLATFORM = "android-30" DEFAULT_ANDROID_PLATFORM = "android-emulator" DEFAULT_ANDROID_INSTRUMENTATION_TESTS_USE_CASE = "instrumentation-tests" RIOT_USE_CASES = ["horizon-os-diff", "horizon-os-other", "horizon-os-human-lease", "wearables-diff", "wearables-other", "wearables-human-lease"] SUPPORTED_POOLS = ["EUREKA_POOL", "HOLLYWOOD_POOL", "STAGE_DELPHI_POOL", "PANTHER_POOL", "SEACLIFF_POOL"] SUPPORTED_PLATFORMS = ["riot", "android-emulator"] SUPPORTED_USE_CASES = RIOT_USE_CASES + [DEFAULT_ANDROID_INSTRUMENTATION_TESTS_USE_CASE] def android_instrumentation_test_impl(ctx: AnalysisContext): android_toolchain = ctx.attrs._android_toolchain[AndroidToolchainInfo] cmd = [ctx.attrs._java_test_toolchain[JavaToolchainInfo].java_for_tests] classpath = android_toolchain.instrumentation_test_runner_classpath classpath_args = cmd_args() classpath_args.add("-classpath") env = ctx.attrs.env or {} extra_classpath = [] if ctx.attrs.instrumentation_test_listener != None: extra_classpath.extend([ get_all_java_packaging_deps_tset(ctx, java_packaging_infos = [ctx.attrs.instrumentation_test_listener[JavaPackagingInfo]]) .project_as_args("full_jar_args", ordering = "bfs"), ]) shared_library_info = merge_shared_libraries( ctx.actions, deps = [ctx.attrs.instrumentation_test_listener[SharedLibraryInfo]], ) cxx_library_symlink_tree = create_shlib_symlink_tree( actions = ctx.actions, out = "cxx_library_symlink_tree", shared_libs = traverse_shared_library_info(shared_library_info), ) env["BUCK_LD_SYMLINK_TREE"] = cxx_library_symlink_tree classpath_args.add(cmd_args(extra_classpath + classpath, delimiter = get_path_separator_for_exec_os(ctx))) cmd.append(at_argfile(actions = ctx.actions, name = "classpath_args_file", args = classpath_args)) cmd.append(android_toolchain.instrumentation_test_runner_main_class) apk_info = ctx.attrs.apk.get(AndroidApkInfo) expect(apk_info != None, "Provided APK must have AndroidApkInfo!") instrumentation_apk_info = ctx.attrs.apk.get(AndroidInstrumentationApkInfo) if instrumentation_apk_info != None: cmd.extend(["--apk-under-test-path", instrumentation_apk_info.apk_under_test]) if ctx.attrs.is_self_instrumenting: cmd.extend(["--is-self-instrumenting"]) extra_instrumentation_args = ctx.attrs.extra_instrumentation_args if extra_instrumentation_args: for arg_name, arg_value in extra_instrumentation_args.items(): cmd.extend( [ "--extra-instrumentation-argument", cmd_args([arg_name, arg_value], delimiter = "="), ], ) target_package_file = ctx.actions.declare_output("target_package_file") package_file = ctx.actions.declare_output("package_file") test_runner_file = ctx.actions.declare_output("test_runner_file") manifest_utils_cmd = cmd_args(ctx.attrs._android_toolchain[AndroidToolchainInfo].manifest_utils[RunInfo]) manifest_utils_cmd.add([ "--manifest-path", apk_info.manifest, "--package-output", package_file.as_output(), "--target-package-output", target_package_file.as_output(), "--instrumentation-test-runner-output", test_runner_file.as_output(), ]) ctx.actions.run(manifest_utils_cmd, category = "get_manifest_info") cmd.extend( [ "--test-package-name", cmd_args(package_file, format = "@{}"), "--target-package-name", cmd_args(target_package_file, format = "@{}"), "--test-runner", cmd_args(test_runner_file, format = "@{}"), ], ) if ctx.attrs.instrumentation_test_listener_class != None: cmd.extend(["--extra-instrumentation-test-listener", ctx.attrs.instrumentation_test_listener_class]) if ctx.attrs.clear_package_data: cmd.append("--clear-package-data") if ctx.attrs.disable_animations: cmd.append("--disable-animations") if ctx.attrs.collect_tombstones: cmd.append("--collect-tombstones") if ctx.attrs.record_video: cmd.append("--record-video") if ctx.attrs.log_extractors: for arg_name, arg_value in ctx.attrs.log_extractors.items(): cmd.extend( [ "--log-extractor", cmd_args([arg_name, arg_value], delimiter = "="), ], ) cmd.extend( [ "--adb-executable-path", "required_but_unused", "--instrumentation-apk-path", apk_info.apk, ], ) test_info = ExternalRunnerTestInfo( type = "android_instrumentation", command = cmd, env = env, labels = ctx.attrs.labels, contacts = ctx.attrs.contacts, run_from_project_root = True, use_project_relative_paths = True, executor_overrides = _compute_executor_overrides(ctx, android_toolchain.instrumentation_test_can_run_locally), local_resources = { "android_emulator": None if ctx.attrs._android_emulators == None else ctx.attrs._android_emulators.label, }, required_local_resources = [RequiredTestLocalResource("android_emulator", listing = True, execution = True)], ) classmap_source_info = [ctx.attrs.apk[JavaClassToSourceMapInfo]] if JavaClassToSourceMapInfo in ctx.attrs.apk else [] test_info, run_info = inject_test_run_info(ctx, test_info) # We append additional args so that "buck2 run" will work with sane defaults run_info.args.add(cmd_args(["--auto-run-on-connected-device", "--output", ".", "--adb-executable-path", "adb"])) return [ test_info, run_info, DefaultInfo(), ] + classmap_source_info def _compute_executor_overrides(ctx: AnalysisContext, instrumentation_test_can_run_locally: bool) -> dict[str, CommandExecutorConfig]: remote_execution_properties = { "platform": _compute_emulator_platform(ctx.attrs.labels or []), "subplatform": _compute_emulator_subplatform(ctx.attrs.labels or []), } re_emulator_abi = _compute_emulator_abi(ctx.attrs.labels or []) if re_emulator_abi != None: remote_execution_properties["abi"] = re_emulator_abi default_executor_override = CommandExecutorConfig( local_enabled = instrumentation_test_can_run_locally, remote_enabled = True, remote_execution_properties = remote_execution_properties, remote_execution_use_case = _compute_re_use_case(ctx.attrs.labels or []), ) dynamic_listing_executor_override = default_executor_override test_execution_executor_override = default_executor_override if ctx.attrs.re_caps and ctx.attrs.re_use_case: if "dynamic-listing" in ctx.attrs.re_caps and "dynamic-listing" in ctx.attrs.re_use_case: _validate_executor_override_re_config(ctx.attrs.re_caps["dynamic-listing"], ctx.attrs.re_use_case["dynamic-listing"]) dynamic_listing_executor_override = CommandExecutorConfig( local_enabled = instrumentation_test_can_run_locally, remote_enabled = True, remote_execution_properties = ctx.attrs.re_caps["dynamic-listing"], remote_execution_use_case = ctx.attrs.re_use_case["dynamic-listing"], meta_internal_extra_params = ctx.attrs.meta_internal_extra_params, ) if "test-execution" in ctx.attrs.re_caps and "test-execution" in ctx.attrs.re_use_case: _validate_executor_override_re_config(ctx.attrs.re_caps["test-execution"], ctx.attrs.re_use_case["test-execution"]) test_execution_executor_override = CommandExecutorConfig( local_enabled = instrumentation_test_can_run_locally, remote_enabled = True, remote_execution_properties = ctx.attrs.re_caps["test-execution"], remote_execution_use_case = ctx.attrs.re_use_case["test-execution"], meta_internal_extra_params = ctx.attrs.meta_internal_extra_params, ) return { "android-emulator": default_executor_override, "dynamic-listing": dynamic_listing_executor_override, "static-listing": CommandExecutorConfig( ## This was set to True as some point and it was causing listing to happen locally, ## which is one of the contributing factors to S504068. local_enabled = instrumentation_test_can_run_locally, remote_enabled = True, remote_execution_properties = { "platform": "linux-remote-execution", }, remote_execution_use_case = "buck2-default", ), "test-execution": test_execution_executor_override, } def _compute_emulator_abi(labels: list[str]): emulator_abi_labels = [label for label in labels if label.startswith(ANDROID_EMULATOR_ABI_LABEL_PREFIX)] expect(len(emulator_abi_labels) <= 1, "multiple '{}' labels were found:[{}], there must be only one!".format(ANDROID_EMULATOR_ABI_LABEL_PREFIX, ", ".join(emulator_abi_labels))) if len(emulator_abi_labels) == 0: return None else: # len(emulator_abi_labels) == 1: return emulator_abi_labels[0].replace(ANDROID_EMULATOR_ABI_LABEL_PREFIX, "") # replicating the logic in https://fburl.com/code/1fqowxu4 to match buck1's behavior def _compute_emulator_subplatform(labels: list[str]) -> str: emulator_subplatform_labels = [label for label in labels if label.startswith("re_emulator_")] expect(len(emulator_subplatform_labels) <= 1, "multiple 're_emulator_' labels were found:[{}], there must be only one!".format(", ".join(emulator_subplatform_labels))) if len(emulator_subplatform_labels) == 0: return DEFAULT_ANDROID_SUBPLATFORM else: # len(emulator_subplatform_labels) == 1: return emulator_subplatform_labels[0].replace("re_emulator_", "") def _compute_emulator_platform(labels: list[str]) -> str: emulator_platform_labels = [label for label in labels if label.startswith("re_platform_")] expect(len(emulator_platform_labels) <= 1, "multiple 're_platform_' labels were found:[{}], there must be only one!".format(", ".join(emulator_platform_labels))) if len(emulator_platform_labels) == 0: return DEFAULT_ANDROID_PLATFORM else: # len(emulator_platform_labels) == 1: return emulator_platform_labels[0].replace("re_platform_", "") def _compute_re_use_case(labels: list[str]) -> str: re_use_case_labels = [label for label in labels if label.startswith("re_opts_use_case=")] expect(len(re_use_case_labels) <= 1, "multiple 're_opts_use_case' labels were found:[{}], there must be only one!".format(", ".join(re_use_case_labels))) if len(re_use_case_labels) == 0: return DEFAULT_ANDROID_INSTRUMENTATION_TESTS_USE_CASE else: # len(re_use_case_labels) == 1: return re_use_case_labels[0].replace("re_opts_use_case=", "") def _validate_executor_override_re_config(re_caps: dict[str, str], re_use_case: str): expect(re_use_case in SUPPORTED_USE_CASES, "Unexpected {} use case found, value is expected to be on of the following: {}", re_use_case, ", ".join(SUPPORTED_USE_CASES)) if "pool" in re_caps: expect(re_caps["pool"] in SUPPORTED_POOLS, "Unexpected {} pool found, value is expected to be on of the following: {}", re_caps["pool"], ", ".join(SUPPORTED_POOLS)) if "platform" in re_caps: expect(re_caps["platform"] in SUPPORTED_PLATFORMS, "Unexpected {} platform found, value is expected to be on of the following: {}", re_caps["platform"], ", ".join(SUPPORTED_PLATFORMS))

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server