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

PiperOrigin-RevId: 608769646
Change-Id: I1a698355e5e977cc86eedc7cf6e8e0f888593cb8
diff --git a/MODULE.bazel b/MODULE.bazel
index f6beb62..f664840 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -12,3 +12,4 @@
 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)
diff --git a/WORKSPACE b/WORKSPACE
index 1bbf0e2..875888e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -90,3 +90,10 @@
 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",
+)
diff --git a/cc/toolchains/cc_toolchain_info.bzl b/cc/toolchains/cc_toolchain_info.bzl
index 4c4d8a1..3f1b9e6 100644
--- a/cc/toolchains/cc_toolchain_info.bzl
+++ b/cc/toolchains/cc_toolchain_info.bzl
@@ -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 @@
     # @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",
     },
 )
diff --git a/tests/rule_based_toolchain/BUILD b/tests/rule_based_toolchain/BUILD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rule_based_toolchain/BUILD
diff --git a/tests/rule_based_toolchain/actions/BUILD b/tests/rule_based_toolchain/actions/BUILD
new file mode 100644
index 0000000..4e45f7e
--- /dev/null
+++ b/tests/rule_based_toolchain/actions/BUILD
@@ -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,
+)
diff --git a/tests/rule_based_toolchain/actions/actions_test.bzl b/tests/rule_based_toolchain/actions/actions_test.bzl
new file mode 100644
index 0000000..284ed9a
--- /dev/null
+++ b/tests/rule_based_toolchain/actions/actions_test.bzl
@@ -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,
+}
diff --git a/tests/rule_based_toolchain/analysis_test_suite.bzl b/tests/rule_based_toolchain/analysis_test_suite.bzl
new file mode 100644
index 0000000..01ba4e2
--- /dev/null
+++ b/tests/rule_based_toolchain/analysis_test_suite.bzl
@@ -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,
+    )
diff --git a/tests/rule_based_toolchain/generate_factory.bzl b/tests/rule_based_toolchain/generate_factory.bzl
new file mode 100644
index 0000000..72c9680
--- /dev/null
+++ b/tests/rule_based_toolchain/generate_factory.bzl
@@ -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(),
+)
diff --git a/tests/rule_based_toolchain/subjects.bzl b/tests/rule_based_toolchain/subjects.bzl
new file mode 100644
index 0000000..b330093
--- /dev/null
+++ b/tests/rule_based_toolchain/subjects.bzl
@@ -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,
+]