| // 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 org.junit.Assert.assertThrows; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.eventbus.EventBus; |
| import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.LabelConstants; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.cmdline.RepositoryMapping; |
| import com.google.devtools.build.lib.events.Reporter; |
| import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap; |
| import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase; |
| import com.google.devtools.build.lib.testutil.TestRuleClassProvider; |
| import com.google.devtools.build.lib.vfs.Path; |
| import com.google.devtools.build.lib.vfs.RootedPath; |
| import java.util.HashMap; |
| import java.util.Map; |
| import net.starlark.java.eval.StarlarkSemantics; |
| import net.starlark.java.eval.StarlarkThread; |
| import net.starlark.java.syntax.Location; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| @RunWith(JUnit4.class) |
| public final class RuleFactoryTest extends PackageLoadingTestCase { |
| |
| private ConfiguredRuleClassProvider provider = TestRuleClassProvider.getRuleClassProvider(); |
| private final RuleFactory ruleFactory = new RuleFactory(provider); |
| |
| private static final ImmutableList<StarlarkThread.CallStackEntry> DUMMY_STACK = |
| ImmutableList.of( |
| new StarlarkThread.CallStackEntry( |
| "<toplevel>", Location.fromFileLineColumn("BUILD", 42, 1)), |
| new StarlarkThread.CallStackEntry("foo", Location.fromFileLineColumn("foo.bzl", 10, 1)), |
| new StarlarkThread.CallStackEntry( |
| "myrule", Location.fromFileLineColumn("bar.bzl", 30, 6))); |
| |
| private Package.Builder newBuilder(PackageIdentifier id, Path filename) { |
| return packageFactory |
| .newPackageBuilder( |
| id, "TESTING", StarlarkSemantics.DEFAULT, RepositoryMapping.ALWAYS_FALLBACK) |
| .setFilename(RootedPath.toRootedPath(root, filename)); |
| } |
| |
| @Test |
| public void testCreateRule() throws Exception { |
| Path myPkgPath = scratch.resolve("/workspace/mypkg/BUILD"); |
| Package.Builder pkgBuilder = newBuilder(PackageIdentifier.createInMainRepo("mypkg"), myPkgPath); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("name", "foo"); |
| attributeValues.put("alwayslink", true); |
| |
| RuleClass ruleClass = provider.getRuleClassMap().get("cc_library"); |
| Rule rule = |
| RuleFactory.createAndAddRule( |
| pkgBuilder, |
| ruleClass, |
| new BuildLangTypedAttributeValuesMap(attributeValues), |
| new Reporter(new EventBus()), |
| StarlarkSemantics.DEFAULT, |
| DUMMY_STACK); |
| |
| assertThat(rule.getAssociatedRule()).isSameInstanceAs(rule); |
| |
| // pkg.getRules() = [rule] |
| Package pkg = pkgBuilder.build(); |
| assertThat(Sets.newHashSet(pkg.getTargets(Rule.class))).hasSize(1); |
| assertThat(pkg.getTargets(Rule.class).iterator().next()).isEqualTo(rule); |
| |
| assertThat(pkg.getTarget("foo")).isSameInstanceAs(rule); |
| |
| assertThat(rule.getLabel()).isEqualTo(Label.parseAbsolute("//mypkg:foo", ImmutableMap.of())); |
| assertThat(rule.getName()).isEqualTo("foo"); |
| |
| assertThat(rule.getRuleClass()).isEqualTo("cc_library"); |
| assertThat(rule.getTargetKind()).isEqualTo("cc_library rule"); |
| // The rule reports the location of the outermost call (aka generator), in the BUILD file. |
| // Thie behavior was added to fix b/23974287, but it loses informtion and is redundant |
| // w.r.t. generator_location. A better fix to that issue would be to keep rule.location as |
| // the innermost call, and to report the entire call stack at the first error for the rule. |
| assertThat(rule.getLocation().file()).isEqualTo("BUILD"); |
| assertThat(rule.getLocation().line()).isEqualTo(42); |
| assertThat(rule.getLocation().column()).isEqualTo(1); |
| assertThat(rule.containsErrors()).isFalse(); |
| |
| // Attr with explicitly-supplied value: |
| AttributeMap attributes = RawAttributeMapper.of(rule); |
| assertThat(attributes.get("alwayslink", Type.BOOLEAN)).isTrue(); |
| assertThrows(Exception.class, () -> attributes.get("alwayslink", Type.STRING)); |
| assertThrows(Exception.class, () -> attributes.get("nosuchattr", Type.STRING)); |
| |
| // Attrs with default values: |
| // cc_library linkstatic default=0 according to build encyc. |
| assertThat(attributes.get("linkstatic", Type.BOOLEAN)).isFalse(); |
| assertThat(attributes.get("testonly", Type.BOOLEAN)).isFalse(); |
| assertThat(attributes.get("srcs", BuildType.LABEL_LIST)).isEmpty(); |
| } |
| |
| @Test |
| public void testCreateWorkspaceRule() throws Exception { |
| Path myPkgPath = scratch.resolve("/workspace/WORKSPACE"); |
| Package.Builder pkgBuilder = |
| packageFactory.newExternalPackageBuilder( |
| RootedPath.toRootedPath(root, myPkgPath), "TESTING", StarlarkSemantics.DEFAULT); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("name", "foo"); |
| attributeValues.put("actual", "//foo:bar"); |
| |
| RuleClass ruleClass = provider.getRuleClassMap().get("bind"); |
| Rule rule = |
| RuleFactory.createAndAddRule( |
| pkgBuilder, |
| ruleClass, |
| new BuildLangTypedAttributeValuesMap(attributeValues), |
| new Reporter(new EventBus()), |
| StarlarkSemantics.DEFAULT, |
| DUMMY_STACK); |
| assertThat(rule.containsErrors()).isFalse(); |
| } |
| |
| @Test |
| public void testWorkspaceRuleFailsInBuildFile() throws Exception { |
| Path myPkgPath = scratch.resolve("/workspace/mypkg/BUILD"); |
| Package.Builder pkgBuilder = newBuilder(PackageIdentifier.createInMainRepo("mypkg"), myPkgPath); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("name", "foo"); |
| attributeValues.put("actual", "//bar:baz"); |
| |
| RuleClass ruleClass = provider.getRuleClassMap().get("bind"); |
| RuleFactory.InvalidRuleException e = |
| assertThrows( |
| RuleFactory.InvalidRuleException.class, |
| () -> |
| RuleFactory.createAndAddRule( |
| pkgBuilder, |
| ruleClass, |
| new BuildLangTypedAttributeValuesMap(attributeValues), |
| new Reporter(new EventBus()), |
| StarlarkSemantics.DEFAULT, |
| DUMMY_STACK)); |
| assertThat(e).hasMessageThat().contains("must be in the WORKSPACE file"); |
| } |
| |
| @Test |
| public void testBuildRuleFailsInWorkspaceFile() throws Exception { |
| Path myPkgPath = scratch.resolve("/workspace/WORKSPACE"); |
| Package.Builder pkgBuilder = newBuilder(LabelConstants.EXTERNAL_PACKAGE_IDENTIFIER, myPkgPath); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("name", "foo"); |
| attributeValues.put("alwayslink", true); |
| |
| RuleClass ruleClass = provider.getRuleClassMap().get("cc_library"); |
| RuleFactory.InvalidRuleException e = |
| assertThrows( |
| RuleFactory.InvalidRuleException.class, |
| () -> |
| RuleFactory.createAndAddRule( |
| pkgBuilder, |
| ruleClass, |
| new BuildLangTypedAttributeValuesMap(attributeValues), |
| new Reporter(new EventBus()), |
| StarlarkSemantics.DEFAULT, |
| DUMMY_STACK)); |
| assertThat(e).hasMessageThat().contains("cannot be in the WORKSPACE file"); |
| } |
| |
| private void assertAttr(RuleClass ruleClass, String attrName, Type<?> type) throws Exception { |
| assertWithMessage( |
| "Rule class '" |
| + ruleClass.getName() |
| + "' should have attribute '" |
| + attrName |
| + "' of type '" |
| + type |
| + "'") |
| .that(ruleClass.hasAttr(attrName, type)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void testOutputFileNotEqualDot() throws Exception { |
| Path myPkgPath = scratch.resolve("/workspace/mypkg"); |
| Package.Builder pkgBuilder = newBuilder(PackageIdentifier.createInMainRepo("mypkg"), myPkgPath); |
| |
| Map<String, Object> attributeValues = new HashMap<>(); |
| attributeValues.put("outs", Lists.newArrayList(".")); |
| attributeValues.put("name", "some"); |
| RuleClass ruleClass = provider.getRuleClassMap().get("genrule"); |
| RuleFactory.InvalidRuleException e = |
| assertThrows( |
| RuleFactory.InvalidRuleException.class, |
| () -> |
| RuleFactory.createAndAddRule( |
| pkgBuilder, |
| ruleClass, |
| new BuildLangTypedAttributeValuesMap(attributeValues), |
| new Reporter(new EventBus()), |
| StarlarkSemantics.DEFAULT, |
| DUMMY_STACK)); |
| assertWithMessage(e.getMessage()) |
| .that(e.getMessage().contains("output file name can't be equal '.'")) |
| .isTrue(); |
| } |
| |
| /** Tests mandatory attribute definitions for test rules. */ |
| // TODO(ulfjack): Remove this check when we switch over to the builder |
| // pattern, which will always guarantee that these attributes are present. |
| @Test |
| public void testTestRules() throws Exception { |
| Path myPkgPath = scratch.resolve("/workspace/mypkg/BUILD"); |
| Package pkg = newBuilder(PackageIdentifier.createInMainRepo("mypkg"), myPkgPath).build(); |
| |
| for (String name : ruleFactory.getRuleClassNames()) { |
| // Create rule instance directly so we'll avoid mandatory attribute check yet will be able |
| // to use TargetUtils.isTestRule() method to identify test rules. |
| RuleClass ruleClass = ruleFactory.getRuleClass(name); |
| Rule rule = |
| new Rule( |
| pkg, |
| Label.create(pkg.getPackageIdentifier(), "myrule"), |
| ruleClass, |
| Location.fromFile(myPkgPath.toString()), |
| CallStack.EMPTY, |
| AttributeContainer.newMutableInstance(ruleClass)); |
| if (TargetUtils.isTestRule(rule)) { |
| assertAttr(ruleClass, "tags", Type.STRING_LIST); |
| assertAttr(ruleClass, "size", Type.STRING); |
| assertAttr(ruleClass, "flaky", Type.BOOLEAN); |
| assertAttr(ruleClass, "shard_count", Type.INTEGER); |
| assertAttr(ruleClass, "local", Type.BOOLEAN); |
| } |
| } |
| } |
| } |