blob: af7c8abbe4805221a6c49b672ced33cb9b3849e6 [file] [log] [blame]
# 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,
)