diff options
author | Han-Wen Nienhuys <hanwen@google.com> | 2015-09-22 16:24:45 +0000 |
---|---|---|
committer | Laszlo Csomor <laszlocsomor@google.com> | 2015-09-22 17:19:53 +0000 |
commit | ceae8c50e8f92c6fbf2394ac7c5eb3b420539225 (patch) | |
tree | 9b10bab2b8e06b61ff268a422ee2f27e09f10382 /src | |
parent | 4671896be8bf0e37c85c3b740bb3621d2a9e1cc8 (diff) |
Open source some skylark tests.
--
MOS_MIGRATED_REVID=103652672
Diffstat (limited to 'src')
22 files changed, 2048 insertions, 150 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java index f3ea46c9cc..69f8e01b6b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java +++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleClassFunctions.java @@ -320,7 +320,7 @@ public class SkylarkRuleClassFunctions { }; // This class is needed for testing - static final class RuleFunction extends BaseFunction { + public static final class RuleFunction extends BaseFunction { // Note that this means that we can reuse the same builder. // This is fine since we don't modify the builder from here. private final RuleClass.Builder builder; @@ -367,7 +367,7 @@ public class SkylarkRuleClassFunctions { } @VisibleForTesting - RuleClass.Builder getBuilder() { + public RuleClass.Builder getBuilder() { return builder; } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java index c283157d41..37769bb239 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Environment.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Environment.java @@ -393,7 +393,8 @@ public final class Environment implements Freezable { // TODO(bazel-team): Delete this function. // This function is currently used in various functions that change their behavior with respect to // lists depending on the Skylark-ness of the code; lists should be unified between the two modes. - boolean isSkylark() { + @VisibleForTesting + public boolean isSkylark() { return isSkylark; } @@ -900,7 +901,7 @@ public final class Environment implements Freezable { * @param input a list of lines of code */ @VisibleForTesting - List<Statement> parseFile(String... input) { + public List<Statement> parseFile(String... input) { return parseFileWithComments(input).statements; } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java index fb95228a65..a385e25c12 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java @@ -45,9 +45,9 @@ import javax.annotation.Nullable; * Recursive descent parser for LL(2) BUILD language. * Loosely based on Python 2 grammar. * See https://docs.python.org/2/reference/grammar.html - * */ -class Parser { +@VisibleForTesting +public class Parser { /** * Combines the parser result into a single value object. diff --git a/src/test/java/BUILD b/src/test/java/BUILD index 64148e6e25..f9d7a59306 100644 --- a/src/test/java/BUILD +++ b/src/test/java/BUILD @@ -536,6 +536,89 @@ java_test( args = ["com.google.devtools.build.lib.AllTests"], deps = [ ":foundations_testutil", + ":syntax_testutil", + ":test_runner", + ":testutil", + "//src/main/java:actions", + "//src/main/java:analysis-exec-rules-skyframe", + "//src/main/java:bazel-core", + "//src/main/java:collect", + "//src/main/java:concurrent", + "//src/main/java:events", + "//src/main/java:packages", + "//src/main/java:util", + "//src/main/java:vfs", + "//third_party:guava", + "//third_party:guava-testlib", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_library( + name = "syntax_testutil", + srcs = glob([ + "com/google/devtools/build/lib/syntax/util/*.java", + ]), + deps = [ + ":foundations_testutil", + ":test_runner", + ":testutil", + "//src/main/java:actions", + "//src/main/java:analysis-exec-rules-skyframe", + "//src/main/java:bazel-core", + "//src/main/java:collect", + "//src/main/java:concurrent", + "//src/main/java:events", + "//src/main/java:packages", + "//src/main/java:util", + "//src/main/java:vfs", + "//third_party:guava", + "//third_party:guava-testlib", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_library( + name = "skylark_testutil", + srcs = glob([ + "com/google/devtools/build/lib/skylark/util/*.java", + ]), + deps = [ + ":analysis_testutil", + ":foundations_testutil", + ":syntax_testutil", + ":testutil", + "//src/main/java:actions", + "//src/main/java:analysis-exec-rules-skyframe", + "//src/main/java:bazel-core", + "//src/main/java:collect", + "//src/main/java:concurrent", + "//src/main/java:events", + "//src/main/java:packages", + "//src/main/java:vfs", + "//third_party:guava", + "//third_party:guava-testlib", + "//third_party:jsr305", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "skylark_test", + srcs = glob([ + "com/google/devtools/build/lib/skylark/*.java", + ]), + args = ["com.google.devtools.build.lib.AllTests"], + deps = [ + ":actions_testutil", + ":analysis_testutil", + ":foundations_testutil", + ":skylark_testutil", ":test_runner", ":testutil", "//src/main/java:actions", diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java new file mode 100644 index 0000000000..c0e6ad0ed9 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java @@ -0,0 +1,47 @@ +// Copyright 2014 Google Inc. 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.skylark; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.lib.skylark.util.SkylarkTestCase; +import com.google.devtools.build.lib.syntax.SkylarkList; + +/** + * Tests for {@link SkylarkCommandLine}. + */ +public class SkylarkCommandLineTest extends SkylarkTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + scratch.file( + "foo/BUILD", + "genrule(name = 'foo',", + " cmd = 'dummy_cmd',", + " srcs = ['a.txt', 'b.img'],", + " tools = ['t.exe'],", + " outs = ['c.txt'])"); + } + + public void testCmdHelperAll() throws Exception { + Object result = + evalRuleContextCode( + createRuleContext("//foo:foo"), + "cmd_helper.template(set(ruleContext.files.srcs), '--%{short_path}=%{path}')"); + SkylarkList list = (SkylarkList) result; + assertThat(list).containsExactly("--foo/a.txt=foo/a.txt", "--foo/b.img=foo/b.img").inOrder(); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java new file mode 100644 index 0000000000..b5b9bfe9c2 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java @@ -0,0 +1,68 @@ +// Copyright 2014 Google Inc. 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.skylark; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.util.ActionsTestUtil; +import com.google.devtools.build.lib.rules.SkylarkRuleContext; +import com.google.devtools.build.lib.skylark.util.SkylarkTestCase; + +/** + * Tests for SkylarkFileset and SkylarkFileType. + */ +public class SkylarkFileHelperTest extends SkylarkTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + scratch.file( + "foo/BUILD", + "genrule(name = 'foo',", + " cmd = 'dummy_cmd',", + " srcs = ['a.txt', 'b.img'],", + " tools = ['t.exe'],", + " outs = ['c.txt'])"); + } + + @SuppressWarnings("unchecked") + public void testFilterPasses() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode(ruleContext, "FileType(['.img']).filter(ruleContext.files.srcs)"); + assertEquals(ActionsTestUtil.baseNamesOf((Iterable<Artifact>) result), "b.img"); + } + + public void testFilterFiltersFilesOut() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode(ruleContext, "FileType(['.xyz']).filter(ruleContext.files.srcs)"); + assertThat(((Iterable<?>) result)).isEmpty(); + } + + public void testArtifactPath() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + String result = (String) evalRuleContextCode(ruleContext, "ruleContext.files.tools[0].path"); + assertEquals("foo/t.exe", result); + } + + public void testArtifactShortPath() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + String result = + (String) evalRuleContextCode(ruleContext, "ruleContext.files.tools[0].short_path"); + assertEquals("foo/t.exe", result); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java new file mode 100644 index 0000000000..c7ea6920b8 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java @@ -0,0 +1,430 @@ +// Copyright 2014 Google Inc. 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.skylark; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.packages.Attribute; +import com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition; +import com.google.devtools.build.lib.packages.BuildType; +import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; +import com.google.devtools.build.lib.packages.PredicateWithMessage; +import com.google.devtools.build.lib.packages.RuleClass; +import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.rules.SkylarkFileType; +import com.google.devtools.build.lib.rules.SkylarkRuleClassFunctions.RuleFunction; +import com.google.devtools.build.lib.skylark.util.SkylarkTestCase; +import com.google.devtools.build.lib.syntax.Type; +import com.google.devtools.build.lib.util.FileTypeSet; + +import org.junit.Before; + +/** + * Tests for SkylarkRuleClassFunctions. + */ +public class SkylarkRuleClassFunctionsTest extends SkylarkTestCase { + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + scratch.file( + "foo/BUILD", + "genrule(name = 'foo',", + " cmd = 'dummy_cmd',", + " srcs = ['a.txt', 'b.img'],", + " tools = ['t.exe'],", + " outs = ['c.txt'])", + "genrule(name = 'bar',", + " cmd = 'dummy_cmd',", + " srcs = [':jl', ':gl'],", + " outs = ['d.txt'])", + "java_library(name = 'jl',", + " srcs = ['a.java'])", + "genrule(name = 'gl',", + " cmd = 'touch $(OUTS)',", + " srcs = ['a.go'],", + " outs = [ 'gl.a', 'gl.gcgox', ],", + " output_to_bindir = 1,", + ")"); + } + + public void testImplicitArgsAttribute() throws Exception { + eval( + "def _impl(ctx):", + " pass", + "exec_rule = rule(implementation = _impl, executable = True)", + "non_exec_rule = rule(implementation = _impl)"); + assertTrue(getRuleClass("exec_rule").hasAttr("args", Type.STRING_LIST)); + assertFalse(getRuleClass("non_exec_rule").hasAttr("args", Type.STRING_LIST)); + } + + private RuleClass getRuleClass(String name) throws Exception { + return ((RuleFunction) lookup(name)).getBuilder().build(name); + } + + private void registerDummyUserDefinedFunction() throws Exception { + eval("def impl():\n" + " return 0\n"); + } + + public void testAttrWithOnlyType() throws Exception { + Object result = evalRuleClassCode("attr.string_list()"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(Type.STRING_LIST, attr.getType()); + } + + public void testOutputListAttr() throws Exception { + Object result = evalRuleClassCode("attr.output_list()"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(BuildType.OUTPUT_LIST, attr.getType()); + } + + public void testIntListAttr() throws Exception { + Object result = evalRuleClassCode("attr.int_list()"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(Type.INTEGER_LIST, attr.getType()); + } + + public void testOutputAttr() throws Exception { + Object result = evalRuleClassCode("attr.output()"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(BuildType.OUTPUT, attr.getType()); + } + + public void testStringDictAttr() throws Exception { + Object result = evalRuleClassCode("attr.string_dict(default = {'a': 'b'})"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(Type.STRING_DICT, attr.getType()); + } + + public void testAttrAllowedFileTypesAnyFile() throws Exception { + Object result = evalRuleClassCode("attr.label_list(allow_files = True)"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(FileTypeSet.ANY_FILE, attr.getAllowedFileTypesPredicate()); + } + + public void testAttrAllowedFileTypesWrongType() throws Exception { + checkErrorContains( + "allow_files should be a boolean or a filetype object.", + "attr.label_list(allow_files = ['.xml'])"); + } + + public void testAttrWithSkylarkFileType() throws Exception { + Object result = evalRuleClassCode("attr.label_list(allow_files = FileType(['.xml']))"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertTrue(attr.getAllowedFileTypesPredicate().apply("a.xml")); + assertFalse(attr.getAllowedFileTypesPredicate().apply("a.txt")); + } + + public void testAttrWithProviders() throws Exception { + Object result = + evalRuleClassCode("attr.label_list(allow_files = True, providers = ['a', 'b'])"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(ImmutableSet.of("a", "b"), attr.getMandatoryProviders()); + } + + public void testNonLabelAttrWithProviders() throws Exception { + checkErrorContains( + "unexpected keyword 'providers' in call to string", "attr.string(providers = ['a'])"); + } + + private static final RuleClass.ConfiguredTargetFactory<Object, Object> + DUMMY_CONFIGURED_TARGET_FACTORY = + new RuleClass.ConfiguredTargetFactory<Object, Object>() { + @Override + public Object create(Object ruleContext) throws InterruptedException { + throw new IllegalStateException(); + } + }; + + private RuleClass ruleClass(String name) { + return new RuleClass.Builder(name, RuleClassType.NORMAL, false) + .factory(DUMMY_CONFIGURED_TARGET_FACTORY) + .add(Attribute.attr("tags", Type.STRING_LIST)) + .build(); + } + + public void testAttrAllowedRuleClassesSpecificRuleClasses() throws Exception { + Object result = + evalRuleClassCode("attr.label_list(allow_rules = ['java_binary'], allow_files = True)"); + Attribute attr = ((Attribute.Builder<?>) result).build("a"); + assertTrue(attr.getAllowedRuleClassesPredicate().apply(ruleClass("java_binary"))); + assertFalse(attr.getAllowedRuleClassesPredicate().apply(ruleClass("genrule"))); + } + + public void testAttrDefaultValue() throws Exception { + Object result = evalRuleClassCode("attr.string(default = 'some value')"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals("some value", attr.getDefaultValueForTesting()); + } + + public void testAttrDefaultValueBadType() throws Exception { + checkErrorContains( + "Method attr.string(*, default: string, mandatory: bool, values: sequence of strings) " + + "is not applicable for arguments (int, bool, list): 'default' is int, " + + "but should be string", + "attr.string(default = 1)"); + } + + public void testAttrMandatory() throws Exception { + Object result = evalRuleClassCode("attr.string(mandatory=True)"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertTrue(attr.isMandatory()); + assertFalse(attr.isNonEmpty()); + } + + public void testAttrNonEmpty() throws Exception { + Object result = evalRuleClassCode("attr.string_list(non_empty=True)"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertTrue(attr.isNonEmpty()); + assertFalse(attr.isMandatory()); + } + + public void testAttrBadKeywordArguments() throws Exception { + checkErrorContains( + "unexpected keyword 'bad_keyword' in call to string", "attr.string(bad_keyword = '')"); + } + + public void testAttrCfg() throws Exception { + Object result = evalRuleClassCode("attr.label(cfg = HOST_CFG, allow_files = True)"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + assertEquals(ConfigurationTransition.HOST, attr.getConfigurationTransition()); + } + + public void testAttrValues() throws Exception { + Object result = evalRuleClassCode("attr.string(values = ['ab', 'cd'])"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + PredicateWithMessage<Object> predicate = attr.getAllowedValues(); + assertThat(predicate.apply("ab")).isTrue(); + assertThat(predicate.apply("xy")).isFalse(); + } + + public void testAttrIntValues() throws Exception { + Object result = evalRuleClassCode("attr.int(values = [1, 2])"); + Attribute attr = ((Attribute.Builder<?>) result).build("a1"); + PredicateWithMessage<Object> predicate = attr.getAllowedValues(); + assertThat(predicate.apply(2)).isTrue(); + assertThat(predicate.apply(3)).isFalse(); + } + + public void testRuleImplementation() throws Exception { + eval("def impl(ctx): return None", "rule1 = rule(impl)"); + RuleClass c = ((RuleFunction) lookup("rule1")).getBuilder().build("rule1"); + assertEquals("impl", c.getConfiguredTargetFunction().getName()); + } + + public void testLateBoundAttrWorksWithOnlyLabel() throws Exception { + checkEvalError( + "Method attr.string(*, default: string, mandatory: bool, values: sequence of strings) " + + "is not applicable for arguments (function, bool, list): 'default' is function, " + + "but should be string", + "def attr_value(cfg): return 'a'", + "attr.string(default=attr_value)"); + } + + public void testRuleAddAttribute() throws Exception { + eval("def impl(ctx): return None", "r1 = rule(impl, attrs={'a1': attr.string()})"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + assertTrue(c.hasAttr("a1", Type.STRING)); + } + + public void testOutputToGenfiles() throws Exception { + eval("def impl(ctx): pass", "r1 = rule(impl, output_to_genfiles=True)"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + assertFalse(c.hasBinaryOutput()); + } + + public void testRuleAddMultipleAttributes() throws Exception { + eval( + "def impl(ctx): return None", + "r1 = rule(impl,", + " attrs = {", + " 'a1': attr.label_list(allow_files=True),", + " 'a2': attr.int()", + "})"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + assertTrue(c.hasAttr("a1", BuildType.LABEL_LIST)); + assertTrue(c.hasAttr("a2", Type.INTEGER)); + } + + public void testRuleAttributeFlag() throws Exception { + eval( + "def impl(ctx): return None", + "r1 = rule(impl, attrs = {'a1': attr.string(mandatory=True)})"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + assertTrue(c.getAttributeByName("a1").isMandatory()); + } + + public void testRuleOutputs() throws Exception { + eval("def impl(ctx): return None", "r1 = rule(impl, outputs = {'a': 'a.txt'})"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + ImplicitOutputsFunction function = c.getImplicitOutputsFunction(); + assertEquals("a.txt", Iterables.getOnlyElement(function.getImplicitOutputs(null))); + } + + public void testRuleUnknownKeyword() throws Exception { + registerDummyUserDefinedFunction(); + checkErrorContains( + "unexpected keyword 'bad_keyword' in call to " + "rule(implementation: function, ", + "rule(impl, bad_keyword = 'some text')"); + } + + public void testRuleImplementationMissing() throws Exception { + checkErrorContains( + "missing mandatory positional argument 'implementation' while calling " + + "rule(implementation", + "rule(attrs = {})"); + } + + public void testRuleBadTypeForAdd() throws Exception { + registerDummyUserDefinedFunction(); + checkErrorContains( + "expected dict or NoneType for 'attrs' while calling rule but got string instead: " + + "some text", + "rule(impl, attrs = 'some text')"); + } + + public void testRuleBadTypeInAdd() throws Exception { + registerDummyUserDefinedFunction(); + checkErrorContains( + "Illegal argument: " + + "expected <String, Builder> type for 'attrs' but got <string, string> instead", + "rule(impl, attrs = {'a1': 'some text'})"); + } + + public void testLabel() throws Exception { + Object result = evalRuleClassCode("Label('//foo/foo:foo')"); + assertEquals("//foo/foo:foo", ((Label) result).toString()); + } + + public void testLabelSameInstance() throws Exception { + Object l1 = evalRuleClassCode("Label('//foo/foo:foo')"); + // Implicitly creates a new pkgContext and environment, yet labels should be the same. + Object l2 = evalRuleClassCode("Label('//foo/foo:foo')"); + assertSame(l2, l1); + } + + public void testLabelNameAndPackage() throws Exception { + Object result = evalRuleClassCode("Label('//foo/bar:baz').name"); + assertEquals("baz", result); + // NB: implicitly creates a new pkgContext and environments, yet labels should be the same. + result = evalRuleClassCode("Label('//foo/bar:baz').package"); + assertEquals("foo/bar", result); + } + + public void testRuleLabelDefaultValue() throws Exception { + eval( + "def impl(ctx): return None\n" + + "r1 = rule(impl, attrs = {'a1': " + + "attr.label(default = Label('//foo:foo'), allow_files=True)})"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + Attribute a = c.getAttributeByName("a1"); + assertEquals("//foo:foo", ((Label) a.getDefaultValueForTesting()).toString()); + } + + public void testIntDefaultValue() throws Exception { + eval("def impl(ctx): return None", "r1 = rule(impl, attrs = {'a1': attr.int(default = 40+2)})"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + Attribute a = c.getAttributeByName("a1"); + assertEquals(42, a.getDefaultValueForTesting()); + } + + public void testFileType() throws Exception { + Object result = evalRuleClassCode("FileType(['.css'])"); + SkylarkFileType fts = (SkylarkFileType) result; + assertEquals(ImmutableList.of(".css"), fts.getExtensions()); + } + + public void testRuleInheritsBaseRuleAttributes() throws Exception { + eval("def impl(ctx): return None", "r1 = rule(impl)"); + RuleClass c = ((RuleFunction) lookup("r1")).getBuilder().build("r1"); + assertTrue(c.hasAttr("tags", Type.STRING_LIST)); + assertTrue(c.hasAttr("visibility", BuildType.NODEP_LABEL_LIST)); + assertTrue(c.hasAttr("deprecation", Type.STRING)); + assertTrue(c.hasAttr(":action_listener", BuildType.LABEL_LIST)); // required for extra actions + } + + private void checkTextMessage(String from, String... lines) throws Exception { + Object result = evalRuleClassCode(from); + assertEquals(Joiner.on("\n").join(lines) + "\n", result); + } + + public void testSimpleTextMessagesBooleanFields() throws Exception { + checkTextMessage("struct(name=True).to_proto()", "name: true"); + checkTextMessage("struct(name=False).to_proto()", "name: false"); + } + + public void testSimpleTextMessages() throws Exception { + checkTextMessage("struct(name='value').to_proto()", "name: \"value\""); + checkTextMessage("struct(name=['a', 'b']).to_proto()", "name: \"a\"", "name: \"b\""); + checkTextMessage("struct(name=123).to_proto()", "name: 123"); + checkTextMessage("struct(name=[1, 2, 3]).to_proto()", "name: 1", "name: 2", "name: 3"); + checkTextMessage("struct(a=struct(b='b')).to_proto()", "a {", " b: \"b\"", "}"); + checkTextMessage( + "struct(a=[struct(b='x'), struct(b='y')]).to_proto()", + "a {", + " b: \"x\"", + "}", + "a {", + " b: \"y\"", + "}"); + checkTextMessage( + "struct(a=struct(b=struct(c='c'))).to_proto()", "a {", " b {", " c: \"c\"", " }", "}"); + } + + public void testTextMessageEscapes() throws Exception { + checkTextMessage("struct(name='a\"b').to_proto()", "name: \"a\\\"b\""); + checkTextMessage("struct(name='a\\'b').to_proto()", "name: \"a'b\""); + checkTextMessage("struct(name='a\\nb').to_proto()", "name: \"a\\nb\""); + } + + public void testTextMessageInvalidElementInListStructure() throws Exception { + checkErrorContains( + "Invalid text format, expected a struct, a string, a bool, or " + + "an int but got a list for list element in struct field 'a'", + "struct(a=[['b']]).to_proto()"); + } + + public void testTextMessageInvalidStructure() throws Exception { + checkErrorContains( + "Invalid text format, expected a struct, a string, a bool, or an int " + + "but got a ConfigurationTransition for struct field 'a'", + "struct(a=DATA_CFG).to_proto()"); + } + + // Regression test for b/18352962 + public void testLabelAttrWrongDefault() throws Exception { + checkErrorContains( + "expected Label or Label-returning function or NoneType for 'default' " + + "while calling label but got string instead: //foo:bar", + "attr.label(default = '//foo:bar')"); + } + + public void testLabelGetRelative() throws Exception { + assertEquals("//foo:baz", eval("Label('//foo:bar').relative('baz')").toString()); + assertEquals("//baz:qux", eval("Label('//foo:bar').relative('//baz:qux')").toString()); + } + + public void testLabelGetRelativeSyntaxError() throws Exception { + checkErrorContains( + "invalid target name 'bad syntax': target names may not contain ' '", + "Label('//foo:bar').relative('bad syntax')"); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java new file mode 100644 index 0000000000..1a37c2954e --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java @@ -0,0 +1,316 @@ +// Copyright 2015 Google Inc. 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.skylark; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.FileConfiguredTarget; +import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.rules.SkylarkRuleContext; +import com.google.devtools.build.lib.rules.java.JavaSourceJarsProvider; +import com.google.devtools.build.lib.rules.python.PythonSourcesProvider; +import com.google.devtools.build.lib.skylark.util.SkylarkTestCase; +import com.google.devtools.build.lib.syntax.Runtime; +import com.google.devtools.build.lib.syntax.SkylarkList; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; +import com.google.devtools.build.lib.testutil.TestConstants; +import com.google.devtools.build.lib.vfs.PathFragment; + +import java.util.List; + +/** + * Tests for SkylarkRuleContext. + */ +public class SkylarkRuleContextTest extends SkylarkTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + scratch.file( + "foo/BUILD", + "genrule(name = 'foo',", + " cmd = 'dummy_cmd',", + " srcs = ['a.txt', 'b.img'],", + " tools = ['t.exe'],", + " outs = ['c.txt'])", + "genrule(name = 'foo2',", + " cmd = 'dummy_cmd',", + " outs = ['e.txt'])", + "genrule(name = 'bar',", + " cmd = 'dummy_cmd',", + " srcs = [':jl', ':gl'],", + " outs = ['d.txt'])", + "java_library(name = 'jl',", + " srcs = ['a.java'])", + "java_import(name = 'asr',", + " jars = [ 'asr.jar' ],", + " srcjar = 'asr-src.jar',", + ")", + "genrule(name = 'gl',", + " cmd = 'touch $(OUTS)',", + " srcs = ['a.go'],", + " outs = [ 'gl.a', 'gl.gcgox', ],", + " output_to_bindir = 1,", + ")"); + } + + public void testGetPrerequisiteArtifacts() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.files.srcs"); + assertArtifactList(result, ImmutableList.of("a.txt", "b.img")); + } + + public void disabledTestGetPrerequisiteArtifact() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.file.tools"); + assertEquals("t.exe", ((Artifact) result).getFilename()); + } + + public void disabledTestGetPrerequisiteArtifactNoFile() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo2"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.file.srcs"); + assertSame(Runtime.NONE, result); + } + + private void assertArtifactList(Object result, List<String> artifacts) { + assertThat(result).isInstanceOf(SkylarkList.class); + SkylarkList resultList = (SkylarkList) result; + assertEquals(artifacts.size(), resultList.size()); + int i = 0; + for (String artifact : artifacts) { + assertEquals(artifact, ((Artifact) resultList.get(i++)).getFilename()); + } + } + + public void testGetPrerequisites() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:bar"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.srcs"); + // Check for a known provider + TransitiveInfoCollection tic1 = (TransitiveInfoCollection) ((SkylarkList) result).get(0); + assertNotNull(tic1.getProvider(JavaSourceJarsProvider.class)); + // Check an unimplemented provider too + assertNull(tic1.getProvider(PythonSourcesProvider.class)); + } + + public void testGetPrerequisite() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:asr"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.srcjar"); + TransitiveInfoCollection tic = (TransitiveInfoCollection) result; + assertThat(tic).isInstanceOf(FileConfiguredTarget.class); + assertEquals("asr-src.jar", tic.getLabel().getName()); + } + + public void testMiddleMan() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:jl"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.middle_man(':host_jdk')"); + assertThat( + Iterables.getOnlyElement(((SkylarkNestedSet) result).getSet(Artifact.class)) + .getExecPathString()) + .contains("middlemen"); + } + + public void testGetRuleAttributeListType() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.outs"); + assertThat(result).isInstanceOf(SkylarkList.class); + } + + public void testGetRuleAttributeListValue() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.outs"); + assertEquals(1, ((SkylarkList) result).size()); + } + + public void testGetRuleAttributeListValueNoGet() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.outs"); + assertEquals(1, ((SkylarkList) result).size()); + } + + public void testGetRuleAttributeStringTypeValue() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.cmd"); + assertEquals("dummy_cmd", (String) result); + } + + public void testGetRuleAttributeStringTypeValueNoGet() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.attr.cmd"); + assertEquals("dummy_cmd", (String) result); + } + + public void testGetRuleAttributeBadAttributeName() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), "No attribute 'bad'", "ruleContext.attr.bad"); + } + + public void testGetLabel() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.label"); + assertEquals("//foo:foo", ((Label) result).toString()); + } + + public void testRuleError() throws Exception { + checkErrorContains(createRuleContext("//foo:foo"), "message", "fail('message')"); + } + + public void testAttributeError() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "attribute srcs: message", + "fail(attr='srcs', msg='message')"); + } + + public void testGetExecutablePrerequisite() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:jl"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.executable._ijar"); + assertEquals("ijar", ((Artifact) result).getFilename()); + } + + public void testCreateSpawnActionArgumentsWithExecutableFilesToRunProvider() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:jl"); + evalRuleContextCode( + ruleContext, + "ruleContext.action(\n" + + " inputs = ruleContext.files.srcs,\n" + + " outputs = ruleContext.files.srcs,\n" + + " arguments = ['--a','--b'],\n" + + " executable = ruleContext.executable._ijar)\n"); + SpawnAction action = + (SpawnAction) + Iterables.getOnlyElement( + ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); + assertThat(action.getCommandFilename()).endsWith("/ijar"); + } + + public void testOutputs() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:bar"); + Iterable<?> result = (Iterable<?>) evalRuleContextCode(ruleContext, "ruleContext.outputs.outs"); + assertEquals("d.txt", ((Artifact) Iterables.getOnlyElement(result)).getFilename()); + } + + public void testSkylarkRuleContextStr() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "'%s' % ruleContext"); + assertEquals("//foo:foo", result); + } + + public void testSkylarkRuleContextGetDefaultShellEnv() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.configuration.default_shell_env"); + assertThat(result).isInstanceOf(ImmutableMap.class); + } + + public void testCheckPlaceholders() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode(ruleContext, "ruleContext.check_placeholders('%{name}', ['name'])"); + assertEquals(true, result); + } + + public void testCheckPlaceholdersBadPlaceholder() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode(ruleContext, "ruleContext.check_placeholders('%{name}', ['abc'])"); + assertEquals(false, result); + } + + public void testExpandMakeVariables() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, "ruleContext.expand_make_variables('cmd', '$(ABC)', {'ABC': 'DEF'})"); + assertEquals("DEF", result); + } + + public void testExpandMakeVariablesShell() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode(ruleContext, "ruleContext.expand_make_variables('cmd', '$$ABC', {})"); + assertEquals("$ABC", result); + } + + public void testConfiguration() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.configuration"); + assertSame(result, ruleContext.getRuleContext().getConfiguration()); + } + + public void testHostConfiguration() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.host_configuration"); + assertSame(result, ruleContext.getRuleContext().getHostConfiguration()); + } + + public void testWorkspaceName() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.workspace_name"); + assertSame(result, TestConstants.WORKSPACE_NAME); + } + + public void testDeriveArtifactLegacy() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, + "ruleContext.new_file(ruleContext.configuration.genfiles_dir," + " 'a/b.txt')"); + PathFragment fragment = ((Artifact) result).getRootRelativePath(); + assertEquals("foo/a/b.txt", fragment.getPathString()); + } + + public void testDeriveArtifact() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.new_file('a/b.txt')"); + PathFragment fragment = ((Artifact) result).getRootRelativePath(); + assertEquals("foo/a/b.txt", fragment.getPathString()); + } + + public void testParamFileLegacy() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, + "ruleContext.new_file(ruleContext.configuration.bin_dir," + + "ruleContext.files.tools[0], '.params')"); + PathFragment fragment = ((Artifact) result).getRootRelativePath(); + assertEquals("foo/t.exe.params", fragment.getPathString()); + } + + public void testParamFile() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, "ruleContext.new_file_suffix(ruleContext.files.tools[0], '.params')"); + PathFragment fragment = ((Artifact) result).getRootRelativePath(); + assertEquals("foo/t.exe.params", fragment.getPathString()); + } + + // new_file_suffix() is deprecated. This test will be removed after the next blaze release (not + // later than end of August, 2015). + public void testParamFileSuffix() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, "ruleContext.new_file_suffix(ruleContext.files.tools[0], '.params')"); + PathFragment fragment = ((Artifact) result).getRootRelativePath(); + assertEquals("foo/t.exe.params", fragment.getPathString()); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java new file mode 100644 index 0000000000..5a6969691b --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java @@ -0,0 +1,810 @@ +// Copyright 2014 Google Inc. 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.skylark; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.actions.Action; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.actions.util.ActionsTestUtil; +import com.google.devtools.build.lib.analysis.CommandHelper; +import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.actions.FileWriteAction; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; +import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; +import com.google.devtools.build.lib.events.Event; +import com.google.devtools.build.lib.rules.SkylarkRuleContext; +import com.google.devtools.build.lib.skylark.util.SkylarkTestCase; +import com.google.devtools.build.lib.syntax.BuiltinFunction; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.EvalUtils; +import com.google.devtools.build.lib.syntax.Runtime; +import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; +import com.google.devtools.build.lib.syntax.SkylarkSignature; +import com.google.devtools.build.lib.syntax.SkylarkSignature.Param; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Tests for SkylarkRuleImplementationFunctions. + */ +public class SkylarkRuleImplementationFunctionsTest extends SkylarkTestCase { + + @SkylarkSignature( + name = "mock", + documented = false, + mandatoryPositionals = {@Param(name = "mandatory", doc = "")}, + optionalPositionals = {@Param(name = "optional", doc = "")}, + mandatoryNamedOnly = {@Param(name = "mandatory_key", doc = "")}, + optionalNamedOnly = {@Param(name = "optional_key", doc = "", defaultValue = "'x'")} + ) + private BuiltinFunction mockFunc; + + /** + * Used for {@link #testStackTraceWithoutOriginalMessage()} and {@link + * #testNoStackTraceOnInterrupt}. + */ + @SkylarkSignature(name = "throw", documented = false) + BuiltinFunction throwFunction; + + @Override + public void setUp() throws Exception { + super.setUp(); + scratch.file( + "foo/BUILD", + "genrule(name = 'foo',", + " cmd = 'dummy_cmd',", + " srcs = ['a.txt', 'b.img'],", + " tools = ['t.exe'],", + " outs = ['c.txt'])", + "genrule(name = 'bar',", + " cmd = 'dummy_cmd',", + " srcs = [':jl', ':gl'],", + " outs = ['d.txt'])", + "genrule(name = 'baz',", + " cmd = 'dummy_cmd',", + " outs = ['e.txt'])", + "java_library(name = 'jl',", + " srcs = ['a.java'])", + "genrule(name = 'gl',", + " cmd = 'touch $(OUTS)',", + " srcs = ['a.go'],", + " outs = [ 'gl.a', 'gl.gcgox', ],", + " output_to_bindir = 1,", + ")"); + } + + private void setupSkylarkFunction(String line) throws Exception { + mockFunc = + new BuiltinFunction("mock") { + @SuppressWarnings("unused") + public Object invoke( + Object mandatory, Object optional, Object mandatoryKey, Object optionalKey) { + return EvalUtils.optionMap( + "mandatory", + mandatory, + "optional", + optional, + "mandatory_key", + mandatoryKey, + "optional_key", + optionalKey); + } + }; + assertFalse(mockFunc.isConfigured()); + mockFunc.configure( + SkylarkRuleImplementationFunctionsTest.class + .getDeclaredField("mockFunc") + .getAnnotation(SkylarkSignature.class)); + update("mock", mockFunc); + eval(line); + } + + private void checkSkylarkFunctionError(String errorMsg, String line) throws Exception { + try { + setupSkylarkFunction(line); + fail(); + } catch (EvalException e) { + assertThat(e).hasMessage(errorMsg); + } + } + + public void testSkylarkFunctionPosArgs() throws Exception { + setupSkylarkFunction("a = mock('a', 'b', mandatory_key='c')"); + Map<?, ?> params = (Map<?, ?>) lookup("a"); + assertEquals("a", params.get("mandatory")); + assertEquals("b", params.get("optional")); + assertEquals("c", params.get("mandatory_key")); + assertEquals("x", params.get("optional_key")); + } + + public void testSkylarkFunctionKwArgs() throws Exception { + setupSkylarkFunction("a = mock(optional='b', mandatory='a', mandatory_key='c')"); + Map<?, ?> params = (Map<?, ?>) lookup("a"); + assertEquals("a", params.get("mandatory")); + assertEquals("b", params.get("optional")); + assertEquals("c", params.get("mandatory_key")); + assertEquals("x", params.get("optional_key")); + } + + public void testSkylarkFunctionTooFewArguments() throws Exception { + checkSkylarkFunctionError( + "insufficient arguments received by mock(" + + "mandatory, optional = None, *, mandatory_key, optional_key = \"x\") " + + "(got 0, expected at least 1)", + "mock()"); + } + + public void testSkylarkFunctionTooManyArguments() throws Exception { + checkSkylarkFunctionError( + "too many (3) positional arguments in call to " + + "mock(mandatory, optional = None, *, mandatory_key, optional_key = \"x\")", + "mock('a', 'b', 'c')"); + } + + public void testSkylarkFunctionAmbiguousArguments() throws Exception { + checkSkylarkFunctionError( + "argument 'mandatory' passed both by position and by name " + + "in call to mock(mandatory, optional = None, *, mandatory_key, optional_key = \"x\")", + "mock('by position', mandatory='by_key', mandatory_key='c')"); + } + + @SuppressWarnings("unchecked") + public void testListComprehensionsWithNestedSet() throws Exception { + Object result = eval("[x + x for x in set([1, 2, 3])]"); + assertThat((Iterable<Object>) result).containsExactly(2, 4, 6).inOrder(); + } + + public void testNestedSetGetsConvertedToSkylarkNestedSet() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, + "dep = ruleContext.attr.tools[0]", + "provider(dep, 'analysis.FileProvider').files_to_build"); + SkylarkNestedSet nset = (SkylarkNestedSet) result; + assertEquals(Artifact.class, nset.getContentType().getType()); + } + + public void testCreateSpawnActionCreatesSpawnAction() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + createTestSpawnAction(ruleContext); + Action action = + Iterables.getOnlyElement( + ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); + assertThat(action).isInstanceOf(SpawnAction.class); + } + + public void testCreateSpawnActionArgumentsWithCommand() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + createTestSpawnAction(ruleContext); + SpawnAction action = + (SpawnAction) + Iterables.getOnlyElement( + ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); + assertArtifactFilenames(action.getInputs(), "a.txt", "b.img"); + assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img"); + assertContainsSublist(action.getArguments(), "-c", "dummy_command", "", "--a", "--b"); + assertEquals("DummyMnemonic", action.getMnemonic()); + assertEquals("dummy_message", action.getProgressMessage()); + assertEquals(targetConfig.getDefaultShellEnvironment(), action.getEnvironment()); + } + + public void testCreateSpawnActionArgumentsWithExecutable() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + evalRuleContextCode( + ruleContext, + "ruleContext.action(", + " inputs = ruleContext.files.srcs,", + " outputs = ruleContext.files.srcs,", + " arguments = ['--a','--b'],", + " executable = ruleContext.files.tools[0])"); + SpawnAction action = + (SpawnAction) + Iterables.getOnlyElement( + ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); + assertArtifactFilenames(action.getInputs(), "a.txt", "b.img", "t.exe"); + assertArtifactFilenames(action.getOutputs(), "a.txt", "b.img"); + assertContainsSublist(action.getArguments(), "foo/t.exe", "--a", "--b"); + } + + public void testCreateSpawnActionArgumentsBadExecutable() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "expected file or PathFragment for executable but got string instead", + "ruleContext.action(", + " inputs = ruleContext.files.srcs,", + " outputs = ruleContext.files.srcs,", + " arguments = ['--a','--b'],", + " executable = 'xyz.exe')"); + } + + public void testCreateSpawnActionShellCommandList() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + evalRuleContextCode( + ruleContext, + "ruleContext.action(", + " inputs = ruleContext.files.srcs,", + " outputs = ruleContext.files.srcs,", + " mnemonic = 'DummyMnemonic',", + " command = ['dummy_command', '--arg1', '--arg2'],", + " progress_message = 'dummy_message')"); + SpawnAction action = + (SpawnAction) + Iterables.getOnlyElement( + ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); + assertThat(action.getArguments()) + .containsExactly("dummy_command", "--arg1", "--arg2") + .inOrder(); + } + + public void testCreateSpawnActionEnvAndExecInfo() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + evalRuleContextCode( + ruleContext, + "env = {'a' : 'b'}", + "ruleContext.action(", + " inputs = ruleContext.files.srcs,", + " outputs = ruleContext.files.srcs,", + " env = env,", + " execution_requirements = env,", + " mnemonic = 'DummyMnemonic',", + " command = 'dummy_command',", + " progress_message = 'dummy_message')"); + SpawnAction action = + (SpawnAction) + Iterables.getOnlyElement( + ruleContext.getRuleContext().getAnalysisEnvironment().getRegisteredActions()); + assertEquals(ImmutableMap.of("a", "b"), action.getEnvironment()); + assertEquals(ImmutableMap.of("a", "b"), action.getExecutionInfo()); + } + + public void testCreateSpawnActionUnknownParam() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + checkErrorContains( + ruleContext, + "unexpected keyword 'bad_param' in call to action(self: ctx, *, ", + "ruleContext.action(outputs=[], bad_param = 'some text')"); + } + + public void testCreateSpawnActionNoExecutable() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + checkErrorContains( + ruleContext, + "You must specify either 'command' or 'executable' argument", + "ruleContext.action(outputs=[])"); + } + + private Object createTestSpawnAction(SkylarkRuleContext ruleContext) throws Exception { + return evalRuleContextCode( + ruleContext, + "ruleContext.action(", + " inputs = ruleContext.files.srcs,", + " outputs = ruleContext.files.srcs,", + " arguments = ['--a','--b'],", + " mnemonic = 'DummyMnemonic',", + " command = 'dummy_command',", + " progress_message = 'dummy_message',", + " use_default_shell_env = True)"); + } + + public void testCreateSpawnActionBadGenericArg() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "Illegal argument in call to action: list element \"a\" is not of type File", + "l = ['a', 'b']", + "ruleContext.action(", + " outputs = l,", + " command = 'dummy_command')"); + } + + public void testCreateSpawnActionCommandsListTooShort() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "'command' list has to be of size at least 3", + "ruleContext.action(", + " outputs = ruleContext.files.srcs,", + " command = ['dummy_command', '--arg'])"); + } + + public void testCreateFileAction() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + FileWriteAction action = + (FileWriteAction) + evalRuleContextCode( + ruleContext, + "ruleContext.file_action(", + " output = ruleContext.files.srcs[0],", + " content = 'hello world',", + " executable = False)"); + assertEquals("foo/a.txt", Iterables.getOnlyElement(action.getOutputs()).getExecPathString()); + assertEquals("hello world", action.getFileContents()); + assertFalse(action.makeExecutable()); + } + + public void testEmptyAction() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + + checkEmptyAction(ruleContext, "mnemonic = 'test'"); + checkEmptyAction(ruleContext, "mnemonic = 'test', inputs = ruleContext.files.srcs"); + + checkErrorContains( + ruleContext, + "missing mandatory named-only argument 'mnemonic' while calling empty_action", + "ruleContext.empty_action(inputs = ruleContext.files.srcs)"); + } + + private void checkEmptyAction(SkylarkRuleContext ruleContext, String namedArgs) throws Exception { + assertThat( + evalRuleContextCode( + ruleContext, String.format("ruleContext.empty_action(%s)", namedArgs))) + .isEqualTo(Runtime.NONE); + } + + public void testEmptyActionWithExtraAction() throws Exception { + scratch.file( + "test/empty.bzl", + "def _impl(ctx):", + " ctx.empty_action(", + " inputs = ctx.files.srcs,", + " mnemonic = 'EA',", + " )", + + "empty_action_rule = rule(", + " implementation = _impl,", + " attrs = {", + " \"srcs\": attr.label_list(allow_files=True),", + " }", + ")"); + + scratch.file( + "test/BUILD", + "load('/test/empty', 'empty_action_rule')", + "empty_action_rule(name = 'my_empty_action',", + " srcs = ['foo.in', 'other_foo.in'])", + + "action_listener(name = 'listener',", + " mnemonics = ['EA'],", + " extra_actions = [':extra'])", + + "extra_action(name = 'extra',", + " cmd='')"); + + getPseudoActionViaExtraAction("//test:my_empty_action", "//test:listener"); + } + + public void testExpandLocation() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:bar"); + + // If there is only a single target, both "location" and "locations" should work + runExpansion(ruleContext, "location :jl", "[blaze]*-out/.*/bin/foo/libjl.jar"); + runExpansion(ruleContext, "locations :jl", "[blaze]*-out/.*/bin/foo/libjl.jar"); + + runExpansion(ruleContext, "location //foo:jl", "[blaze]*-out/.*/bin/foo/libjl.jar"); + + // Multiple targets and "location" should result in an error + checkReportedErrorStartsWith( + ruleContext, + "in genrule rule //foo:bar: label '//foo:gl' " + + "in $(location) expression expands to more than one file, please use $(locations " + + "//foo:gl) instead.", + "ruleContext.expand_location('$(location :gl)')"); + + // We have to use "locations" for multiple targets + runExpansion( + ruleContext, + "locations :gl", + "[blaze]*-out/.*/bin/foo/gl.a [blaze]*-out/.*/bin/foo/gl.gcgox"); + + // LocationExpander just returns the input string if there is no label + runExpansion(ruleContext, "location", "\\$\\(location\\)"); + + checkReportedErrorStartsWith( + ruleContext, + "in genrule rule //foo:bar: label '//foo:abc' in $(locations) expression " + + "is not a declared prerequisite of this rule", + "ruleContext.expand_location('$(locations :abc)')"); + } + + /** + * Invokes ctx.expand_location() with the given parameters and checks whether this led to the + * expected result + * @param ruleContext The rule context + * @param command Either "location" or "locations". This only matters when the label has multiple + * targets + * @param expectedPattern Regex pattern that matches the expected result + */ + private void runExpansion(SkylarkRuleContext ruleContext, String command, String expectedPattern) + throws Exception { + String expanded = + (String) + evalRuleContextCode( + ruleContext, String.format("ruleContext.expand_location('$(%s)')", command)); + + assertTrue( + String.format("Expanded string '%s' did not match pattern '%s'", expanded, expectedPattern), + Pattern.matches(expectedPattern, expanded)); + } + + public void testBadParamTypeErrorMessage() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + checkErrorContains( + ruleContext, + "Method ctx.file_action(output: File, content: string, executable: bool) is not " + + "applicable for arguments (File, int, bool): 'content' is int, but should be string", + "ruleContext.file_action(", + " output = ruleContext.files.srcs[0],", + " content = 1,", + " executable = False)"); + } + + public void testCreateTemplateAction() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + TemplateExpansionAction action = + (TemplateExpansionAction) + evalRuleContextCode( + ruleContext, + "ruleContext.template_action(", + " template = ruleContext.files.srcs[0],", + " output = ruleContext.files.srcs[1],", + " substitutions = {'a': 'b'},", + " executable = False)"); + assertEquals("foo/a.txt", Iterables.getOnlyElement(action.getInputs()).getExecPathString()); + assertEquals("foo/b.img", Iterables.getOnlyElement(action.getOutputs()).getExecPathString()); + assertEquals("a", Iterables.getOnlyElement(action.getSubstitutions()).getKey()); + assertEquals("b", Iterables.getOnlyElement(action.getSubstitutions()).getValue()); + assertFalse(action.makeExecutable()); + } + + /** + * Simulates the fact that the Parser currently uses Latin1 to read BUILD files, while users + * usually write those files using UTF-8 encoding. + * Once {@link + * com.google.devtools.build.lib.syntax.ParserInputSource#create(com.google.devtools.build.lib.vfs.Path)} parses files using UTF-8, this test will fail. + */ + public void testCreateTemplateActionWithWrongEncoding() throws Exception { + String value = "Š©±½"; + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + TemplateExpansionAction action = + (TemplateExpansionAction) + evalRuleContextCode( + ruleContext, + "ruleContext.template_action(", + " template = ruleContext.files.srcs[0],", + " output = ruleContext.files.srcs[1],", + " substitutions = {'a': '" + convertUtf8ToLatin1(value) + "'},", + " executable = False)"); + + List<Substitution> substitutions = action.getSubstitutions(); + assertThat(substitutions).hasSize(1); + assertThat(substitutions.get(0).getValue()).isEqualTo(value); + } + + /** + * Turns the given UTF-8 input into an "unreadable" Latin1 string + */ + private String convertUtf8ToLatin1(String input) { + return new String(input.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); + } + + public void testGetProviderNotTransitiveInfoCollection() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "Method provider(target: Target, type: string) is not applicable for arguments " + + "(string, string): 'target' is string, but should be Target", + "provider('some string', 'FileProvider')"); + } + + public void testGetProviderNonExistingClassType() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "Unknown class type bad.Bad", + "def func():", // we need a func to hold the for loop + " for tic in ruleContext.attr.srcs:", + " provider(tic, 'bad.Bad')", + "func()"); + } + + public void testGetProviderNotTransitiveInfoProviderClassType() throws Exception { + checkErrorContains( + createRuleContext("//foo:foo"), + "Not a TransitiveInfoProvider rules.java.JavaBinary", + "def func():", // we need a func to hold the for loop + " for tic in ruleContext.attr.srcs:", + " provider(tic, 'rules.java.JavaBinary')", + "func()"); + } + + public void testRunfilesAddFromDependencies() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:bar"); + Object result = + evalRuleContextCode(ruleContext, "ruleContext.runfiles(collect_default = True)"); + assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result))) + .contains("libjl.jar"); + } + + public void testRunfilesStatelessWorksAsOnlyPosArg() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:bar"); + Object result = + evalRuleContextCode(ruleContext, "ruleContext.runfiles(collect_default = True)"); + assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result))) + .contains("libjl.jar"); + } + + public void testRunfilesBadListGenericType() throws Exception { + checkErrorContains( + "Illegal argument in call to runfiles: list element \"some string\" is not of type File", + "ruleContext.runfiles(files = ['some string'])"); + } + + public void testRunfilesBadSetGenericType() throws Exception { + checkErrorContains( + "expected set of Files or NoneType for 'transitive_files' while calling runfiles " + + "but got set of ints instead: set([1, 2, 3])", + "ruleContext.runfiles(transitive_files=set([1, 2, 3]))"); + } + + public void testRunfilesArtifactsFromArtifact() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, + "artifacts = ruleContext.files.tools", + "ruleContext.runfiles(files = artifacts)"); + assertThat(ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result))).contains("t.exe"); + } + + public void testRunfilesArtifactsFromIterableArtifacts() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, + "artifacts = ruleContext.files.srcs", + "ruleContext.runfiles(files = artifacts)"); + assertEquals( + ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)), + ImmutableList.of("a.txt", "b.img")); + } + + public void testRunfilesArtifactsFromNestedSetArtifacts() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, + "ftb = set() + ruleContext.files.srcs", + "ruleContext.runfiles(transitive_files = ftb)"); + assertEquals( + ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)), + ImmutableList.of("a.txt", "b.img")); + } + + public void testRunfilesArtifactsFromDefaultAndFiles() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:bar"); + Object result = + evalRuleContextCode( + ruleContext, + "artifacts = ruleContext.files.srcs", + // It would be nice to write [DEFAULT] + artifacts, but artifacts + // is an ImmutableList and Skylark interprets it as a tuple. + "ruleContext.runfiles(collect_default = True, files = artifacts)"); + // From DEFAULT only libjl.jar comes, see testRunfilesAddFromDependencies(). + assertEquals( + ActionsTestUtil.baseArtifactNames(getRunfileArtifacts(result)), + ImmutableList.of("libjl.jar", "gl.a", "gl.gcgox")); + } + + private Iterable<Artifact> getRunfileArtifacts(Object runfiles) { + return ((Runfiles) runfiles).getAllArtifacts(); + } + + public void testRunfilesBadKeywordArguments() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + checkErrorContains( + ruleContext, + "unexpected keyword 'bad_keyword' in call to runfiles(self: ctx, ", + "ruleContext.runfiles(bad_keyword = '')"); + } + + public void testCreateCommandHelper() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = evalRuleContextCode(ruleContext, "ruleContext.command_helper([], {})"); + assertThat(result).isInstanceOf(CommandHelper.class); + } + + public void testNsetContainsList() throws Exception { + checkErrorContains( + "sets cannot contain items of type 'list'", "set() + [ruleContext.files.srcs]"); + } + + public void testCmdJoinPaths() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + Object result = + evalRuleContextCode( + ruleContext, "f = set(ruleContext.files.srcs)", "cmd_helper.join_paths(':', f)"); + assertEquals("foo/a.txt:foo/b.img", result); + } + + public void testStructPlusArtifactErrorMessage() throws Exception { + SkylarkRuleContext ruleContext = createRuleContext("//foo:foo"); + checkErrorContains( + ruleContext, + "unsupported operand type(s) for +: 'File' and 'struct'", + "ruleContext.files.tools[0] + struct(a = 1)"); + } + + public void testNoSuchProviderErrorMessage() throws Exception { + checkErrorContains( + createRuleContext("//foo:bar"), + "target (rule class of 'java_library') " + "doesn't have provider 'my_provider'.", + "ruleContext.attr.srcs[0].my_provider"); + } + + public void testFilesForRuleConfiguredTarget() throws Exception { + Object result = + evalRuleContextCode(createRuleContext("//foo:foo"), "ruleContext.attr.srcs[0].files"); + assertEquals( + "a.txt", ActionsTestUtil.baseNamesOf(((SkylarkNestedSet) result).getSet(Artifact.class))); + } + + public void testFilesForFileConfiguredTarget() throws Exception { + Object result = + evalRuleContextCode(createRuleContext("//foo:bar"), "ruleContext.attr.srcs[0].files"); + assertEquals( + "libjl.jar", + ActionsTestUtil.baseNamesOf(((SkylarkNestedSet) result).getSet(Artifact.class))); + } + + public void testCtxStructFieldsCustomErrorMessages() throws Exception { + checkErrorContains("No attribute 'foo' in attr.", "ruleContext.attr.foo"); + checkErrorContains("No attribute 'foo' in outputs.", "ruleContext.outputs.foo"); + checkErrorContains("No attribute 'foo' in files.", "ruleContext.files.foo"); + checkErrorContains("No attribute 'foo' in file.", "ruleContext.file.foo"); + checkErrorContains("No attribute 'foo' in executable.", "ruleContext.executable.foo"); + } + + public void testBinDirPath() throws Exception { + SkylarkRuleContext ctx = createRuleContext("//foo:bar"); + Object result = evalRuleContextCode(ctx, "ruleContext.configuration.bin_dir.path"); + assertEquals(ctx.getConfiguration().getBinFragment().getPathString(), result); + } + + public void testEmptyLabelListTypeAttrInCtx() throws Exception { + SkylarkRuleContext ctx = createRuleContext("//foo:baz"); + Object result = evalRuleContextCode(ctx, "ruleContext.attr.srcs"); + assertEquals(MutableList.EMPTY, result); + } + + public void testDefinedMakeVariable() throws Exception { + SkylarkRuleContext ctx = createRuleContext("//foo:baz"); + String javac = (String) evalRuleContextCode(ctx, "ruleContext.var['JAVAC']"); + // Get the last path segment + javac = javac.substring(javac.lastIndexOf('/')); + assertEquals("/javac", javac); + } + + public void testCodeCoverageConfigurationAccess() throws Exception { + SkylarkRuleContext ctx = createRuleContext("//foo:baz"); + boolean coverage = + (Boolean) evalRuleContextCode(ctx, "ruleContext.configuration.coverage_enabled"); + assertEquals(coverage, ctx.getRuleContext().getConfiguration().isCodeCoverageEnabled()); + } + + @Override + protected void checkErrorContains(String errorMsg, String... lines) throws Exception { + super.checkErrorContains(createRuleContext("//foo:foo"), errorMsg, lines); + } + + /** + * Checks whether the given (invalid) statement leads to the expected error + */ + private void checkReportedErrorStartsWith( + SkylarkRuleContext ruleContext, String errorMsg, String... statements) throws Exception { + // If the component under test relies on Reporter and EventCollector for error handling, any + // error would lead to an asynchronous AssertionFailedError thanks to failFastHandler in + // FoundationTestCase. + // + // Consequently, we disable failFastHandler and check all events for the expected error message + reporter.removeHandler(failFastHandler); + + Object result = evalRuleContextCode(ruleContext, statements); + + String first = null; + int count = 0; + + try { + for (Event evt : eventCollector) { + if (evt.getMessage().startsWith(errorMsg)) { + return; + } + + ++count; + first = evt.getMessage(); + } + + if (count == 0) { + fail( + String.format( + "checkReportedErrorStartsWith(): There was no error; the result is '%s'", result)); + } else { + fail( + String.format( + "Found %d error(s), but none with the expected message '%s'. First error: '%s'", + count, + errorMsg, + first)); + } + } finally { + eventCollector.clear(); + } + } + + public void testStackTraceWithoutOriginalMessage() throws Exception { + setupThrowFunction( + new BuiltinFunction("throw") { + @SuppressWarnings("unused") + public Object invoke() throws Exception { + throw new ThereIsNoMessageException(); + } + }); + + checkEvalErrorContains( + "There Is No Message: SkylarkRuleImplementationFunctionsTest$2.invoke() in " + + "SkylarkRuleImplementationFunctionsTest.java:", + // This test skips the line number since it was not consistent across local tests and TAP. + "throw()"); + } + + public void testNoStackTraceOnInterrupt() throws Exception { + setupThrowFunction( + new BuiltinFunction("throw") { + @SuppressWarnings("unused") + public Object invoke() throws Exception { + throw new InterruptedException(); + } + }); + try { + eval("throw()"); + fail("Expected an InterruptedException"); + } catch (InterruptedException ex) { + // Expected. + } + } + + private void setupThrowFunction(BuiltinFunction func) throws Exception { + throwFunction = func; + throwFunction.configure( + getClass().getDeclaredField("throwFunction").getAnnotation(SkylarkSignature.class)); + update("throw", throwFunction); + } + + private static class ThereIsNoMessageException extends EvalException { + public ThereIsNoMessageException() { + super(null, "This is not the message you are looking for."); // Unused dummy message + } + + @Override + public String getMessage() { + return ""; + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java new file mode 100644 index 0000000000..11c928ecf2 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java @@ -0,0 +1,164 @@ +// Copyright 2014 Google Inc. 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.skylark.util; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.base.Joiner; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import com.google.devtools.build.lib.packages.PackageFactory; +import com.google.devtools.build.lib.packages.PackageFactory.PackageContext; +import com.google.devtools.build.lib.rules.SkylarkModules; +import com.google.devtools.build.lib.rules.SkylarkRuleContext; +import com.google.devtools.build.lib.syntax.Environment; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; + +import org.junit.Before; + +/** + * A class to contain the common functionality for Skylark tests. + */ +public abstract class SkylarkTestCase extends BuildViewTestCase { + + // We don't have multiple inheritance, so we fake it. + protected EvaluationTestCase ev; + + protected void setUpEvaluator() throws Exception { + ev = + new EvaluationTestCase() { + @Override + public Environment newEnvironment() throws Exception { + return Environment.builder(mutability) + .setSkylark() + .setEventHandler(getEventHandler()) + .setGlobals(SkylarkModules.GLOBALS) + .setLoadingPhase() + .build() + .setupDynamic( + PackageFactory.PKG_CONTEXT, + // This dummy pkgContext works because no Skylark unit test attempts to actually + // create rules. Creating actual rules is tested in SkylarkIntegrationTest. + new PackageContext(null, null, getEventHandler())); + } + }; + ev.setUp(); + } + + @Before + @Override + protected void setUp() throws Exception { + super.setUp(); + setUpEvaluator(); + } + + protected Object eval(String... input) throws Exception { + return ev.eval(input); + } + + protected void update(String name, Object value) throws Exception { + ev.update(name, value); + } + + protected Object lookup(String name) throws Exception { + return ev.lookup(name); + } + + protected void checkEvalError(String msg, String... input) throws Exception { + ev.checkEvalError(msg, input); + } + + protected void checkEvalErrorContains(String msg, String... input) throws Exception { + ev.checkEvalErrorContains(msg, input); + } + + protected SkylarkRuleContext dummyRuleContext() throws Exception { + return createRuleContext("//foo:foo"); + } + + protected SkylarkRuleContext createRuleContext(String label) throws Exception { + return new SkylarkRuleContext(getRuleContextForSkylark(getConfiguredTarget(label))); + } + + protected Object evalRuleContextCode(String... lines) throws Exception { + return evalRuleContextCode(dummyRuleContext(), lines); + } + + /** + * RuleContext can't be null, SkylarkBuiltInFunctions checks it. + * However not all built in functions use it, if usesRuleContext == false + * the wrapping function won't need a ruleContext parameter. + */ + protected Object evalRuleContextCode(SkylarkRuleContext ruleContext, String... code) + throws Exception { + setUpEvaluator(); + if (ruleContext != null) { + update("ruleContext", ruleContext); + } + return eval(code); + } + + protected void assertArtifactFilenames(Iterable<Artifact> artifacts, String... expected) { + int i = 0; + for (Artifact artifact : artifacts) { + assertEquals(expected[i++], artifact.getFilename()); + } + } + + protected Object evalRuleClassCode(String... lines) throws Exception { + setUpEvaluator(); + return eval("def impl(ctx): return None\n" + Joiner.on("\n").join(lines)); + } + + protected void checkError(SkylarkRuleContext ruleContext, String errorMsg, String... lines) + throws Exception { + try { + evalRuleContextCode(ruleContext, lines); + fail(); + } catch (EvalException e) { + assertThat(e).hasMessage(errorMsg); + } + } + + protected void checkErrorStartsWith( + SkylarkRuleContext ruleContext, String errorMsg, String... lines) throws Exception { + try { + evalRuleContextCode(ruleContext, lines); + fail(); + } catch (EvalException e) { + assertThat(e.getMessage()).startsWith(errorMsg); + } + } + + protected void checkErrorContains(String errorMsg, String... lines) throws Exception { + try { + eval(lines); + fail("checkErrorContains(String, String...): There was no error"); + } catch (EvalException e) { + assertThat(e.getMessage()).contains(errorMsg); + } + } + + protected void checkErrorContains( + SkylarkRuleContext ruleContext, String errorMsg, String... lines) throws Exception { + try { + evalRuleContextCode(ruleContext, lines); + fail("checkErrorContains(SkylarkRuleContext, String, String...): There was no error"); + } catch (EvalException e) { + assertThat(e.getMessage()).contains(errorMsg); + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java b/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java index da709a002b..5d437d6fd5 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java @@ -17,6 +17,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; + import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java b/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java index ecbb85bc34..b036e5aae7 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java @@ -23,6 +23,7 @@ import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.events.EventCollector; import com.google.devtools.build.lib.packages.CachingPackageLocator; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import com.google.devtools.build.lib.testutil.Scratch; import com.google.devtools.build.lib.vfs.Path; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java index 36fc76179d..fd52209d0f 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import com.google.common.collect.Sets; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java index 14b26ec218..aafbe7ce29 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import com.google.devtools.build.lib.testutil.TestMode; import org.junit.Test; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java b/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java index ade0bbffb1..ef87be7d50 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/FunctionTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java index 1a11e4cc89..72fd205ecf 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Before; import org.junit.Test; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java index 9dc2a15db0..7b2759d3f5 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java @@ -24,6 +24,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.syntax.DictionaryLiteral.DictionaryEntryLiteral; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Before; import org.junit.Test; @@ -976,7 +977,7 @@ public class ParserTest extends EvaluationTestCase { List<Statement> stmts3 = parseFile("[ i for (i, j, k) in [(1, 2, 3)] ]\n"); assertThat(stmts3).hasSize(1); } - + @Test public void testReturnNone() throws Exception { List<Statement> defNone = parseFileForSkylark("def foo():", " return None\n"); @@ -993,10 +994,10 @@ public class ParserTest extends EvaluationTestCase { List<Statement> defNoExpr = parseFileForSkylark("def bar" + i + "():", " return" + end); i++; assertThat(defNoExpr).hasSize(1); - + List<Statement> bodyNoExpr = ((FunctionDefStatement) defNoExpr.get(0)).getStatements(); assertThat(bodyNoExpr).hasSize(1); - + ReturnStatement returnNoExpr = (ReturnStatement) bodyNoExpr.get(0); Identifier none = (Identifier) returnNoExpr.getReturnExpression(); assertEquals("None", none.getName()); @@ -1149,7 +1150,7 @@ public class ParserTest extends EvaluationTestCase { parseFileForSkylark("load('/foo', test3 = old)\n"); assertContainsEvent("syntax error at 'old': expected string"); } - + @Test public void testParseErrorNotComparison() throws Exception { setFailFast(false); diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java index 00bc347436..dad7076221 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java index fbceff9ae9..1eb1338d11 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkNestedSetTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Test; import org.junit.runner.RunWith; @@ -175,7 +176,7 @@ public class SkylarkNestedSetTest extends EvaluationTestCase { private SkylarkNestedSet get(String varname) throws Exception { return (SkylarkNestedSet) lookup(varname); } - + @Test public void testSetOuterOrderWins() throws Exception { // The order of the outer set should define the final iteration order, @@ -236,7 +237,7 @@ public class SkylarkNestedSetTest extends EvaluationTestCase { private boolean areOrdersCompatible(Order first, Order second) { return first == Order.STABLE_ORDER || second == Order.STABLE_ORDER || first == second; } - + @Test public void testSetOrderComplexUnion() throws Exception { // {1, 11, {2, 22}, {3, 33}, {4, 44}} diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java index fd3002e1eb..3a9c4d564e 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTest.java @@ -20,6 +20,7 @@ import com.google.devtools.build.lib.analysis.RuleConfiguredTarget; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.syntax.SkylarkList.MutableList; import com.google.devtools.build.lib.syntax.SkylarkList.Tuple; +import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,7 +30,7 @@ import org.junit.runners.JUnit4; * Tests for the validation process of Skylark files. */ @RunWith(JUnit4.class) -public class ValidationTests extends EvaluationTestCase { +public class ValidationTest extends EvaluationTestCase { @Test public void testAssignmentNotValidLValue() { @@ -48,16 +49,14 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testTwoFunctionsWithTheSameName() throws Exception { - checkError("Variable foo is read only", - "def foo():", - " return 1", - "def foo(x, y):", - " return 1"); + checkError( + "Variable foo is read only", "def foo():", " return 1", "def foo(x, y):", " return 1"); } @Test public void testFunctionLocalVariable() throws Exception { - checkError("name 'a' is not defined", + checkError( + "name 'a' is not defined", "def func2(b):", " c = b", " c = a", @@ -68,29 +67,17 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testFunctionLocalVariableDoesNotEffectGlobalValidationEnv() throws Exception { - checkError("name 'a' is not defined", - "def func1():", - " a = 1", - "def func2(b):", - " b = a"); + checkError("name 'a' is not defined", "def func1():", " a = 1", "def func2(b):", " b = a"); } @Test public void testFunctionParameterDoesNotEffectGlobalValidationEnv() throws Exception { - checkError("name 'a' is not defined", - "def func1(a):", - " return a", - "def func2():", - " b = a"); + checkError("name 'a' is not defined", "def func1(a):", " return a", "def func2():", " b = a"); } @Test public void testLocalValidationEnvironmentsAreSeparated() throws Exception { - parse( - "def func1():", - " a = 1", - "def func2():", - " a = 'abc'\n"); + parse("def func1():", " a = 1", "def func2():", " a = 'abc'\n"); } @Test @@ -100,51 +87,41 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testSkylarkGlobalVariablesAreReadonly() throws Exception { - checkError("Variable a is read only", - "a = 1", - "a = 2"); + checkError("Variable a is read only", "a = 1", "a = 2"); } @Test public void testFunctionDefRecursion() throws Exception { - parse( - "def func():", - " func()\n"); + parse("def func():", " func()\n"); } @Test public void testMutualRecursion() throws Exception { - parse( - "def foo(i):", - " bar(i)", - "def bar(i):", - " foo(i)", - "foo(4)"); + parse("def foo(i):", " bar(i)", "def bar(i):", " foo(i)", "foo(4)"); } @Test public void testFunctionDefinedBelow() { - parse( - "def bar(): a = foo() + 'a'", - "def foo(): return 1\n"); + parse("def bar(): a = foo() + 'a'", "def foo(): return 1\n"); } @Test public void testFunctionDoesNotExist() { - checkError("function 'foo' does not exist", - "def bar(): a = foo() + 'a'"); + checkError("function 'foo' does not exist", "def bar(): a = foo() + 'a'"); } @Test public void testStructMembersAreImmutable() { - checkError("can only assign to variables and tuples, not to 's.x'", + checkError( + "can only assign to variables and tuples, not to 's.x'", "s = struct(x = 'a')", "s.x = 'b'\n"); } @Test public void testStructDictMembersAreImmutable() { - checkError("can only assign to variables and tuples, not to 's.x['b']'", + checkError( + "can only assign to variables and tuples, not to 's.x['b']'", "s = struct(x = {'a' : 1})", "s.x['b'] = 2\n"); } @@ -161,10 +138,7 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testNoneAssignment() throws Exception { - parse("def func():", - " a = None", - " a = 2", - " a = None\n"); + parse("def func():", " a = None", " a = 2", " a = None\n"); } @Test @@ -180,7 +154,8 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testFuncReturningDictAssignmentAsLValue() throws Exception { - checkError("can only assign to variables and tuples, not to 'my_dict()['b']'", + checkError( + "can only assign to variables and tuples, not to 'my_dict()['b']'", "def my_dict():", " return {'a': 1}", "def func():", @@ -190,22 +165,18 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testEmptyLiteralGenericIsSetInLaterConcatWorks() { - parse("def func():", - " s = {}", - " s['a'] = 'b'\n"); + parse("def func():", " s = {}", " s['a'] = 'b'\n"); } @Test public void testReadOnlyWorksForSimpleBranching() { - parse("if 1:", - " v = 'a'", - "else:", - " v = 'b'"); + parse("if 1:", " v = 'a'", "else:", " v = 'b'"); } @Test public void testReadOnlyWorksForNestedBranching() { - parse("if 1:", + parse( + "if 1:", " if 0:", " v = 'a'", " else:", @@ -219,58 +190,43 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testReadOnlyWorksForDifferentLevelBranches() { - checkError("Variable v is read only", - "if 1:", - " if 1:", - " v = 'a'", - " v = 'b'\n"); + checkError("Variable v is read only", "if 1:", " if 1:", " v = 'a'", " v = 'b'\n"); } @Test public void testReadOnlyWorksWithinSimpleBranch() { - checkError("Variable v is read only", - "if 1:", - " v = 'a'", - "else:", - " v = 'b'", - " v = 'c'\n"); + checkError( + "Variable v is read only", "if 1:", " v = 'a'", "else:", " v = 'b'", " v = 'c'\n"); } @Test public void testReadOnlyWorksWithinNestedBranch() { - checkError("Variable v is read only", + checkError( + "Variable v is read only", "if 1:", - " v = 'a'", - "else:", - " if 1:", - " v = 'b'", - " else:", - " v = 'c'", - " v = 'd'\n"); + " v = 'a'", + "else:", + " if 1:", + " v = 'b'", + " else:", + " v = 'c'", + " v = 'd'\n"); } @Test public void testReadOnlyWorksAfterSimpleBranch() { - checkError("Variable v is read only", - "if 1:", - " v = 'a'", - "else:", - " w = 'a'", - "v = 'b'"); + checkError("Variable v is read only", "if 1:", " v = 'a'", "else:", " w = 'a'", "v = 'b'"); } @Test public void testReadOnlyWorksAfterNestedBranch() { - checkError("Variable v is read only", - "if 1:", - " if 1:", - " v = 'a'", - "v = 'b'"); + checkError("Variable v is read only", "if 1:", " if 1:", " v = 'a'", "v = 'b'"); } @Test public void testReadOnlyWorksAfterNestedBranch2() { - checkError("Variable v is read only", + checkError( + "Variable v is read only", "if 1:", " v = 'a'", "else:", @@ -281,20 +237,17 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testModulesReadOnlyInFuncDefBody() { - parse("def func():", - " cmd_helper = set()"); + parse("def func():", " cmd_helper = set()"); } @Test public void testBuiltinGlobalFunctionsReadOnlyInFuncDefBody() { - parse("def func():", - " rule = 'abc'"); + parse("def func():", " rule = 'abc'"); } @Test public void testBuiltinGlobalFunctionsReadOnlyAsFuncDefArg() { - parse("def func(rule):", - " return rule"); + parse("def func(rule):", " return rule"); } @Test @@ -327,19 +280,17 @@ public class ValidationTests extends EvaluationTestCase { @Test public void testLoadRelativePathMultipleSegments() throws Exception { - checkError("Path 'pkg/extension.bzl' is not valid. It should either start with " - + "a slash or refer to a file in the current directory.", + checkError( + "Path 'pkg/extension.bzl' is not valid. It should either start with " + + "a slash or refer to a file in the current directory.", "load('pkg/extension', 'a')\n"); } @Test public void testDollarErrorDoesNotLeak() throws Exception { setFailFast(false); - parseFile("def GenerateMapNames():", - " a = 2", - " b = [3, 4]", - " if a not b:", - " print(a)"); + parseFile( + "def GenerateMapNames():", " a = 2", " b = [3, 4]", " if a not b:", " print(a)"); assertContainsEvent("syntax error at 'b': expected in"); // Parser uses "$error" symbol for error recovery. // It should not be used in error messages. @@ -364,14 +315,14 @@ public class ValidationTests extends EvaluationTestCase { assertThat(EvalUtils.getParentWithSkylarkModule(tupleClass)).isEqualTo(Tuple.class); // TODO(bazel-team): fix that? - assertThat(ClassObject.class.isAnnotationPresent(SkylarkModule.class)) - .isFalse(); + assertThat(ClassObject.class.isAnnotationPresent(SkylarkModule.class)).isFalse(); assertThat(ClassObject.SkylarkClassObject.class.isAnnotationPresent(SkylarkModule.class)) .isTrue(); - assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.SkylarkClassObject.class) - == ClassObject.SkylarkClassObject.class).isTrue(); - assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.class)) - .isNull(); + assertThat( + EvalUtils.getParentWithSkylarkModule(ClassObject.SkylarkClassObject.class) + == ClassObject.SkylarkClassObject.class) + .isTrue(); + assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.class)).isNull(); } @Test @@ -386,20 +337,16 @@ public class ValidationTests extends EvaluationTestCase { assertThat(SkylarkType.of(tupleClass)).isEqualTo(SkylarkType.TUPLE); assertThat(SkylarkType.TUPLE).isNotEqualTo(SkylarkType.LIST); - // Also for ClassObject - assertThat(SkylarkType.of(ClassObject.SkylarkClassObject.class)) - .isEqualTo(SkylarkType.STRUCT); + assertThat(SkylarkType.of(ClassObject.SkylarkClassObject.class)).isEqualTo(SkylarkType.STRUCT); // TODO(bazel-team): fix that? - assertThat(SkylarkType.of(ClassObject.class)) - .isNotEqualTo(SkylarkType.STRUCT); + assertThat(SkylarkType.of(ClassObject.class)).isNotEqualTo(SkylarkType.STRUCT); // Also test for these bazel classes, to avoid some regression. // TODO(bazel-team): move to some other place to remove dependency of syntax tests on Artifact? assertThat(SkylarkType.of(Artifact.SpecialArtifact.class)) .isEqualTo(SkylarkType.of(Artifact.class)); - assertThat(SkylarkType.of(RuleConfiguredTarget.class)) - .isNotEqualTo(SkylarkType.STRUCT); + assertThat(SkylarkType.of(RuleConfiguredTarget.class)).isNotEqualTo(SkylarkType.STRUCT); } @Test @@ -411,15 +358,16 @@ public class ValidationTests extends EvaluationTestCase { SkylarkType combo1 = SkylarkType.Combination.of(SkylarkType.LIST, SkylarkType.INT); assertThat(SkylarkType.LIST.includes(combo1)).isTrue(); - SkylarkType union1 = SkylarkType.Union.of( - SkylarkType.MAP, SkylarkType.LIST, SkylarkType.STRUCT); + SkylarkType union1 = + SkylarkType.Union.of(SkylarkType.MAP, SkylarkType.LIST, SkylarkType.STRUCT); assertThat(union1.includes(SkylarkType.MAP)).isTrue(); assertThat(union1.includes(SkylarkType.STRUCT)).isTrue(); assertThat(union1.includes(combo1)).isTrue(); assertThat(union1.includes(SkylarkType.STRING)).isFalse(); - SkylarkType union2 = SkylarkType.Union.of( - SkylarkType.LIST, SkylarkType.MAP, SkylarkType.STRING, SkylarkType.INT); + SkylarkType union2 = + SkylarkType.Union.of( + SkylarkType.LIST, SkylarkType.MAP, SkylarkType.STRING, SkylarkType.INT); SkylarkType inter1 = SkylarkType.intersection(union1, union2); assertThat(inter1.includes(SkylarkType.MAP)).isTrue(); assertThat(inter1.includes(SkylarkType.LIST)).isTrue(); diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java index 553385d58b..be5317d1bf 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/util/EvaluationTestCase.java @@ -11,11 +11,12 @@ // 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.syntax; +package com.google.devtools.build.lib.syntax.util; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.truth.Ordered; import com.google.devtools.build.lib.events.Event; @@ -24,6 +25,13 @@ import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.EventKind; import com.google.devtools.build.lib.events.util.EventCollectionApparatus; import com.google.devtools.build.lib.packages.PackageFactory; +import com.google.devtools.build.lib.syntax.Environment; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.Expression; +import com.google.devtools.build.lib.syntax.Mutability; +import com.google.devtools.build.lib.syntax.Parser; +import com.google.devtools.build.lib.syntax.ParserInputSource; +import com.google.devtools.build.lib.syntax.Statement; import com.google.devtools.build.lib.testutil.TestMode; import com.google.devtools.build.lib.testutil.TestRuleClassProvider; @@ -43,7 +51,7 @@ public class EvaluationTestCase { protected Environment env; protected Mutability mutability = Mutability.create("test"); - public EvaluationTestCase() { + public EvaluationTestCase() { createNewInfrastructure(); } @@ -88,12 +96,12 @@ public class EvaluationTestCase { if (testMode == null) { throw new IllegalArgumentException( "TestMode is null. Please set a Testmode via setMode() or set the " - + "Environment manually by overriding newEnvironment()"); + + "Environment manually by overriding newEnvironment()"); } return testMode.createEnvironment(getEventHandler(), null); } - protected void createNewInfrastructure() { + protected void createNewInfrastructure() { eventCollectionApparatus = new EventCollectionApparatus(EventKind.ALL_EVENTS); factory = new PackageFactory(TestRuleClassProvider.getRuleClassProvider()); } @@ -137,10 +145,10 @@ public class EvaluationTestCase { } /** Parses an Expression from string without a supporting file */ - Expression parseExpression(String... input) { + @VisibleForTesting + public Expression parseExpression(String... input) { return Parser.parseExpression( - ParserInputSource.create(Joiner.on("\n").join(input), null), - getEventHandler()); + ParserInputSource.create(Joiner.on("\n").join(input), null), getEventHandler()); } public EvaluationTestCase update(String varname, Object value) throws Exception { @@ -189,24 +197,30 @@ public class EvaluationTestCase { eventCollectionApparatus.setFailFast(failFast); return this; } + public EvaluationTestCase assertNoEvents() { eventCollectionApparatus.assertNoEvents(); return this; } + public EventCollector getEventCollector() { return eventCollectionApparatus.collector(); } + public Event assertContainsEvent(String expectedMessage) { return eventCollectionApparatus.assertContainsEvent(expectedMessage); } - public List<Event> assertContainsEventWithFrequency(String expectedMessage, - int expectedFrequency) { + + public List<Event> assertContainsEventWithFrequency( + String expectedMessage, int expectedFrequency) { return eventCollectionApparatus.assertContainsEventWithFrequency( expectedMessage, expectedFrequency); } + public Event assertContainsEventWithWordsInQuotes(String... words) { return eventCollectionApparatus.assertContainsEventWithWordsInQuotes(words); } + public EvaluationTestCase clearEvents() { eventCollectionApparatus.collector().clear(); return this; @@ -347,8 +361,8 @@ public class EvaluationTestCase { * @param exactMatch If true, the error message has to be identical to the expected error * @return An instance of Testable that runs the error check */ - protected Testable errorTestable(final boolean exactMatch, final String error, - final String... statements) { + protected Testable errorTestable( + final boolean exactMatch, final String error, final String... statements) { return new Testable() { @Override public void run() throws Exception { @@ -479,12 +493,13 @@ public class EvaluationTestCase { * @param value */ public void registerUpdate(final String name, final Object value) { - setup.add(new Testable() { - @Override - public void run() throws Exception { - EvaluationTestCase.this.update(name, value); - } - }); + setup.add( + new Testable() { + @Override + public void run() throws Exception { + EvaluationTestCase.this.update(name, value); + } + }); } /** @@ -493,12 +508,13 @@ public class EvaluationTestCase { * @param statements */ public void registerEval(final String... statements) { - setup.add(new Testable() { - @Override - public void run() throws Exception { - EvaluationTestCase.this.eval(statements); - } - }); + setup.add( + new Testable() { + @Override + public void run() throws Exception { + EvaluationTestCase.this.eval(statements); + } + }); } /** diff --git a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java index dfcbfcd358..b94cf17510 100644 --- a/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java +++ b/src/test/java/com/google/devtools/build/lib/testutil/TestConstants.java @@ -44,6 +44,11 @@ public class TestConstants { public static final String RUNFILES_PREFIX = "DOES-NOT-WORK-YET"; /** + * Default workspace name. + */ + public static final String WORKSPACE_NAME = ""; + + /** * Name of a class with an INSTANCE field of type AnalysisMock to be used for analysis tests. */ public static final String TEST_ANALYSIS_MOCK = |