blob: ced3bf236d95a83b006212252246d60603478ee6 [file] [log] [blame]
// Copyright 2015 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.packages;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.devtools.build.lib.analysis.testing.ExecGroupSubject.assertThat;
import static com.google.devtools.build.lib.analysis.testing.RuleClassSubject.assertThat;
import static com.google.devtools.build.lib.packages.Attribute.attr;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL;
import static com.google.devtools.build.lib.packages.BuildType.OUTPUT_LIST;
import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.substitutePlaceholderIntoTemplate;
import static com.google.devtools.build.lib.packages.RuleClass.Builder.STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME;
import static com.google.devtools.build.lib.packages.RuleClass.NO_EXTERNAL_BINDINGS;
import static com.google.devtools.build.lib.packages.RuleClass.NO_TOOLCHAINS_TO_REGISTER;
import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
import static com.google.devtools.build.lib.packages.Type.INTEGER;
import static com.google.devtools.build.lib.packages.Type.STRING;
import static com.google.devtools.build.lib.packages.Types.STRING_LIST;
import static org.junit.Assert.assertThrows;
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.Lists;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement;
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventCollector;
import com.google.devtools.build.lib.events.EventKind;
import com.google.devtools.build.lib.packages.Attribute.StarlarkComputedDefaultTemplate.CannotPrecomputeDefaultsException;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory;
import com.google.devtools.build.lib.packages.RuleClass.ToolchainResolutionMode;
import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap;
import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.RootedPath;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import net.starlark.java.eval.StarlarkFunction;
import net.starlark.java.eval.StarlarkInt;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.Location;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link RuleClass}. */
@RunWith(JUnit4.class)
public final class RuleClassTest extends PackageLoadingTestCase {
private static final RuleClass.ConfiguredTargetFactory<Object, Object, Exception>
DUMMY_CONFIGURED_TARGET_FACTORY =
ruleContext -> {
throw new IllegalStateException();
};
private static final ImmutableList<StarlarkThread.CallStackEntry> DUMMY_STACK =
ImmutableList.of(
StarlarkThread.callStackEntry(
StarlarkThread.TOP_LEVEL, Location.fromFileLineColumn("BUILD", 10, 1)),
StarlarkThread.callStackEntry("bar", Location.fromFileLineColumn("bar.bzl", 42, 1)),
StarlarkThread.callStackEntry("rule", Location.BUILTIN));
private static final class DummyFragment extends Fragment {}
private static RuleClass createRuleClassA() throws LabelSyntaxException {
return newRuleClass(
"ruleA",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("my-string-attr", STRING).mandatory().build(),
attr("my-label-attr", LABEL)
.mandatory()
.legacyAllowAnyFileType()
.value(Label.parseCanonical("//default:label"))
.build(),
attr("my-labellist-attr", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(),
attr("my-integer-attr", INTEGER).value(StarlarkInt.of(42)).build(),
attr("my-string-attr2", STRING).mandatory().value((String) null).build(),
attr("my-stringlist-attr", STRING_LIST).build(),
attr("my-sorted-stringlist-attr", STRING_LIST).orderIndependent().build());
}
private static RuleClass createRuleClassB(RuleClass ruleClassA) {
// emulates attribute inheritance
List<Attribute> attributes = new ArrayList<>(ruleClassA.getAttributes());
attributes.add(attr("another-string-attr", STRING).mandatory().build());
return newRuleClass(
"ruleB",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attributes.toArray(new Attribute[0]));
}
// Helper method to paper over how some test cases don't supply the mandatory name attribute for
// historic reasons.
private static ImmutableMap<String, Object> ensureNameAttrValuePresent(
Map<String, Object> attrValues) {
if (attrValues.containsKey("name")) {
return ImmutableMap.copyOf(attrValues);
} else {
return ImmutableMap.<String, Object>builder()
.putAll(attrValues)
.put("name", "my-name")
.buildOrThrow();
}
}
@Test
public void testRuleClassBasics() throws Exception {
RuleClass ruleClassA = createRuleClassA();
assertThat(ruleClassA.getName()).isEqualTo("ruleA");
assertThat(ruleClassA.getAttributeCount()).isEqualTo(8);
assertThat(ruleClassA.getAttributeIndex("name")).isEqualTo(0);
assertThat(ruleClassA.getAttributeIndex("my-string-attr")).isEqualTo(1);
assertThat(ruleClassA.getAttributeIndex("my-label-attr")).isEqualTo(2);
assertThat(ruleClassA.getAttributeIndex("my-labellist-attr")).isEqualTo(3);
assertThat(ruleClassA.getAttributeIndex("my-integer-attr")).isEqualTo(4);
assertThat(ruleClassA.getAttributeIndex("my-string-attr2")).isEqualTo(5);
assertThat(ruleClassA.getAttributeIndex("my-stringlist-attr")).isEqualTo(6);
assertThat(ruleClassA.getAttributeIndex("my-sorted-stringlist-attr")).isEqualTo(7);
assertThat(ruleClassA.getAttributeByName("name")).isEqualTo(ruleClassA.getAttribute(0));
assertThat(ruleClassA.getAttributeByName("my-string-attr"))
.isEqualTo(ruleClassA.getAttribute(1));
assertThat(ruleClassA.getAttributeByName("my-label-attr"))
.isEqualTo(ruleClassA.getAttribute(2));
assertThat(ruleClassA.getAttributeByName("my-labellist-attr"))
.isEqualTo(ruleClassA.getAttribute(3));
assertThat(ruleClassA.getAttributeByName("my-integer-attr"))
.isEqualTo(ruleClassA.getAttribute(4));
assertThat(ruleClassA.getAttributeByName("my-string-attr2"))
.isEqualTo(ruleClassA.getAttribute(5));
assertThat(ruleClassA.getAttributeByName("my-stringlist-attr"))
.isEqualTo(ruleClassA.getAttribute(6));
assertThat(ruleClassA.getAttributeByName("my-sorted-stringlist-attr"))
.isEqualTo(ruleClassA.getAttribute(7));
// default based on type
assertThat(ruleClassA.getAttribute(0).getDefaultValue(null)).isEqualTo("");
assertThat(ruleClassA.getAttribute(1).getDefaultValue(null)).isEqualTo("");
assertThat(ruleClassA.getAttribute(2).getDefaultValue(null))
.isEqualTo(Label.parseCanonical("//default:label"));
assertThat(ruleClassA.getAttribute(3).getDefaultValue(null)).isEqualTo(ImmutableList.of());
assertThat(ruleClassA.getAttribute(4).getDefaultValue(null)).isEqualTo(StarlarkInt.of(42));
// default explicitly specified
assertThat(ruleClassA.getAttribute(5).getDefaultValue(null)).isNull();
assertThat(ruleClassA.getAttribute(6).getDefaultValue(null)).isEqualTo(ImmutableList.of());
assertThat(ruleClassA.getAttribute(7).getDefaultValue(null)).isEqualTo(ImmutableList.of());
}
@Test
public void testRuleClassInheritance() throws Exception {
RuleClass ruleClassA = createRuleClassA();
RuleClass ruleClassB = createRuleClassB(ruleClassA);
assertThat(ruleClassB.getName()).isEqualTo("ruleB");
assertThat(ruleClassB.getAttributeCount()).isEqualTo(9);
assertThat(ruleClassB.getAttributeIndex("name")).isEqualTo(0);
assertThat(ruleClassB.getAttributeIndex("my-string-attr")).isEqualTo(1);
assertThat(ruleClassB.getAttributeIndex("my-label-attr")).isEqualTo(2);
assertThat(ruleClassB.getAttributeIndex("my-labellist-attr")).isEqualTo(3);
assertThat(ruleClassB.getAttributeIndex("my-integer-attr")).isEqualTo(4);
assertThat(ruleClassB.getAttributeIndex("my-string-attr2")).isEqualTo(5);
assertThat(ruleClassB.getAttributeIndex("my-stringlist-attr")).isEqualTo(6);
assertThat(ruleClassB.getAttributeIndex("my-sorted-stringlist-attr")).isEqualTo(7);
assertThat(ruleClassB.getAttributeIndex("another-string-attr")).isEqualTo(8);
assertThat(ruleClassB.getAttributeByName("name")).isEqualTo(ruleClassB.getAttribute(0));
assertThat(ruleClassB.getAttributeByName("my-string-attr"))
.isEqualTo(ruleClassB.getAttribute(1));
assertThat(ruleClassB.getAttributeByName("my-label-attr"))
.isEqualTo(ruleClassB.getAttribute(2));
assertThat(ruleClassB.getAttributeByName("my-labellist-attr"))
.isEqualTo(ruleClassB.getAttribute(3));
assertThat(ruleClassB.getAttributeByName("my-integer-attr"))
.isEqualTo(ruleClassB.getAttribute(4));
assertThat(ruleClassB.getAttributeByName("my-string-attr2"))
.isEqualTo(ruleClassB.getAttribute(5));
assertThat(ruleClassB.getAttributeByName("my-stringlist-attr"))
.isEqualTo(ruleClassB.getAttribute(6));
assertThat(ruleClassB.getAttributeByName("my-sorted-stringlist-attr"))
.isEqualTo(ruleClassB.getAttribute(7));
assertThat(ruleClassB.getAttributeByName("another-string-attr"))
.isEqualTo(ruleClassB.getAttribute(8));
}
private static final String TEST_PACKAGE_NAME = "testpackage";
private static final String TEST_RULE_NAME = "my-rule-A";
private static final int TEST_RULE_DEFINED_AT_LINE = 42;
private static final String TEST_RULE_LABEL = "@//" + TEST_PACKAGE_NAME + ":" + TEST_RULE_NAME;
private Path testBuildfilePath;
private Location testRuleLocation;
@Before
public void setRuleLocation() {
testBuildfilePath = root.getRelative("testpackage/BUILD");
testRuleLocation =
Location.fromFileLineColumn(testBuildfilePath.toString(), TEST_RULE_DEFINED_AT_LINE, 0);
}
private Package.Builder createDummyPackageBuilder() {
return packageFactory.newPackageBuilder(
PackageIdentifier.createInMainRepo(TEST_PACKAGE_NAME),
RootedPath.toRootedPath(root, testBuildfilePath),
"TESTING",
Optional.empty(),
Optional.empty(),
StarlarkSemantics.DEFAULT,
/* repositoryMapping= */ RepositoryMapping.ALWAYS_FALLBACK,
/* cpuBoundSemaphore= */ null,
/* generatorMap= */ null,
/* configSettingVisibilityPolicy= */ null,
/* globber= */ null);
}
@Test
public void testDuplicatedDeps() throws Exception {
RuleClass depsRuleClass =
newRuleClass(
"ruleDeps",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(),
attr("list2", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(),
attr("list3", LABEL_LIST).mandatory().legacyAllowAnyFileType().build());
// LinkedHashMap -> predictable iteration order for testing
Map<String, Object> attributeValues = new LinkedHashMap<>();
attributeValues.put("list1", Lists.newArrayList("//testpackage:dup1", ":dup1", ":nodup"));
attributeValues.put("list2", Lists.newArrayList(":nodup1", ":nodup2"));
attributeValues.put("list3", Lists.newArrayList(":dup1", ":dup1", ":dup2", ":dup2"));
reporter.removeHandler(failFastHandler);
createRule(depsRuleClass, "depsRule", attributeValues);
assertThat(eventCollector.count()).isSameInstanceAs(3);
assertDupError("//testpackage:dup1", "list1", "depsRule");
assertDupError("//testpackage:dup1", "list3", "depsRule");
assertDupError("//testpackage:dup2", "list3", "depsRule");
}
private void assertDupError(String label, String attrName, String ruleName) {
assertContainsEvent(String.format("Label '%s' is duplicated in the '%s' attribute of rule '%s'",
label, attrName, ruleName));
}
@Test
public void testDuplicatedDepsWithinSingleSelectConditionError() throws Exception {
RuleClass depsRuleClass =
newRuleClass(
"ruleDeps",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build());
SelectorList selectorList1 =
SelectorList.of(
new SelectorValue(
ImmutableMap.of("//conditions:a", ImmutableList.of(":dup1", ":dup1")), ""));
// expect errors
reporter.removeHandler(failFastHandler);
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("list1", selectorList1);
createRule(depsRuleClass, "depsRule", attributeValues);
assertThat(eventCollector.count()).isSameInstanceAs(1);
assertDupError("//testpackage:dup1", "list1", "depsRule");
}
@Test
public void testDuplicatedDepsWithinConditionMultipleSelectsErrors() throws Exception {
RuleClass depsRuleClass =
newRuleClass(
"ruleDeps",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build());
SelectorList selectorList1a =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:a", ImmutableList.of(":dup1", "dup1"),
"//conditions:b", ImmutableList.of(":nodup1")),
""));
SelectorList selectorList1b =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:c", ImmutableList.of(":dup2", "dup2"),
"//conditions:d", ImmutableList.of(":nodup1")),
""));
SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b);
// expect errors
reporter.removeHandler(failFastHandler);
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("list1", selectorList1);
createRule(depsRuleClass, "depsRule", attributeValues);
assertThat(eventCollector.count()).isSameInstanceAs(2);
assertDupError("//testpackage:dup1", "list1", "depsRule");
assertDupError("//testpackage:dup2", "list1", "depsRule");
}
@Test
public void testSameDepAcrossMultipleSelectsNoDuplicateNoError() throws Exception {
RuleClass depsRuleClass =
newRuleClass(
"ruleDeps",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build());
// ignore duplicatess across selects where values appear duplicated but are not
SelectorList selectorList1a =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:a", ImmutableList.of(":nodup1"),
"//conditions:b", ImmutableList.of(":nodup2")),
""));
SelectorList selectorList1b =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:a", ImmutableList.of(":nodup2"),
"//conditions:b", ImmutableList.of(":nodup1")),
""));
SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b);
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("list1", selectorList1);
createRule(depsRuleClass, "depsRule", attributeValues);
}
@Test
public void testSameDepAcrossMultipleSelectsIsDuplicateNoError() throws Exception {
RuleClass depsRuleClass =
newRuleClass(
"ruleDeps",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build());
// repetition of dup1 is identified at analysis time, not loading time
SelectorList selectorList1a =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:a", ImmutableList.of(":dup1"),
"//conditions:b", ImmutableList.of(":nodup1")),
""));
SelectorList selectorList1b =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:a", ImmutableList.of(":dup1"),
"//conditions:b", ImmutableList.of(":nodup2")),
""));
SelectorList selectorList1 = SelectorList.concat(selectorList1a, selectorList1b);
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("list1", selectorList1);
createRule(depsRuleClass, "depsRule", attributeValues);
}
@Test
public void testSameDepAcrossConditionsInSelectNoError() throws Exception {
RuleClass depsRuleClass =
newRuleClass(
"ruleDeps",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("list1", LABEL_LIST).mandatory().legacyAllowAnyFileType().build());
SelectorList selectorList1 =
SelectorList.of(
new SelectorValue(
ImmutableMap.of(
"//conditions:a", ImmutableList.of(":nodup1"),
"//conditions:b", ImmutableList.of(":nodup1")),
""));
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("list1", selectorList1);
createRule(depsRuleClass, "depsRule", attributeValues);
}
@Test
public void testCreateRule() throws Exception {
RuleClass ruleClassA = createRuleClassA();
// LinkedHashMap -> predictable iteration order for testing
Map<String, Object> attributeValues = new LinkedHashMap<>();
attributeValues.put("my-labellist-attr", "foobar"); // wrong type
attributeValues.put("bogus-attr", "foobar"); // no such attr
attributeValues.put("my-stringlist-attr", Arrays.asList("foo", "bar"));
reporter.removeHandler(failFastHandler);
EventCollector collector = new EventCollector(EventKind.ERRORS);
reporter.addHandler(collector);
Rule rule = createRule(ruleClassA, TEST_RULE_NAME, attributeValues);
// TODO(blaze-team): (2009) refactor to use assertContainsEvent
Iterator<String> expectedMessages =
Arrays.asList(
"""
expected value of type 'list(label)' for attribute 'my-labellist-attr' \
of 'ruleA', but got \"foobar\" (string)""",
"no such attribute 'bogus-attr' in 'ruleA' rule",
"missing value for mandatory attribute 'my-string-attr' in 'ruleA' rule",
"missing value for mandatory attribute 'my-label-attr' in 'ruleA' rule",
"missing value for mandatory attribute 'my-labellist-attr' in 'ruleA' rule",
"missing value for mandatory attribute 'my-string-attr2' in 'ruleA' rule")
.iterator();
for (Event event : collector) {
assertThat(event.getLocation().line()).isEqualTo(TEST_RULE_DEFINED_AT_LINE);
assertThat(event.getLocation().file()).isEqualTo(testBuildfilePath.toString());
assertThat(event.getMessage())
.isEqualTo(TEST_RULE_LABEL.substring(1) + ": " + expectedMessages.next());
}
// Test basic rule properties:
assertThat(rule.getRuleClass()).isEqualTo("ruleA");
assertThat(rule.getName()).isEqualTo(TEST_RULE_NAME);
assertThat(rule.getLabel().toString()).isEqualTo(TEST_RULE_LABEL.substring(1));
// Test attribute access:
AttributeMap attributes = RawAttributeMapper.of(rule);
assertThat(attributes.get("my-label-attr", BuildType.LABEL).toString())
.isEqualTo("//default:label");
assertThat(attributes.get("my-integer-attr", Type.INTEGER).toIntUnchecked()).isEqualTo(42);
// missing attribute -> default chosen based on type
assertThat(attributes.get("my-string-attr", Type.STRING)).isEmpty();
assertThat(attributes.get("my-labellist-attr", BuildType.LABEL_LIST)).isEmpty();
assertThat(attributes.get("my-stringlist-attr", Types.STRING_LIST))
.isEqualTo(Arrays.asList("foo", "bar"));
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class, () -> attributes.get("my-labellist-attr", Type.STRING));
assertThat(e)
.hasMessageThat()
.isEqualTo(
"Attribute my-labellist-attr is of type list(label) "
+ "and not of type string in ruleA rule //testpackage:my-rule-A");
}
@Test
public void testImplicitOutputs() throws Exception {
RuleClass ruleClassC =
newRuleClass(
"ruleC",
false,
false,
false,
false,
false,
false,
ImplicitOutputsFunction.fromTemplates(
"foo-%{name}.bar", "lib%{name}-wazoo-%{name}.mumble", "stuff-%{outs}-bar"),
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("outs", OUTPUT_LIST).build());
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("outs", Collections.singletonList("explicit_out"));
attributeValues.put("name", "myrule");
Rule rule = createRule(ruleClassC, "myrule", attributeValues);
Set<String> set = new HashSet<>();
for (OutputFile outputFile : rule.getOutputFiles()) {
set.add(outputFile.getName());
assertThat(outputFile.getGeneratingRule()).isSameInstanceAs(rule);
}
assertThat(set).containsExactly("foo-myrule.bar", "libmyrule-wazoo-myrule.mumble",
"stuff-explicit_out-bar", "explicit_out");
}
@Test
public void testImplicitOutsWithBasenameDirname() throws Exception {
RuleClass ruleClass =
newRuleClass(
"ruleClass",
false,
false,
false,
false,
false,
false,
ImplicitOutputsFunction.fromTemplates("%{dirname}lib%{basename}.bar"),
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true);
Rule rule = createRule(ruleClass, "myRule", ImmutableMap.of());
assertThat(Iterables.getOnlyElement(rule.getOutputFiles()).getName())
.isEqualTo("libmyRule.bar");
Rule ruleWithSlash = createRule(ruleClass, "myRule/with/slash", ImmutableMap.of());
assertThat(Iterables.getOnlyElement(ruleWithSlash.getOutputFiles()).getName())
.isEqualTo("myRule/with/libslash.bar");
}
/**
* Helper routine that instantiates a rule class with the given computed default and supporting
* attributes for the default to reference.
*/
private static RuleClass getRuleClassWithComputedDefault(Attribute computedDefault) {
return newRuleClass(
"ruleClass",
false,
false,
false,
false,
false,
false,
ImplicitOutputsFunction.fromTemplates("empty"),
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("condition", BOOLEAN).value(false).build(),
attr("declared1", BOOLEAN).value(false).build(),
attr("declared2", BOOLEAN).value(false).build(),
attr("nonconfigurable", BOOLEAN).nonconfigurable("test").value(false).build(),
computedDefault);
}
/**
* Helper routine that checks that a computed default is valid and bound to the expected value.
*/
private void checkValidComputedDefault(Object expectedValue, Attribute computedDefault,
ImmutableMap<String, Object> attrValueMap) throws Exception {
assertThat(computedDefault.getDefaultValueUnchecked())
.isInstanceOf(Attribute.ComputedDefault.class);
Rule rule =
createRule(getRuleClassWithComputedDefault(computedDefault), "myRule", attrValueMap);
AttributeMap attributes = RawAttributeMapper.of(rule);
assertThat(attributes.get(computedDefault.getName(), computedDefault.getType()))
.isEqualTo(expectedValue);
}
/**
* Helper routine that checks that a computed default is invalid due to declared dependency issues
* and fails with the expected message.
*/
private void checkInvalidComputedDefault(Attribute computedDefault, String expectedMessage) {
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class,
() ->
createRule(
getRuleClassWithComputedDefault(computedDefault), "myRule", ImmutableMap.of()));
assertThat(e).hasMessageThat().isEqualTo(expectedMessage);
}
/** Tests computed default values are computed as expected. */
@Test
public void testComputedDefault() throws Exception {
Attribute computedDefault =
attr("$result", BOOLEAN)
.value(
new Attribute.ComputedDefault("condition") {
@Override
public Object getDefault(AttributeMap rule) {
return rule.get("condition", Type.BOOLEAN);
}
})
.build();
checkValidComputedDefault(
Boolean.FALSE, computedDefault, ImmutableMap.of("condition", Boolean.FALSE));
checkValidComputedDefault(
Boolean.TRUE, computedDefault, ImmutableMap.of("condition", Boolean.TRUE));
}
/**
* Tests that computed defaults can only read attribute values for configurable attributes that
* have been explicitly declared.
*/
@Test
public void testComputedDefaultDeclarations() throws Exception {
checkValidComputedDefault(
Boolean.FALSE,
attr("$good_default_no_declares", BOOLEAN)
.value(
new Attribute.ComputedDefault() {
@Override
public Object getDefault(AttributeMap rule) {
// OK: not a value check:
return rule.isAttributeValueExplicitlySpecified("undeclared");
}
})
.build(),
ImmutableMap.of());
checkValidComputedDefault(
Boolean.FALSE,
attr("$good_default_one_declare", BOOLEAN)
.value(
new Attribute.ComputedDefault("declared1") {
@Override
public Object getDefault(AttributeMap rule) {
return rule.get("declared1", Type.BOOLEAN);
}
})
.build(),
ImmutableMap.of());
checkValidComputedDefault(
Boolean.FALSE,
attr("$good_default_two_declares", BOOLEAN)
.value(
new Attribute.ComputedDefault("declared1", "declared2") {
@Override
public Object getDefault(AttributeMap rule) {
return rule.get("declared1", Type.BOOLEAN)
&& rule.get("declared2", Type.BOOLEAN);
}
})
.build(),
ImmutableMap.of());
checkInvalidComputedDefault(
attr("$bad_default_no_declares", BOOLEAN).value(
new Attribute.ComputedDefault() {
@Override public Object getDefault(AttributeMap rule) {
return rule.get("declared1", Type.BOOLEAN);
}
}).build(),
"attribute \"declared1\" isn't available in this computed default context");
checkInvalidComputedDefault(
attr("$bad_default_one_declare", BOOLEAN).value(
new Attribute.ComputedDefault("declared1") {
@Override public Object getDefault(AttributeMap rule) {
return rule.get("declared1", Type.BOOLEAN) || rule.get("declared2", Type.BOOLEAN);
}
}).build(),
"attribute \"declared2\" isn't available in this computed default context");
checkInvalidComputedDefault(
attr("$bad_default_two_declares", BOOLEAN).value(
new Attribute.ComputedDefault("declared1", "declared2") {
@Override public Object getDefault(AttributeMap rule) {
return rule.get("condition", Type.BOOLEAN);
}
}).build(),
"attribute \"condition\" isn't available in this computed default context");
}
/**
* Tests that computed defaults *can* read attribute values for non-configurable attributes
* without needing to explicitly declare them.
*/
@Test
public void testComputedDefaultWithNonConfigurableAttributes() throws Exception {
checkValidComputedDefault(
Boolean.FALSE,
attr("$good_default_reading_undeclared_nonconfigurable_attribute", BOOLEAN)
.value(
new Attribute.ComputedDefault() {
@Override
public Object getDefault(AttributeMap rule) {
return rule.get("nonconfigurable", Type.BOOLEAN);
}
})
.build(),
ImmutableMap.of());
}
@Test
public void testOutputsAreOrdered() throws Exception {
RuleClass ruleClassC =
newRuleClass(
"ruleC",
false,
false,
false,
false,
false,
false,
ImplicitOutputsFunction.fromTemplates("first-%{name}", "second-%{name}", "out-%{outs}"),
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("outs", OUTPUT_LIST).build());
Map<String, Object> attributeValues = new HashMap<>();
attributeValues.put("outs", ImmutableList.of("third", "fourth"));
attributeValues.put("name", "myrule");
Rule rule = createRule(ruleClassC, "myrule", attributeValues);
List<String> actual = new ArrayList<>();
for (OutputFile outputFile : rule.getOutputFiles()) {
actual.add(outputFile.getName());
assertThat(outputFile.getGeneratingRule()).isSameInstanceAs(rule);
}
assertWithMessage("unexpected output set").that(actual).containsExactly("first-myrule",
"second-myrule", "out-third", "out-fourth", "third", "fourth");
assertWithMessage("invalid output ordering").that(actual).containsExactly("first-myrule",
"second-myrule", "out-third", "out-fourth", "third", "fourth").inOrder();
}
@Test
public void testSubstitutePlaceholderIntoTemplate() throws Exception {
RuleClass ruleClass =
newRuleClass(
"ruleA",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(),
true,
attr("a", STRING_LIST).mandatory().build(),
attr("b", STRING_LIST).mandatory().build(),
attr("c", STRING_LIST).mandatory().build(),
attr("baz", STRING_LIST).mandatory().build(),
attr("empty", STRING_LIST).build());
Map<String, Object> attributeValues = new LinkedHashMap<>();
attributeValues.put("a", ImmutableList.of("a", "A"));
attributeValues.put("b", ImmutableList.of("b", "B"));
attributeValues.put("c", ImmutableList.of("c", "C"));
attributeValues.put("baz", ImmutableList.of("baz", "BAZ"));
attributeValues.put("empty", ImmutableList.<String>of());
AttributeMap rule = RawAttributeMapper.of(createRule(ruleClass, "testrule", attributeValues));
assertThat(substitutePlaceholderIntoTemplate("foo", rule)).containsExactly("foo");
assertThat(substitutePlaceholderIntoTemplate("foo-%{baz}-bar", rule)).containsExactly(
"foo-baz-bar", "foo-BAZ-bar").inOrder();
assertThat(substitutePlaceholderIntoTemplate("%{a}-%{b}-%{c}", rule)).containsExactly("a-b-c",
"a-b-C", "a-B-c", "a-B-C", "A-b-c", "A-b-C", "A-B-c", "A-B-C").inOrder();
assertThat(substitutePlaceholderIntoTemplate("%{a", rule)).containsExactly("%{a");
assertThat(substitutePlaceholderIntoTemplate("%{a}}", rule)).containsExactly("a}", "A}")
.inOrder();
assertThat(substitutePlaceholderIntoTemplate("x%{a}y%{empty}", rule)).isEmpty();
}
@Test
public void testOrderIndependentAttribute() throws Exception {
RuleClass ruleClassA = createRuleClassA();
List<String> list = Arrays.asList("foo", "bar", "baz");
Map<String, Object> attributeValues = new LinkedHashMap<>();
// mandatory values
attributeValues.put("my-string-attr", "");
attributeValues.put("my-label-attr", "//project");
attributeValues.put("my-string-attr2", "");
attributeValues.put("my-labellist-attr", Collections.emptyList());
// to compare the effect of .orderIndependent()
attributeValues.put("my-stringlist-attr", list);
attributeValues.put("my-sorted-stringlist-attr", list);
Rule rule = createRule(ruleClassA, "testrule", attributeValues);
AttributeMap attributes = RawAttributeMapper.of(rule);
assertThat(attributes.get("my-stringlist-attr", Types.STRING_LIST)).isEqualTo(list);
assertThat(attributes.get("my-sorted-stringlist-attr", Types.STRING_LIST))
.isEqualTo(Arrays.asList("bar", "baz", "foo"));
}
@CanIgnoreReturnValue
private Rule createRule(RuleClass ruleClass, String name, Map<String, Object> attributeValues)
throws LabelSyntaxException, InterruptedException, CannotPrecomputeDefaultsException {
Package.Builder pkgBuilder = createDummyPackageBuilder();
Label ruleLabel;
try {
ruleLabel = pkgBuilder.createLabel(name);
} catch (LabelSyntaxException e) {
throw new IllegalArgumentException("Rule has illegal label", e);
}
attributeValues = ensureNameAttrValuePresent(attributeValues);
Rule rule =
ruleClass.createRule(
pkgBuilder,
ruleLabel,
new BuildLangTypedAttributeValuesMap(attributeValues),
true,
ImmutableList.of(
StarlarkThread.callStackEntry(StarlarkThread.TOP_LEVEL, testRuleLocation)));
pkgBuilder.getLocalEventHandler().replayOn(reporter);
return rule;
}
@Test
public void testOverrideWithWrongType() {
RuleClass parentRuleClass = createParentRuleClass();
RuleClass.Builder childRuleClassBuilder =
new RuleClass.Builder("child_rule", RuleClassType.NORMAL, false, parentRuleClass);
IllegalStateException e =
assertThrows(
IllegalStateException.class,
() -> childRuleClassBuilder.override(attr("attr", INTEGER)));
assertThat(e)
.hasMessageThat()
.isEqualTo(
"The type of the new attribute 'int' is different from "
+ "the original one 'string'.");
}
@Test
public void testOverrideWithRightType() {
RuleClass parentRuleClass = createParentRuleClass();
RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder(
"child_rule", RuleClassType.NORMAL, false, parentRuleClass);
childRuleClassBuilder.override(attr("attr", STRING));
}
@Test
public void testCopyAndOverrideAttribute() throws Exception {
RuleClass parentRuleClass = createParentRuleClass();
RuleClass childRuleClass = createChildRuleClass(parentRuleClass);
Map<String, Object> parentValues = new LinkedHashMap<>();
Map<String, Object> childValues = new LinkedHashMap<>();
childValues.put("attr", "somevalue");
createRule(parentRuleClass, "parent_rule", parentValues);
createRule(childRuleClass, "child_rule", childValues);
}
@Test
public void testCopyAndOverrideAttributeMandatoryMissing() throws Exception {
RuleClass parentRuleClass = createParentRuleClass();
RuleClass childRuleClass = createChildRuleClass(parentRuleClass);
Map<String, Object> childValues = new LinkedHashMap<>();
reporter.removeHandler(failFastHandler);
createRule(childRuleClass, "child_rule", childValues);
assertThat(eventCollector.count()).isSameInstanceAs(1);
assertContainsEvent("//testpackage:child_rule: missing value for mandatory "
+ "attribute 'attr' in 'child_rule' rule");
}
@Test
public void testRequiredFragmentInheritance() {
RuleClass parentRuleClass = createParentRuleClass();
RuleClass childRuleClass = createChildRuleClass(parentRuleClass);
assertThat(parentRuleClass.getConfigurationFragmentPolicy().getRequiredConfigurationFragments())
.containsExactly(DummyFragment.class);
assertThat(childRuleClass.getConfigurationFragmentPolicy().getRequiredConfigurationFragments())
.containsExactly(DummyFragment.class);
}
private static RuleClass newRuleClass(
String name,
boolean starlarkExecutable,
boolean documented,
boolean binaryOutput,
boolean workspaceOnly,
boolean outputsDefaultExecutable,
boolean isAnalysisTest,
ImplicitOutputsFunction implicitOutputsFunction,
TransitionFactory<RuleTransitionData> transitionFactory,
ConfiguredTargetFactory<?, ?, ?> configuredTargetFactory,
AdvertisedProviderSet advertisedProviders,
@Nullable StarlarkFunction configuredTargetFunction,
Set<Class<? extends Fragment>> allowedConfigurationFragments,
boolean supportsConstraintChecking,
Attribute... attributes) {
return new RuleClass(
name,
DUMMY_STACK,
/* key= */ name,
RuleClassType.NORMAL,
/* starlarkParent= */ null,
/* initializer= */ null,
/* labelConverterForInitializer= */ null,
/* isStarlark= */ starlarkExecutable,
/* extendable= */ false,
/* extendableAllowlist= */ null,
/* starlarkTestable= */ false,
documented,
binaryOutput,
workspaceOnly,
outputsDefaultExecutable,
isAnalysisTest,
/* hasAnalysisTestTransition= */ false,
/* allowlistCheckers= */ ImmutableList.of(),
/* ignoreLicenses= */ false,
implicitOutputsFunction,
transitionFactory,
configuredTargetFactory,
advertisedProviders,
configuredTargetFunction,
NO_EXTERNAL_BINDINGS,
NO_TOOLCHAINS_TO_REGISTER,
/* optionReferenceFunction= */ RuleClass.NO_OPTION_REFERENCE,
/* ruleDefinitionEnvironmentLabel= */ null,
/* ruleDefinitionEnvironmentDigest= */ null,
/* ruleDefinitionEnvironmentRepoMappingEntries= */ null,
new ConfigurationFragmentPolicy.Builder()
.requiresConfigurationFragments(allowedConfigurationFragments)
.build(),
supportsConstraintChecking,
/* toolchainTypes= */ ImmutableSet.of(),
/* useToolchainResolution= */ ToolchainResolutionMode.ENABLED,
/* executionPlatformConstraints= */ ImmutableSet.of(),
/* execGroups= */ ImmutableMap.of(),
OutputFile.Kind.FILE,
attributes.length > 0 && attributes[0].equals(RuleClass.NAME_ATTRIBUTE)
? ImmutableList.copyOf(attributes)
: ImmutableList.<Attribute>builder()
.add(RuleClass.NAME_ATTRIBUTE)
.add(attributes)
.build(),
/* buildSetting= */ null,
/* subrules= */ ImmutableList.of());
}
private static RuleClass createParentRuleClass() {
return newRuleClass(
"parent_rule",
false,
false,
false,
false,
false,
false,
SafeImplicitOutputsFunction.NONE,
null,
DUMMY_CONFIGURED_TARGET_FACTORY,
AdvertisedProviderSet.EMPTY,
null,
ImmutableSet.of(DummyFragment.class),
true,
attr("attr", STRING).build());
}
private static RuleClass createChildRuleClass(RuleClass parentRuleClass) {
RuleClass.Builder childRuleClassBuilder = new RuleClass.Builder(
"child_rule", RuleClassType.NORMAL, false, parentRuleClass);
return childRuleClassBuilder.override(
childRuleClassBuilder
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.copy("attr").mandatory())
.add(attr("tags", STRING_LIST))
.build();
}
@Test
public void testBadRuleClassNames() {
expectError(RuleClassType.NORMAL, "8abc");
expectError(RuleClassType.NORMAL, "!abc");
expectError(RuleClassType.NORMAL, "a b");
}
private static void expectError(RuleClassType type, String name) {
assertThrows(IllegalArgumentException.class, () -> type.checkName(name));
}
@Test
public void testToolchainTypes() throws Exception {
RuleClass.Builder ruleClassBuilder =
new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST));
ruleClassBuilder.addToolchainTypes(
ToolchainTypeRequirement.create(Label.parseCanonical("//toolchain:tc1")),
ToolchainTypeRequirement.create(Label.parseCanonical("//toolchain:tc2")));
RuleClass ruleClass = ruleClassBuilder.build();
assertThat(ruleClass).hasToolchainType("//toolchain:tc1");
assertThat(ruleClass).hasToolchainType("//toolchain:tc2");
}
@Test
public void testExecutionPlatformConstraints() throws Exception {
RuleClass.Builder ruleClassBuilder =
new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST));
ruleClassBuilder.addExecutionPlatformConstraints(
Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2"));
RuleClass ruleClass = ruleClassBuilder.build();
assertThat(ruleClass.getExecutionPlatformConstraints())
.containsExactly(
Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2"));
}
@Test
public void testExecutionPlatformConstraints_inheritConstraintsFromParent() throws Exception {
RuleClass parentRuleClass =
new RuleClass.Builder("$parentRuleClass", RuleClassType.ABSTRACT, false)
.add(attr("tags", STRING_LIST))
.addExecutionPlatformConstraints(
Label.parseCanonical("//constraints:cv1"),
Label.parseCanonical("//constraints:cv2"))
.build();
RuleClass childRuleClass =
new RuleClass.Builder("childRuleClass", RuleClassType.NORMAL, false, parentRuleClass)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.build();
assertThat(childRuleClass.getExecutionPlatformConstraints())
.containsExactly(
Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2"));
}
@Test
public void testExecutionPlatformConstraints_inheritAndAddConstraints() throws Exception {
RuleClass parentRuleClass =
new RuleClass.Builder("$parentRuleClass", RuleClassType.ABSTRACT, false)
.add(attr("tags", STRING_LIST))
.build();
RuleClass.Builder childRuleClassBuilder =
new RuleClass.Builder("childRuleClass", RuleClassType.NORMAL, false, parentRuleClass)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.addExecutionPlatformConstraints(
Label.parseCanonical("//constraints:cv1"),
Label.parseCanonical("//constraints:cv2"));
RuleClass childRuleClass = childRuleClassBuilder.build();
assertThat(childRuleClass.getExecutionPlatformConstraints())
.containsExactly(
Label.parseCanonical("//constraints:cv1"), Label.parseCanonical("//constraints:cv2"));
}
@Test
public void testExecGroups() {
RuleClass.Builder ruleClassBuilder =
new RuleClass.Builder("ruleClass", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST));
Label toolchain = Label.parseCanonicalUnchecked("//toolchain");
Label constraint = Label.parseCanonicalUnchecked("//constraint");
// TODO(https://github.com/bazelbuild/bazel/issues/14726): Add tests of optional toolchains.
ruleClassBuilder.addExecGroups(
ImmutableMap.of(
"cherry",
ExecGroup.builder()
.addToolchainType(ToolchainTypeRequirement.create(toolchain))
.execCompatibleWith(ImmutableSet.of(constraint))
.copyFrom(null)
.build()));
RuleClass ruleClass = ruleClassBuilder.build();
assertThat(ruleClass.getExecGroups()).hasSize(1);
assertThat(ruleClass.getExecGroups().get("cherry")).hasToolchainType(toolchain);
assertThat(ruleClass.getExecGroups().get("cherry")).toolchainType(toolchain).isMandatory();
assertThat(ruleClass.getExecGroups().get("cherry")).hasExecCompatibleWith(constraint);
}
@Test
public void testBuildSetting_createsDefaultAttribute() {
RuleClass labelFlag =
new RuleClass.Builder("label_flag", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST))
.setBuildSetting(BuildSetting.create(true, NODEP_LABEL))
.build();
RuleClass stringSetting =
new RuleClass.Builder("string_setting", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST))
.setBuildSetting(BuildSetting.create(false, STRING))
.build();
assertThat(labelFlag.hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, NODEP_LABEL)).isTrue();
assertThat(stringSetting.hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, STRING)).isTrue();
}
@Test
public void testBuildSetting_doesNotCreateDefaultAttributeIfNotBuildSetting() {
RuleClass stringSetting =
new RuleClass.Builder("non_build_setting", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST))
.build();
assertThat(stringSetting.hasAttr(STARLARK_BUILD_SETTING_DEFAULT_ATTR_NAME, LABEL)).isFalse();
}
@Test
public void testBuildTooManyAttributesRejected() {
RuleClass.Builder builder =
new RuleClass.Builder("myclass", RuleClassType.NORMAL, /*starlark=*/ false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST));
for (int i = 0; i < 200; i++) {
builder.add(attr("attr" + i, STRING));
}
IllegalArgumentException expected =
assertThrows(IllegalArgumentException.class, builder::build);
assertThat(expected)
.hasMessageThat()
.isEqualTo("Rule class myclass declared too many attributes (202 > 200)");
}
@Test
public void testBuildTooLongAttributeNameRejected() {
IllegalArgumentException expected =
assertThrows(
IllegalArgumentException.class,
() ->
new RuleClass.Builder("myclass", RuleClassType.NORMAL, /*starlark=*/ false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST))
.add(attr("x".repeat(150), STRING))
.build());
assertThat(expected)
.hasMessageThat()
.matches("Attribute myclass\\.x{150}'s name is too long \\(150 > 128\\)");
}
@Test
public void testPackageMetadataAlternateName() throws Exception {
RuleClass noopClass =
new RuleClass.Builder("noop", RuleClassType.NORMAL, false)
.factory(DUMMY_CONFIGURED_TARGET_FACTORY)
.add(attr("tags", STRING_LIST))
.add(attr(RuleClass.APPLICABLE_METADATA_ATTR, LABEL_LIST).legacyAllowAnyFileType())
.build();
Map<String, Object> attributeValues = new LinkedHashMap<>();
attributeValues.put("applicable_licenses", Lists.newArrayList(":info"));
Rule noopRule = createRule(noopClass, "noop", attributeValues);
assertThat(noopRule.getAttr(RuleClass.APPLICABLE_METADATA_ATTR, LABEL_LIST))
.isEqualTo(Lists.newArrayList(Label.parseCanonical("//testpackage:info")));
}
}