// 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 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.ImmutableSet; import com.google.common.collect.Multimap; import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; 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.CcToolchainFeatures.Variables; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.IntegerValue; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.SequenceBuilder; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.StringSequenceBuilder; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.StructureBuilder; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.Variables.VariableValueBuilder; import com.google.devtools.build.lib.testutil.Suite; import com.google.devtools.build.lib.testutil.TestSpec; import com.google.devtools.build.lib.testutil.TestUtils; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.view.config.crosstool.CrosstoolConfig.CToolchain; import com.google.protobuf.TextFormat; import java.util.Arrays; 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) @TestSpec(size = Suite.MEDIUM_TESTS) public class CcToolchainFeaturesTest { /** * Creates a {@code Variables} configuration from a list of key/value pairs. * *
If there are multiple entries with the same key, the variable will be treated as sequence
* type.
*/
private Variables createVariables(String... entries) {
if (entries.length % 2 != 0) {
throw new IllegalArgumentException(
"createVariables takes an even number of arguments (key/value pairs)");
}
Multimap TODO(b/32655571): Get rid of this test once implicit iteration is not supported.
// It's there only to document a known limitation of the system.
@Test
public void testVariableLookupIsBrokenForImplicitStructFieldIteration() throws Exception {
assertThat(
getCommandLineForFlagGroups(
"flag_group { flag: '-A%{struct.sequence}' }",
createStructureVariables(
"struct",
new StructureBuilder().addField("sequence", ImmutableList.of("foo", "bar")))))
.containsExactly("-Afoo", "-Abar");
}
@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 Variables.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 Variables.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 Variables.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 Variables.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 Variables.StructureBuilder().addField("bar", "barValue"))))
.containsExactly("-BbarValue");
}
@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 Variables.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 Variables.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 testLegacyListVariableExpansion() throws Exception {
assertThat(getCommandLineForFlag("%{v}", createVariables("v", "1", "v", "2")))
.containsExactly("1", "2");
assertThat(getCommandLineForFlag("%{v1} %{v2}",
createVariables("v1", "a1", "v1", "a2", "v2", "b")))
.containsExactly("a1 b", "a2 b");
assertThat(getFlagExpansionError("%{v1} %{v2}",
createVariables("v1", "a1", "v1", "a2", "v2", "b1", "v2", "b2")))
.contains("'v1' and 'v2'");
}
@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 testListVariableExpansionMixedWithImplicitlyAccessedListVariableWithinFlagGroupWorks()
throws Exception {
assertThat(
getCommandLineForFlagGroups(
"flag_group {"
+ " iterate_over: 'v1'"
+ " flag_group {"
+ " flag: '-A%{v1} -B%{v2}'"
+ " }"
+ "}",
createVariables("v1", "a1", "v1", "a2", "v2", "b1", "v2", "b2")))
.containsExactly("-Aa1 -Bb1", "-Aa1 -Bb2", "-Aa2 -Bb1", "-Aa2 -Bb2");
}
@Test
public void testFlagGroupVariableExpansion() throws Exception {
assertThat(getCommandLineForFlagGroups(
"flag_group { flag: '-f' flag: '%{v}' } flag_group { flag: '-end' }",
createVariables("v", "1", "v", "2")))
.containsExactly("-f", "1", "-f", "2", "-end");
assertThat(getCommandLineForFlagGroups(
"flag_group { flag: '-f' flag: '%{v}' } flag_group { flag: '%{v}' }",
createVariables("v", "1", "v", "2")))
.containsExactly("-f", "1", "-f", "2", "1", "2");
assertThat(getCommandLineForFlagGroups(
"flag_group { flag: '-f' flag: '%{v}' } flag_group { flag: '%{v}' }",
createVariables("v", "1", "v", "2")))
.containsExactly("-f", "1", "-f", "2", "1", "2");
try {
getCommandLineForFlagGroups(
"flag_group { flag: '%{v1}' flag: '%{v2}' }",
createVariables("v1", "1", "v1", "2", "v2", "1", "v2", "2"));
fail("Expected ExpansionException");
} catch (ExpansionException e) {
assertThat(e.getMessage()).contains("'v1' and 'v2'");
}
}
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 Variables createNestedVariables(String name, int depth, int count) {
return new Variables.Builder()
.addCustomBuiltVariable(name, createNestedSequence(depth, count, ""))
.build();
}
@Test
public void testFlagTreeVariableExpansion() throws Exception {
String nestedGroup =
"flag_group {"
+ " flag_group { flag: '-a' }"
+ " flag_group {"
+ " 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");
assertThat(getCommandLineForFlagGroups(nestedGroup, createNestedVariables("v", 0, 3)))
.containsExactly("-a", "0", "-b", "-a", "1", "-b", "-a", "2", "-b");
try {
getCommandLineForFlagGroups(nestedGroup, createNestedVariables("v", 2, 3));
fail("Expected ExpansionException");
} catch (ExpansionException e) {
assertThat(e.getMessage()).contains("'v'");
}
try {
buildFeatures(
"feature {",
" name: 'a'",
" flag_set {",
" action: 'c++-compile'",
" flag_group {",
" flag_group { flag: '-f' }",
" flag: '-f'",
" }",
" }",
"}");
fail("Expected ExpansionException");
} catch (ExpansionException e) {
assertThat(e.getMessage()).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 {
try {
buildFeatures(
"feature { name: '<<