aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/skylark
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-09-22 16:24:45 +0000
committerGravatar Laszlo Csomor <laszlocsomor@google.com>2015-09-22 17:19:53 +0000
commitceae8c50e8f92c6fbf2394ac7c5eb3b420539225 (patch)
tree9b10bab2b8e06b61ff268a422ee2f27e09f10382 /src/test/java/com/google/devtools/build/lib/skylark
parent4671896be8bf0e37c85c3b740bb3621d2a9e1cc8 (diff)
Open source some skylark tests.
-- MOS_MIGRATED_REVID=103652672
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/skylark')
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkCommandLineTest.java47
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkFileHelperTest.java68
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java430
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java316
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java810
-rw-r--r--src/test/java/com/google/devtools/build/lib/skylark/util/SkylarkTestCase.java164
6 files changed, 1835 insertions, 0 deletions
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);
+ }
+ }
+}