// Copyright 2018 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.
package com.google.devtools.build.lib.rules.cpp;

import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.testutil.MoreAsserts.assertThrows;
import static org.junit.Assert.fail;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.packages.SkylarkInfo;
import com.google.devtools.build.lib.packages.StructImpl;
import com.google.devtools.build.lib.packages.util.MockCcSupport;
import com.google.devtools.build.lib.packages.util.ResourceLoader;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ActionConfig;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ArtifactNamePattern;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.EnvEntry;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.EnvSet;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Feature;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FlagGroup;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FlagSet;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Tool;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.VariableWithValue;
import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.WithFeatureSet;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringValueParser;
import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
import com.google.devtools.build.lib.skylarkbuildapi.cpp.LibraryToLinkApi;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.SkylarkDict;
import com.google.devtools.build.lib.syntax.SkylarkList;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.testutil.TestConstants;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CompilationModeFlags;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LinkingMode;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.LinkingModeFlags;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.MakeVariable;
import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.ToolPath;
import com.google.protobuf.TextFormat;
import java.io.IOException;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/**
 * Unit tests for the {@code cc_common} Skylark module.
 */
@RunWith(JUnit4.class)
public class SkylarkCcCommonTest extends BuildViewTestCase {

  @Before
  public void setSkylarkSemanticsOptions() throws Exception {
    setSkylarkSemanticsOptions(SkylarkCcCommonTestHelper.CC_SKYLARK_WHITELIST_FLAG);
    invalidatePackages();
  }

  @Test
  public void testGetToolForAction() throws Exception {
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  return struct(",
        "    action_tool_path = cc_common.get_tool_for_action(",
        "        feature_configuration = feature_configuration,",
        "        action_name = 'c++-compile'))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    String actionToolPath = (String) r.get("action_tool_path");
    RuleContext ruleContext = getRuleContext(r);
    CcToolchainProvider toolchain =
        CppHelper.getToolchain(
            ruleContext, ruleContext.getPrerequisite("$cc_toolchain", Mode.TARGET));
    FeatureConfiguration featureConfiguration =
        CcCommon.configureFeaturesOrThrowEvalException(
            ImmutableSet.of(), ImmutableSet.of(), toolchain);
    assertThat(actionToolPath)
        .isEqualTo(featureConfiguration.getToolPathForAction(CppActionNames.CPP_COMPILE));
  }

  @Test
  public void testFeatureConfigurationWithAdditionalEnabledFeature() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(mockToolsConfig, "feature { name: 'foo_feature' }");
    useConfiguration();
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(",
        "      cc_toolchain = toolchain,",
        "      requested_features = ['foo_feature'])",
        "  return struct(",
        "    foo_feature_enabled = cc_common.is_enabled(",
        "        feature_configuration = feature_configuration,",
        "        feature_name = 'foo_feature'))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    boolean fooFeatureEnabled = (boolean) r.get("foo_feature_enabled");
    assertThat(fooFeatureEnabled).isTrue();
  }

  @Test
  public void testFeatureConfigurationWithAdditionalUnsupportedFeature() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(mockToolsConfig, "feature { name: 'foo_feature' }");
    useConfiguration("--features=foo_feature");
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(",
        "      cc_toolchain = toolchain,",
        "      unsupported_features = ['foo_feature'])",
        "  return struct(",
        "    foo_feature_enabled = cc_common.is_enabled(",
        "        feature_configuration = feature_configuration,",
        "        feature_name = 'foo_feature'))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    boolean fooFeatureEnabled = (boolean) r.get("foo_feature_enabled");
    assertThat(fooFeatureEnabled).isFalse();
  }

  @Test
  public void testGetCommandLine() throws Exception {
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  return struct(",
        "    command_line = cc_common.get_memory_inefficient_command_line(",
        "        feature_configuration = feature_configuration,",
        "        action_name = 'c++-link-executable',",
        "        variables = cc_common.empty_variables()))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    SkylarkList<String> commandLine = (SkylarkList<String>) r.get("command_line");
    RuleContext ruleContext = getRuleContext(r);
    CcToolchainProvider toolchain =
        CppHelper.getToolchain(
            ruleContext, ruleContext.getPrerequisite("$cc_toolchain", Mode.TARGET));
    FeatureConfiguration featureConfiguration =
        CcCommon.configureFeaturesOrThrowEvalException(
            ImmutableSet.of(), ImmutableSet.of(), toolchain);
    assertThat(commandLine)
        .containsExactlyElementsIn(
            featureConfiguration.getCommandLine("c++-link-executable", CcToolchainVariables.EMPTY));
  }

  @Test
  public void testGetEnvironment() throws Exception {
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  return struct(",
        "    environment_variables = cc_common.get_environment_variables(",
        "        feature_configuration = feature_configuration,",
        "        action_name = 'c++-compile',",
        "        variables = cc_common.empty_variables()))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    SkylarkDict<String, String> environmentVariables =
        (SkylarkDict<String, String>) r.get("environment_variables");
    RuleContext ruleContext = getRuleContext(r);
    CcToolchainProvider toolchain =
        CppHelper.getToolchain(
            ruleContext, ruleContext.getPrerequisite("$cc_toolchain", Mode.TARGET));
    FeatureConfiguration featureConfiguration =
        CcCommon.configureFeaturesOrThrowEvalException(
            ImmutableSet.of(), ImmutableSet.of(), toolchain);
    assertThat(environmentVariables)
        .containsExactlyEntriesIn(
            featureConfiguration.getEnvironmentVariables(
                CppActionNames.CPP_COMPILE, CcToolchainVariables.EMPTY));
  }

  @Test
  public void testIsEnabled() throws Exception {
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  return struct(",
        "    enabled_feature = cc_common.is_enabled(",
        "        feature_configuration = feature_configuration,",
        "        feature_name = 'libraries_to_link'),",
        "    disabled_feature = cc_common.is_enabled(",
        "        feature_configuration = feature_configuration,",
        "        feature_name = 'wololoo'))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    boolean enabledFeatureIsEnabled = (boolean) r.get("enabled_feature");
    @SuppressWarnings("unchecked")
    boolean disabledFeatureIsDisabled = (boolean) r.get("disabled_feature");
    assertThat(enabledFeatureIsEnabled).isTrue();
    assertThat(disabledFeatureIsDisabled).isFalse();
  }

  @Test
  public void testActionNames() throws Exception {
    scratch.file(
        "a/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");
    scratch.file("tools/build_defs/cc/BUILD");
    scratch.file(
        "tools/build_defs/cc/action_names.bzl",
        ResourceLoader.readFromResources(
            TestConstants.BAZEL_REPO_PATH + "tools/build_defs/cc/action_names.bzl"));

    scratch.file(
        "a/rule.bzl",
        "load('//tools/build_defs/cc:action_names.bzl',",
        "    'C_COMPILE_ACTION_NAME',",
        "    'CPP_COMPILE_ACTION_NAME',",
        "    'LINKSTAMP_COMPILE_ACTION_NAME',",
        "    'CC_FLAGS_MAKE_VARIABLE_ACTION_NAME_ACTION_NAME',",
        "    'CPP_MODULE_CODEGEN_ACTION_NAME',",
        "    'CPP_HEADER_PARSING_ACTION_NAME',",
        "    'CPP_MODULE_COMPILE_ACTION_NAME',",
        "    'ASSEMBLE_ACTION_NAME',",
        "    'PREPROCESS_ASSEMBLE_ACTION_NAME',",
        "    'LTO_INDEXING_ACTION_NAME',",
        "    'LTO_BACKEND_ACTION_NAME',",
        "    'CPP_LINK_EXECUTABLE_ACTION_NAME',",
        "    'CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME',",
        "    'CPP_LINK_NODEPS_DYNAMIC_LIBRARY_ACTION_NAME',",
        "    'CPP_LINK_STATIC_LIBRARY_ACTION_NAME',",
        "    'STRIP_ACTION_NAME')",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  return struct(",
        "      c_compile_action_name=C_COMPILE_ACTION_NAME,",
        "      cpp_compile_action_name=CPP_COMPILE_ACTION_NAME,",
        "      linkstamp_compile_action_name=LINKSTAMP_COMPILE_ACTION_NAME,",
        "      cc_flags_make_variable_action_name_action_name=CC_FLAGS_MAKE_VARIABLE_ACTION_NAME_ACTION_NAME,",
        "      cpp_module_codegen_action_name=CPP_MODULE_CODEGEN_ACTION_NAME,",
        "      cpp_header_parsing_action_name=CPP_HEADER_PARSING_ACTION_NAME,",
        "      cpp_module_compile_action_name=CPP_MODULE_COMPILE_ACTION_NAME,",
        "      assemble_action_name=ASSEMBLE_ACTION_NAME,",
        "      preprocess_assemble_action_name=PREPROCESS_ASSEMBLE_ACTION_NAME,",
        "      lto_indexing_action_name=LTO_INDEXING_ACTION_NAME,",
        "      lto_backend_action_name=LTO_BACKEND_ACTION_NAME,",
        "      cpp_link_executable_action_name=CPP_LINK_EXECUTABLE_ACTION_NAME,",
        "      cpp_link_dynamic_library_action_name=CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME,",
        "      cpp_link_nodeps_dynamic_library_action_name=CPP_LINK_NODEPS_DYNAMIC_LIBRARY_ACTION_NAME,",
        "      cpp_link_static_library_action_name=CPP_LINK_STATIC_LIBRARY_ACTION_NAME,",
        "      strip_action_name=STRIP_ACTION_NAME)",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ")");

    assertThat(getTarget("//a:r")).isNotNull();
    ConfiguredTarget r = getConfiguredTarget("//a:r");
    assertThat(r.get("c_compile_action_name")).isEqualTo(CppActionNames.C_COMPILE);
    assertThat(r.get("cpp_compile_action_name")).isEqualTo(CppActionNames.CPP_COMPILE);
    assertThat(r.get("linkstamp_compile_action_name")).isEqualTo(CppActionNames.LINKSTAMP_COMPILE);
    assertThat(r.get("cc_flags_make_variable_action_name_action_name"))
        .isEqualTo(CppActionNames.CC_FLAGS_MAKE_VARIABLE);
    assertThat(r.get("cpp_module_codegen_action_name"))
        .isEqualTo(CppActionNames.CPP_MODULE_CODEGEN);
    assertThat(r.get("cpp_header_parsing_action_name"))
        .isEqualTo(CppActionNames.CPP_HEADER_PARSING);
    assertThat(r.get("cpp_module_compile_action_name"))
        .isEqualTo(CppActionNames.CPP_MODULE_COMPILE);
    assertThat(r.get("assemble_action_name")).isEqualTo(CppActionNames.ASSEMBLE);
    assertThat(r.get("preprocess_assemble_action_name"))
        .isEqualTo(CppActionNames.PREPROCESS_ASSEMBLE);
    assertThat(r.get("lto_indexing_action_name")).isEqualTo(CppActionNames.LTO_INDEXING);
    assertThat(r.get("lto_backend_action_name")).isEqualTo(CppActionNames.LTO_BACKEND);
    assertThat(r.get("cpp_link_executable_action_name"))
        .isEqualTo(CppActionNames.CPP_LINK_EXECUTABLE);
    assertThat(r.get("cpp_link_dynamic_library_action_name"))
        .isEqualTo(CppActionNames.CPP_LINK_DYNAMIC_LIBRARY);
    assertThat(r.get("cpp_link_nodeps_dynamic_library_action_name"))
        .isEqualTo(CppActionNames.CPP_LINK_NODEPS_DYNAMIC_LIBRARY);
    assertThat(r.get("cpp_link_static_library_action_name"))
        .isEqualTo(CppActionNames.CPP_LINK_STATIC_LIBRARY);
    assertThat(r.get("strip_action_name")).isEqualTo(CppActionNames.STRIP);
  }

  @Test
  public void testEmptyCompileBuildVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(mockToolsConfig, "compiler_flag: '-foo'", "cxx_flag: '-foo_for_cxx_only'");
    useConfiguration();
    SkylarkList<String> commandLine =
        commandLineForVariables(
            CppActionNames.CPP_COMPILE,
            "cc_common.create_compile_variables(",
            "feature_configuration = feature_configuration,",
            "cc_toolchain = toolchain,",
            ")");
    assertThat(commandLine).contains("-foo");
    assertThat(commandLine).doesNotContain("-foo_for_cxx_only");
  }

  @Test
  public void testEmptyCompileBuildVariablesForCxx() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(mockToolsConfig, "compiler_flag: '-foo'", "cxx_flag: '-foo_for_cxx_only'");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "add_legacy_cxx_options = True",
                ")"))
        .containsAllOf("-foo", "-foo_for_cxx_only")
        .inOrder();
  }

  @Test
  public void testCompileBuildVariablesWithSourceFile() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "source_file = 'foo/bar/hello'",
                ")"))
        .containsAllOf("-c", "foo/bar/hello")
        .inOrder();
  }

  @Test
  public void testCompileBuildVariablesWithOutputFile() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "output_file = 'foo/bar/hello.o'",
                ")"))
        .containsAllOf("-o", "foo/bar/hello.o")
        .inOrder();
  }

  @Test
  public void testCompileBuildVariablesForIncludes() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "include_directories = depset(['foo/bar/include'])",
                ")"))
        .contains("-Ifoo/bar/include");
  }

  @Test
  public void testCompileBuildVariablesForDefines() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "preprocessor_defines = depset(['DEBUG_FOO'])",
                ")"))
        .contains("-DDEBUG_FOO");
  }

  @Test
  public void testCompileBuildVariablesForPic() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "use_pic = True",
                ")"))
        .contains("-fPIC");
  }

  @Test
  public void testUserCompileFlags() throws Exception {
    useConfiguration("--noincompatible_disable_depset_in_cc_user_flags");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "user_compile_flags = depset(['-foo'])",
                ")"))
        .contains("-foo");
  }

  @Test
  public void testUserCompileFlagsAsList() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "user_compile_flags = ['-foo']",
                ")"))
        .contains("-foo");
  }

  @Test
  public void testUserCompileFlagsAsDepsetWhenDisabled() throws Exception {
    useConfiguration("--incompatible_disable_depset_in_cc_user_flags");
    reporter.removeHandler(failFastHandler);
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_COMPILE,
                "cc_common.create_compile_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "user_compile_flags = depset(['-foo'])",
                ")"))
        .isNull();
    assertContainsEvent("Passing depset into user flags is deprecated");
  }

  @Test
  public void testEmptyLinkVariables() throws Exception {
    useConfiguration("--linkopt=-foo");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                ")"))
        .contains("-foo");
  }

  @Test
  public void testEmptyLinkVariablesContainSysroot() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "builtin_sysroot: '/foo/bar/sysroot'",
            "feature {",
            "  name: 'sysroot'",
            "  enabled: true",
            "  flag_set {",
            "    action: 'c++-link-executable'",
            "    flag_group {",
            "      expand_if_all_available: 'sysroot'",
            "      flag: '--yolo_sysroot_flag=%{sysroot}'",
            "    }",
            "  }",
            "}");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                ")"))
        .contains("--yolo_sysroot_flag=/foo/bar/sysroot");
  }

  @Test
  public void testLibrarySearchDirectoriesLinkVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "feature {",
            "  name: 'library_search_directories'",
            "  enabled: true",
            "  flag_set {",
            "    action: 'c++-link-executable'",
            "    flag_group {",
            "      expand_if_all_available: 'library_search_directories'",
            "      iterate_over: 'library_search_directories'",
            "      flag: '--library=%{library_search_directories}'",
            "    }",
            "  }",
            "}");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "library_search_directories = depset([ 'a', 'b', 'c' ]),",
                ")"))
        .containsAllOf("--library=a", "--library=b", "--library=c")
        .inOrder();
  }

  @Test
  public void testRuntimeLibrarySearchDirectoriesLinkVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "feature {",
            "  name: 'runtime_library_search_directories'",
            "  enabled: true",
            "  flag_set {",
            "    action: 'c++-link-executable'",
            "    flag_group {",
            "      expand_if_all_available: 'runtime_library_search_directories'",
            "      iterate_over: 'runtime_library_search_directories'",
            "      flag: '--runtime_library=%{runtime_library_search_directories}'",
            "    }",
            "  }",
            "}");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "runtime_library_search_directories = depset([ 'a', 'b', 'c' ]),",
                ")"))
        .containsAllOf("--runtime_library=a", "--runtime_library=b", "--runtime_library=c")
        .inOrder();
  }

  @Test
  public void testUserLinkFlagsLinkVariables() throws Exception {
    useConfiguration("--noincompatible_disable_depset_in_cc_user_flags");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "user_link_flags = depset([ '-avocado' ]),",
                ")"))
        .contains("-avocado");
  }

  @Test
  public void testUserLinkFlagsLinkVariablesAsList() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "user_link_flags = [ '-avocado' ],",
                ")"))
        .contains("-avocado");
  }

  @Test
  public void testUserLinkFlagsLinkVariablesAsDepsetWhenDisabled() throws Exception {
    useConfiguration("--incompatible_disable_depset_in_cc_user_flags");
    reporter.removeHandler(failFastHandler);
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "user_link_flags = depset([ '-avocado' ]),",
                ")"))
        .isNull();
    assertContainsEvent("Passing depset into user flags is deprecated");
  }

  @Test
  public void testIfsoRelatedVariablesAreNotExposed() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "feature {",
            "  name: 'uses_ifso_variables'",
            "  enabled: true",
            "  flag_set {",
            "    action: 'c++-link-dynamic-library'",
            "    flag_group {",
            "      expand_if_all_available: 'generate_interface_library'",
            "      flag: '--generate_interface_library_was_available'",
            "    }",
            "  }",
            "}");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_DYNAMIC_LIBRARY,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                ")"))
        .doesNotContain("--generate_interface_library_was_available");
  }

  @Test
  public void testOutputFileLinkVariables() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "output_file = 'foo/bar/executable',",
                ")"))
        .contains("foo/bar/executable");
  }

  @Test
  public void testParamFileLinkVariables() throws Exception {
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "param_file = 'foo/bar/params',",
                ")"))
        .contains("-Wl,@foo/bar/params");
  }

  @Test
  public void testDefFileLinkVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "feature {",
            "  name: 'def'",
            "  enabled: true",
            "  flag_set {",
            "    action: 'c++-link-executable'",
            "    flag_group {",
            "      expand_if_all_available: 'def_file_path'",
            "      flag: '-qux_%{def_file_path}'",
            "    }",
            "  }",
            "}");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "def_file = 'foo/bar/def',",
                ")"))
        .contains("-qux_foo/bar/def");
  }

  @Test
  public void testMustKeepDebugLinkVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "feature {",
            "  name: 'strip_debug_symbols'",
            "  enabled: true",
            "  flag_set {",
            "    action: 'c++-link-executable'",
            "    flag_group {",
            "      expand_if_all_available: 'strip_debug_symbols'",
            "      flag: '-strip_stuff'",
            "    }",
            "  }",
            "}");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                0,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "must_keep_debug = False,",
                ")"))
        .contains("-strip_stuff");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                1,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "must_keep_debug = True,",
                ")"))
        .doesNotContain("-strip_stuff");
  }

  @Test
  public void testIsLinkingDynamicLibraryLinkVariables() throws Exception {
    useConfiguration("--linkopt=-pie");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                0,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "is_linking_dynamic_library = True,",
                ")"))
        .doesNotContain("-pie");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                1,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "is_linking_dynamic_library = False,",
                ")"))
        .contains("-pie");
  }

  @Test
  public void testIsUsingLinkerLinkVariables() throws Exception {
    useConfiguration("--linkopt=-i_dont_want_to_see_this_on_archiver_command_line");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                0,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "is_using_linker = True,",
                ")"))
        .contains("-i_dont_want_to_see_this_on_archiver_command_line");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                1,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "is_using_linker = False,",
                ")"))
        .doesNotContain("-i_dont_want_to_see_this_on_archiver_command_line");
  }

  @Test
  public void testUseTestOnlyFlagsLinkVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(mockToolsConfig, "test_only_linker_flag: '-im_only_testing_flag'");
    useConfiguration();
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                0,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "use_test_only_flags = False,",
                ")"))
        .doesNotContain("-im_only_testing_flag");
    assertThat(
            commandLineForVariables(
                CppActionNames.CPP_LINK_EXECUTABLE,
                1,
                "cc_common.create_link_variables(",
                "feature_configuration = feature_configuration,",
                "cc_toolchain = toolchain,",
                "use_test_only_flags = True,",
                ")"))
        .contains("-im_only_testing_flag");
  }

  @Test
  public void testIsStaticLinkingModeLinkVariables() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "linking_mode_flags {",
            "  mode: MOSTLY_STATIC",
            "  linker_flag: '-static_linking_mode_flag'",
            "}",
            "linking_mode_flags {",
            "  mode: DYNAMIC",
            "  linker_flag: '-dynamic_linking_mode_flag'",
            "}");
    useConfiguration();
    SkylarkList<String> staticLinkingModeFlags =
        commandLineForVariables(
            CppActionNames.CPP_LINK_EXECUTABLE,
            0,
            "cc_common.create_link_variables(",
            "feature_configuration = feature_configuration,",
            "cc_toolchain = toolchain,",
            "is_static_linking_mode = True,",
            ")");
    assertThat(staticLinkingModeFlags).contains("-static_linking_mode_flag");
    assertThat(staticLinkingModeFlags).doesNotContain("-dynamic_linking_mode_flag");

    SkylarkList<String> dynamicLinkingModeFlags =
        commandLineForVariables(
            CppActionNames.CPP_LINK_EXECUTABLE,
            1,
            "cc_common.create_link_variables(",
            "feature_configuration = feature_configuration,",
            "cc_toolchain = toolchain,",
            "is_static_linking_mode = False,",
            ")");
    assertThat(dynamicLinkingModeFlags).doesNotContain("-static_linking_mode_flag");
    assertThat(dynamicLinkingModeFlags).contains("-dynamic_linking_mode_flag");
  }

  private SkylarkList<String> commandLineForVariables(String actionName, String... variables)
      throws Exception {
    return commandLineForVariables(actionName, 0, variables);
  }

  // This method is only there to change the package to fix multiple runs of this method in a single
  // test.
  // TODO(b/109917616): Remove pkgSuffix argument when bzl files are not cached within single test
  private SkylarkList<String> commandLineForVariables(
      String actionName, int pkgSuffix, String... variables) throws Exception {
    scratch.file(
        "a" + pkgSuffix + "/BUILD",
        "load(':rule.bzl', 'crule')",
        "cc_toolchain_alias(name='alias')",
        "crule(name='r')");

    scratch.file(
        "a" + pkgSuffix + "/rule.bzl",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  variables = " + Joiner.on("\n").join(variables),
        "  return struct(",
        "    command_line = cc_common.get_memory_inefficient_command_line(",
        "        feature_configuration = feature_configuration,",
        "        action_name = '" + actionName + "',",
        "        variables = variables))",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_cc_toolchain': attr.label(default=Label('//a" + pkgSuffix + ":alias'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    /** Calling {@link #getTarget} to get loading errors */
    getTarget("//a" + pkgSuffix + ":r");
    ConfiguredTarget r = getConfiguredTarget("//a" + pkgSuffix + ":r");
    if (r == null) {
      return null;
    }
    @SuppressWarnings("unchecked")
    SkylarkList<String> result = (SkylarkList<String>) r.get("command_line");
    return result;
  }

  @Test
  public void testCcCompilationProvider() throws Exception {
    scratch.file(
        "a/BUILD",
        "load('//tools/build_defs/cc:rule.bzl', 'crule')",
        "cc_library(",
        "    name='lib',",
        "    hdrs = ['lib.h'],",
        "    srcs = ['lib.cc'],",
        "    deps = ['r']",
        ")",
        "cc_library(",
        "    name = 'dep1',",
        "    srcs = ['dep1.cc'],",
        "    hdrs = ['dep1.h'],",
        "    includes = ['dep1/baz'],",
        "    defines = ['DEP1'],",
        ")",
        "cc_library(",
        "    name = 'dep2',",
        "    srcs = ['dep2.cc'],",
        "    hdrs = ['dep2.h'],",
        "    includes = ['dep2/qux'],",
        "    defines = ['DEP2'],",
        ")",
        "crule(name='r')");
    scratch.file("tools/build_defs/cc/BUILD", "");
    scratch.file(
        "tools/build_defs/cc/rule.bzl",
        "def _impl(ctx):",
        "  compilation_context = cc_common.create_compilation_context(",
        "    ctx=ctx,",
        "    headers=depset([ctx.file._header]),",
        "    system_includes=depset([ctx.attr._include]), defines=depset([ctx.attr._define]))",
        "  cc_infos = [CcInfo(compilation_context=compilation_context)]",
        "  for dep in ctx.attr._deps:",
        "      cc_infos.append(dep[CcInfo])",
        "  merged_cc_info=cc_common.merge_cc_infos(cc_infos=cc_infos)",
        "  return struct(",
        "    providers=[merged_cc_info, cc_common.create_cc_skylark_info(ctx=ctx)],",
        "    merged_headers=merged_cc_info.compilation_context.headers,",
        "    merged_system_includes=merged_cc_info.compilation_context.system_includes,",
        "    merged_defines=merged_cc_info.compilation_context.defines",
        "  )",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    '_header': attr.label(allow_single_file=True, default=Label('//a:header.h')),",
        "    '_include': attr.string(default='foo/bar'),",
        "    '_define': attr.string(default='MYDEFINE'),",
        "    '_deps': attr.label_list(default=['//a:dep1', '//a:dep2'])",
        "  },",
        "  fragments = ['cpp'],",
        ");");

    ConfiguredTarget lib = getConfiguredTarget("//a:lib");
    @SuppressWarnings("unchecked")
    CcCompilationContext ccCompilationContext = lib.get(CcInfo.PROVIDER).getCcCompilationContext();
    assertThat(
            ccCompilationContext.getDeclaredIncludeSrcs().toCollection().stream()
                .map(Artifact::getFilename)
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("lib.h", "header.h", "dep1.h", "dep2.h");

    ConfiguredTarget r = getConfiguredTarget("//a:r");

    List<Artifact> mergedHeaders =
        ((SkylarkNestedSet) r.get("merged_headers")).getSet(Artifact.class).toList();
    assertThat(
            mergedHeaders
                .stream()
                .map(Artifact::getFilename)
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("header.h", "dep1.h", "dep2.h");

    List<String> mergedDefines =
        ((SkylarkNestedSet) r.get("merged_defines")).getSet(String.class).toList();
    assertThat(mergedDefines).containsAllOf("MYDEFINE", "DEP1", "DEP2");

    List<String> mergedIncludes =
        ((SkylarkNestedSet) r.get("merged_system_includes")).getSet(String.class).toList();
    assertThat(mergedIncludes).containsAllOf("foo/bar", "a/dep1/baz", "a/dep2/qux");
  }

  @Test
  @Deprecated
  // TODO(118663806): Libraries won't be created this way from Skylark after removing CcLinkParams.
  public void testLibraryLinkerInputs() throws Exception {
    scratch.file("a/BUILD", "load('//tools/build_defs/cc:rule.bzl', 'crule')", "crule(name='r')");
    scratch.file("a/lib.a", "");
    scratch.file("a/lib.lo", "");
    scratch.file("a/lib.so", "");
    scratch.file("a/lib.ifso", "");
    scratch.file("tools/build_defs/cc/BUILD", "");
    scratch.file(
        "tools/build_defs/cc/rule.bzl",
        "def _create(ctx, lib, c):",
        "  return cc_common.create_library_to_link(ctx=ctx, library=lib, artifact_category=c)",
        "def _impl(ctx):",
        "  static_library = _create(ctx, ctx.file.liba, 'static_library')",
        "  alwayslink_static_library = _create(ctx, ctx.file.liblo, 'alwayslink_static_library')",
        "  dynamic_library = _create(ctx, ctx.file.libso, 'dynamic_library')",
        "  interface_library = _create(ctx, ctx.file.libifso, 'interface_library')",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  symlink_library = cc_common.create_symlink_library_to_link(",
        "      ctx=ctx, cc_toolchain=toolchain, library=ctx.file.libso)",
        "  return struct(",
        "    static_library = static_library,",
        "    alwayslink_static_library = alwayslink_static_library,",
        "    dynamic_library = dynamic_library,",
        "    interface_library = interface_library,",
        "    symlink_library = symlink_library",
        "  )",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    'liba': attr.label(default='//a:lib.a', allow_single_file=True),",
        "    'liblo': attr.label(default='//a:lib.lo', allow_single_file=True),",
        "    'libso': attr.label(default='//a:lib.so', allow_single_file=True),",
        "    'libifso': attr.label(default='//a:lib.ifso', allow_single_file=True),",
        "     '_cc_toolchain': attr.label(default =",
        "         configuration_field(fragment = 'cpp', name = 'cc_toolchain'))",
        "  },",
        "  fragments = ['cpp'],",
        ");");
    ConfiguredTarget r = getConfiguredTarget("//a:r");
    @SuppressWarnings("unchecked")
    LibraryToLink staticLibrary = (LibraryToLink) r.get("static_library");
    assertThat(staticLibrary.getArtifact().getFilename()).isEqualTo("lib.a");
    LibraryToLink alwaysLinkStaticLibrary = (LibraryToLink) r.get("alwayslink_static_library");
    assertThat(alwaysLinkStaticLibrary.getArtifact().getFilename()).isEqualTo("lib.lo");
    LibraryToLink dynamicLibrary = (LibraryToLink) r.get("dynamic_library");
    assertThat(dynamicLibrary.getArtifact().getFilename()).isEqualTo("lib.so");
    LibraryToLink interfaceLibrary = (LibraryToLink) r.get("interface_library");
    assertThat(interfaceLibrary.getArtifact().getFilename()).isEqualTo("lib.ifso");
    LibraryToLink symlinkLibrary = (LibraryToLink) r.get("symlink_library");
    assertThat(symlinkLibrary.getArtifact().getFilename()).isEqualTo("lib.so");
    assertThat(symlinkLibrary.getArtifact().getPath())
        .isNotEqualTo(symlinkLibrary.getOriginalLibraryArtifact().getPath());
  }

  @Test
  @Deprecated
  // TODO(118663806): Artifact category won't be part of API.
  public void testLibraryLinkerInputArtifactCategoryError() throws Exception {
    scratch.file("a/BUILD", "load('//tools/build_defs/cc:rule.bzl', 'crule')", "crule(name='r')");
    scratch.file("a/lib.a", "");
    scratch.file("tools/build_defs/cc/BUILD", "");
    scratch.file(
        "tools/build_defs/cc/rule.bzl",
        "def _impl(ctx):",
        "  executable = cc_common.create_library_to_link(",
        "    ctx=ctx, library=ctx.file.lib, artifact_category='executable')",
        "  return struct(",
        "    executable = executable,",
        "  )",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    'lib': attr.label(default='//a:lib.a', allow_single_file=True),",
        "  },",
        "  fragments = ['cpp'],",
        ");");
    reporter.removeHandler(failFastHandler);
    assertThat(getConfiguredTarget("//a:r")).isNull();
    assertContainsEvent(
        "Possible values for artifact_category: static_library, "
            + "alwayslink_static_library, dynamic_library, interface_library");
  }

  @Test
  public void testFlagWhitelist() throws Exception {
    setSkylarkSemanticsOptions("--experimental_cc_skylark_api_enabled_packages=\"\"");
    SkylarkCcCommonTestHelper.createFiles(scratch, "foo/bar");
    reporter.removeHandler(failFastHandler);
    getConfiguredTarget("//foo:bin");
    assertContainsEvent(
        "You can try it out by passing "
            + "--experimental_cc_skylark_api_enabled_packages=<list of packages>. Beware that we "
            + "will be making breaking changes to this API without prior warning.");
  }

  @Test
  public void testOldCcLinkingProvider() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            "supports_interface_shared_objects: false");
    useConfiguration();
    setUpOldCcLinkingProviderParamsTest();
    ConfiguredTarget r = getConfiguredTarget("//a:r");

    List<String> staticSharedLinkopts =
        ((SkylarkNestedSet) r.get("sd_linkopts")).getSet(String.class).toList();
    List<String> staticNoSharedLinkopts =
        ((SkylarkNestedSet) r.get("se_linkopts")).getSet(String.class).toList();
    List<String> noStaticSharedLinkopts =
        ((SkylarkNestedSet) r.get("dd_linkopts")).getSet(String.class).toList();
    List<String> noStaticNoSharedLinkopts =
        ((SkylarkNestedSet) r.get("de_linkopts")).getSet(String.class).toList();
    assertThat(staticSharedLinkopts)
        .containsAllOf("-static_for_dynamic", "-DEP1_LINKOPT", "-DEP2_LINKOPT");
    assertThat(staticNoSharedLinkopts)
        .containsAllOf("-static_for_executable", "-DEP1_LINKOPT", "-DEP2_LINKOPT");
    assertThat(noStaticSharedLinkopts)
        .containsAllOf("-dynamic_for_dynamic", "-DEP1_LINKOPT", "-DEP2_LINKOPT");
    assertThat(noStaticNoSharedLinkopts)
        .containsAllOf("-dynamic_for_executable", "-DEP1_LINKOPT", "-DEP2_LINKOPT");

    List<LibraryToLink> staticSharedLibs =
        ((SkylarkNestedSet) r.get("sd_libs")).getSet(LibraryToLink.class).toList();
    List<LibraryToLink> staticNoSharedLibs =
        ((SkylarkNestedSet) r.get("se_libs")).getSet(LibraryToLink.class).toList();
    List<LibraryToLink> noStaticSharedLibs =
        ((SkylarkNestedSet) r.get("dd_libs")).getSet(LibraryToLink.class).toList();
    List<LibraryToLink> noStaticNoSharedLibs =
        ((SkylarkNestedSet) r.get("de_libs")).getSet(LibraryToLink.class).toList();
    assertThat(
            staticSharedLibs
                .stream()
                .map(x -> x.getOriginalLibraryArtifact().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("lib.a", "libdep1.a", "libdep2.a");
    assertThat(
            staticNoSharedLibs
                .stream()
                .map(x -> x.getOriginalLibraryArtifact().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("lib.a", "libdep1.a", "libdep2.a");

    assertThat(
            noStaticSharedLibs
                .stream()
                .map(x -> x.getOriginalLibraryArtifact().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("lib.so", "libdep1.so", "libdep2.so");
    assertThat(
            noStaticNoSharedLibs
                .stream()
                .map(x -> x.getOriginalLibraryArtifact().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("lib.so", "libdep1.so", "libdep2.so");

    List<Artifact> staticSharedRunLibs =
        ((SkylarkNestedSet) r.get("sd_runlibs")).getSet(Artifact.class).toList();
    List<Artifact> staticNoSharedRunLibs =
        ((SkylarkNestedSet) r.get("se_runlibs")).getSet(Artifact.class).toList();
    List<Artifact> noStaticSharedRunLibs =
        ((SkylarkNestedSet) r.get("dd_runlibs")).getSet(Artifact.class).toList();
    List<Artifact> noStaticNoSharedRunLibs =
        ((SkylarkNestedSet) r.get("de_runlibs")).getSet(Artifact.class).toList();
    assertThat(
            staticSharedRunLibs
                .stream()
                .map(Artifact::getFilename)
                .collect(ImmutableList.toImmutableList()))
        .isEmpty();
    assertThat(
            staticNoSharedRunLibs
                .stream()
                .map(Artifact::getFilename)
                .collect(ImmutableList.toImmutableList()))
        .isEmpty();
    assertThat(
            noStaticSharedRunLibs
                .stream()
                .map(Artifact::getFilename)
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("lib.so", "liba_Slibdep1.so", "liba_Slibdep2.so");
    assertThat(
            noStaticNoSharedRunLibs
                .stream()
                .map(Artifact::getFilename)
                .collect(ImmutableList.toImmutableList()))
        .containsAllOf("lib.so", "liba_Slibdep1.so", "liba_Slibdep2.so");
  }

  private void setUpOldCcLinkingProviderParamsTest() throws Exception {
    scratch.file(
        "a/BUILD",
        "load('//tools/build_defs/cc:rule.bzl', 'crule')",
        "crule(name='r')",
        "cc_library(",
        "    name = 'dep1',",
        "    srcs = ['dep1.cc'],",
        "    hdrs = ['dep1.h'],",
        "    linkopts = ['-DEP1_LINKOPT'],",
        ")",
        "cc_library(",
        "    name = 'dep2',",
        "    srcs = ['dep2.cc'],",
        "    hdrs = ['dep2.h'],",
        "    linkopts = ['-DEP2_LINKOPT'],",
        ")");
    scratch.file("a/lib.a", "");
    scratch.file("a/lib.so", "");
    scratch.file("tools/build_defs/cc/BUILD", "");
    scratch.file(
        "tools/build_defs/cc/rule.bzl",
        "def _create(ctx, l, r, f):",
        "  return cc_common.create_cc_link_params(",
        "    ctx=ctx, libraries_to_link=depset(l), dynamic_libraries_for_runtime=depset(r),",
        "        user_link_flags=depset(f))",
        "def _impl(ctx):",
        "  liba = cc_common.create_library_to_link(ctx=ctx, library=ctx.file.liba,",
        "    artifact_category='static_library')",
        "  libso = cc_common.create_library_to_link(ctx=ctx, library=ctx.file.libso,",
        "    artifact_category='dynamic_library')",
        "  sd = _create(ctx, [liba], [], ['-static_for_dynamic'])",
        "  se = _create(ctx, [liba], [], ['-static_for_executable'])",
        "  dd = _create(ctx, [libso], [ctx.file.libso], ['-dynamic_for_dynamic'])",
        "  de = _create(ctx, [libso], [ctx.file.libso], ['-dynamic_for_executable'])",
        "  linking_context = cc_common.create_linking_context(ctx=ctx,",
        "    static_mode_params_for_dynamic_library=sd, static_mode_params_for_executable=se,",
        "    dynamic_mode_params_for_dynamic_library=dd, dynamic_mode_params_for_executable=de)",
        "  cc_infos = [CcInfo(linking_context=linking_context)]",
        "  for dep in ctx.attr._deps:",
        "      cc_infos.append(dep[CcInfo])",
        "  merged_cc_info = cc_common.merge_cc_infos(cc_infos=cc_infos)",
        "  sd = merged_cc_info.linking_context.static_mode_params_for_dynamic_library",
        "  se = merged_cc_info.linking_context.static_mode_params_for_executable",
        "  dd = merged_cc_info.linking_context.dynamic_mode_params_for_dynamic_library",
        "  de = merged_cc_info.linking_context.dynamic_mode_params_for_executable",
        "  return struct(",
        "    cc_info = merged_cc_info,",
        "    sd_linkopts = sd.user_link_flags,",
        "    se_linkopts = se.user_link_flags,",
        "    dd_linkopts = dd.user_link_flags,",
        "    de_linkopts = de.user_link_flags,",
        "    sd_libs = sd.libraries_to_link,",
        "    se_libs = se.libraries_to_link,",
        "    dd_libs = dd.libraries_to_link,",
        "    de_libs = de.libraries_to_link,",
        "    sd_runlibs = sd.dynamic_libraries_for_runtime,",
        "    se_runlibs = se.dynamic_libraries_for_runtime,",
        "    dd_runlibs = dd.dynamic_libraries_for_runtime,",
        "    de_runlibs = de.dynamic_libraries_for_runtime,",
        "  )",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    'liba': attr.label(default='//a:lib.a', allow_single_file=True),",
        "    'libso': attr.label(default='//a:lib.so', allow_single_file=True),",
        "    '_deps': attr.label_list(default=['//a:dep1', '//a:dep2']),",
        "  },",
        "  fragments = ['cpp'],",
        ");");
  }

  @Test
  public void testCcLinkingContext() throws Exception {
    AnalysisMock.get()
        .ccSupport()
        .setupCrosstool(
            mockToolsConfig,
            MockCcSupport.PIC_FEATURE,
            "supports_interface_shared_objects: false",
            "needsPic: true");
    useConfiguration();
    setUpCcLinkingContextTest();
    ConfiguredTarget a = getConfiguredTarget("//a:a");

    StructImpl info = ((StructImpl) a.get("info"));
    @SuppressWarnings("unchecked")
    SkylarkList<String> userLinkFlags =
        (SkylarkList<String>) info.getValue("user_link_flags", SkylarkList.class);
    assertThat(userLinkFlags.getImmutableList())
        .containsExactly("-la", "-lc2", "-DEP2_LINKOPT", "-lc1", "-lc2", "-DEP1_LINKOPT");
    @SuppressWarnings("unchecked")
    SkylarkList<LibraryToLinkWrapper> librariesToLink =
        (SkylarkList<LibraryToLinkWrapper>) info.getValue("libraries_to_link", SkylarkList.class);
    assertThat(
            librariesToLink.stream()
                .filter(x -> x.getStaticLibrary() != null)
                .map(x -> x.getStaticLibrary().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("a.a", "b.a", "c.a", "d.a");
    assertThat(
            librariesToLink.stream()
                .filter(x -> x.getPicStaticLibrary() != null)
                .map(x -> x.getPicStaticLibrary().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("a.pic.a", "libdep2.a", "b.pic.a", "c.pic.a", "e.pic.a", "libdep1.a");
    assertThat(
            librariesToLink.stream()
                .filter(x -> x.getDynamicLibrary() != null)
                .map(x -> x.getDynamicLibrary().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("a.so", "liba_Slibdep2.so", "b.so", "e.so", "liba_Slibdep1.so");
    assertThat(
            librariesToLink.stream()
                .filter(x -> x.getInterfaceLibrary() != null)
                .map(x -> x.getInterfaceLibrary().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("a.ifso");
    Artifact staticLibrary = info.getValue("static_library", Artifact.class);
    assertThat(staticLibrary.getFilename()).isEqualTo("a.a");
    Artifact picStaticLibrary = info.getValue("pic_static_library", Artifact.class);
    assertThat(picStaticLibrary.getFilename()).isEqualTo("a.pic.a");
    Artifact dynamicLibrary = info.getValue("dynamic_library", Artifact.class);
    assertThat(dynamicLibrary.getFilename()).isEqualTo("a.so");
    Artifact interfaceLibrary = info.getValue("interface_library", Artifact.class);
    assertThat(interfaceLibrary.getFilename()).isEqualTo("a.ifso");

    ConfiguredTarget bin = getConfiguredTarget("//a:bin");
    assertThat(bin).isNotNull();
  }

  private void setUpCcLinkingContextTest() throws Exception {
    scratch.file(
        "a/BUILD",
        "load('//tools/build_defs/cc:rule.bzl', 'crule')",
        "cc_binary(name='bin',",
        "   deps = [':a'],",
        ")",
        "crule(name='a',",
        "   static_library = 'a.a',",
        "   pic_static_library = 'a.pic.a',",
        "   dynamic_library = 'a.so',",
        "   interface_library = 'a.ifso',",
        "   user_link_flags = ['-la', '-lc2'],",
        "   deps = [':c', ':dep2', ':b'],",
        ")",
        "crule(name='b',",
        "   static_library = 'b.a',",
        "   pic_static_library = 'b.pic.a',",
        "   dynamic_library = 'b.so',",
        "   deps = [':c', ':d'],",
        ")",
        "crule(name='c',",
        "   static_library = 'c.a',",
        "   pic_static_library = 'c.pic.a',",
        "   user_link_flags = ['-lc1', '-lc2'],",
        ")",
        "crule(name='d',",
        "   static_library = 'd.a',",
        "   deps = [':e'],",
        ")",
        "crule(name='e',",
        "   pic_static_library = 'e.pic.a',",
        "   dynamic_library = 'e.so',",
        "   deps = [':dep1'],",
        ")",
        "cc_toolchain_alias(name='alias')",
        "cc_library(",
        "    name = 'dep1',",
        "    srcs = ['dep1.cc'],",
        "    hdrs = ['dep1.h'],",
        "    linkopts = ['-DEP1_LINKOPT'],",
        ")",
        "cc_library(",
        "    name = 'dep2',",
        "    srcs = ['dep2.cc'],",
        "    hdrs = ['dep2.h'],",
        "    linkopts = ['-DEP2_LINKOPT'],",
        ")");
    scratch.file("a/lib.a", "");
    scratch.file("a/lib.so", "");
    scratch.file("tools/build_defs/cc/BUILD", "");
    scratch.file(
        "tools/build_defs/cc/rule.bzl",
        "def _create(ctx, feature_configuration, static_library, pic_static_library,",
        "  dynamic_library, interface_library, alwayslink):",
        "  return cc_common.create_library_to_link(",
        "    actions=ctx.actions, feature_configuration=feature_configuration, ",
        "    cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo], ",
        "    static_library=static_library, pic_static_library=pic_static_library,",
        "    dynamic_library=dynamic_library, interface_library=interface_library,",
        "    alwayslink=alwayslink)",
        "def _impl(ctx):",
        "  toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
        "  feature_configuration = cc_common.configure_features(cc_toolchain = toolchain)",
        "  library_to_link = _create(ctx, feature_configuration, ctx.file.static_library, ",
        "     ctx.file.pic_static_library, ctx.file.dynamic_library, ctx.file.interface_library,",
        "     ctx.attr.alwayslink)",
        "  linking_context = cc_common.create_linking_context(libraries_to_link=[library_to_link],",
        "     user_link_flags=ctx.attr.user_link_flags)",
        "  cc_infos = [CcInfo(linking_context=linking_context)]",
        "  for dep in ctx.attr.deps:",
        "      cc_infos.append(dep[CcInfo])",
        "  merged_cc_info = cc_common.merge_cc_infos(cc_infos=cc_infos)",
        "  return struct(",
        "     info = struct(",
        "       cc_info = merged_cc_info,",
        "       user_link_flags = merged_cc_info.linking_context.user_link_flags,",
        "       libraries_to_link = merged_cc_info.linking_context.libraries_to_link,",
        "       static_library = library_to_link.static_library,",
        "       pic_static_library = library_to_link.pic_static_library,",
        "       dynamic_library = library_to_link.dynamic_library,",
        "       interface_library = library_to_link.interface_library),",
        "     providers = [merged_cc_info]",
        "  )",
        "crule = rule(",
        "  _impl,",
        "  attrs = { ",
        "    'user_link_flags' : attr.string_list(),",
        "    'static_library': attr.label(allow_single_file=True),",
        "    'pic_static_library': attr.label(allow_single_file=True),",
        "    'dynamic_library': attr.label(allow_single_file=True),",
        "    'interface_library': attr.label(allow_single_file=True),",
        "    'alwayslink': attr.bool(),",
        "    '_cc_toolchain': attr.label(default=Label('//a:alias')),",
        "    'deps': attr.label_list(),",
        "  },",
        "  fragments = ['cpp'],",
        ");");
  }

  @Test
  public void testCcNativeRuleDependingOnSkylarkDefinedRule() throws Exception {
    SkylarkCcCommonTestHelper.createFiles(scratch, "tools/build_defs/cc");
    assertThat(getConfiguredTarget("//foo:bin")).isNotNull();
  }

  @Test
  public void testCopts() throws Exception {
    SkylarkCcCommonTestHelper.createFilesForTestingCompilation(
        scratch, "tools/build_defs/foo", "copts=depset(['-COMPILATION_OPTION'])");
    assertThat(getConfiguredTarget("//foo:bin")).isNotNull();
    ConfiguredTarget target = getConfiguredTarget("//foo:skylark_lib");
    CppCompileAction action =
        (CppCompileAction) getGeneratingAction(artifactByPath(getFilesToBuild(target), ".o"));
    assertThat(action.getArguments()).contains("-COMPILATION_OPTION");
  }

  @Test
  public void testIncludeDirs() throws Exception {
    SkylarkCcCommonTestHelper.createFilesForTestingCompilation(
        scratch, "tools/build_defs/foo", "includes=depset(['foo/bar', 'baz/qux'])");
    ConfiguredTarget target = getConfiguredTarget("//foo:skylark_lib");
    assertThat(target).isNotNull();
    CppCompileAction action =
        (CppCompileAction) getGeneratingAction(artifactByPath(getFilesToBuild(target), ".o"));
    assertThat(action.getArguments()).containsAllOf("-Ifoo/bar", "-Ibaz/qux");
  }

  @Test
  public void testLinkingOutputs() throws Exception {
    SkylarkCcCommonTestHelper.createFiles(scratch, "tools/build_defs/foo");
    ConfiguredTarget target = getConfiguredTarget("//foo:skylark_lib");
    assertThat(target).isNotNull();
    @SuppressWarnings("unchecked")
    SkylarkList<LibraryToLinkApi> libraries =
        (SkylarkList<LibraryToLinkApi>) target.get("libraries");
    assertThat(
            libraries
                .stream()
                .map(x -> x.getOriginalLibraryArtifact().getFilename())
                .collect(ImmutableList.toImmutableList()))
        .contains("libskylark_lib.so");
  }

  @Test
  public void testLinkopts() throws Exception {
    SkylarkCcCommonTestHelper.createFilesForTestingLinking(
        scratch, "tools/build_defs/foo", "linkopts=depset(['-LINKING_OPTION'])");
    ConfiguredTarget target = getConfiguredTarget("//foo:skylark_lib");
    assertThat(target).isNotNull();
    CppLinkAction action =
        (CppLinkAction) getGeneratingAction(artifactByPath(getFilesToBuild(target), ".so"));
    assertThat(action.getArguments()).contains("-LINKING_OPTION");
  }

  @Test
  public void testSettingDynamicLibraryArtifact() throws Exception {
    SkylarkCcCommonTestHelper.createFilesForTestingLinking(
        scratch,
        "tools/build_defs/foo",
        "dynamic_library=ctx.actions.declare_file('dynamic_lib_artifact.so')");
    assertThat(getConfiguredTarget("//foo:skylark_lib")).isNotNull();
    ConfiguredTarget target = getConfiguredTarget("//foo:skylark_lib");
    assertThat(
            getFilesToBuild(target)
                .toCollection()
                .stream()
                .map(x -> x.getFilename())
                .collect(ImmutableList.toImmutableList()))
        .contains("dynamic_lib_artifact.so");
  }

  @Test
  public void testCcLinkingContexts() throws Exception {
    SkylarkCcCommonTestHelper.createFilesForTestingLinking(
        scratch, "tools/build_defs/foo", "linking_contexts=dep_linking_contexts");
    assertThat(getConfiguredTarget("//foo:bin")).isNotNull();
    ConfiguredTarget target = getConfiguredTarget("//foo:bin");
    CppLinkAction action =
        (CppLinkAction) getGeneratingAction(artifactByPath(getFilesToBuild(target), "bin"));
    assertThat(action.getArguments()).containsAllOf("-DEP1_LINKOPT", "-DEP2_LINKOPT");
  }

  @Test
  public void testNeverlinkTrue() throws Exception {
    assertThat(setUpNeverlinkTest("True").getArguments()).doesNotContain("-NEVERLINK_OPTION");
  }

  @Test
  public void testNeverlinkFalse() throws Exception {
    assertThat(setUpNeverlinkTest("False").getArguments()).contains("-NEVERLINK_OPTION");
  }

  private CppLinkAction setUpNeverlinkTest(String value) throws Exception {
    SkylarkCcCommonTestHelper.createFilesForTestingLinking(
        scratch,
        "tools/build_defs/foo",
        String.join(",", "linkopts=depset(['-NEVERLINK_OPTION'])", "neverlink=" + value));
    assertThat(getConfiguredTarget("//foo:bin")).isNotNull();
    ConfiguredTarget target = getConfiguredTarget("//foo:bin");
    return (CppLinkAction) getGeneratingAction(artifactByPath(getFilesToBuild(target), "bin"));
  }

  private void loadCcToolchainConfigLib() throws IOException {
    scratch.appendFile("tools/cpp/BUILD", "");
    scratch.file(
        "tools/cpp/cc_toolchain_config_lib.bzl",
        ResourceLoader.readFromResources(
            TestConstants.BAZEL_REPO_PATH + "tools/cpp/cc_toolchain_config_lib.bzl"));
  }

  @Test
  public void testVariableWithValue() throws Exception {
    loadCcToolchainConfigLib();
    createVariableWithValueRule("one", /* name= */ "None", /* value= */ "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of variable_with_value should be a string, found NoneType");

    createVariableWithValueRule("two", /* name= */ "'abc'", /* value= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("value parameter of variable_with_value should be a string, found NoneType");

    createVariableWithValueRule("three", /* name= */ "''", /* value= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of variable_with_value must be a nonempty string");

    createVariableWithValueRule("four", /* name= */ "'abc'", /* value= */ "''");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("value parameter of variable_with_value must be a nonempty string");

    createVariableWithValueRule("five", /* name= */ "'abc'", /* value= */ "'def'");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    assertThat(variable).isNotNull();
    VariableWithValue v = CcModule.variableWithValueFromSkylark(variable);
    assertThat(v).isNotNull();
    assertThat(v.variable).isEqualTo("abc");
    assertThat(v.value).isEqualTo("def");

    createEnvEntryRule("six", /* key= */ "'abc'", /* value= */ "'def'");
    t = getConfiguredTarget("//six:a");
    SkylarkInfo envEntry = (SkylarkInfo) t.get("entry");
    try {
      CcModule.variableWithValueFromSkylark(envEntry);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'variable_with_value', received 'env_entry");
    }
  }

  private void createVariableWithValueRule(String pkg, String name, String value)
      throws IOException {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'variable_with_value')",
        "def _impl(ctx):",
        "   return struct(variable = variable_with_value(",
        "       name = " + name + ",",
        "       value = " + value + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomVariableWithValue() throws Exception {
    loadCcToolchainConfigLib();
    createCustomVariableWithValueRule("one", /* name= */ "None", /* value= */ "None");
    ConfiguredTarget t = getConfiguredTarget("//one:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.variableWithValueFromSkylark(variable);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'name' parameter of variable_with_value must be a nonempty string.");
    }

    createCustomVariableWithValueRule("two", /* name= */ "'abc'", /* value= */ "None");

    t = getConfiguredTarget("//two:a");
    variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.variableWithValueFromSkylark(variable);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'value' parameter of variable_with_value must be a nonempty string.");
    }

    createCustomVariableWithValueRule("three", /* name= */ "'abc'", /* value= */ "struct()");

    t = getConfiguredTarget("//three:a");
    variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.variableWithValueFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'value' is not of 'java.lang.String' type.");
    }

    createCustomVariableWithValueRule("four", /* name= */ "True", /* value= */ "'abc'");

    t = getConfiguredTarget("//four:a");
    variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.variableWithValueFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'name' is not of 'java.lang.String' type.");
    }
  }

  private void createCustomVariableWithValueRule(String pkg, String name, String value)
      throws IOException {
    scratch.file(
        pkg + "/foo.bzl",
        "def _impl(ctx):",
        "   return struct(variable = struct(",
        "       name = " + name + ",",
        "       value = " + value + ",",
        "       type_name = 'variable_with_value'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testEnvEntry() throws Exception {
    loadCcToolchainConfigLib();
    createEnvEntryRule("one", "None", /* value= */ "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("key parameter of env_entry should be a string, found NoneType");

    createEnvEntryRule("two", "'abc'", /* value= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("value parameter of env_entry should be a string, found NoneType");

    createEnvEntryRule("three", "''", /* value= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e).hasMessageThat().contains("key parameter of env_entry must be a nonempty string");

    createEnvEntryRule("four", "'abc'", /* value= */ "''");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("value parameter of env_entry must be a nonempty string");

    createEnvEntryRule("five", "'abc'", /* value= */ "'def'");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo entryProvider = (SkylarkInfo) t.get("entry");
    assertThat(entryProvider).isNotNull();
    EnvEntry entry = CcModule.envEntryFromSkylark(entryProvider);
    assertThat(entry).isNotNull();
    StringValueParser parser = new StringValueParser("def");
    assertThat(entry).isEqualTo(new EnvEntry("abc", parser.getChunks()));

    createVariableWithValueRule("six", /* name= */ "'abc'", /* value= */ "'def'");
    t = getConfiguredTarget("//six:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.envEntryFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'env_entry', received 'variable_with_value");
    }
  }

  private void createEnvEntryRule(String pkg, String key, String value) throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'env_entry')",
        "def _impl(ctx):",
        "   return struct(entry = env_entry(",
        "       key = " + key + ",",
        "       value = " + value + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomEnvEntry() throws Exception {
    loadCcToolchainConfigLib();

    createCustomEnvEntryRule("one", /* key= */ "None", /* value= */ "None");

    ConfiguredTarget t = getConfiguredTarget("//one:a");
    SkylarkInfo entry = (SkylarkInfo) t.get("entry");
    try {
      CcModule.envEntryFromSkylark(entry);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'key' parameter of env_entry must be a nonempty string.");
    }

    createCustomEnvEntryRule("two", /* key= */ "'abc'", /* value= */ "None");

    t = getConfiguredTarget("//two:a");
    entry = (SkylarkInfo) t.get("entry");
    try {
      CcModule.envEntryFromSkylark(entry);
      fail("Should have failed because of empty string.");
      ;
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'value' parameter of env_entry must be a nonempty string.");
    }

    createCustomEnvEntryRule("three", /* key= */ "'abc'", /* value= */ "struct()");

    t = getConfiguredTarget("//three:a");
    entry = (SkylarkInfo) t.get("entry");
    try {
      CcModule.envEntryFromSkylark(entry);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'value' is not of 'java.lang.String' type.");
    }

    createCustomEnvEntryRule("four", /* key= */ "True", /* value= */ "'abc'");

    t = getConfiguredTarget("//four:a");
    entry = (SkylarkInfo) t.get("entry");
    try {
      CcModule.envEntryFromSkylark(entry);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'key' is not of 'java.lang.String' type.");
    }
  }

  private void createCustomEnvEntryRule(String pkg, String key, String value) throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "def _impl(ctx):",
        "   return struct(entry = struct(",
        "       key = " + key + ",",
        "       value = " + value + ",",
        "       type_name = 'env_entry'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testToolPath() throws Exception {
    loadCcToolchainConfigLib();
    createToolPathRule("one", /* name= */ "None", "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of tool_path should be a string, found NoneType");

    createToolPathRule("two", /* name= */ "'abc'", "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("path parameter of tool_path should be a string, found NoneType");

    createToolPathRule("three", /* name= */ "''", "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of tool_path must be a nonempty string");

    createToolPathRule("four", /* name= */ "'abc'", "''");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("path parameter of tool_path must be a nonempty string");

    createToolPathRule("five", /* name= */ "'abc'", "'/d/e/f'");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo toolPathProvider = (SkylarkInfo) t.get("toolpath");
    assertThat(toolPathProvider).isNotNull();
    Pair<String, String> toolPath = CcModule.toolPathFromSkylark(toolPathProvider);
    assertThat(toolPath).isNotNull();
    assertThat(toolPath.first).isEqualTo("abc");
    assertThat(toolPath.second).isEqualTo("/d/e/f");

    createVariableWithValueRule("six", /* name= */ "'abc'", /* value= */ "'def'");
    t = getConfiguredTarget("//six:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.toolPathFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'tool_path', received 'variable_with_value");
    }
  }

  private void createToolPathRule(String pkg, String name, String path) throws IOException {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'tool_path')",
        "def _impl(ctx):",
        "   return struct(toolpath = tool_path(",
        "       name = " + name + ",",
        "       path = " + path + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomToolPath() throws Exception {
    loadCcToolchainConfigLib();

    createCustomToolPathRule("one", /* name= */ "None", /* path= */ "None");

    ConfiguredTarget t = getConfiguredTarget("//one:a");
    SkylarkInfo toolPath = (SkylarkInfo) t.get("toolpath");
    try {
      CcModule.toolPathFromSkylark(toolPath);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'name' parameter of tool_path must be a nonempty string.");
    }

    createCustomToolPathRule("two", /* name= */ "'abc'", /* path= */ "None");

    t = getConfiguredTarget("//two:a");
    toolPath = (SkylarkInfo) t.get("toolpath");
    try {
      CcModule.toolPathFromSkylark(toolPath);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'path' parameter of tool_path must be a nonempty string.");
    }

    createCustomToolPathRule("three", /* name= */ "'abc'", /* path= */ "struct()");

    t = getConfiguredTarget("//three:a");
    toolPath = (SkylarkInfo) t.get("toolpath");
    try {
      CcModule.toolPathFromSkylark(toolPath);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'path' is not of 'java.lang.String' type.");
    }

    createCustomToolPathRule("four", /* name= */ "True", /* path= */ "'abc'");

    t = getConfiguredTarget("//four:a");
    toolPath = (SkylarkInfo) t.get("toolpath");
    try {
      CcModule.toolPathFromSkylark(toolPath);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'name' is not of 'java.lang.String' type.");
    }
  }

  private void createCustomToolPathRule(String pkg, String name, String path) throws IOException {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'tool_path')",
        "def _impl(ctx):",
        "   return struct(toolpath = struct(",
        "       name = " + name + ",",
        "       path = " + path + ",",
        "       type_name = 'tool_path'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testMakeVariable() throws Exception {
    loadCcToolchainConfigLib();
    createMakeVariablerule("one", /* name= */ "None", /* value= */ "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of make_variable should be a string, found NoneType");

    createMakeVariablerule("two", /* name= */ "'abc'", /* value= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("value parameter of make_variable should be a string, found NoneType");

    createMakeVariablerule("three", /* name= */ "''", /* value= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of make_variable must be a nonempty string");

    createMakeVariablerule("four", /* name= */ "'abc'", /* value= */ "''");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("value parameter of make_variable must be a nonempty string");

    createMakeVariablerule("five", /* name= */ "'abc'", /* value= */ "'val'");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo makeVariableProvider = (SkylarkInfo) t.get("variable");
    assertThat(makeVariableProvider).isNotNull();
    Pair<String, String> makeVariable = CcModule.makeVariableFromSkylark(makeVariableProvider);
    assertThat(makeVariable).isNotNull();
    assertThat(makeVariable.first).isEqualTo("abc");
    assertThat(makeVariable.second).isEqualTo("val");

    createVariableWithValueRule("six", /* name= */ "'abc'", /* value= */ "'def'");
    t = getConfiguredTarget("//six:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.makeVariableFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'make_variable', received 'variable_with_value");
    }
  }

  private void createMakeVariablerule(String pkg, String name, String value) throws IOException {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'make_variable')",
        "def _impl(ctx):",
        "   return struct(variable = make_variable(",
        "       name = " + name + ",",
        "       value = " + value + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomMakeVariable() throws Exception {
    createCustomMakeVariablerule("one", /* name= */ "None", /* value= */ "None");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo makeVariableProvider = (SkylarkInfo) t.get("variable");
      CcModule.makeVariableFromSkylark(makeVariableProvider);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'name' parameter of make_variable must be a nonempty string.");
    }

    createCustomMakeVariablerule("two", /* name= */ "'abc'", /* value= */ "None");

    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo makeVariableProvider = (SkylarkInfo) t.get("variable");
      CcModule.makeVariableFromSkylark(makeVariableProvider);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'value' parameter of make_variable must be a nonempty string.");
    }

    createCustomMakeVariablerule("three", /* name= */ "[]", /* value= */ "None");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo makeVariableProvider = (SkylarkInfo) t.get("variable");
      CcModule.makeVariableFromSkylark(makeVariableProvider);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'name' is not of 'java.lang.String' type.");
    }

    createCustomMakeVariablerule("four", /* name= */ "'abc'", /* value= */ "True");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo makeVariableProvider = (SkylarkInfo) t.get("variable");
      CcModule.makeVariableFromSkylark(makeVariableProvider);
      fail("Should have failed because of empty string.");
    } catch (EvalException e) {
      assertThat(e).hasMessageThat().contains("Field 'value' is not of 'java.lang.String' type.");
    }
  }

  private void createCustomMakeVariablerule(String pkg, String name, String value)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "def _impl(ctx):",
        "   return struct(variable = struct(",
        "       name = " + name + ",",
        "       value = " + value + ",",
        "       type_name = 'make_variable'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testWithFeatureSet() throws Exception {
    loadCcToolchainConfigLib();
    createWithFeatureSetRule("one", /* features= */ "None", /* notFeatures= */ "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("features parameter of with_feature_set should be a list, found NoneType");

    createWithFeatureSetRule("two", /* features= */ "['abc']", /* notFeatures= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("not_features parameter of with_feature_set should be a list, found NoneType");

    createWithFeatureSetRule("three", /* features= */ "'asdf'", /* notFeatures= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("features parameter of with_feature_set should be a list, found string");

    createWithFeatureSetRule("four", /* features= */ "['abc']", /* notFeatures= */ "'def'");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("not_features parameter of with_feature_set should be a list, found string");

    createWithFeatureSetRule(
        "five", /* features= */ "['f1', 'f2']", /* notFeatures= */ "['nf1', 'nf2']");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo withFeatureSetProvider = (SkylarkInfo) t.get("wfs");
    assertThat(withFeatureSetProvider).isNotNull();
    WithFeatureSet withFeatureSet = CcModule.withFeatureSetFromSkylark(withFeatureSetProvider);
    assertThat(withFeatureSet).isNotNull();
    assertThat(withFeatureSet.getFeatures()).containsExactly("f1", "f2");
    assertThat(withFeatureSet.getNotFeatures()).containsExactly("nf1", "nf2");

    createVariableWithValueRule("six", /* name= */ "'abc'", /* value= */ "'def'");
    t = getConfiguredTarget("//six:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.withFeatureSetFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'with_feature_set', received 'variable_with_value");
    }
  }

  private void createWithFeatureSetRule(String pkg, String features, String notFeatures)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set')",
        "def _impl(ctx):",
        "   return struct(wfs = with_feature_set(",
        "       features = " + features + ",",
        "       not_features = " + notFeatures + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomWithFeatureSet() throws Exception {
    createCustomWithFeatureSetRule("one", /* features= */ "struct()", /* notFeatures= */ "None");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo withFeatureSetProvider = (SkylarkInfo) t.get("wfs");
      assertThat(withFeatureSetProvider).isNotNull();
      CcModule.withFeatureSetFromSkylark(withFeatureSetProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("Illegal argument: 'features' is not of expected type list or NoneType");
    }

    createCustomWithFeatureSetRule("two", /* features= */ "['abc']", /* notFeatures= */ "struct()");

    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo withFeatureSetProvider = (SkylarkInfo) t.get("wfs");
      assertThat(withFeatureSetProvider).isNotNull();
      CcModule.withFeatureSetFromSkylark(withFeatureSetProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("Illegal argument: 'not_features' is not of expected type list or NoneType");
    }

    createCustomWithFeatureSetRule("three", /* features= */ "[struct()]", /* notFeatures= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo withFeatureSetProvider = (SkylarkInfo) t.get("wfs");
      assertThat(withFeatureSetProvider).isNotNull();
      CcModule.withFeatureSetFromSkylark(withFeatureSetProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("expected type 'string' for 'features' element but got type 'struct' instead");
    }

    createCustomWithFeatureSetRule("four", /* features= */ "[]", /* notFeatures= */ "[struct()]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo withFeatureSetProvider = (SkylarkInfo) t.get("wfs");
      assertThat(withFeatureSetProvider).isNotNull();
      CcModule.withFeatureSetFromSkylark(withFeatureSetProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains(
              "expected type 'string' for 'not_features' element but got type 'struct' instead");
    }
  }

  private void createCustomWithFeatureSetRule(String pkg, String features, String notFeatures)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "def _impl(ctx):",
        "   return struct(wfs = struct(",
        "       features = " + features + ",",
        "       not_features = " + notFeatures + ",",
        "       type_name = 'with_feature_set'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testEnvSet() throws Exception {
    loadCcToolchainConfigLib();
    createEnvSetRule(
        "one", /* actions= */ "['a1']", /* envEntries= */ "None", /* withFeatures= */ "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("env_entries parameter of env_set should be a list, found NoneType");

    createEnvSetRule(
        "two", /* actions= */ "['a1']", /* envEntries= */ "['abc']", /* withFeatures= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("with_features parameter of env_set should be a list, found NoneType");

    createEnvSetRule(
        "three", /* actions= */ "['a1']", /* envEntries= */ "'asdf'", /* withFeatures= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("env_entries parameter of env_set should be a list, found string");

    createEnvSetRule(
        "four", /* actions= */ "['a1']", /* envEntries= */ "['abc']", /* withFeatures= */ "'def'");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("with_features parameter of env_set should be a list, found string");

    createEnvSetRule(
        "five",
        /* actions= */ "['a1']",
        /* envEntries= */ "[env_entry(key = 'a', value = 'b'),"
            + "variable_with_value(name = 'a', value = 'b')]",
        /* withFeatures= */ "[]");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    try {
      CcModule.envSetFromSkylark(envSetProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'env_entry', received 'variable_with_value'");
    }

    createEnvSetRule("six", /* actions= */ "[]", /* envEntries= */ "[]", /* withFeatures= */ "[]");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//six:a"));
    assertThat(e).hasMessageThat().contains("actions parameter of env_set must be a nonempty list");

    createEnvSetRule(
        "seven",
        /* actions= */ "['a1']",
        /* envEntries= */ "[env_entry(key = 'a', value = 'b')]",
        /* withFeatures= */ "[with_feature_set(features = ['a'])]");

    t = getConfiguredTarget("//seven:a");
    envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    EnvSet envSet = CcModule.envSetFromSkylark(envSetProvider);
    assertThat(envSet).isNotNull();

    createVariableWithValueRule("eight", /* name= */ "'abc'", /* value= */ "'def'");
    t = getConfiguredTarget("//eight:a");
    SkylarkInfo variable = (SkylarkInfo) t.get("variable");
    try {
      CcModule.envSetFromSkylark(variable);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'env_set', received 'variable_with_value");
    }
  }

  private void createEnvSetRule(String pkg, String actions, String envEntries, String withFeatures)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "   'env_set', 'env_entry', 'with_feature_set', 'variable_with_value')",
        "def _impl(ctx):",
        "   return struct(envset = env_set(",
        "       actions = " + actions + ",",
        "       env_entries = " + envEntries + ",",
        "       with_features = " + withFeatures + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomEnvSet() throws Exception {
    loadCcToolchainConfigLib();
    createCustomEnvSetRule(
        "one", /* actions= */ "[]", /* envEntries= */ "None", /* withFeatures= */ "None");
    ConfiguredTarget t = getConfiguredTarget("//one:a");
    SkylarkInfo envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    try {
      CcModule.envSetFromSkylark(envSetProvider);
      fail("Should have failed because of empty action list.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("actions parameter of env_set must be a nonempty list");
    }

    createCustomEnvSetRule(
        "two", /* actions= */ "['a1']", /* envEntries= */ "struct()", /* withFeatures= */ "None");
    t = getConfiguredTarget("//two:a");
    envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    try {
      CcModule.envSetFromSkylark(envSetProvider);
      fail("Should have failed because of wrong envEntries type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'env_entries' is not of expected type list or NoneType");
    }

    createCustomEnvSetRule(
        "three",
        /* actions= */ "['a1']",
        /* envEntries= */ "[struct()]",
        /* withFeatures= */ "None");
    t = getConfiguredTarget("//three:a");
    envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    try {
      CcModule.envSetFromSkylark(envSetProvider);
      fail("Should have failed because of wrong envEntry type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("Expected object of type 'env_entry', received 'struct'");
    }

    createCustomEnvSetRule(
        "four",
        /* actions= */ "['a1']",
        /* envEntries= */ "[env_entry(key = 'a', value = 'b')]",
        /* withFeatures= */ "'a'");
    t = getConfiguredTarget("//four:a");
    envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    try {
      CcModule.envSetFromSkylark(envSetProvider);
      fail("Should have failed because of wrong withFeatures type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("'with_features' is not of expected type list or NoneType");
    }

    createCustomEnvSetRule(
        "five",
        /* actions= */ "['a1']",
        /* envEntries= */ "[env_entry(key = 'a', value = 'b')]",
        /* withFeatures= */ "[env_entry(key = 'a', value = 'b')]");
    t = getConfiguredTarget("//five:a");
    envSetProvider = (SkylarkInfo) t.get("envset");
    assertThat(envSetProvider).isNotNull();
    try {
      CcModule.envSetFromSkylark(envSetProvider);
      fail("Should have failed because of wrong withFeatures type.");
    } catch (EvalException e) {
      assertThat(e)
          .hasMessageThat()
          .contains("Expected object of type 'with_feature_set', received 'env_entry'.");
    }
  }

  private void createCustomEnvSetRule(
      String pkg, String actions, String envEntries, String withFeatures) throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "   'env_entry', 'with_feature_set', 'variable_with_value')",
        "def _impl(ctx):",
        "   return struct(envset = struct(",
        "       actions = " + actions + ",",
        "       env_entries = " + envEntries + ",",
        "       with_features = " + withFeatures + ",",
        "       type_name = 'env_set'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testFlagGroup() throws Exception {
    loadCcToolchainConfigLib();
    createFlagGroupRule(
        "one",
        /* flags= */ "[]",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "None",
        /* expandIfTrue= */ "None",
        /* expandIfFalse= */ "None",
        /* expandIfAvailable= */ "None",
        /* expandIfNotAvailable= */ "None",
        /* expandIfEqual= */ "None");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("flag_group must contain either a list of flags or a list of flag_groups");

    createFlagGroupRule(
        "two",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "struct(val = 'a')",
        /* expandIfTrue= */ "None",
        /* expandIfFalse= */ "None",
        /* expandIfAvailable= */ "None",
        /* expandIfNotAvailable= */ "None",
        /* expandIfEqual= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("iterate_over parameter of flag_group should be a string, found struct");

    createFlagGroupRule(
        "three",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "None",
        /* expandIfTrue= */ "struct(val = 'a')",
        /* expandIfFalse= */ "None",
        /* expandIfAvailable= */ "None",
        /* expandIfNotAvailable= */ "None",
        /* expandIfEqual= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("expand_if_true parameter of flag_group should be a string, found struct");

    createFlagGroupRule(
        "four",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "None",
        /* expandIfTrue= */ "None",
        /* expandIfFalse= */ "struct(val = 'a')",
        /* expandIfAvailable= */ "None",
        /* expandIfNotAvailable= */ "None",
        /* expandIfEqual= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//four:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("expand_if_false parameter of flag_group should be a string, found struct");

    createFlagGroupRule(
        "five",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "None",
        /* expandIfTrue= */ "None",
        /* expandIfFalse= */ "None",
        /* expandIfAvailable= */ "struct(val = 'a')",
        /* expandIfNotAvailable= */ "None",
        /* expandIfEqual= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//five:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("expand_if_available parameter of flag_group should be a string, found struct");

    createFlagGroupRule(
        "six",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "None",
        /* expandIfTrue= */ "None",
        /* expandIfFalse= */ "None",
        /* expandIfAvailable= */ "None",
        /* expandIfNotAvailable= */ "struct(val = 'a')",
        /* expandIfEqual= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//six:a"));
    assertThat(e)
        .hasMessageThat()
        .contains(
            "expand_if_not_available parameter of flag_group should be a string, found struct");

    createFlagGroupRule(
        "seven",
        /* flags= */ "['a']",
        /* flagGroups= */ "['b']",
        /* iterateOver= */ "None",
        /* expandIfTrue= */ "None",
        /* expandIfFalse= */ "None",
        /* expandIfAvailable= */ "None",
        /* expandIfNotAvailable= */ "struct(val = 'a')",
        /* expandIfEqual= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//seven:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("flag_group must not contain both a flag and another flag_group");

    createFlagGroupRule(
        "eight",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "'a'",
        /* expandIfTrue= */ "'b'",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "'a'");

    ConfiguredTarget t = getConfiguredTarget("//eight:a");
    SkylarkInfo flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "Field 'expand_if_equal' is not of "
                  + "'com.google.devtools.build.lib.packages.SkylarkInfo' type.");
    }

    createFlagGroupRule(
        "nine",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a']), flag_group(flags = ['b'])]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "''",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "variable_with_value(name = 'a', value = 'b')");

    t = getConfiguredTarget("//nine:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    FlagGroup f = CcModule.flagGroupFromSkylark(flagGroupProvider);
    assertThat(f).isNotNull();

    createFlagGroupRule(
        "ten",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a']), struct(value = 'a')]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "''",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "variable_with_value(name = 'a', value = 'b')");

    t = getConfiguredTarget("//ten:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'flag_group', received 'struct'");
    }
  }

  private void createFlagGroupRule(
      String pkg,
      String flags,
      String flagGroups,
      String iterateOver,
      String expandIfTrue,
      String expandIfFalse,
      String expandIfAvailable,
      String expandIfNotAvailable,
      String expandIfEqual)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "   'env_set', 'env_entry', 'with_feature_set', 'variable_with_value', 'flag_group')",
        "def _impl(ctx):",
        "   return struct(flaggroup = flag_group(",
        "       flags = " + flags + ",",
        "       flag_groups = " + flagGroups + ",",
        "       expand_if_true = " + expandIfTrue + ",",
        "       expand_if_false = " + expandIfFalse + ",",
        "       expand_if_available = " + expandIfAvailable + ",",
        "       expand_if_not_available = " + expandIfNotAvailable + ",",
        "       expand_if_equal = " + expandIfEqual + ",",
        "       iterate_over = " + iterateOver + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomFlagGroup() throws Exception {
    loadCcToolchainConfigLib();

    createCustomFlagGroupRule(
        "one",
        /* flags= */ "['a']",
        /* flagGroups= */ "[]",
        /* iterateOver= */ "struct()",
        /* expandIfTrue= */ "'b'",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "None");

    ConfiguredTarget t = getConfiguredTarget("//one:a");
    SkylarkInfo flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'iterate_over' is not of 'java.lang.String' type.");
    }

    createCustomFlagGroupRule(
        "two",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a']), flag_group(flags = ['b'])]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "struct()",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "variable_with_value(name = 'a', value = 'b')");

    t = getConfiguredTarget("//two:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'expand_if_true' is not of 'java.lang.String' type.");
    }

    createCustomFlagGroupRule(
        "three",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a'])]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "''",
        /* expandIfFalse= */ "True",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "variable_with_value(name = 'a', value = 'b')");

    t = getConfiguredTarget("//three:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'expand_if_false' is not of 'java.lang.String' type.");
    }

    createCustomFlagGroupRule(
        "four",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a'])]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "''",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "struct()",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "variable_with_value(name = 'a', value = 'b')");

    t = getConfiguredTarget("//four:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'expand_if_available' is not of 'java.lang.String' type.");
    }

    createCustomFlagGroupRule(
        "five",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a'])]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "''",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "3",
        /* expandIfEqual= */ "variable_with_value(name = 'a', value = 'b')");

    t = getConfiguredTarget("//five:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'expand_if_not_available' is not of 'java.lang.String' type.");
    }

    createCustomFlagGroupRule(
        "six",
        /* flags= */ "[]",
        /* flagGroups= */ "[flag_group(flags = ['a'])]",
        /* iterateOver= */ "''",
        /* expandIfTrue= */ "''",
        /* expandIfFalse= */ "''",
        /* expandIfAvailable= */ "''",
        /* expandIfNotAvailable= */ "''",
        /* expandIfEqual= */ "struct(name = 'a', value = 'b')");

    t = getConfiguredTarget("//six:a");
    flagGroupProvider = (SkylarkInfo) t.get("flaggroup");
    assertThat(flagGroupProvider).isNotNull();
    try {
      CcModule.flagGroupFromSkylark(flagGroupProvider);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'variable_with_value', received 'struct'.");
    }
  }

  private void createCustomFlagGroupRule(
      String pkg,
      String flags,
      String flagGroups,
      String iterateOver,
      String expandIfTrue,
      String expandIfFalse,
      String expandIfAvailable,
      String expandIfNotAvailable,
      String expandIfEqual)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "   'env_set', 'env_entry', 'with_feature_set', 'variable_with_value', 'flag_group')",
        "def _impl(ctx):",
        "   return struct(flaggroup = struct(",
        "       flags = " + flags + ",",
        "       flag_groups = " + flagGroups + ",",
        "       expand_if_true = " + expandIfTrue + ",",
        "       expand_if_false = " + expandIfFalse + ",",
        "       expand_if_available = " + expandIfAvailable + ",",
        "       expand_if_not_available = " + expandIfNotAvailable + ",",
        "       expand_if_equal = " + expandIfEqual + ",",
        "       iterate_over = " + iterateOver + ",",
        "       type_name = 'flag_group'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testTool() throws Exception {
    loadCcToolchainConfigLib();
    createToolRule("one", /* path= */ "''", /* withFeatures= */ "[]", /* requirements= */ "[]");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e).hasMessageThat().contains("path parameter of tool must be a nonempty string");

    createToolRule("two", /* path= */ "'a'", /* withFeatures= */ "None", /* requirements= */ "[]");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("with_features parameter of tool should be a list, found NoneType");

    createToolRule(
        "three", /* path= */ "'a'", /* withFeatures= */ "[]", /* requirements= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("execution_requirements parameter of tool should be a list, found NoneType");

    createToolRule(
        "four",
        /* path= */ "'a'",
        /* withFeatures= */ "[struct(val = 'a')]",
        /* requirements= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'with_feature_set', received 'struct'");
    }

    createToolRule(
        "five",
        /* path= */ "'a'",
        /* withFeatures= */ "[]",
        /* requirements= */ "[struct(val = 'a')]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//five:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "expected type 'string' for 'execution_requirements' "
                  + "element but got type 'struct' instead");
    }

    createToolRule(
        "six",
        /* path= */ "'/a/b/c'",
        /* withFeatures= */ "[with_feature_set(features = ['a'])]",
        /* requirements= */ "['a', 'b']");

    ConfiguredTarget t = getConfiguredTarget("//six:a");
    SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
    assertThat(toolStruct).isNotNull();
    Tool tool = CcModule.toolFromSkylark(toolStruct);
    assertThat(tool.getExecutionRequirements()).containsExactly("a", "b");
    assertThat(tool.getToolPathString(PathFragment.EMPTY_FRAGMENT)).isEqualTo("/a/b/c");
    assertThat(tool.getWithFeatureSetSets())
        .contains(new WithFeatureSet(ImmutableSet.of("a"), ImmutableSet.of()));
  }

  private void createToolRule(String pkg, String path, String withFeatures, String requirements)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set', 'tool')",
        "def _impl(ctx):",
        "   return struct(tool = tool(",
        "       path = " + path + ",",
        "       with_features = " + withFeatures + ",",
        "       execution_requirements = " + requirements + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomTool() throws Exception {
    loadCcToolchainConfigLib();
    createCustomToolRule(
        "one", /* path= */ "''", /* withFeatures= */ "[]", /* requirements= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("The 'path' field of tool must be a nonempty string.");
    }

    createCustomToolRule(
        "two", /* path= */ "struct()", /* withFeatures= */ "[]", /* requirements= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee).hasMessageThat().contains("Field 'path' is not of 'java.lang.String' type.");
    }

    createCustomToolRule(
        "three", /* path= */ "'a'", /* withFeatures= */ "struct()", /* requirements= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'with_features' is not of expected type list or NoneType");
    }

    createCustomToolRule(
        "four",
        /* path= */ "'a'",
        /* withFeatures= */ "[struct(val = 'a')]",
        /* requirements= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'with_feature_set', received 'struct'");
    }

    createCustomToolRule(
        "five", /* path= */ "'a'", /* withFeatures= */ "[]", /* requirements= */ "'a'");

    try {
      ConfiguredTarget t = getConfiguredTarget("//five:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "llegal argument: 'execution_requirements' is not of expected type list or NoneType");
    }

    createCustomToolRule(
        "six", /* path= */ "'a'", /* withFeatures= */ "[]", /* requirements= */ "[struct()]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//six:a");
      SkylarkInfo toolStruct = (SkylarkInfo) t.getValue("tool");
      assertThat(toolStruct).isNotNull();
      CcModule.toolFromSkylark(toolStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "expected type 'string' for 'execution_requirements' "
                  + "element but got type 'struct' instead");
    }
  }

  private void createCustomToolRule(
      String pkg, String path, String withFeatures, String requirements) throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set')",
        "def _impl(ctx):",
        "   return struct(tool = struct(",
        "       path = " + path + ",",
        "       with_features = " + withFeatures + ",",
        "       execution_requirements = " + requirements + ",",
        "       type_name = 'tool'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testFlagSet() throws Exception {
    loadCcToolchainConfigLib();
    createFlagSetRule("one", /* actions= */ "[]", /* flagGroups= */ "[]", /* withFeatures= */ "[]");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("actions parameter of flag_set must be a nonempty list");

    createFlagSetRule(
        "two", /* actions= */ "['a']", /* flagGroups= */ "[]", /* withFeatures= */ "None");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("with_features parameter of flag_set should be a list, found NoneType");

    createFlagSetRule(
        "three", /* actions= */ "['a']", /* flagGroups= */ "None", /* withFeatures= */ "[]");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//three:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("flag_groups parameter of flag_set should be a list, found NoneType");

    createFlagSetRule(
        "four",
        /* actions= */ "['a', struct(val = 'a')]",
        /* flagGroups= */ "[]",
        /* withFeatures= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("expected type 'string' for 'actions' element but got type 'struct' instead");
    }

    createFlagSetRule(
        "five",
        /* actions= */ "['a']",
        /* flagGroups= */ "[flag_group(flags = ['a']), struct(value = 'a')]",
        /* withFeatures= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//five:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'flag_group', received 'struct'");
    }

    createFlagSetRule(
        "six",
        /* actions= */ "['a']",
        /* flagGroups= */ "[flag_group(flags = ['a'])]",
        /* withFeatures= */ "[struct(val = 'a')]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//six:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'with_feature_set', received 'struct'");
    }

    createFlagSetRule(
        "seven",
        /* actions= */ "['a']",
        /* flagGroups= */ "[flag_group(flags = ['a'])]",
        /* withFeatures= */ "[with_feature_set(features = ['a'])]");
    ConfiguredTarget t = getConfiguredTarget("//seven:a");
    SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
    assertThat(flagSetStruct).isNotNull();
    FlagSet f = CcModule.flagSetFromSkylark(flagSetStruct);
    assertThat(f).isNotNull();
  }

  private void createFlagSetRule(String pkg, String actions, String flagGroups, String withFeatures)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "   'env_set', 'env_entry', 'with_feature_set', 'variable_with_value', 'flag_group',",
        "   'flag_set')",
        "def _impl(ctx):",
        "   return struct(flagset = flag_set(",
        "       flag_groups = " + flagGroups + ",",
        "       actions = " + actions + ",",
        "       with_features = " + withFeatures + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomFlagSet() throws Exception {
    loadCcToolchainConfigLib();
    createCustomFlagSetRule(
        "one", /* actions= */ "[]", /* flagGroups= */ "[]", /* withFeatures= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because 'actions' field is empty.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("'actions' field of flag_set must be a nonempty list");
    }

    createCustomFlagSetRule(
        "two", /* actions= */ "['a']", /* flagGroups= */ "struct()", /* withFeatures= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'flag_groups' is not of expected type list or NoneType");
    }

    createCustomFlagSetRule(
        "three", /* actions= */ "['a']", /* flagGroups= */ "[]", /* withFeatures= */ "struct()");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'with_features' is not of expected type list or NoneType");
    }

    createCustomFlagSetRule(
        "four", /* actions= */ "struct()", /* flagGroups= */ "[]", /* withFeatures= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo flagSetStruct = (SkylarkInfo) t.getValue("flagset");
      assertThat(flagSetStruct).isNotNull();
      CcModule.flagSetFromSkylark(flagSetStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'actions' is not of expected type list or NoneType");
    }
  }

  private void createCustomFlagSetRule(
      String pkg, String actions, String flagGroups, String withFeatures) throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "   'env_set', 'env_entry', 'with_feature_set', 'variable_with_value', 'flag_group',",
        "   'flag_set')",
        "def _impl(ctx):",
        "   return struct(flagset = struct(",
        "       flag_groups = " + flagGroups + ",",
        "       actions = " + actions + ",",
        "       with_features = " + withFeatures + ",",
        "       type_name = 'flag_set'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testActionConfig() throws Exception {
    loadCcToolchainConfigLib();
    createActionConfigRule(
        "one",
        /* actionName= */ "''",
        /* enabled= */ "True",
        /* tools= */ "[]",
        /* flagSets= */ "[]",
        /* implies= */ "[]");

    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//one:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("name parameter of action_config must be a nonempty string");

    createActionConfigRule(
        "two",
        /* actionName= */ "'actionname'",
        /* enabled= */ "['asd']",
        /* tools= */ "[]",
        /* flagSets= */ "[]",
        /* implies= */ "[]");
    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("enabled parameter of action_config should be a bool, found list");

    createActionConfigRule(
        "three",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[with_feature_set(features = ['a'])]",
        /* flagSets= */ "[]",
        /* implies= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'tool', received 'with_feature_set'");
    }

    createActionConfigRule(
        "four",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[tool(path = 'a/b/c')]",
        /* implies= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'flag_set', received 'tool'");
    }

    createActionConfigRule(
        "five",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[flag_set(actions = ['a', 'b'])]",
        /* implies= */ "flag_set(actions = ['a', 'b'])");

    e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//five:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("implies parameter of action_config should be a list, found struct");

    createActionConfigRule(
        "six",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[flag_set(actions = ['a', 'b'])]",
        /* implies= */ "[flag_set(actions = ['a', 'b'])]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//six:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("expected type 'string' for 'implies' element but got type 'struct' instead");
    }

    createActionConfigRule(
        "seven",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[flag_set(actions = ['a', 'b'])]",
        /* implies= */ "[flag_set(actions = ['a', 'b'])]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//seven:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("expected type 'string' for 'implies' element but got type 'struct' instead");
    }

    createActionConfigRule(
        "eight",
        /* actionName= */ "'actionname_++-'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[flag_set(actions = ['a', 'b'])]",
        /* implies= */ "['a', 'b']");

    ConfiguredTarget t = getConfiguredTarget("//eight:a");
    SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
    assertThat(actionConfigStruct).isNotNull();
    ActionConfig a = CcModule.actionConfigFromSkylark(actionConfigStruct);
    assertThat(a).isNotNull();
    assertThat(a.getActionName()).isEqualTo("actionname_++-");
    assertThat(a.getImplies()).containsExactly("a", "b").inOrder();

    createActionConfigRule(
        "nine",
        /* actionName= */ "'Upper'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[flag_set(actions = ['a', 'b'])]",
        /* implies= */ "[flag_set(actions = ['a', 'b'])]");

    try {
      t = getConfiguredTarget("//nine:a");
      actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "An action_config's name must consist solely "
                  + "of lowercase ASCII letters, '_', '+', and '-', got 'Upper'");
    }

    createActionConfigRule(
        "ten",
        /* actionName= */ "'white\tspace'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[flag_set(actions = ['a', 'b'])]",
        /* implies= */ "[flag_set(actions = ['a', 'b'])]");

    try {
      t = getConfiguredTarget("//ten:a");
      actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "An action_config's name must consist solely "
                  + "of lowercase ASCII letters, '_', '+', and '-', got 'white\tspace'");
    }
  }

  private void createActionConfigRule(
      String pkg, String actionName, String enabled, String tools, String flagSets, String implies)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set',",
        "             'tool', 'flag_set', 'action_config', )",
        "def _impl(ctx):",
        "   return struct(config = action_config(",
        "       action_name = " + actionName + ",",
        "       enabled = " + enabled + ",",
        "       tools = " + tools + ",",
        "       flag_sets = " + flagSets + ",",
        "       implies = " + implies + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomActionConfig() throws Exception {
    loadCcToolchainConfigLib();
    createCustomActionConfigRule(
        "one",
        /* actionName= */ "struct()",
        /* enabled= */ "True",
        /* tools= */ "[]",
        /* flagSets= */ "[]",
        /* implies= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'action_name' is not of 'java.lang.String' type.");
    }

    createCustomActionConfigRule(
        "two",
        /* actionName= */ "'actionname'",
        /* enabled= */ "['asd']",
        /* tools= */ "[]",
        /* flagSets= */ "[]",
        /* implies= */ "[]");
    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'enabled' is not of 'java.lang.Boolean' type.");
    }

    createCustomActionConfigRule(
        "three",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "struct()",
        /* flagSets= */ "[]",
        /* implies= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'tools' is not of expected type list or NoneType");
    }

    createCustomActionConfigRule(
        "four",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "True",
        /* implies= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'flag_sets' is not of expected type list or NoneType");
    }

    createCustomActionConfigRule(
        "five",
        /* actionName= */ "'actionname'",
        /* enabled= */ "True",
        /* tools= */ "[tool(path = 'a/b/c')]",
        /* flagSets= */ "[]",
        /* implies= */ "flag_set(actions = ['a', 'b'])");

    try {
      ConfiguredTarget t = getConfiguredTarget("//five:a");
      SkylarkInfo actionConfigStruct = (SkylarkInfo) t.getValue("config");
      assertThat(actionConfigStruct).isNotNull();
      CcModule.actionConfigFromSkylark(actionConfigStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'implies' is not of expected type list or NoneType");
    }
  }

  private void createCustomActionConfigRule(
      String pkg, String actionName, String enabled, String tools, String flagSets, String implies)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set',",
        "             'tool', 'flag_set', 'action_config', )",
        "def _impl(ctx):",
        "   return struct(config = struct(",
        "       action_name = " + actionName + ",",
        "       enabled = " + enabled + ",",
        "       tools = " + tools + ",",
        "       flag_sets = " + flagSets + ",",
        "       implies = " + implies + ",",
        "       type_name = 'action_config'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testFeature() throws Exception {
    loadCcToolchainConfigLib();
    createFeatureRule(
        "one",
        /* name= */ "''",
        /* enabled= */ "False",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("A feature must either have a nonempty 'name' field or be enabled.");
    }

    createFeatureRule(
        "two",
        /* name= */ "'featurename'",
        /* enabled= */ "None",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//two:a"));
    assertThat(e)
        .hasMessageThat()
        .contains("enabled parameter of feature should be a bool, found NoneType");

    createFeatureRule(
        "three",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[struct()]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'flag_set', received 'struct'");
    }

    createFeatureRule(
        "four",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[flag_set(actions = ['a'], flag_groups = [flag_group(flags = ['a'])])]",
        /* envSets= */ "[tool(path = 'a/b/c')]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Expected object of type 'env_set', received 'tool'");
    }

    createFeatureRule(
        "five",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[flag_set(actions = ['a'], flag_groups = [flag_group(flags = ['a'])])]",
        /* envSets= */ "[env_set(actions = ['a1'], "
            + "env_entries = [env_entry(key = 'a', value = 'b')])]",
        /* requires= */ "[tool(path = 'a/b/c')]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//five:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee).hasMessageThat().contains("expected object of type 'feature_set'");
    }

    createFeatureRule(
        "six",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[flag_set(actions = ['a'], flag_groups = [flag_group(flags = ['a'])])]",
        /* envSets= */ "[env_set(actions = ['a1'], "
            + "env_entries = [env_entry(key = 'a', value = 'b')])]",
        /* requires= */ "[feature_set(features = ['f1', 'f2'])]",
        /* implies= */ "[tool(path = 'a/b/c')]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//six:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("expected type 'string' for 'implies' element but got type 'struct' instead");
    }

    createFeatureRule(
        "seven",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[flag_set(actions = ['a'], flag_groups = [flag_group(flags = ['a'])])]",
        /* envSets= */ "[env_set(actions = ['a1'], "
            + "env_entries = [env_entry(key = 'a', value = 'b')])]",
        /* requires= */ "[feature_set(features = ['f1', 'f2'])]",
        /* implies= */ "['a', 'b', 'c']",
        /* provides= */ "[struct()]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//seven:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("expected type 'string' for 'provides' element but got type 'struct' instead");
    }

    createFeatureRule(
        "eight",
        /* name= */ "'featurename+-_'",
        /* enabled= */ "True",
        /* flagSets= */ "[flag_set(actions = ['a'], flag_groups = [flag_group(flags = ['a'])])]",
        /* envSets= */ "[env_set(actions = ['a1'], "
            + "env_entries = [env_entry(key = 'a', value = 'b')])]",
        /* requires= */ "[feature_set(features = ['f1', 'f2'])]",
        /* implies= */ "['a', 'b', 'c']",
        /* provides= */ "['a', 'b', 'c']");

    ConfiguredTarget t = getConfiguredTarget("//eight:a");
    SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
    assertThat(featureStruct).isNotNull();
    Feature a = CcModule.featureFromSkylark(featureStruct);
    assertThat(a).isNotNull();

    createFeatureRule(
        "nine",
        /* name= */ "'UpperCase'",
        /* enabled= */ "False",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      t = getConfiguredTarget("//nine:a");
      featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "A feature's name must consist solely of lowercase ASCII letters, "
                  + "'_', '+', and '-', got 'UpperCase'");
    }

    createFeatureRule(
        "ten",
        /* name= */ "'white space'",
        /* enabled= */ "False",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      t = getConfiguredTarget("//ten:a");
      featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "A feature's name must consist solely of "
                  + "lowercase ASCII letters, '_', '+', and '-', got 'white space");
    }
  }

  private void createFeatureRule(
      String pkg,
      String name,
      String enabled,
      String flagSets,
      String envSets,
      String requires,
      String implies,
      String provides)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set', 'feature_set',",
        "             'flag_set', 'flag_group', 'tool', 'env_set', 'env_entry', 'feature')",
        "def _impl(ctx):",
        "   return struct(f = feature(",
        "       name = " + name + ",",
        "       enabled = " + enabled + ",",
        "       flag_sets = " + flagSets + ",",
        "       env_sets = " + envSets + ",",
        "       requires = " + requires + ",",
        "       implies = " + implies + ",",
        "       provides = " + provides + "))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomFeature() throws Exception {
    loadCcToolchainConfigLib();
    createCustomFeatureRule(
        "one",
        /* name= */ "struct()",
        /* enabled= */ "False",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee).hasMessageThat().contains("Field 'name' is not of 'java.lang.String' type.");
    }

    createCustomFeatureRule(
        "two",
        /* name= */ "'featurename'",
        /* enabled= */ "struct()",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");
    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'enabled' is not of 'java.lang.Boolean' type.");
    }

    createCustomFeatureRule(
        "three",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "struct()",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'flag_sets' is not of expected type list or NoneType");
    }

    createCustomFeatureRule(
        "four",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[]",
        /* envSets= */ "struct()",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'env_sets' is not of expected type list or NoneType");
    }

    createCustomFeatureRule(
        "five",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "struct()",
        /* implies= */ "[]",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//five:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'requires' is not of expected type list or NoneType");
    }

    createCustomFeatureRule(
        "six",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "struct()",
        /* provides= */ "[]");

    try {
      ConfiguredTarget t = getConfiguredTarget("//six:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'implies' is not of expected type list or NoneType");
    }

    createCustomFeatureRule(
        "seven",
        /* name= */ "'featurename'",
        /* enabled= */ "True",
        /* flagSets= */ "[]",
        /* envSets= */ "[]",
        /* requires= */ "[]",
        /* implies= */ "[]",
        /* provides= */ "struct()");

    try {
      ConfiguredTarget t = getConfiguredTarget("//seven:a");
      SkylarkInfo featureStruct = (SkylarkInfo) t.getValue("f");
      assertThat(featureStruct).isNotNull();
      CcModule.featureFromSkylark(featureStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Illegal argument: 'provides' is not of expected type list or NoneType");
    }
  }

  private void createCustomFeatureRule(
      String pkg,
      String name,
      String enabled,
      String flagSets,
      String envSets,
      String requires,
      String implies,
      String provides)
      throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl', 'with_feature_set', 'feature_set',",
        "             'flag_set', 'flag_group', 'tool', 'env_set', 'env_entry', 'feature')",
        "def _impl(ctx):",
        "   return struct(f = struct(",
        "       name = " + name + ",",
        "       enabled = " + enabled + ",",
        "       flag_sets = " + flagSets + ",",
        "       env_sets = " + envSets + ",",
        "       requires = " + requires + ",",
        "       implies = " + implies + ",",
        "       provides = " + provides + ",",
        "       type_name = 'feature'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCustomArtifactNamePattern() throws Exception {
    loadCcToolchainConfigLib();
    createCustomArtifactNamePatternRule(
        "one", /* categoryName= */ "struct()", /* extension= */ "'a'", /* prefix= */ "'a'");

    try {
      ConfiguredTarget t = getConfiguredTarget("//one:a");
      SkylarkInfo artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
      assertThat(artifactNamePatternStruct).isNotNull();
      CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'category_name' is not of 'java.lang.String' type.");
    }

    createCustomArtifactNamePatternRule(
        "two",
        /* categoryName= */ "'static_library'",
        /* extension= */ "struct()",
        /* prefix= */ "'a'");

    try {
      ConfiguredTarget t = getConfiguredTarget("//two:a");
      SkylarkInfo artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
      assertThat(artifactNamePatternStruct).isNotNull();
      CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains("Field 'extension' is not of 'java.lang.String' type.");
    }

    createCustomArtifactNamePatternRule(
        "three",
        /* categoryName= */ "'static_library'",
        /* extension= */ "'.a'",
        /* prefix= */ "struct()");

    try {
      ConfiguredTarget t = getConfiguredTarget("//three:a");
      SkylarkInfo artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
      assertThat(artifactNamePatternStruct).isNotNull();
      CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
      fail("Should have failed because of wrong object type.");
    } catch (EvalException ee) {
      assertThat(ee).hasMessageThat().contains("Field 'prefix' is not of 'java.lang.String' type.");
    }

    createCustomArtifactNamePatternRule(
        "four", /* categoryName= */ "''", /* extension= */ "'.a'", /* prefix= */ "'a'");

    try {
      ConfiguredTarget t = getConfiguredTarget("//four:a");
      SkylarkInfo artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
      assertThat(artifactNamePatternStruct).isNotNull();
      CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
      fail("Should have failed because of empty string.");
    } catch (EvalException ee) {
      assertThat(ee)
          .hasMessageThat()
          .contains(
              "The 'category_name' field of artifact_name_pattern must be a nonempty string.");
    }

    createCustomArtifactNamePatternRule(
        "five", /* categoryName= */ "'executable'", /* extension= */ "''", /* prefix= */ "''");

    ConfiguredTarget t = getConfiguredTarget("//five:a");
    SkylarkInfo artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
    assertThat(artifactNamePatternStruct).isNotNull();
    ArtifactNamePattern artifactNamePattern =
        CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
    assertThat(artifactNamePattern).isNotNull();

    createCustomArtifactNamePatternRule(
        "six", /* categoryName= */ "'executable'", /* extension= */ "None", /* prefix= */ "None");

    t = getConfiguredTarget("//six:a");
    artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
    assertThat(artifactNamePatternStruct).isNotNull();
    artifactNamePattern = CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
    assertThat(artifactNamePattern).isNotNull();

    createCustomArtifactNamePatternRule(
        "seven", /* categoryName= */ "'unknown'", /* extension= */ "'.a'", /* prefix= */ "'a'");

    try {
      t = getConfiguredTarget("//seven:a");
      artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
      assertThat(artifactNamePatternStruct).isNotNull();
      CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
      fail("Should have failed because of unrecognized category.");
    } catch (EvalException ee) {
      assertThat(ee).hasMessageThat().contains("Artifact category unknown not recognized");
    }

    createCustomArtifactNamePatternRule(
        "eight",
        /* categoryName= */ "'static_library'",
        /* extension= */ "'a'",
        /* prefix= */ "'a'");

    try {
      t = getConfiguredTarget("//eight:a");
      artifactNamePatternStruct = (SkylarkInfo) t.getValue("namepattern");
      assertThat(artifactNamePatternStruct).isNotNull();
      CcModule.artifactNamePatternFromSkylark(artifactNamePatternStruct);
      fail("Should have failed because of unrecognized extension.");
    } catch (EvalException ee) {
      assertThat(ee).hasMessageThat().contains("Unrecognized file extension 'a'");
    }
  }

  private void createCustomArtifactNamePatternRule(
      String pkg, String categoryName, String extension, String prefix) throws Exception {
    scratch.file(
        pkg + "/foo.bzl",
        "def _impl(ctx):",
        "   return struct(namepattern = struct(",
        "       category_name = " + categoryName + ",",
        "       extension = " + extension + ",",
        "       prefix = " + prefix + ",",
        "       type_name = 'artifact_name_pattern'))",
        "crule = rule(implementation = _impl)");
    scratch.file(pkg + "/BUILD", "load(':foo.bzl', 'crule')", "crule(name = 'a')");
  }

  @Test
  public void testCcToolchainInfoFromSkylark() throws Exception {
    loadCcToolchainConfigLib();
    scratch.file(
        "foo/crosstool.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "        'feature',",
        "        'action_config',",
        "        'artifact_name_pattern',",
        "        'env_entry',",
        "        'variable_with_value',",
        "        'make_variable',",
        "        'feature_set',",
        "        'with_feature_set',",
        "        'env_set',",
        "        'flag_group',",
        "        'flag_set',",
        "        'tool_path',",
        "        'tool')",
        "",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "                ctx = ctx,",
        "                features = [feature(name = 'featureone')],",
        "                action_configs = [action_config(action_name = 'action', enabled=True)],",
        "                artifact_name_patterns = [artifact_name_pattern(",
        "                   category_name = 'static_library',",
        "                   prefix = 'prefix',",
        "                   extension = '.a')],",
        "                cxx_builtin_include_directories = ['dir1', 'dir2', 'dir3'],",
        "                toolchain_identifier = 'toolchain',",
        "                host_system_name = 'host',",
        "                target_system_name = 'target',",
        "                target_cpu = 'cpu',",
        "                target_libc = 'libc',",
        "                default_libc_top = 'libc_top',",
        "                compiler = 'compiler',",
        "                abi_libc_version = 'abi_libc',",
        "                abi_version = 'abi',",
        "                supports_gold_linker = True,",
        "                supports_start_end_lib = True,",
        "                supports_embedded_runtimes = True,",
        "                tool_paths = [tool_path(name = 'name1', path = 'path1')],",
        "                cc_target_os = 'os',",
        "                compiler_flags = ['flag1', 'flag2', 'flag3'],",
        "                linker_flags = ['flag1'],",
        "                objcopy_embed_flags = ['flag1'],",
        "                needs_pic = True,",
        "                builtin_sysroot = 'sysroot',",
        "                make_variables = [make_variable(name = 'acs', value = 'asd')])",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");
    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    assertThat(ccToolchainConfigInfo).isNotNull();

    useConfiguration();
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("Creating a CcToolchainConfigInfo is not enabled.");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredToolchainIdentifier() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("toolchain_identifier");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e)
        .hasMessageThat()
        .contains("parameter 'toolchain_identifier' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredHostSystemName() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("host_system_name");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'host_system_name' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredTargetSystemName() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("target_system_name");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'target_system_name' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredTargetCpu() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("target_cpu");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'target_cpu' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredTargetLibc() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("target_libc");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'target_libc' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredCompiler() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("compiler");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'compiler' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredAbiVersion() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("abi_version");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'abi_version' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkRequiredAbiLibcVersion() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("abi_libc_version");
    AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:r"));
    assertThat(e).hasMessageThat().contains("parameter 'abi_libc_version' has no default value");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkAllRequiredStringsPresent() throws Exception {
    setupSkylarkRuleForStringFieldsTesting("");
    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    assertThat(ccToolchainConfigInfo).isNotNull();
  }

  private void setupSkylarkRuleForStringFieldsTesting(String fieldToExclude) throws Exception {
    ImmutableList<String> fields =
        ImmutableList.of(
            "toolchain_identifier = 'identifier'",
            "host_system_name = 'host_system_name'",
            "target_system_name = 'target_system_name'",
            "target_cpu = 'target_cpu'",
            "target_libc = 'target_libc'",
            "compiler = 'compiler'",
            "abi_version = 'abi'",
            "abi_libc_version = 'abi_libc'");

    scratch.file(
        "foo/crosstool.bzl",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "    ctx = ctx,",
        Joiner.on(",\n")
            .join(fields.stream().filter(el -> !el.startsWith(fieldToExclude + " =")).toArray()),
        ")",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");
    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkNoLegacyFeatures() throws Exception {
    loadCcToolchainConfigLib();
    scratch.file(
        "foo/crosstool.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "        'feature',",
        "        'action_config',",
        "        'artifact_name_pattern',",
        "        'env_entry',",
        "        'variable_with_value',",
        "        'make_variable',",
        "        'feature_set',",
        "        'with_feature_set',",
        "        'env_set',",
        "        'flag_group',",
        "        'flag_set',",
        "        'tool_path',",
        "        'tool')",
        "",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "                ctx = ctx,",
        "                features = [",
        "                    feature(name = 'no_legacy_features'),",
        "                    feature(name = 'custom_feature'),",
        "                ],",
        "                action_configs = [action_config(action_name = 'custom_action')],",
        "                artifact_name_patterns = [artifact_name_pattern(",
        "                   category_name = 'static_library',",
        "                   prefix = 'prefix',",
        "                   extension = '.a')],",
        "                toolchain_identifier = 'toolchain',",
        "                host_system_name = 'host',",
        "                target_system_name = 'target',",
        "                target_cpu = 'cpu',",
        "                target_libc = 'libc',",
        "                default_libc_top = 'libc_top',",
        "                compiler = 'compiler',",
        "                abi_libc_version = 'abi_libc',",
        "                abi_version = 'abi')",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");
    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    ImmutableSet<String> featureNames =
        ccToolchainConfigInfo.getFeatures().stream()
            .map(feature -> feature.getName())
            .collect(ImmutableSet.toImmutableSet());
    ImmutableSet<String> actionConfigNames =
        ccToolchainConfigInfo.getActionConfigs().stream()
            .map(actionConfig -> actionConfig.getActionName())
            .collect(ImmutableSet.toImmutableSet());
    assertThat(featureNames).containsExactly("no_legacy_features", "custom_feature");
    assertThat(actionConfigNames).containsExactly("custom_action");
  }

  @Test
  public void testCcToolchainInfoFromSkylarkWithLegacyFeatures() throws Exception {
    loadCcToolchainConfigLib();
    scratch.file(
        "foo/crosstool.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "        'feature',",
        "        'action_config',",
        "        'artifact_name_pattern',",
        "        'env_entry',",
        "        'variable_with_value',",
        "        'make_variable',",
        "        'feature_set',",
        "        'with_feature_set',",
        "        'env_set',",
        "        'flag_group',",
        "        'flag_set',",
        "        'tool_path',",
        "        'tool')",
        "",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "                ctx = ctx,",
        "                features = [",
        "                    feature(name = 'custom_feature'),",
        "                    feature(name = 'legacy_compile_flags'),",
        "                    feature(name = 'fdo_optimize'),",
        "                ],",
        "                action_configs = [action_config(action_name = 'custom-action')],",
        "                artifact_name_patterns = [artifact_name_pattern(",
        "                   category_name = 'static_library',",
        "                   prefix = 'prefix',",
        "                   extension = '.a')],",
        "                toolchain_identifier = 'toolchain',",
        "                host_system_name = 'host',",
        "                target_system_name = 'target',",
        "                target_cpu = 'cpu',",
        "                target_libc = 'libc',",
        "                default_libc_top = 'libc_top',",
        "                compiler = 'compiler',",
        "                abi_libc_version = 'abi_libc',",
        "                abi_version = 'abi')",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");
    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    ImmutableList<String> featureNames =
        ccToolchainConfigInfo.getFeatures().stream()
            .map(feature -> feature.getName())
            .collect(ImmutableList.toImmutableList());
    ImmutableSet<String> actionConfigNames =
        ccToolchainConfigInfo.getActionConfigs().stream()
            .map(actionConfig -> actionConfig.getActionName())
            .collect(ImmutableSet.toImmutableSet());
    // fdo_optimize should not be re-added to the list of features by legacy behavior
    assertThat(featureNames).containsNoDuplicates();
    // legacy_compile_flags should appear first in the list of features
    assertThat(featureNames.get(0)).isEqualTo("legacy_compile_flags");
    assertThat(featureNames)
        .containsAllOf("legacy_compile_flags", "custom_feature", "fdo_optimize")
        .inOrder();
    // assemble is one of the action_configs added as a legacy behavior, therefore it needs to be
    // prepended to the action configs defined by the user.
    assertThat(actionConfigNames).containsAllOf("assemble", "custom-action").inOrder();
  }

  @Test
  public void testCcToolchainInfoToProto() throws Exception {
    loadCcToolchainConfigLib();
    scratch.file(
        "foo/crosstool.bzl",
        "load('//tools/cpp:cc_toolchain_config_lib.bzl',",
        "        'feature',",
        "        'action_config',",
        "        'artifact_name_pattern',",
        "        'env_entry',",
        "        'variable_with_value',",
        "        'make_variable',",
        "        'feature_set',",
        "        'with_feature_set',",
        "        'env_set',",
        "        'flag_group',",
        "        'flag_set',",
        "        'tool_path',",
        "        'tool')",
        "",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "                ctx = ctx,",
        "                features = [",
        "                    feature(name = 'featureone', enabled = True),",
        "                    feature(name = 'sysroot',",
        "                        flag_sets = [",
        "                            flag_set(",
        "                                actions = ['assemble', 'preprocess-assemble'],",
        "                                flag_groups = [",
        "                                    flag_group(",
        "                                        expand_if_available= 'available',",
        "                                        expand_if_not_available= 'notavailable',",
        "                                        expand_if_true = 'true',",
        "                                        expand_if_false = 'false',",
        "                                        expand_if_equal = variable_with_value(",
        "                                            name = 'variable', value = 'value'),",
        "                                        iterate_over = 'iterate_over',",
        "                                        flag_groups = [",
        "                                            flag_group(flags = ",
        "                                                ['foo%{libraries_to_link}bar',",
        "                                                 '-D__DATE__=\"redacted\"'])],",
        "                                    ),",
        "                                    flag_group(flags = ['a', 'b'])],",
        "                        )],",
        "                        implies = ['imply2', 'imply1'],",
        "                        provides = ['provides2', 'provides1'],",
        "                        requires = [feature_set(features = ['r1', 'r2']),",
        "                                    feature_set(features = ['r3'])],",
        "                        env_sets = [",
        "                             env_set(actions = ['a1', 'a2'],",
        "                                     env_entries = [",
        "                                         env_entry(key = 'k1', value = 'v1'),",
        "                                         env_entry(key = 'k2', value = 'v2')],",
        "                                     with_features = [",
        "                                         with_feature_set(",
        "                                             features = ['f1', 'f2'],",
        "                                             not_features = ['nf1', 'nf2'])]),",
        "                             env_set(actions = ['a1', 'a2'])]",
        "                )],",
        "                action_configs = [",
        "                    action_config(action_name = 'action_one', enabled=True),",
        "                    action_config(",
        "                        action_name = 'action_two',",
        "                        tools = [tool(path = 'fake/path',",
        "                                      with_features = [",
        "                                          with_feature_set(features = ['a'],",
        "                                                           not_features=['b', 'c']),",
        "                                          with_feature_set(features=['b'])],",
        "                                      execution_requirements = ['a', 'b', 'c'])],",
        "                        implies = ['compiler_input_flags', 'compiler_output_flags'])],",
        "                artifact_name_patterns = [artifact_name_pattern(",
        "                   category_name = 'static_library',",
        "                   prefix = 'prefix',",
        "                   extension = '.a')],",
        "                cxx_builtin_include_directories = ['dir1', 'dir2', 'dir3'],",
        "                toolchain_identifier = 'toolchain',",
        "                host_system_name = 'host',",
        "                target_system_name = 'target',",
        "                target_cpu = 'cpu',",
        "                target_libc = 'libc',",
        "                compiler = 'compiler',",
        "                abi_libc_version = 'abi_libc',",
        "                abi_version = 'abi',",
        "                supports_gold_linker = True,",
        "                supports_start_end_lib = True,",
        "                supports_interface_shared_objects = True,",
        "                supports_embedded_runtimes = True,",
        "                static_runtime_filegroup = 'static',",
        "                dynamic_runtime_filegroup = 'dynamic',",
        "                supports_fission = True,",
        "                supports_dsym = True,",
        "                needs_pic = True,",
        "                tool_paths = [tool_path(name = 'name1', path = 'path1')],",
        "                compiler_flags = ['flag1', 'flag2', 'flag3'],",
        "                cxx_flags = ['cxx1', 'cxx2'],",
        "                unfiltered_cxx_flags = ['ucxx1', 'ucxx2'],",
        "                linker_flags = ['l2', 'l1'],",
        "                dynamic_library_linker_flags = ['dll1', 'dll2'],",
        "                test_only_linker_flags = ['t1', 't2'],",
        "                objcopy_embed_flags = ['o1', 'o2'],",
        "                ld_embed_flags = ['ld1', 'ld2'],",
        "                compilation_mode_compiler_flags = {",
        "                    'OPT' : ['opt1', 'opt2'],",
        "                    'FASTBUILD' : ['fbd1', 'fbd2'],",
        "                    'DBG' : ['dbg1', 'dbg2'] },",
        "                compilation_mode_cxx_flags = {",
        "                    'OPT' : ['optcxx1', 'optcxx2'],",
        "                    'FASTBUILD' : ['fbdcxx1', 'fbdcxx2'],",
        "                    'DBG' : ['dbgcxx1', 'dbgcxx2'] },",
        "                compilation_mode_linker_flags = {",
        "                    'OPT' : ['optl1', 'optl2'],",
        "                    'FASTBUILD' : ['fbdl1', 'fbdl2'],",
        "                    'DBG' : ['dbgl1', 'dbgl2'] },",
        "                mostly_static_linking_mode_flags = ['ms1', 'ms2'],",
        "                dynamic_linking_mode_flags = ['d1', 'd2'],",
        "                fully_static_linking_mode_flags = ['fs1', 'fs2'],",
        "                mostly_static_libraries_linking_mode_flags = ['msl1', 'msl2'],",
        "                make_variables = [make_variable(name = 'variable', value = '--a -b -c')],",
        "                builtin_sysroot = 'sysroot',",
        "                default_libc_top = 'libc_top',",
        "                cc_target_os = 'os',",
        "        )",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");

    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    assertThat(ccToolchainConfigInfo).isNotNull();
    CToolchain.Builder toolchainBuilder = CToolchain.newBuilder();
    TextFormat.merge(ccToolchainConfigInfo.getProto(), toolchainBuilder);
    CToolchain toolchain = toolchainBuilder.build();

    assertThat(toolchain.getCxxBuiltinIncludeDirectoryList())
        .containsExactly("dir1", "dir2", "dir3");
    assertThat(toolchain.getToolchainIdentifier()).isEqualTo("toolchain");
    assertThat(toolchain.getHostSystemName()).isEqualTo("host");
    assertThat(toolchain.getTargetSystemName()).isEqualTo("target");
    assertThat(toolchain.getTargetCpu()).isEqualTo("cpu");
    assertThat(toolchain.getTargetLibc()).isEqualTo("libc");
    assertThat(toolchain.getCompiler()).isEqualTo("compiler");
    assertThat(toolchain.getAbiLibcVersion()).isEqualTo("abi_libc");
    assertThat(toolchain.getAbiVersion()).isEqualTo("abi");
    assertThat(toolchain.getSupportsGoldLinker()).isTrue();
    assertThat(toolchain.getSupportsStartEndLib()).isTrue();
    assertThat(toolchain.getSupportsInterfaceSharedObjects()).isTrue();
    assertThat(toolchain.getSupportsEmbeddedRuntimes()).isTrue();
    assertThat(toolchain.getStaticRuntimesFilegroup()).isEqualTo("static");
    assertThat(toolchain.getDynamicRuntimesFilegroup()).isEqualTo("dynamic");
    assertThat(toolchain.getSupportsFission()).isTrue();
    assertThat(toolchain.getSupportsDsym()).isTrue();
    assertThat(toolchain.getNeedsPic()).isTrue();
    ToolPath toolPath = Iterables.getOnlyElement(toolchain.getToolPathList());
    assertThat(toolPath.getName()).isEqualTo("name1");
    assertThat(toolPath.getPath()).isEqualTo("path1");
    assertThat(toolchain.getCompilerFlagList())
        .containsExactly("flag1", "flag2", "flag3")
        .inOrder();
    assertThat(toolchain.getCxxFlagList()).containsExactly("cxx1", "cxx2").inOrder();
    assertThat(toolchain.getUnfilteredCxxFlagList()).containsExactly("ucxx1", "ucxx2").inOrder();
    assertThat(toolchain.getLinkerFlagList()).containsExactly("l2", "l1").inOrder();
    assertThat(toolchain.getTestOnlyLinkerFlagList()).containsExactly("t1", "t2");
    assertThat(toolchain.getObjcopyEmbedFlagList()).containsExactly("o1", "o2").inOrder();
    assertThat(toolchain.getLdEmbedFlagList()).containsExactly("ld1", "ld2").inOrder();
    assertThat(toolchain.getCompilationModeFlagsCount()).isEqualTo(3);
    for (CompilationModeFlags compilationModeFlags : toolchain.getCompilationModeFlagsList()) {
      switch (compilationModeFlags.getMode()) {
        case OPT:
          assertThat(compilationModeFlags.getCompilerFlagList())
              .containsExactly("opt1", "opt2")
              .inOrder();
          assertThat(compilationModeFlags.getCxxFlagList())
              .containsExactly("optcxx1", "optcxx2")
              .inOrder();
          assertThat(compilationModeFlags.getLinkerFlagList())
              .containsExactly("optl1", "optl2")
              .inOrder();
          break;
        case FASTBUILD:
          assertThat(compilationModeFlags.getCompilerFlagList())
              .containsExactly("fbd1", "fbd2")
              .inOrder();
          assertThat(compilationModeFlags.getCxxFlagList())
              .containsExactly("fbdcxx1", "fbdcxx2")
              .inOrder();
          assertThat(compilationModeFlags.getLinkerFlagList())
              .containsExactly("fbdl1", "fbdl2")
              .inOrder();
          break;
        case DBG:
          assertThat(compilationModeFlags.getCompilerFlagList())
              .containsExactly("dbg1", "dbg2")
              .inOrder();
          assertThat(compilationModeFlags.getCxxFlagList())
              .containsExactly("dbgcxx1", "dbgcxx2")
              .inOrder();
          assertThat(compilationModeFlags.getLinkerFlagList())
              .containsExactly("dbgl1", "dbgl2")
              .inOrder();
          break;
        default:
          // COVERAGE mode is ignored
      }
    }
    assertThat(toolchain.getLinkingModeFlagsCount()).isEqualTo(4);
    for (LinkingModeFlags linkingModeFlags : toolchain.getLinkingModeFlagsList()) {
      switch (linkingModeFlags.getMode()) {
        case MOSTLY_STATIC:
          assertThat(linkingModeFlags.getLinkerFlagList()).containsExactly("ms1", "ms2").inOrder();
          break;
        case DYNAMIC:
          assertThat(linkingModeFlags.getLinkerFlagList()).containsExactly("d1", "d2").inOrder();
          break;
        case FULLY_STATIC:
          assertThat(linkingModeFlags.getLinkerFlagList()).containsExactly("fs1", "fs2").inOrder();
          break;
        case MOSTLY_STATIC_LIBRARIES:
          assertThat(linkingModeFlags.getLinkerFlagList())
              .containsExactly("msl1", "msl2")
              .inOrder();
          break;
      }
    }
    MakeVariable makeVariable = Iterables.getOnlyElement(toolchain.getMakeVariableList());
    assertThat(makeVariable.getName()).isEqualTo("variable");
    assertThat(makeVariable.getValue()).isEqualTo("--a -b -c");
    assertThat(toolchain.getBuiltinSysroot()).isEqualTo("sysroot");
    assertThat(toolchain.getCcTargetOs()).isEqualTo("os");
    assertThat(
            toolchain.getFeatureList().stream()
                .map(feature -> feature.getName())
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("featureone", "sysroot")
        .inOrder();

    CToolchain.Feature feature = toolchain.getFeature(1);
    assertThat(feature.getName()).isEqualTo("sysroot");
    assertThat(feature.getImpliesList()).containsExactly("imply2", "imply1").inOrder();
    assertThat(feature.getProvidesList()).containsExactly("provides2", "provides1").inOrder();
    assertThat(feature.getRequires(0).getFeatureList()).containsExactly("r1", "r2");
    assertThat(feature.getRequires(1).getFeatureList()).containsExactly("r3");
    assertThat(feature.getEnvSetCount()).isEqualTo(2);
    CToolchain.EnvSet envSet = feature.getEnvSet(0);
    assertThat(envSet.getActionList()).containsExactly("a1", "a2");
    assertThat(envSet.getEnvEntry(0).getKey()).isEqualTo("k1");
    assertThat(envSet.getEnvEntry(0).getValue()).isEqualTo("v1");
    assertThat(Iterables.getOnlyElement(envSet.getWithFeatureList()).getFeatureList())
        .containsExactly("f1", "f2");
    assertThat(Iterables.getOnlyElement(envSet.getWithFeatureList()).getNotFeatureList())
        .containsExactly("nf1", "nf2");
    CToolchain.FlagSet flagSet = Iterables.getOnlyElement(feature.getFlagSetList());
    assertThat(flagSet.getActionList()).containsExactly("assemble", "preprocess-assemble");
    assertThat(flagSet.getFlagGroupCount()).isEqualTo(2);
    CToolchain.FlagGroup flagGroup = flagSet.getFlagGroup(0);
    assertThat(flagGroup.getExpandIfAllAvailableList()).containsExactly("available");
    assertThat(flagGroup.getExpandIfNoneAvailableList()).containsExactly("notavailable");
    assertThat(flagGroup.getIterateOver()).isEqualTo("iterate_over");
    assertThat(flagGroup.getExpandIfTrue()).isEqualTo("true");
    assertThat(flagGroup.getExpandIfFalse()).isEqualTo("false");
    CToolchain.VariableWithValue variableWithValue = flagGroup.getExpandIfEqual();
    assertThat(variableWithValue.getVariable()).isEqualTo("variable");
    assertThat(variableWithValue.getValue()).isEqualTo("value");
    assertThat(flagGroup.getFlagGroup(0).getFlagList())
        .containsExactly("foo%{libraries_to_link}bar", "-D__DATE__=\"redacted\"")
        .inOrder();

    assertThat(
            toolchain.getActionConfigList().stream()
                .map(actionConfig -> actionConfig.getActionName())
                .collect(ImmutableList.toImmutableList()))
        .containsExactly("action_one", "action_two")
        .inOrder();
    CToolchain.ActionConfig actionConfig = toolchain.getActionConfig(1);
    assertThat(actionConfig.getImpliesList())
        .containsExactly("compiler_input_flags", "compiler_output_flags")
        .inOrder();
    CToolchain.Tool tool = Iterables.getOnlyElement(actionConfig.getToolList());
    assertThat(tool.getToolPath()).isEqualTo("fake/path");
    assertThat(tool.getExecutionRequirementList()).containsExactly("a", "b", "c");
    assertThat(tool.getWithFeature(0).getFeatureList()).containsExactly("a");
    assertThat(tool.getWithFeature(0).getNotFeatureList()).containsExactly("b", "c");

    assertThat(Iterables.getOnlyElement(toolchain.getArtifactNamePatternList()))
        .isEqualTo(
            CToolchain.ArtifactNamePattern.newBuilder()
                .setCategoryName("static_library")
                .setPrefix("prefix")
                .setExtension(".a")
                .build());
  }

  // Tests that default None values in non-required fields do not cause trouble while building
  // the CToolchain
  @Test
  public void testCcToolchainInfoToProtoBareMinimum() throws Exception {
    loadCcToolchainConfigLib();
    scratch.file(
        "foo/crosstool.bzl",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "                ctx = ctx,",
        "                toolchain_identifier = 'toolchain',",
        "                host_system_name = 'host',",
        "                target_system_name = 'target',",
        "                target_cpu = 'cpu',",
        "                target_libc = 'libc',",
        "                compiler = 'compiler',",
        "                abi_libc_version = 'abi_libc',",
        "                abi_version = 'abi',",
        "        )",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");

    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    assertThat(ccToolchainConfigInfo).isNotNull();
    CToolchain.Builder toolchainBuilder = CToolchain.newBuilder();
    TextFormat.merge(ccToolchainConfigInfo.getProto(), toolchainBuilder);
    CToolchain toolchain = toolchainBuilder.build();

    // Our generated CToolchain must not have a LinkingModeFlags entry with DYNAMIC mode, even
    // if the list of flags for that entry is empty.
    assertThat(
            toolchain.getLinkingModeFlagsList().stream()
                .noneMatch(
                    linkingModeFlags -> linkingModeFlags.getMode().equals(LinkingMode.DYNAMIC)))
        .isTrue();
  }

  @Test
  public void testCcToolchainInfoToProtoDynamicLinkingModeEnabledWithEmptyList() throws Exception {
    loadCcToolchainConfigLib();
    scratch.file(
        "foo/crosstool.bzl",
        "def _impl(ctx):",
        "    return cc_common.create_cc_toolchain_config_info(",
        "                ctx = ctx,",
        "                toolchain_identifier = 'toolchain',",
        "                host_system_name = 'host',",
        "                target_system_name = 'target',",
        "                target_cpu = 'cpu',",
        "                target_libc = 'libc',",
        "                compiler = 'compiler',",
        "                abi_libc_version = 'abi_libc',",
        "                abi_version = 'abi',",
        "                dynamic_linking_mode_flags = [],",
        "        )",
        "cc_toolchain_config_rule = rule(",
        "    implementation = _impl,",
        "    attrs = {},",
        "    provides = [CcToolchainConfigInfo], ",
        ")");

    scratch.file(
        "foo/BUILD",
        "load(':crosstool.bzl', 'cc_toolchain_config_rule')",
        "cc_toolchain_alias(name='alias')",
        "cc_toolchain_config_rule(name='r')");

    useConfiguration("--experimental_enable_cc_toolchain_config_info");
    ConfiguredTarget target = getConfiguredTarget("//foo:r");
    assertThat(target).isNotNull();
    CcToolchainConfigInfo ccToolchainConfigInfo =
        (CcToolchainConfigInfo) target.get(CcToolchainConfigInfo.PROVIDER.getKey());
    assertThat(ccToolchainConfigInfo).isNotNull();
    CToolchain.Builder toolchainBuilder = CToolchain.newBuilder();
    TextFormat.merge(ccToolchainConfigInfo.getProto(), toolchainBuilder);
    CToolchain toolchain = toolchainBuilder.build();

    // If parameter dynamic_linking_mode_flags is passed to the create_cc_toolchain_config_info
    // method, the corresponding CToolchain should have an entry in its linking_mode_flags list
    // that has dynamic linking mode.
    assertThat(
            toolchain.getLinkingModeFlagsList().stream()
                .filter(linkingModeFlags -> linkingModeFlags.getMode().equals(LinkingMode.DYNAMIC))
                .count())
        .isEqualTo(1);
  }
}
