| # Copyright 2023 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Defines the base fuchsia_test_group builddef.""" |
| |
| #TODO: Make this not private |
| load("@fuchsia_sdk//fuchsia/private:providers.bzl", "FuchsiaPackageInfo", "FuchsiaProductBundleInfo", "FuchsiaRunnableInfo") |
| load("@fuchsia_infra_workspace//:workspace_info.bzl", "WORKSPACE_OUTPUT_BASE") |
| load("//infra/private:providers.bzl", "FuchsiaTestEnvironmentInfo") |
| load("//infra/private:utils.bzl", "collect_runfiles", "get_project_execroot", "get_target_execroot") |
| load(":common.bzl", "BUILDDEF", "generate_FuchsiaTestGroupInfo", "write_test_manifests") |
| load(":flags.bzl", "ENABLED_EXPERIMENTS", "get_effective_experiments") |
| load(":fuchsia_test_target.bzl", "TestTarget") |
| |
| def fuchsia_test_group( |
| *, |
| name, |
| test_target, |
| product_bundle, |
| environment_setup = [], |
| deps = [], |
| non_test_deps = [], |
| asan_enabled = False, |
| hermetic = True, |
| tags = [], |
| **kwargs): |
| """Defines a collection of tests that can be run against a fuchsia target. |
| |
| This lets your specify a collection of tests (typically |
| `fuchsia_test_package` targets) to be scheduled against a `test_target` that |
| is provisioned with `product_bundle`. |
| |
| The target produced by this rule can be used in `fuchsia_builder_group` to |
| be included in a builder's CQ/CI test runs. |
| An infrastructure CQ/CI run can also be simulated by running `bazel test` on |
| the test group target. |
| |
| Example usage: |
| ``` |
| load("@fuchsia_infra//:infra.bzl", "TEST_TARGET", "fuchsia_test_group") |
| |
| fuchsia_test_group( |
| name = "tests_qemu_asan", |
| test_target = TEST_TARGET.QEMU, |
| product_bundle = "core.x64", |
| asan_enabled = True, |
| drivers = [ "//src/foo:driver_pkg.component" ], |
| deps = [ "//src/foo:test_package" ], |
| ) |
| |
| // In this example, an infrastructure test execution run can be simulated by |
| // running `bazel test :tests_qemu_asan`. |
| ``` |
| |
| Args: |
| name: The target name. |
| test_target: Specify the `TEST_TARGET` device to test against. Examples: |
| - `TEST_TARGET.QEMU` |
| - `TEST_TARGET.VIM3` |
| product_bundle: Specify the product bundle string or label to provision |
| the `test_target`. |
| environment_setup: A list of target actions to run before tests. |
| Requires `hermetic = False`. |
| deps: A list of tests to run in this test group. |
| non_test_deps: Non-test runtime file dependencies to include in the |
| testing environment. |
| asan_enabled: Whether to build the `deps` with asan. |
| hermetic: Controls whether this test group is automatically resharded. |
| tags: Typical meaning in Bazel. |
| **kwargs: extra attributes to pass along to the build rule. |
| """ |
| |
| _test_target = TestTarget(None, test_target) |
| if _test_target.is_host: |
| fail("Please use `fuchsia_host_test_group` instead.") |
| if not _test_target.is_fuchsia: |
| fail("Incompatible test target: `%s`" % _test_target.key) |
| |
| # TODO(http://b/329311059): This is a temporary measure. Eventually we'll |
| # deprecate specifying the product bundle as a string (eg: "core.x64") and |
| # move to using labels instead (eg: "@fuchsia_products//:core.x64"). |
| remote_product_bundle = None |
| local_product_bundle = None |
| if _is_remote_pb(product_bundle): |
| remote_product_bundle = product_bundle |
| else: |
| local_product_bundle = product_bundle |
| |
| fuchsia_test_group_test( |
| name = name, |
| test_target = test_target, |
| product_bundle = remote_product_bundle, |
| local_product_bundle = local_product_bundle, |
| environment_setup = environment_setup, |
| deps = deps, |
| non_test_deps = non_test_deps + ["@fuchsia_sdk//:all_files"], |
| hermetic = hermetic, |
| asan_enabled = asan_enabled, |
| tags = tags + BUILDDEF.tags, |
| **kwargs |
| ) |
| |
| _REMOTE_PB_CHAR_ALLOWLIST = "abcdefghijklmnopqrstuvwxyz0123456789-" |
| |
| def _is_remote_pb(pb): |
| # All remote pbs have the form r"[a-z0-9-]+\.[a-z0-9-]+". |
| product, _, board = pb.partition(".") |
| return product and board and not [ |
| local_char |
| for local_char in (product + board).elems() |
| if local_char not in _REMOTE_PB_CHAR_ALLOWLIST |
| ] |
| |
| def _build_with_asan_transition_impl(settings, attr): |
| current_features = settings["//command_line_option:features"] |
| |
| # Don't add asan. |
| if not attr.asan_enabled: |
| return {} |
| |
| # Don't add asan twice. |
| if "asan" in current_features: |
| return {} |
| |
| return {"//command_line_option:features": current_features + ["asan"]} |
| |
| build_with_asan_transition = transition( |
| implementation = _build_with_asan_transition_impl, |
| inputs = ["//command_line_option:features"], |
| outputs = ["//command_line_option:features"], |
| ) |
| |
| def _fuchsia_test_group_impl(ctx): |
| sdk = ctx.toolchains["@fuchsia_sdk//fuchsia:toolchain"] |
| test_target = TestTarget(ctx, ctx.attr.test_target) |
| _validate_direct_dependencies(test_target, ctx.attr.deps) |
| |
| if ctx.attr.environment_setup and ctx.attr.hermetic: |
| fail("Specifying `environment_setup` requires `hermetic = False`.") |
| |
| # Non-hermetic test groups are not automatically resharded. |
| # Using a experiment flag that's unique to this target is enough for this. |
| # See `test_group_primary_key`. |
| non_hermetic_unique_experiment = [] |
| if not ctx.attr.hermetic: |
| non_hermetic_unique_experiment = ["shard-%s-independent" % str(ctx.label)] |
| |
| # Account for experiments. |
| experiments = get_effective_experiments( |
| test_target, |
| ENABLED_EXPERIMENTS + ctx.attr.experiments_INTERNAL_USE_ONLY + non_hermetic_unique_experiment, |
| ) |
| |
| local_pb = None |
| if ctx.attr.local_product_bundle: |
| local_pb = ctx.attr.local_product_bundle[FuchsiaProductBundleInfo].product_bundle |
| |
| # Generate FuchsiaTestGroupInfo. |
| test_group = generate_FuchsiaTestGroupInfo( |
| ctx = ctx, |
| name = str(ctx.label), |
| environment = FuchsiaTestEnvironmentInfo( |
| exec_cpu = sdk.exec_cpu, |
| test_target = test_target, |
| product_bundle = ctx.attr.product_bundle or local_pb or None, |
| experiments = experiments, |
| ), |
| runfiles = [ |
| ctx.attr._run_tests_tool, |
| local_pb, |
| ctx.attr.environment_setup, |
| ctx.attr.non_test_deps, |
| ctx.attr.deps, |
| ], |
| environment_setup = [ |
| struct( |
| name = str(setup_dep.label), |
| executable = setup_dep[DefaultInfo].files_to_run.executable, |
| execroot = get_target_execroot(ctx, setup_dep), |
| ) |
| for setup_dep in ctx.attr.environment_setup |
| ], |
| tests = [ |
| struct( |
| name = str(dep.label), |
| executable = dep[DefaultInfo].files_to_run.executable, |
| execroot = get_target_execroot(ctx, dep), |
| ) |
| for dep in ctx.attr.deps |
| ], |
| ) |
| |
| # Write test group configurations and a high level manifest. |
| test_manifest = write_test_manifests(ctx, test_group) |
| |
| # Generate the local test orchestration executable for replicating infra testing. |
| executable, executable_runfiles = _local_test_orchestration_executable(ctx, test_manifest) |
| |
| return [ |
| test_group, |
| DefaultInfo( |
| executable = executable, |
| files = depset([ |
| test_manifest, |
| test_group.orchestrate_manifest, |
| test_group.subrunner_manifest, |
| executable, |
| ]), |
| runfiles = executable_runfiles.merge(test_group.runfiles), |
| ), |
| ] |
| |
| def _validate_direct_dependencies(test_target, deps): |
| for dep in deps: |
| if FuchsiaRunnableInfo in dep: |
| # We cannot run `fuchsia_package`, only `fuchsia_test_package`. |
| if not dep[FuchsiaRunnableInfo].is_test: |
| fail("%s is not a test" % dep) |
| |
| if FuchsiaPackageInfo in dep: |
| # `fuchsia_test_package` cannot be run against host. |
| if test_target.is_host: |
| fail("%s is incompatible with `%s`" % (dep, test_target.key)) |
| |
| # `fuchsia_test_package` must be built against the correct cpu architecture. |
| expected_cpu = test_target.fuchsia_cpu |
| actual_cpu = dep[FuchsiaPackageInfo].fuchsia_cpu |
| if expected_cpu != actual_cpu: |
| fail("%s (cpu %s) is incompatible with `%s` (cpu %s)" % ( |
| dep, |
| actual_cpu, |
| test_target.key, |
| expected_cpu, |
| )) |
| |
| def _local_test_orchestration_executable(ctx, test_manifest): |
| """Generates an executable capable of running the global test manifest.""" |
| |
| # Generate an executable for running the test groups. |
| executable = ctx.actions.declare_file("run_%s.sh" % ctx.attr.name) |
| |
| ctx.actions.write( |
| executable, |
| """#!/bin/bash |
| %s --manifest %s $@""" % ( |
| WORKSPACE_OUTPUT_BASE + "/" + get_project_execroot(ctx) + "/" + ctx.executable._run_tests_tool.path, |
| WORKSPACE_OUTPUT_BASE + "/" + get_project_execroot(ctx) + "/" + test_manifest.path, |
| ), |
| is_executable = True, |
| ) |
| |
| # Collect runfiles for the executable. |
| runfiles = collect_runfiles( |
| ctx, |
| executable, |
| test_manifest, |
| ctx.attr._run_tests_tool, |
| ) |
| return executable, runfiles |
| |
| fuchsia_test_group_test = rule( |
| implementation = _fuchsia_test_group_impl, |
| doc = """Defines a collection of tests to be run together in a shared environment.""", |
| toolchains = ["@fuchsia_sdk//fuchsia:toolchain"], |
| attrs = { |
| "test_target": attr.string( |
| doc = "Specify a `FUCHSIA_TEST_TARGET` to run the tests on.", |
| ), |
| # TODO(https://fxbug.dev/106473): Product bundles should already be |
| # fetched after checkout. |
| "product_bundle": attr.string( |
| doc = "Specify a product bundle name for provisioning the `test_target`.", |
| ), |
| "local_product_bundle": attr.label( |
| doc = "Specify a product bundle target for provisioning the `test_target`.", |
| providers = [FuchsiaProductBundleInfo], |
| ), |
| "environment_setup": attr.label_list( |
| doc = "A list of target actions to run before tests. Requires `hermetic = False`.", |
| cfg = build_with_asan_transition, |
| ), |
| "deps": attr.label_list( |
| doc = "The tests to include in this test group.", |
| cfg = build_with_asan_transition, |
| ), |
| "hermetic": attr.bool( |
| doc = "Controls whether this test group is automatically resharded.", |
| ), |
| "asan_enabled": attr.bool( |
| doc = "Determines if the tests in this group should run under ASAN.", |
| ), |
| "non_test_deps": attr.label_list( |
| doc = "Non-test runtime file dependencies to include in the testing environment.", |
| ), |
| "experiments_INTERNAL_USE_ONLY": attr.string_list( |
| doc = "Configure experiments to enable for this test group. Unsupported, for internal use only.", |
| ), |
| "_allowlist_function_transition": attr.label( |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", |
| ), |
| "_run_tests_tool": attr.label( |
| doc = "The command used to run all test groups for local test orchestration only, before _run_test_group_tool.", |
| default = "//infra/tools:run_tests", |
| executable = True, |
| cfg = "target", |
| ), |
| } | BUILDDEF.attrs, |
| test = True, |
| ) |