BEGIN_PUBLIC

Add support for testing rules_cc's new toolchains with rules_testing.
END_PUBLIC

PiperOrigin-RevId: 608769646
Change-Id: I1a698355e5e977cc86eedc7cf6e8e0f888593cb8
This commit is contained in:
Googler 2024-02-20 15:53:42 -08:00 committed by Copybara-Service
parent 833f17060c
commit 2e780ceda9
9 changed files with 454 additions and 5 deletions

View File

@ -12,3 +12,4 @@ use_repo(cc_configure, "local_config_cc_toolchains")
register_toolchains("@local_config_cc_toolchains//:all")
bazel_dep(name = "bazel_skylib", version = "1.3.0", dev_dependency = True)
bazel_dep(name = "rules_testing", version = "0.6.0", dev_dependency = True)

View File

@ -90,3 +90,10 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_
rules_proto_dependencies()
rules_proto_toolchains()
http_archive(
name = "rules_testing",
sha256 = "02c62574631876a4e3b02a1820cb51167bb9cdcdea2381b2fa9d9b8b11c407c4",
strip_prefix = "rules_testing-0.6.0",
url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.6.0/rules_testing-v0.6.0.tar.gz",
)

View File

@ -17,7 +17,10 @@
# that can access the providers directly.
# Once it's stabilized, we *may* consider opening up parts of the API, or we may
# decide to just require users to use the public user-facing rules.
visibility("//third_party/bazel_rules/rules_cc/toolchains/...")
visibility([
"//cc/toolchains/...",
"//tests/...",
])
# Note that throughout this file, we never use a list. This is because mutable
# types cannot be stored in depsets. Thus, we type them as a sequence in the
@ -120,12 +123,11 @@ ActionConfigInfo = provider(
# @unsorted-dict-items
fields = {
"label": "(Label) The label defining this provider. Place in error messages to simplify debugging",
"action_name": "(str) The name of the action",
"action": "(ActionTypeInfo) The name of the action",
"enabled": "(bool) If True, this action is enabled unless a rule type explicitly marks it as unsupported",
"tools": "(Sequence[ToolInfo]) The tool applied to the action will be the first tool in the sequence with a feature set that matches the feature configuration",
"flag_sets": "(depset[FlagSetInfo]) Set of flag sets the action sets",
"implies_features": "(depset[FeatureInfo]) Set of features implied by this action config",
"implies_action_configs": "(depset[ActionConfigInfo]) Set of action configs enabled by this action config",
"flag_sets": "(Sequence[FlagSetInfo]) Set of flag sets the action sets",
"implies": "(depset[FeatureInfo]) Set of features implied by this action config",
"files": "(depset[File]) The files required to run these actions",
},
)

View File

View File

@ -0,0 +1,34 @@
load("@rules_testing//lib:util.bzl", "util")
load("//cc/toolchains:actions.bzl", "cc_action_type", "cc_action_type_set")
load("//tests/rule_based_toolchain:analysis_test_suite.bzl", "analysis_test_suite")
load(":actions_test.bzl", "TARGETS", "TESTS")
util.helper_target(
cc_action_type,
name = "c_compile",
action_name = "c_compile",
visibility = ["//tests/rule_based_toolchain:__subpackages__"],
)
util.helper_target(
cc_action_type,
name = "cpp_compile",
action_name = "cpp_compile",
visibility = ["//tests/rule_based_toolchain:__subpackages__"],
)
util.helper_target(
cc_action_type_set,
name = "all_compile",
actions = [
":c_compile",
":cpp_compile",
],
visibility = ["//tests/rule_based_toolchain:__subpackages__"],
)
analysis_test_suite(
name = "test_suite",
targets = TARGETS,
tests = TESTS,
)

View File

@ -0,0 +1,43 @@
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for actions for the rule based toolchain."""
load(
"//cc/toolchains:cc_toolchain_info.bzl",
"ActionTypeInfo",
"ActionTypeSetInfo",
)
visibility("private")
def _test_action_types_impl(env, targets):
env.expect.that_target(targets.c_compile).provider(ActionTypeInfo) \
.name().equals("c_compile")
env.expect.that_target(targets.cpp_compile).provider(ActionTypeSetInfo) \
.actions().contains_exactly([targets.cpp_compile.label])
env.expect.that_target(targets.all_compile).provider(ActionTypeSetInfo) \
.actions().contains_exactly([
targets.c_compile.label,
targets.cpp_compile.label,
])
TARGETS = [
":c_compile",
":cpp_compile",
":all_compile",
]
TESTS = {
"actions_test": _test_action_types_impl,
}

View File

@ -0,0 +1,50 @@
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Test suites for the rule based toolchain."""
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
load(":subjects.bzl", "FACTORIES")
visibility("//tests/rule_based_toolchain/...")
_DEFAULT_TARGET = "//tests/rule_based_toolchain/actions:c_compile"
# Tests of internal starlark functions will often not require any targets,
# but analysis_test requires at least one, so we pick an arbitrary one.
def analysis_test_suite(name, tests, targets = [_DEFAULT_TARGET]):
"""A test suite for the internals of the toolchain.
Args:
name: (str) The name of the test suite.
tests: (dict[str, fn]) A mapping from test name to implementations.
targets: (List[Label|str]) List of targets accessible to the test.
"""
targets = [native.package_relative_label(target) for target in targets]
test_case_names = []
for test_name, impl in tests.items():
if not test_name.endswith("_test"):
fail("Expected test keys to end with '_test', got test case %r" % test_name)
test_case_names.append(":" + test_name)
analysis_test(
name = test_name,
impl = impl,
provider_subject_factories = FACTORIES,
targets = {label.name: label for label in targets},
)
native.test_suite(
name = name,
tests = test_case_names,
)

View File

@ -0,0 +1,127 @@
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generates provider factories."""
load("@bazel_skylib//lib:structs.bzl", "structs")
load("@rules_testing//lib:truth.bzl", "subjects")
visibility("private")
def generate_factory(type, name, attrs):
"""Generates a factory for a custom struct.
There are three reasons we need to do so:
1. It's very difficult to read providers printed by these types.
eg. If you have a 10 layer deep diamond dependency graph, and try to
print the top value, the bottom value will be printed 2^10 times.
2. Collections of subjects are not well supported by rules_testing
eg. `FeatureInfo(flag_sets = [FlagSetInfo(...)])`
(You can do it, but the inner values are just regular bazel structs and
you can't do fluent assertions on them).
3. Recursive types are not supported at all
eg. `FeatureInfo(implies = depset([FeatureInfo(...)]))`
To solve this, we create a factory that:
* Validates that the types of the children are correct.
* Inlines providers to their labels when unambiguous.
For example, given:
```
foo = FeatureInfo(name = "foo", label = Label("//:foo"))
bar = FeatureInfo(..., implies = depset([foo]))
```
It would convert itself a subject for the following struct:
`FeatureInfo(..., implies = depset([Label("//:foo")]))`
Args:
type: (type) The type to create a factory for (eg. FooInfo)
name: (str) The name of the type (eg. "FooInfo")
attrs: (dict[str, Factory]) The attributes associated with this type.
Returns:
A struct `FooFactory` suitable for use with
* `analysis_test(provider_subject_factories=[FooFactory])`
* `generate_factory(..., attrs=dict(foo = FooFactory))`
* `ProviderSequence(FooFactory)`
* `DepsetSequence(FooFactory)`
"""
attrs["label"] = subjects.label
want_keys = sorted(attrs.keys())
def validate(*, value, meta):
got_keys = sorted(structs.to_dict(value).keys())
if got_keys != want_keys:
meta.add_failure("Wanted a %s with keys %r, got %r" % (name, want_keys, got_keys), "")
def type_factory(value, *, meta):
validate(value = value, meta = meta)
transformed_value = {}
transformed_factories = {}
for field, factory in attrs.items():
field_value = getattr(value, field)
# If it's a type generated by generate_factory, inline it.
if hasattr(factory, "factory"):
factory.validate(value = field_value, meta = meta.derive(field))
transformed_value[field] = field_value.label
transformed_factories[field] = subjects.label
else:
transformed_value[field] = field_value
transformed_factories[field] = factory
return subjects.struct(
struct(**transformed_value),
meta = meta,
attrs = transformed_factories,
)
return struct(
type = type,
name = name,
factory = type_factory,
validate = validate,
)
def _provider_collection(element_factory, fn):
def factory(value, *, meta):
value = fn(value)
# Validate that it really is the correct type
for i in range(len(value)):
element_factory.validate(
value = value[i],
meta = meta.derive("offset({})".format(i)),
)
# Inline the providers to just labels.
return subjects.collection([v.label for v in value], meta = meta)
return factory
# This acts like a class, so we name it like one.
# buildifier: disable=name-conventions
ProviderSequence = lambda element_factory: _provider_collection(
element_factory,
fn = lambda x: list(x),
)
# buildifier: disable=name-conventions
ProviderDepset = lambda element_factory: _provider_collection(
element_factory,
fn = lambda x: x.to_list(),
)

View File

@ -0,0 +1,185 @@
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Test subjects for cc_toolchain_info providers."""
load("@rules_testing//lib:truth.bzl", "subjects")
load(
"//cc/toolchains:cc_toolchain_info.bzl",
"ActionConfigInfo",
"ActionConfigSetInfo",
"ActionTypeInfo",
"ActionTypeSetInfo",
"FeatureConstraintInfo",
"FeatureInfo",
"FeatureSetInfo",
"FlagGroupInfo",
"FlagSetInfo",
"MutuallyExclusiveCategoryInfo",
"ToolInfo",
)
load(":generate_factory.bzl", "ProviderDepset", "ProviderSequence", "generate_factory")
visibility("private")
# buildifier: disable=name-conventions
_ActionTypeFactory = generate_factory(
ActionTypeInfo,
"ActionTypeInfo",
dict(
name = subjects.str,
),
)
# buildifier: disable=name-conventions
_ActionTypeSetFactory = generate_factory(
ActionTypeSetInfo,
"ActionTypeInfo",
dict(
actions = ProviderDepset(_ActionTypeFactory),
),
)
# buildifier: disable=name-conventions
_MutuallyExclusiveCategoryFactory = generate_factory(
MutuallyExclusiveCategoryInfo,
"MutuallyExclusiveCategoryInfo",
dict(name = subjects.str),
)
_FEATURE_FLAGS = dict(
name = subjects.str,
enabled = subjects.bool,
flag_sets = None,
implies = None,
requires_any_of = None,
provides = ProviderSequence(_MutuallyExclusiveCategoryFactory),
known = subjects.bool,
overrides = None,
)
# Break the dependency loop.
# buildifier: disable=name-conventions
_FakeFeatureFactory = generate_factory(
FeatureInfo,
"FeatureInfo",
_FEATURE_FLAGS,
)
# buildifier: disable=name-conventions
_FeatureSetFactory = generate_factory(
FeatureSetInfo,
"FeatureSetInfo",
dict(features = _FakeFeatureFactory),
)
# buildifier: disable=name-conventions
_FeatureConstraintFactory = generate_factory(
FeatureConstraintInfo,
"FeatureConstraintInfo",
dict(
all_of = ProviderDepset(_FakeFeatureFactory),
none_of = ProviderDepset(_FakeFeatureFactory),
),
)
# buildifier: disable=name-conventions
_FlagGroupFactory = generate_factory(
FlagGroupInfo,
"FlagGroupInfo",
dict(
flags = subjects.collection,
),
)
# buildifier: disable=name-conventions
_FlagSetFactory = generate_factory(
FlagSetInfo,
"FlagSetInfo",
dict(
actions = ProviderDepset(_ActionTypeFactory),
# Use a collection here because we don't want to
flag_groups = subjects.collection,
requires_any_of = ProviderSequence(_FeatureConstraintFactory),
),
)
# buildifier: disable=name-conventions
_FeatureFactory = generate_factory(
FeatureInfo,
"FeatureInfo",
_FEATURE_FLAGS | dict(
implies = ProviderDepset(_FakeFeatureFactory),
requires_any_of = ProviderSequence(_FeatureSetFactory),
overrides = _FakeFeatureFactory,
),
)
# buildifier: disable=name-conventions
_ToolFactory = generate_factory(
ToolInfo,
"ToolInfo",
dict(
exe = subjects.file,
runifles = subjects.depset_file,
requires_any_of = ProviderSequence(_FeatureConstraintFactory),
),
)
# buildifier: disable=name-conventions
_ActionConfigFactory = generate_factory(
ActionConfigInfo,
"ActionConfigInfo",
dict(
action = _ActionTypeFactory,
enabled = subjects.bool,
tools = ProviderSequence(_ToolFactory),
flag_sets = ProviderSequence(_FlagSetFactory),
implies = ProviderDepset(_FeatureFactory),
files = subjects.depset_file,
),
)
def _action_config_set_factory_impl(value, *, meta):
# We can't use the usual strategy of "inline the labels" since all labels
# are the same.
transformed = {}
for ac in value.action_configs.to_list():
key = ac.action.label
if key in transformed:
meta.add_failure("Action declared twice in action config", key)
transformed[key] = _ActionConfigFactory.factory(
value = ac,
meta = meta.derive(".get({})".format(key)),
)
return transformed
# buildifier: disable=name-conventions
_ActionConfigSetFactory = struct(
type = ActionConfigSetInfo,
name = "ActionConfigSetInfo",
factory = _action_config_set_factory_impl,
)
FACTORIES = [
_ActionTypeFactory,
_ActionTypeSetFactory,
_FlagGroupFactory,
_FlagSetFactory,
_MutuallyExclusiveCategoryFactory,
_FeatureFactory,
_FeatureConstraintFactory,
_FeatureSetFactory,
_ToolFactory,
_ActionConfigSetFactory,
]