| // Copyright 2014 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.ArrayListMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Multimap; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ArtifactRoot; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ActionConfig; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.ExpansionException; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.IntegerValue; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StringSequenceBuilder; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.StructureBuilder; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValue; |
| import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValueBuilder; |
| import com.google.devtools.build.lib.skyframe.serialization.AutoRegistry; |
| import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecs; |
| import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializationTester; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.testutil.TestUtils; |
| import com.google.devtools.build.lib.vfs.Path; |
| 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.CToolchain.Feature; |
| import com.google.protobuf.TextFormat; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for toolchain features. */ |
| @RunWith(JUnit4.class) |
| public class CcToolchainFeaturesTest extends BuildViewTestCase { |
| |
| /** |
| * Creates a {@code Variables} configuration from a list of key/value pairs. |
| * |
| * <p>If there are multiple entries with the same key, the variable will be treated as sequence |
| * type. |
| */ |
| private CcToolchainVariables createVariables(String... entries) { |
| if (entries.length % 2 != 0) { |
| throw new IllegalArgumentException( |
| "createVariables takes an even number of arguments (key/value pairs)"); |
| } |
| Multimap<String, String> entryMap = ArrayListMultimap.create(); |
| for (int i = 0; i < entries.length; i += 2) { |
| entryMap.put(entries[i], entries[i + 1]); |
| } |
| CcToolchainVariables.Builder variables = CcToolchainVariables.builder(); |
| for (String name : entryMap.keySet()) { |
| Collection<String> value = entryMap.get(name); |
| if (value.size() == 1) { |
| variables.addStringVariable(name, value.iterator().next()); |
| } else { |
| variables.addStringSequenceVariable(name, ImmutableList.copyOf(value)); |
| } |
| } |
| return variables.build(); |
| } |
| |
| /** Creates a CcToolchainFeatures from features described in the given toolchain fragment. */ |
| public static CcToolchainFeatures buildFeatures(String... toolchain) throws Exception { |
| CToolchain.Builder toolchainBuilder = CToolchain.newBuilder(); |
| TextFormat.merge(Joiner.on("").join(toolchain), toolchainBuilder); |
| return new CcToolchainFeatures( |
| CcToolchainConfigInfo.fromToolchain(toolchainBuilder.buildPartial()), |
| PathFragment.create("crosstool/")); |
| } |
| |
| /** Creates a CcToolchainFeatures from given features and action configs. */ |
| public static CcToolchainFeatures buildFeatures( |
| ImmutableList<Feature> features, ImmutableList<CToolchain.ActionConfig> actionConfigs) |
| throws Exception { |
| CToolchain.Builder toolchainBuilder = CToolchain.newBuilder(); |
| toolchainBuilder.addAllFeature(features); |
| toolchainBuilder.addAllActionConfig(actionConfigs); |
| return new CcToolchainFeatures( |
| CcToolchainConfigInfo.fromToolchain(toolchainBuilder.buildPartial()), |
| PathFragment.create("crosstool/")); |
| } |
| |
| /** Creates an empty CcToolchainFeatures. */ |
| public CcToolchainFeatures buildEmptyFeatures(String... toolchain) throws Exception { |
| CToolchain.Builder toolchainBuilder = CToolchain.newBuilder(); |
| TextFormat.merge(Joiner.on("").join(toolchain), toolchainBuilder); |
| return new CcToolchainFeatures( |
| CcToolchainConfigInfo.fromToolchain(toolchainBuilder.buildPartial()), |
| PathFragment.EMPTY_FRAGMENT); |
| } |
| |
| private Set<String> getEnabledFeatures(CcToolchainFeatures features, |
| String... requestedFeatures) throws Exception { |
| FeatureConfiguration configuration = |
| features.getFeatureConfiguration(ImmutableSet.copyOf(requestedFeatures)); |
| ImmutableSet.Builder<String> enabledFeatures = ImmutableSet.builder(); |
| for (String feature : features.getActivatableNames()) { |
| if (configuration.isEnabled(feature)) { |
| enabledFeatures.add(feature); |
| } |
| } |
| return enabledFeatures.build(); |
| } |
| |
| private Artifact scratchArtifact(String s) { |
| Path execRoot = outputBase.getRelative("exec"); |
| Path outputRoot = execRoot.getRelative("out"); |
| ArtifactRoot root = ArtifactRoot.asDerivedRoot(execRoot, outputRoot); |
| try { |
| return ActionsTestUtil.createArtifact( |
| root, scratch.overwriteFile(outputRoot.getRelative(s).toString())); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @Test |
| public void testFeatureConfigurationCodec() throws Exception { |
| FeatureConfiguration emptyConfiguration = |
| buildEmptyFeatures("").getFeatureConfiguration(ImmutableSet.of()); |
| FeatureConfiguration emptyFeatures = |
| buildFeatures("feature {name: 'a'}", "feature {name: 'b'}") |
| .getFeatureConfiguration(ImmutableSet.of("a", "b")); |
| FeatureConfiguration featuresWithFlags = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'action-a'", |
| " flag_group { flag: 'flag-a'}", |
| " }", |
| " flag_set {", |
| " action: 'action-b'", |
| " flag_group { flag: 'flag-b'}", |
| " }", |
| "}", |
| "feature {", |
| " name: 'b'", |
| " flag_set {", |
| " action: 'action-c'", |
| " flag_group { flag: 'flag-c'}", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a", "b")); |
| FeatureConfiguration featureWithEnvSet = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " env_set {", |
| " action: 'action-a'", |
| " env_entry { key: 'foo', value: 'bar'}", |
| " env_entry { key: 'baz', value: 'zee'}", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a")); |
| |
| new SerializationTester(emptyConfiguration, emptyFeatures, featuresWithFlags, featureWithEnvSet) |
| .runTests(); |
| } |
| |
| @Test |
| public void testCrosstoolProtoCanBeSerialized() throws Exception { |
| ObjectCodecs objectCodecs = |
| new ObjectCodecs(AutoRegistry.get().getBuilder().build(), ImmutableMap.of()); |
| objectCodecs.serialize(CToolchain.WithFeatureSet.getDefaultInstance()); |
| objectCodecs.serialize(CToolchain.VariableWithValue.getDefaultInstance()); |
| objectCodecs.serialize(CToolchain.FlagGroup.getDefaultInstance()); |
| objectCodecs.serialize(CToolchain.FlagSet.getDefaultInstance()); |
| objectCodecs.serialize(CToolchain.EnvSet.getDefaultInstance()); |
| } |
| |
| @Test |
| public void testUnconditionalFeature() throws Exception { |
| assertThat(buildFeatures("").getFeatureConfiguration(ImmutableSet.of("a")).isEnabled("a")) |
| .isFalse(); |
| assertThat( |
| buildFeatures("feature { name: 'a' }") |
| .getFeatureConfiguration(ImmutableSet.of("b")) |
| .isEnabled("a")) |
| .isFalse(); |
| assertThat( |
| buildFeatures("feature { name: 'a' }") |
| .getFeatureConfiguration(ImmutableSet.of("a")) |
| .isEnabled("a")) |
| .isTrue(); |
| } |
| |
| @Test |
| public void testUnsupportedAction() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures("").getFeatureConfiguration(ImmutableSet.of()); |
| assertThat(configuration.getCommandLine("invalid-action", createVariables())).isEmpty(); |
| } |
| |
| @Test |
| public void testFlagOrderEqualsSpecOrder() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: '-a-c++-compile' }", |
| " }", |
| " flag_set {", |
| " action: 'link'", |
| " flag_group { flag: '-a-c++-compile' }", |
| " }", |
| "}", |
| "feature {", |
| " name: 'b'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: '-b-c++-compile' }", |
| " }", |
| " flag_set {", |
| " action: 'link'", |
| " flag_group { flag: '-b-link' }", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a", "b")); |
| List<String> commandLine = |
| configuration.getCommandLine(CppActionNames.CPP_COMPILE, createVariables()); |
| assertThat(commandLine).containsExactly("-a-c++-compile", "-b-c++-compile").inOrder(); |
| } |
| |
| @Test |
| public void testEnvVars() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " env_set {", |
| " action: 'c++-compile'", |
| " env_entry { key: 'foo', value: 'bar' }", |
| " env_entry { key: 'cat', value: 'meow' }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: '-a-c++-compile' }", |
| " }", |
| "}", |
| "feature {", |
| " name: 'b'", |
| " env_set {", |
| " action: 'c++-compile'", |
| " env_entry { key: 'dog', value: 'woof' }", |
| " }", |
| " env_set {", |
| " action: 'c++-compile'", |
| " with_feature: { feature: 'd' }", |
| " env_entry { key: 'withFeature', value: 'value1' }", |
| " }", |
| " env_set {", |
| " action: 'c++-compile'", |
| " with_feature: { feature: 'e' }", |
| " env_entry { key: 'withoutFeature', value: 'value2' }", |
| " }", |
| " env_set {", |
| " action: 'c++-compile'", |
| " with_feature: { not_feature: 'f' }", |
| " env_entry { key: 'withNotFeature', value: 'value3' }", |
| " }", |
| " env_set {", |
| " action: 'c++-compile'", |
| " with_feature: { not_feature: 'g' }", |
| " env_entry { key: 'withoutNotFeature', value: 'value4' }", |
| " }", |
| "}", |
| "feature {", |
| " name: 'c'", |
| " env_set {", |
| " action: 'c++-compile'", |
| " env_entry { key: 'doNotInclude', value: 'doNotIncludePlease' }", |
| " }", |
| "}", |
| "feature { name: 'd' }", |
| "feature { name: 'e' }", |
| "feature { name: 'f' }", |
| "feature { name: 'g' }") |
| .getFeatureConfiguration(ImmutableSet.of("a", "b", "d", "f")); |
| Map<String, String> env = |
| configuration.getEnvironmentVariables(CppActionNames.CPP_COMPILE, createVariables()); |
| assertThat(env) |
| .containsExactly( |
| "foo", "bar", "cat", "meow", "dog", "woof", |
| "withFeature", "value1", "withoutNotFeature", "value4") |
| .inOrder(); |
| assertThat(env).doesNotContainEntry("withoutFeature", "value2"); |
| assertThat(env).doesNotContainEntry("withNotFeature", "value3"); |
| assertThat(env).doesNotContainEntry("doNotInclude", "doNotIncludePlease"); |
| } |
| |
| private String getExpansionOfFlag(String value) throws Exception { |
| return getExpansionOfFlag(value, createVariables()); |
| } |
| |
| private String getExpansionOfFlag(String value, CcToolchainVariables variables) throws Exception { |
| return getCommandLineForFlag(value, variables).get(0); |
| } |
| |
| private List<String> getCommandLineForFlagGroups(String groups, CcToolchainVariables variables) |
| throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " " + groups, |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a")); |
| return configuration.getCommandLine(CppActionNames.CPP_COMPILE, variables); |
| } |
| |
| private List<String> getCommandLineForFlag(String value, CcToolchainVariables variables) |
| throws Exception { |
| return getCommandLineForFlagGroups("flag_group { flag: '" + value + "' }", variables); |
| } |
| |
| private String getFlagParsingError(String value) throws Exception { |
| try { |
| getExpansionOfFlag(value); |
| fail("Expected EvalException"); |
| return ""; |
| } catch (EvalException e) { |
| return e.getMessage(); |
| } |
| } |
| |
| private String getFlagExpansionError(String value, CcToolchainVariables variables) |
| throws Exception { |
| try { |
| getExpansionOfFlag(value, variables); |
| fail("Expected ExpansionException"); |
| return ""; |
| } catch (ExpansionException e) { |
| return e.getMessage(); |
| } |
| } |
| |
| private String getFlagGroupsExpansionError(String flagGroups, CcToolchainVariables variables) |
| throws Exception { |
| try { |
| getCommandLineForFlagGroups(flagGroups, variables).get(0); |
| fail("Expected ExpansionException"); |
| return ""; |
| } catch (ExpansionException e) { |
| return e.getMessage(); |
| } |
| } |
| |
| @Test |
| public void testVariableExpansion() throws Exception { |
| assertThat(getExpansionOfFlag("%%")).isEqualTo("%"); |
| assertThat(getExpansionOfFlag("%% a %% b %%")).isEqualTo("% a % b %"); |
| assertThat(getExpansionOfFlag("%%{var}")).isEqualTo("%{var}"); |
| assertThat(getExpansionOfFlag("%{v}", createVariables("v", "<flag>"))).isEqualTo("<flag>"); |
| assertThat(getExpansionOfFlag(" %{v1} %{v2} ", createVariables("v1", "1", "v2", "2"))) |
| .isEqualTo(" 1 2 "); |
| assertThat(getFlagParsingError("%")).contains("expected '{'"); |
| assertThat(getFlagParsingError("% ")).contains("expected '{'"); |
| assertThat(getFlagParsingError("%{")).contains("expected variable name"); |
| assertThat(getFlagParsingError("%{}")).contains("expected variable name"); |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group{ iterate_over: 'v' flag: '%{v}' }", |
| CcToolchainVariables.builder() |
| .addStringSequenceVariable("v", ImmutableList.<String>of()) |
| .build())) |
| .isEmpty(); |
| assertThat(getFlagExpansionError("%{v}", createVariables())) |
| .contains("Invalid toolchain configuration: Cannot find variable named 'v'"); |
| } |
| |
| private CcToolchainVariables createStructureSequenceVariables( |
| String name, StructureBuilder... values) { |
| SequenceBuilder builder = new SequenceBuilder(); |
| for (StructureBuilder value : values) { |
| builder.addValue(value.build()); |
| } |
| return CcToolchainVariables.builder().addCustomBuiltVariable(name, builder).build(); |
| } |
| |
| private CcToolchainVariables createStructureVariables(String name, StructureBuilder value) { |
| return CcToolchainVariables.builder().addCustomBuiltVariable(name, value).build(); |
| } |
| |
| @Test |
| public void testSimpleStructureVariableExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group { flag: '-A%{struct.foo}' flag: '-B%{struct.bar}' }", |
| createStructureVariables( |
| "struct", |
| new StructureBuilder() |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .containsExactly("-AfooValue", "-BbarValue"); |
| } |
| |
| @Test |
| public void testNestedStructureVariableExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group { flag: '-A%{struct.foo.bar}' }", |
| createStructureVariables( |
| "struct", |
| new StructureBuilder() |
| .addField("foo", new StructureBuilder().addField("bar", "fooBarValue"))))) |
| .containsExactly("-AfooBarValue"); |
| } |
| |
| @Test |
| public void testAccessingStructureAsStringFails() throws Exception { |
| assertThat( |
| getFlagGroupsExpansionError( |
| "flag_group { flag: '-A%{struct}' }", |
| createStructureVariables( |
| "struct", |
| new StructureBuilder() |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .isEqualTo( |
| "Invalid toolchain configuration: Cannot expand variable 'struct': expected string, " |
| + "found structure"); |
| } |
| |
| @Test |
| public void testAccessingStringValueAsStructureFails() throws Exception { |
| assertThat( |
| getFlagGroupsExpansionError( |
| "flag_group { flag: '-A%{stringVar.foo}' }", |
| createVariables("stringVar", "stringVarValue"))) |
| .isEqualTo( |
| "Invalid toolchain configuration: Cannot expand variable 'stringVar.foo': variable " |
| + "'stringVar' is string, expected structure"); |
| } |
| |
| @Test |
| public void testAccessingSequenceAsStructureFails() throws Exception { |
| assertThat( |
| getFlagGroupsExpansionError( |
| "flag_group { flag: '-A%{sequence.foo}' }", |
| createVariables("sequence", "foo1", "sequence", "foo2"))) |
| .isEqualTo( |
| "Invalid toolchain configuration: Cannot expand variable 'sequence.foo': variable " |
| + "'sequence' is sequence, expected structure"); |
| } |
| |
| @Test |
| public void testAccessingMissingStructureFieldFails() throws Exception { |
| assertThat( |
| getFlagGroupsExpansionError( |
| "flag_group { flag: '-A%{struct.missing}' }", |
| createStructureVariables( |
| "struct", new StructureBuilder().addField("bar", "barValue")))) |
| .isEqualTo( |
| "Invalid toolchain configuration: Cannot expand variable 'struct.missing': structure " |
| + "struct doesn't have a field named 'missing'"); |
| } |
| |
| @Test |
| public void testSequenceOfStructuresExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group { iterate_over: 'structs' flag: '-A%{structs.foo}' }", |
| createStructureSequenceVariables( |
| "structs", |
| new StructureBuilder().addField("foo", "foo1Value"), |
| new StructureBuilder().addField("foo", "foo2Value")))) |
| .containsExactly("-Afoo1Value", "-Afoo2Value"); |
| } |
| |
| @Test |
| public void testStructureOfSequencesExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " iterate_over: 'struct.sequences'" |
| + " flag: '-A%{struct.sequences.foo}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new StructureBuilder() |
| .addField( |
| "sequences", |
| new SequenceBuilder() |
| .addValue(new StructureBuilder().addField("foo", "foo1Value")) |
| .addValue(new StructureBuilder().addField("foo", "foo2Value")))))) |
| .containsExactly("-Afoo1Value", "-Afoo2Value"); |
| } |
| |
| @Test |
| public void testDottedNamesNotAlwaysMeanStructures() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " iterate_over: 'struct.sequence'" |
| + " flag_group {" |
| + " iterate_over: 'other_sequence'" |
| + " flag_group {" |
| + " flag: '-A%{struct.sequence} -B%{other_sequence}'" |
| + " }" |
| + " }" |
| + "}", |
| CcToolchainVariables.builder() |
| .addCustomBuiltVariable( |
| "struct", |
| new StructureBuilder() |
| .addField("sequence", ImmutableList.of("first", "second"))) |
| .addStringSequenceVariable("other_sequence", ImmutableList.of("foo", "bar")) |
| .build())) |
| .containsExactly("-Afirst -Bfoo", "-Afirst -Bbar", "-Asecond -Bfoo", "-Asecond -Bbar"); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructsExpandsIfPresent() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_all_available: 'struct'" |
| + " flag: '-A%{struct.foo}'" |
| + " flag: '-B%{struct.bar}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder() |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .containsExactly("-AfooValue", "-BbarValue"); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructsDoesntExpandIfMissing() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_all_available: 'nonexistent'" |
| + " flag: '-A%{struct.foo}'" |
| + " flag: '-B%{struct.bar}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder() |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructsDoesntCrashIfMissing() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_all_available: 'nonexistent'" |
| + " flag: '-A%{nonexistent.foo}'" |
| + " flag: '-B%{nonexistent.bar}'" |
| + "}", |
| createVariables())) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructFieldDoesntCrashIfMissing() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_all_available: 'nonexistent.nonexistant_field'" |
| + " flag: '-A%{nonexistent.foo}'" |
| + " flag: '-B%{nonexistent.bar}'" |
| + "}", |
| createVariables())) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructFieldExpandsIfPresent() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_all_available: 'struct.foo'" |
| + " flag: '-A%{struct.foo}'" |
| + " flag: '-B%{struct.bar}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder() |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .containsExactly("-AfooValue", "-BbarValue"); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructFieldDoesntExpandIfMissing() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_all_available: 'struct.foo'" |
| + " flag: '-A%{struct.foo}'" |
| + " flag: '-B%{struct.bar}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder().addField("bar", "barValue")))) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testExpandIfAllAvailableWithStructFieldScopesRight() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " flag_group {" |
| + " expand_if_all_available: 'struct.foo'" |
| + " flag: '-A%{struct.foo}'" |
| + " }" |
| + " flag_group { " |
| + " flag: '-B%{struct.bar}'" |
| + " }" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder().addField("bar", "barValue")))) |
| .containsExactly("-BbarValue"); |
| } |
| |
| @Test |
| public void testExpandIfNoneAvailableExpandsIfNotAvailable() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " flag_group {" |
| + " expand_if_none_available: 'not_available'" |
| + " flag: '-foo'" |
| + " }" |
| + " flag_group { " |
| + " expand_if_none_available: 'available'" |
| + " flag: '-bar'" |
| + " }" |
| + "}", |
| createVariables("available", "available"))) |
| .containsExactly("-foo"); |
| } |
| |
| @Test |
| public void testExpandIfNoneAvailableDoesntExpandIfThereIsOneOfManyAvailable() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " flag_group {" |
| + " expand_if_none_available: 'not_available'" |
| + " expand_if_none_available: 'available'" |
| + " flag: '-foo'" |
| + " }" |
| + "}", |
| createVariables("available", "available"))) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testExpandIfTrueDoesntExpandIfMissing() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_true: 'missing'" |
| + " flag: '-A%{missing}'" |
| + "}" |
| + "flag_group {" |
| + " expand_if_false: 'missing'" |
| + " flag: '-B%{missing}'" |
| + "}", |
| createVariables())) |
| .isEmpty(); |
| } |
| |
| @Test |
| public void testExpandIfTrueExpandsIfOne() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_true: 'struct.bool'" |
| + " flag: '-A%{struct.foo}'" |
| + " flag: '-B%{struct.bar}'" |
| + "}" |
| + "flag_group {" |
| + " expand_if_false: 'struct.bool'" |
| + " flag: '-X%{struct.foo}'" |
| + " flag: '-Y%{struct.bar}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder() |
| .addField("bool", new IntegerValue(1)) |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .containsExactly("-AfooValue", "-BbarValue"); |
| } |
| |
| @Test |
| public void testExpandIfTrueExpandsIfZero() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_true: 'struct.bool'" |
| + " flag: '-A%{struct.foo}'" |
| + " flag: '-B%{struct.bar}'" |
| + "}" |
| + "flag_group {" |
| + " expand_if_false: 'struct.bool'" |
| + " flag: '-X%{struct.foo}'" |
| + " flag: '-Y%{struct.bar}'" |
| + "}", |
| createStructureVariables( |
| "struct", |
| new CcToolchainVariables.StructureBuilder() |
| .addField("bool", new IntegerValue(0)) |
| .addField("foo", "fooValue") |
| .addField("bar", "barValue")))) |
| .containsExactly("-XfooValue", "-YbarValue"); |
| } |
| |
| @Test |
| public void testExpandIfEqual() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " expand_if_equal: { variable: 'var' value: 'equal_value' }" |
| + " flag: '-foo_%{var}'" |
| + "}" |
| + "flag_group {" |
| + " expand_if_equal: { variable: 'var' value: 'non_equal_value' }" |
| + " flag: '-bar_%{var}'" |
| + "}" |
| + "flag_group {" |
| + " expand_if_equal: { variable: 'non_existing_var' value: 'non_existing' }" |
| + " flag: '-baz_%{non_existing_var}'" |
| + "}", |
| createVariables("var", "equal_value"))) |
| .containsExactly("-foo_equal_value"); |
| } |
| |
| @Test |
| public void testListVariableExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group { iterate_over: 'v' flag: '%{v}' }", |
| createVariables("v", "1", "v", "2"))) |
| .containsExactly("1", "2"); |
| } |
| |
| @Test |
| public void testListVariableExpansionMixedWithNonListVariable() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group { iterate_over: 'v1' flag: '%{v1} %{v2}' }", |
| createVariables("v1", "a1", "v1", "a2", "v2", "b"))) |
| .containsExactly("a1 b", "a2 b"); |
| } |
| |
| @Test |
| public void testNestedListVariableExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "flag_group {" |
| + " iterate_over: 'v1'" |
| + " flag_group {" |
| + " iterate_over: 'v2'" |
| + " flag: '%{v1} %{v2}'" |
| + " }" |
| + "}", |
| createVariables("v1", "a1", "v1", "a2", "v2", "b1", "v2", "b2"))) |
| .containsExactly("a1 b1", "a1 b2", "a2 b1", "a2 b2"); |
| } |
| |
| @Test |
| public void testListVariableExpansionMixedWithImplicitlyAccessedListVariableFails() |
| throws Exception { |
| assertThat( |
| getFlagGroupsExpansionError( |
| "flag_group { iterate_over: 'v1' flag: '%{v1} %{v2}' }", |
| createVariables("v1", "a1", "v1", "a2", "v2", "b1", "v2", "b2"))) |
| .contains("Cannot expand variable 'v2': expected string, found sequence"); |
| } |
| |
| @Test |
| public void testFlagGroupVariableExpansion() throws Exception { |
| assertThat( |
| getCommandLineForFlagGroups( |
| "" |
| + "flag_group { iterate_over: 'v' flag: '-f' flag: '%{v}' }" |
| + "flag_group { flag: '-end' }", |
| createVariables("v", "1", "v", "2"))) |
| .containsExactly("-f", "1", "-f", "2", "-end"); |
| assertThat( |
| getCommandLineForFlagGroups( |
| "" |
| + "flag_group { iterate_over: 'v' flag: '-f' flag: '%{v}' }" |
| + "flag_group { iterate_over: 'v' flag: '%{v}' }", |
| createVariables("v", "1", "v", "2"))) |
| .containsExactly("-f", "1", "-f", "2", "1", "2"); |
| assertThat( |
| getCommandLineForFlagGroups( |
| "" |
| + "flag_group { iterate_over: 'v' flag: '-f' flag: '%{v}' } " |
| + "flag_group { iterate_over: 'v' flag: '%{v}' }", |
| createVariables("v", "1", "v", "2"))) |
| .containsExactly("-f", "1", "-f", "2", "1", "2"); |
| } |
| |
| private VariableValueBuilder createNestedSequence(int depth, int count, String prefix) { |
| if (depth == 0) { |
| StringSequenceBuilder builder = new StringSequenceBuilder(); |
| for (int i = 0; i < count; ++i) { |
| String value = prefix + i; |
| builder.addValue(value); |
| } |
| return builder; |
| } else { |
| SequenceBuilder builder = new SequenceBuilder(); |
| for (int i = 0; i < count; ++i) { |
| String value = prefix + i; |
| builder.addValue(createNestedSequence(depth - 1, count, value)); |
| } |
| return builder; |
| } |
| } |
| |
| private CcToolchainVariables createNestedVariables(String name, int depth, int count) { |
| return CcToolchainVariables.builder() |
| .addCustomBuiltVariable(name, createNestedSequence(depth, count, "")) |
| .build(); |
| } |
| |
| @Test |
| public void testFlagTreeVariableExpansion() throws Exception { |
| String nestedGroup = |
| "" |
| + "flag_group {" |
| + " iterate_over: 'v'" |
| + " flag_group { flag: '-a' }" |
| + " flag_group { iterate_over: 'v' flag: '%{v}' }" |
| + " flag_group { flag: '-b' }" |
| + "}"; |
| assertThat(getCommandLineForFlagGroups(nestedGroup, createNestedVariables("v", 1, 3))) |
| .containsExactly( |
| "-a", "00", "01", "02", "-b", "-a", "10", "11", "12", "-b", "-a", "20", "21", "22", |
| "-b"); |
| |
| ExpansionException e = |
| assertThrows( |
| ExpansionException.class, |
| () -> getCommandLineForFlagGroups(nestedGroup, createNestedVariables("v", 2, 3))); |
| assertThat(e).hasMessageThat().contains("'v'"); |
| |
| e = |
| assertThrows( |
| ExpansionException.class, |
| () -> |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " flag_group { flag: '-f' }", |
| " flag: '-f'", |
| " }", |
| " }", |
| "}")); |
| assertThat(e).hasMessageThat().contains("Invalid toolchain configuration"); |
| } |
| |
| @Test |
| public void testImplies() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' implies: 'b' implies: 'c' }", |
| "feature { name: 'b' }", |
| "feature { name: 'c' implies: 'd' }", |
| "feature { name: 'd' }", |
| "feature { name: 'e' }"); |
| assertThat(getEnabledFeatures(features, "a")).containsExactly("a", "b", "c", "d"); |
| } |
| |
| @Test |
| public void testRequires() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' requires: { feature: 'b' } }", |
| "feature { name: 'b' requires: { feature: 'c' } }", |
| "feature { name: 'c' }"); |
| assertThat(getEnabledFeatures(features, "a")).isEmpty(); |
| assertThat(getEnabledFeatures(features, "a", "b")).isEmpty(); |
| assertThat(getEnabledFeatures(features, "a", "c")).containsExactly("c"); |
| assertThat(getEnabledFeatures(features, "a", "b", "c")).containsExactly("a", "b", "c"); |
| } |
| |
| @Test |
| public void testDisabledRequirementChain() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' }", |
| "feature { name: 'b' requires: { feature: 'c' } implies: 'a' }", |
| "feature { name: 'c' }"); |
| assertThat(getEnabledFeatures(features, "b")).isEmpty(); |
| features = |
| buildFeatures( |
| "feature { name: 'a' }", |
| "feature { name: 'b' requires: { feature: 'a' } implies: 'c' }", |
| "feature { name: 'c' }", |
| "feature { name: 'd' requires: { feature: 'c' } implies: 'e' }", |
| "feature { name: 'e' }"); |
| assertThat(getEnabledFeatures(features, "b", "d")).isEmpty(); |
| } |
| |
| @Test |
| public void testEnabledRequirementChain() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: '0' implies: 'a' }", |
| "feature { name: 'a' }", |
| "feature { name: 'b' requires: { feature: 'a' } implies: 'c' }", |
| "feature { name: 'c' }", |
| "feature { name: 'd' requires: { feature: 'c' } implies: 'e' }", |
| "feature { name: 'e' }"); |
| assertThat(getEnabledFeatures(features, "0", "b", "d")).containsExactly( |
| "0", "a", "b", "c", "d", "e"); |
| } |
| |
| @Test |
| public void testLogicInRequirements() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " requires: { feature: 'b' feature: 'c' }", |
| " requires: { feature: 'd' }", |
| "}", |
| "feature { name: 'b' }", |
| "feature { name: 'c' }", |
| "feature { name: 'd' }"); |
| assertThat(getEnabledFeatures(features, "a", "b", "c")).containsExactly("a", "b", "c"); |
| assertThat(getEnabledFeatures(features, "a", "b")).containsExactly("b"); |
| assertThat(getEnabledFeatures(features, "a", "c")).containsExactly("c"); |
| assertThat(getEnabledFeatures(features, "a", "d")).containsExactly("a", "d"); |
| } |
| |
| @Test |
| public void testImpliesImpliesRequires() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' implies: 'b' }", |
| "feature { name: 'b' requires: { feature: 'c' } }", |
| "feature { name: 'c' }"); |
| assertThat(getEnabledFeatures(features, "a")).isEmpty(); |
| } |
| |
| @Test |
| public void testMultipleImplies() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' implies: 'b' implies: 'c' implies: 'd' }", |
| "feature { name: 'b' }", |
| "feature { name: 'c' requires: { feature: 'e' } }", |
| "feature { name: 'd' }", |
| "feature { name: 'e' }"); |
| assertThat(getEnabledFeatures(features, "a")).isEmpty(); |
| assertThat(getEnabledFeatures(features, "a", "e")).containsExactly("a", "b", "c", "d", "e"); |
| } |
| |
| @Test |
| public void testDisabledFeaturesDoNotEnableImplications() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' implies: 'b' requires: { feature: 'c' } }", |
| "feature { name: 'b' }", |
| "feature { name: 'c' }"); |
| assertThat(getEnabledFeatures(features, "a")).isEmpty(); |
| } |
| |
| @Test |
| public void testFeatureNameCollision() throws Exception { |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> |
| buildFeatures( |
| "feature { name: '<<<collision>>>' }", "feature { name: '<<<collision>>>' }")); |
| assertThat(e).hasMessageThat().contains("<<<collision>>>"); |
| } |
| |
| @Test |
| public void testReferenceToUndefinedFeature() throws Exception { |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> buildFeatures("feature { name: 'a' implies: '<<<undefined>>>' }")); |
| assertThat(e).hasMessageThat().contains("<<<undefined>>>"); |
| } |
| |
| @Test |
| public void testImpliesWithCycle() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' implies: 'b' }", |
| "feature { name: 'b' implies: 'a' }"); |
| assertThat(getEnabledFeatures(features, "a")).containsExactly("a", "b"); |
| assertThat(getEnabledFeatures(features, "b")).containsExactly("a", "b"); |
| } |
| |
| @Test |
| public void testMultipleImpliesCycle() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' implies: 'b' implies: 'c' implies: 'd' }", |
| "feature { name: 'b' }", |
| "feature { name: 'c' requires: { feature: 'e' } }", |
| "feature { name: 'd' requires: { feature: 'f' } }", |
| "feature { name: 'e' requires: { feature: 'c' } }", |
| "feature { name: 'f' }"); |
| assertThat(getEnabledFeatures(features, "a", "e")).isEmpty(); |
| assertThat(getEnabledFeatures(features, "a", "e", "f")).containsExactly( |
| "a", "b", "c", "d", "e", "f"); |
| } |
| |
| @Test |
| public void testRequiresWithCycle() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' requires: { feature: 'b' } }", |
| "feature { name: 'b' requires: { feature: 'a' } }", |
| "feature { name: 'c' implies: 'a' }", |
| "feature { name: 'd' implies: 'b' }"); |
| assertThat(getEnabledFeatures(features, "c")).isEmpty(); |
| assertThat(getEnabledFeatures(features, "d")).isEmpty(); |
| assertThat(getEnabledFeatures(features, "c", "d")).containsExactly("a", "b", "c", "d"); |
| } |
| |
| @Test |
| public void testImpliedByOneEnabledAndOneDisabledFeature() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' }", |
| "feature { name: 'b' requires: { feature: 'a' } implies: 'd' }", |
| "feature { name: 'c' implies: 'd' }", |
| "feature { name: 'd' }"); |
| assertThat(getEnabledFeatures(features, "b", "c")).containsExactly("c", "d"); |
| } |
| |
| @Test |
| public void testRequiresOneEnabledAndOneUnsupportedFeature() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature { name: 'a' requires: { feature: 'b' } requires: { feature: 'c' } }", |
| "feature { name: 'b' }", |
| "feature { name: 'c' requires: { feature: 'd' } }", |
| "feature { name: 'd' }"); |
| assertThat(getEnabledFeatures(features, "a", "b", "c")).containsExactly("a", "b"); |
| } |
| |
| @Test |
| public void testFlagGroupsWithMissingVariableIsNotExpanded() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { expand_if_all_available: 'v' flag: '%{v}' }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: 'unconditional' }", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a")); |
| |
| assertThat(configuration.getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("unconditional"); |
| } |
| |
| @Test |
| public void testOnlyFlagGroupsWithAllVariablesPresentAreExpanded() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { expand_if_all_available: 'v' flag: '%{v}' }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " expand_if_all_available: 'v'", |
| " expand_if_all_available: 'w'", |
| " flag: '%{v}%{w}'", |
| " }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: 'unconditional' }", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a")); |
| |
| assertThat(configuration.getCommandLine(CppActionNames.CPP_COMPILE, createVariables("v", "1"))) |
| .containsExactly("1", "unconditional"); |
| } |
| |
| @Test |
| public void testOnlyInnerFlagGroupIsIteratedWithSequenceVariable() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { expand_if_all_available: 'v' iterate_over: 'v' flag: '%{v}' }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { ", |
| " iterate_over: 'v'", |
| " expand_if_all_available: 'v'", |
| " expand_if_all_available: 'w'", |
| " flag: '%{v}%{w}'", |
| " }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: 'unconditional' }", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a")); |
| |
| assertThat( |
| configuration.getCommandLine( |
| CppActionNames.CPP_COMPILE, createVariables("v", "1", "v", "2"))) |
| .containsExactly("1", "2", "unconditional") |
| .inOrder(); |
| } |
| |
| @Test |
| public void testFlagSetsAreIteratedIndividuallyForSequenceVariables() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { expand_if_all_available: 'v' iterate_over: 'v' flag: '%{v}' }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { ", |
| " iterate_over: 'v'", |
| " expand_if_all_available: 'v'", |
| " expand_if_all_available: 'w'", |
| " flag: '%{v}%{w}'", |
| " }", |
| " }", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group { flag: 'unconditional' }", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a")); |
| |
| assertThat( |
| configuration.getCommandLine( |
| CppActionNames.CPP_COMPILE, createVariables("v", "1", "v", "2", "w", "3"))) |
| .containsExactly("1", "2", "13", "23", "unconditional") |
| .inOrder(); |
| } |
| |
| @Test |
| public void testConfiguration() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " flag: '-f'", |
| " flag: '%{v}'", |
| " }", |
| " }", |
| "}", |
| "feature { name: 'b' implies: 'a' }"); |
| assertThat(getEnabledFeatures(features, "b")).containsExactly("a", "b"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("b")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables("v", "1"))) |
| .containsExactly("-f", "1"); |
| byte[] serialized = TestUtils.serializeObject(features); |
| CcToolchainFeatures deserialized = |
| (CcToolchainFeatures) TestUtils.deserializeObject(serialized); |
| assertThat(getEnabledFeatures(deserialized, "b")).containsExactly("a", "b"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("b")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables("v", "1"))) |
| .containsExactly("-f", "1"); |
| } |
| |
| @Test |
| public void testDefaultFeatures() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures("feature { name: 'a' }", "feature { name: 'b' enabled: true }"); |
| assertThat(features.getDefaultFeaturesAndActionConfigs()).containsExactly("b"); |
| } |
| |
| @Test |
| public void testDefaultActionConfigs() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "action_config { config_name: 'a' action_name: 'a'}", |
| "action_config { config_name: 'b' action_name: 'b' enabled: true }"); |
| assertThat(features.getDefaultFeaturesAndActionConfigs()).containsExactly("b"); |
| } |
| |
| @Test |
| public void testWithFeature_OneSetOneFeature() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " with_feature {feature: 'b'}", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " flag: 'dummy_flag'", |
| " }", |
| " }", |
| "}", |
| "feature {name: 'b'}"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "b")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| } |
| |
| @Test |
| public void testWithFeature_OneSetMultipleFeatures() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " with_feature {feature: 'b', feature: 'c'}", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " flag: 'dummy_flag'", |
| " }", |
| " }", |
| "}", |
| "feature {name: 'b'}", |
| "feature {name: 'c'}"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "b", "c")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "b")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| } |
| |
| @Test |
| public void testWithFeature_MulipleSetsMultipleFeatures() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " with_feature {feature: 'b1', feature: 'c1'}", |
| " with_feature {feature: 'b2', feature: 'c2'}", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " flag: 'dummy_flag'", |
| " }", |
| " }", |
| "}", |
| "feature {name: 'b1'}", |
| "feature {name: 'c1'}", |
| "feature {name: 'b2'}", |
| "feature {name: 'c2'}"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "b1", "c1", "b2", "c2")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "b1", "c1")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "b1", "b2")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| } |
| |
| @Test |
| public void testWithFeature_NotFeature() throws Exception { |
| CcToolchainFeatures features = |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " flag_set {", |
| " with_feature { not_feature: 'x', not_feature: 'y', feature: 'z' }", |
| " with_feature { not_feature: 'q' }", |
| " action: 'c++-compile'", |
| " flag_group {", |
| " flag: 'dummy_flag'", |
| " }", |
| " }", |
| "}", |
| "feature {name: 'x'}", |
| "feature {name: 'y'}", |
| "feature {name: 'z'}", |
| "feature {name: 'q'}"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "q")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "q", "z")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .containsExactly("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "q", "x", "z")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| assertThat( |
| features |
| .getFeatureConfiguration(ImmutableSet.of("a", "q", "x", "y", "z")) |
| .getCommandLine(CppActionNames.CPP_COMPILE, createVariables())) |
| .doesNotContain("dummy_flag"); |
| } |
| |
| @Test |
| public void testActivateActionConfigFromFeature() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/feature-a'", |
| " with_feature: { feature: 'feature-a' }", |
| " }", |
| "}", |
| "feature {", |
| " name: 'activates-action-a'", |
| " implies: 'action-a'", |
| "}"); |
| |
| FeatureConfiguration featureConfiguration = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("activates-action-a")); |
| |
| assertThat(featureConfiguration.actionIsConfigured("action-a")).isTrue(); |
| } |
| |
| @Test |
| public void testFeatureCanRequireActionConfig() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/feature-a'", |
| " with_feature: { feature: 'feature-a' }", |
| " }", |
| "}", |
| "feature {", |
| " name: 'requires-action-a'", |
| " requires: { feature: 'action-a' }", |
| "}"); |
| |
| FeatureConfiguration featureConfigurationWithoutAction = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("requires-action-a")); |
| assertThat(featureConfigurationWithoutAction.isEnabled("requires-action-a")).isFalse(); |
| |
| FeatureConfiguration featureConfigurationWithAction = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("action-a", "requires-action-a")); |
| assertThat(featureConfigurationWithAction.isEnabled("requires-action-a")).isTrue(); |
| } |
| |
| @Test |
| public void testSimpleActionTool() throws Exception { |
| FeatureConfiguration configuration = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/a'", |
| " }", |
| "}", |
| "feature {", |
| " name: 'activates-action-a'", |
| " implies: 'action-a'", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("activates-action-a")); |
| assertThat(configuration.getToolPathForAction("action-a")).isEqualTo("crosstool/toolchain/a"); |
| } |
| |
| @Test |
| public void testActionToolFromFeatureSet() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/features-a-and-b'", |
| " with_feature: {", |
| " feature: 'feature-a'", |
| " feature: 'feature-b'", |
| " }", |
| " }", |
| " tool {", |
| " tool_path: 'toolchain/feature-a-and-not-c'", |
| " with_feature: {", |
| " feature: 'feature-a'", |
| " not_feature: 'feature-c'", |
| " }", |
| " }", |
| " tool {", |
| " tool_path: 'toolchain/feature-b-or-c'", |
| " with_feature: { feature: 'feature-b' }", |
| " with_feature: { feature: 'feature-c' }", |
| " }", |
| " tool {", |
| " tool_path: 'toolchain/default'", |
| " }", |
| "}", |
| "feature {", |
| " name: 'feature-a'", |
| "}", |
| "feature {", |
| " name: 'feature-b'", |
| "}", |
| "feature {", |
| " name: 'feature-c'", |
| "}", |
| "feature {", |
| " name: 'activates-action-a'", |
| " implies: 'action-a'", |
| "}"); |
| |
| FeatureConfiguration featureAConfiguration = |
| toolchainFeatures.getFeatureConfiguration( |
| ImmutableSet.of("feature-a", "activates-action-a")); |
| assertThat(featureAConfiguration.getToolPathForAction("action-a")) |
| .isEqualTo("crosstool/toolchain/feature-a-and-not-c"); |
| |
| FeatureConfiguration featureAAndCConfiguration = |
| toolchainFeatures.getFeatureConfiguration( |
| ImmutableSet.of("feature-a", "feature-c", "activates-action-a")); |
| assertThat(featureAAndCConfiguration.getToolPathForAction("action-a")) |
| .isEqualTo("crosstool/toolchain/feature-b-or-c"); |
| |
| FeatureConfiguration featureBConfiguration = |
| toolchainFeatures.getFeatureConfiguration( |
| ImmutableSet.of("feature-b", "activates-action-a")); |
| assertThat(featureBConfiguration.getToolPathForAction("action-a")) |
| .isEqualTo("crosstool/toolchain/feature-b-or-c"); |
| |
| FeatureConfiguration featureCConfiguration = |
| toolchainFeatures.getFeatureConfiguration( |
| ImmutableSet.of("feature-c", "activates-action-a")); |
| assertThat(featureCConfiguration.getToolPathForAction("action-a")) |
| .isEqualTo("crosstool/toolchain/feature-b-or-c"); |
| |
| FeatureConfiguration featureAAndBConfiguration = |
| toolchainFeatures.getFeatureConfiguration( |
| ImmutableSet.of("feature-a", "feature-b", "activates-action-a")); |
| assertThat(featureAAndBConfiguration.getToolPathForAction("action-a")) |
| .isEqualTo("crosstool/toolchain/features-a-and-b"); |
| |
| FeatureConfiguration noFeaturesConfiguration = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("activates-action-a")); |
| assertThat(noFeaturesConfiguration.getToolPathForAction("action-a")) |
| .isEqualTo("crosstool/toolchain/default"); |
| } |
| |
| @Test |
| public void testErrorForNoMatchingTool() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/feature-a'", |
| " with_feature: { feature: 'feature-a' }", |
| " }", |
| "}", |
| "feature {", |
| " name: 'feature-a'", |
| "}", |
| "feature {", |
| " name: 'activates-action-a'", |
| " implies: 'action-a'", |
| "}"); |
| |
| FeatureConfiguration noFeaturesConfiguration = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("activates-action-a")); |
| |
| IllegalArgumentException e = |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> noFeaturesConfiguration.getToolPathForAction("action-a")); |
| assertThat(e) |
| .hasMessageThat() |
| .contains("Matching tool for action action-a not found for given feature configuration"); |
| } |
| |
| @Test |
| public void testActivateActionConfigDirectly() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/feature-a'", |
| " with_feature: { feature: 'feature-a' }", |
| " }", |
| "}"); |
| |
| FeatureConfiguration featureConfiguration = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("action-a")); |
| |
| assertThat(featureConfiguration.actionIsConfigured("action-a")).isTrue(); |
| } |
| |
| @Test |
| public void testActionConfigCanActivateFeature() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-a'", |
| " tool {", |
| " tool_path: 'toolchain/feature-a'", |
| " with_feature: { feature: 'feature-a' }", |
| " }", |
| " implies: 'activated-feature'", |
| "}", |
| "feature {", |
| " name: 'activated-feature'", |
| "}"); |
| |
| FeatureConfiguration featureConfiguration = |
| toolchainFeatures.getFeatureConfiguration(ImmutableSet.of("action-a")); |
| |
| assertThat(featureConfiguration.isEnabled("activated-feature")).isTrue(); |
| } |
| |
| @Test |
| public void testInvalidActionConfigurationDuplicateActionConfigs() throws Exception { |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> |
| buildFeatures( |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-1'", |
| "}", |
| "action_config {", |
| " config_name: 'action-a'", |
| " action_name: 'action-2'", |
| "}")); |
| assertThat(e) |
| .hasMessageThat() |
| .contains("feature or action config 'action-a' was specified multiple times."); |
| } |
| |
| @Test |
| public void testInvalidActionConfigurationMultipleActionConfigsForAction() throws Exception { |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> |
| buildFeatures( |
| "action_config {", |
| " config_name: 'name-a'", |
| " action_name: 'action-a'", |
| "}", |
| "action_config {", |
| " config_name: 'name-b'", |
| " action_name: 'action-a'", |
| "}")); |
| assertThat(e).hasMessageThat().contains("multiple action configs for action 'action-a'"); |
| } |
| |
| @Test |
| public void testFlagsFromActionConfig() throws Exception { |
| FeatureConfiguration featureConfiguration = |
| buildFeatures( |
| "action_config {", |
| " config_name: 'c++-compile'", |
| " action_name: 'c++-compile'", |
| " flag_set {", |
| " flag_group {flag: 'foo'}", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("c++-compile")); |
| List<String> commandLine = |
| featureConfiguration.getCommandLine("c++-compile", createVariables()); |
| assertThat(commandLine).contains("foo"); |
| } |
| |
| @Test |
| public void testErrorForFlagFromActionConfigWithSpecifiedAction() throws Exception { |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> |
| buildFeatures( |
| "action_config {", |
| " config_name: 'c++-compile'", |
| " action_name: 'c++-compile'", |
| " flag_set {", |
| " action: 'c++-compile'", |
| " flag_group {flag: 'foo'}", |
| " }", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("c++-compile"))); |
| assertThat(e) |
| .hasMessageThat() |
| .contains(String.format(ActionConfig.FLAG_SET_WITH_ACTION_ERROR, "c++-compile")); |
| } |
| |
| @Test |
| public void testLibraryToLinkValue() throws ExpansionException { |
| assertThat( |
| LibraryToLinkValue.forDynamicLibrary("foo") |
| .getFieldValue("LibraryToLinkValue", LibraryToLinkValue.NAME_FIELD_NAME) |
| .getStringValue(LibraryToLinkValue.NAME_FIELD_NAME)) |
| .isEqualTo("foo"); |
| assertThat( |
| LibraryToLinkValue.forDynamicLibrary("foo") |
| .getFieldValue("LibraryToLinkValue", LibraryToLinkValue.OBJECT_FILES_FIELD_NAME)) |
| .isNull(); |
| |
| ImmutableList<Artifact> testArtifacts = |
| ImmutableList.of(scratchArtifact("foo"), scratchArtifact("bar")); |
| |
| assertThat( |
| LibraryToLinkValue.forObjectFileGroup(testArtifacts, false) |
| .getFieldValue("LibraryToLinkValue", LibraryToLinkValue.NAME_FIELD_NAME)) |
| .isNull(); |
| Iterable<? extends VariableValue> objects = |
| LibraryToLinkValue.forObjectFileGroup(testArtifacts, false) |
| .getFieldValue("LibraryToLinkValue", LibraryToLinkValue.OBJECT_FILES_FIELD_NAME) |
| .getSequenceValue(LibraryToLinkValue.OBJECT_FILES_FIELD_NAME); |
| ImmutableList.Builder<String> objectNames = ImmutableList.builder(); |
| for (VariableValue object : objects) { |
| objectNames.add(object.getStringValue("name")); |
| } |
| assertThat(objectNames.build()) |
| .containsExactlyElementsIn( |
| Iterables.transform(testArtifacts, testArtifact -> testArtifact.getExecPathString())); |
| } |
| |
| @Test |
| public void testProvidesCollision() throws Exception { |
| Exception e = |
| assertThrows( |
| Exception.class, |
| () -> |
| buildFeatures( |
| "feature {", |
| " name: 'a'", |
| " provides: 'provides_string'", |
| "}", |
| "feature {", |
| " name: 'b'", |
| " provides: 'provides_string'", |
| "}") |
| .getFeatureConfiguration(ImmutableSet.of("a", "b"))); |
| assertThat(e).hasMessageThat().contains("a b"); |
| } |
| |
| @Test |
| public void testErrorForNoMatchingArtifactNamePatternCategory() throws Exception { |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> |
| buildFeatures( |
| "artifact_name_pattern {", |
| "category_name: 'NONEXISTENT_CATEGORY'", |
| "prefix: 'foo'", |
| "extension: 'bar'}")); |
| assertThat(e) |
| .hasMessageThat() |
| .contains("Artifact category NONEXISTENT_CATEGORY not recognized"); |
| } |
| |
| @Test |
| public void testErrorForNoMatchingArtifactPatternForCategory() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "artifact_name_pattern {", |
| "category_name: 'static_library'", |
| "prefix: 'foo'", |
| "extension: '.a'}"); |
| EvalException e = |
| assertThrows( |
| EvalException.class, |
| () -> |
| toolchainFeatures.getArtifactNameForCategory( |
| ArtifactCategory.DYNAMIC_LIBRARY, "output_name")); |
| assertThat(e) |
| .hasMessageThat() |
| .contains( |
| String.format( |
| CcToolchainFeatures.MISSING_ARTIFACT_NAME_PATTERN_ERROR_TEMPLATE, |
| ArtifactCategory.DYNAMIC_LIBRARY.toString().toLowerCase())); |
| } |
| |
| @Test |
| public void testGetArtifactNameExtensionForCategory() throws Exception { |
| CcToolchainFeatures toolchainFeatures = |
| buildFeatures( |
| "artifact_name_pattern {", |
| " category_name: 'object_file'", |
| " prefix: ''", |
| " extension: '.obj'", |
| "}", |
| "artifact_name_pattern {", |
| " category_name: 'executable'", |
| " prefix: ''", |
| " extension: ''", |
| "}", |
| "artifact_name_pattern {", |
| " category_name: 'static_library'", |
| " prefix: ''", |
| " extension: '.a'", |
| "}"); |
| assertThat(toolchainFeatures.getArtifactNameExtensionForCategory(ArtifactCategory.OBJECT_FILE)) |
| .isEqualTo(".obj"); |
| assertThat(toolchainFeatures.getArtifactNameExtensionForCategory(ArtifactCategory.EXECUTABLE)) |
| .isEmpty(); |
| assertThat( |
| toolchainFeatures.getArtifactNameExtensionForCategory(ArtifactCategory.STATIC_LIBRARY)) |
| .isEqualTo(".a"); |
| } |
| } |