diff options
author | 2015-09-22 16:24:45 +0000 | |
---|---|---|
committer | 2015-09-22 17:19:53 +0000 | |
commit | ceae8c50e8f92c6fbf2394ac7c5eb3b420539225 (patch) | |
tree | 9b10bab2b8e06b61ff268a422ee2f27e09f10382 /src/test/java/com/google/devtools/build/lib/skylark | |
parent | 4671896be8bf0e37c85c3b740bb3621d2a9e1cc8 (diff) |
Open source some skylark tests.
--
MOS_MIGRATED_REVID=103652672
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/skylark')
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); + } + } +} |