// 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.syntax; import static com.google.common.truth.Truth.assertThat; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget; import com.google.devtools.build.lib.events.Event; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.Arrays; /** * Tests for the validation process of Skylark files. */ @RunWith(JUnit4.class) public class ValidationTests extends EvaluationTestCase { @Test public void testAssignmentNotValidLValue() { checkError("can only assign to variables and tuples, not to ''a''", "'a' = 1"); } @Test public void testTopLevelForStatement() throws Exception { checkError("'For' is not allowed as a top level statement", "for i in [1,2,3]: a = i\n"); } @Test public void testReturnOutsideFunction() throws Exception { checkError("Return statements must be inside a function", "return 2\n"); } @Test public void testTwoFunctionsWithTheSameName() throws Exception { checkError("Variable foo is read only", "def foo():", " return 1", "def foo(x, y):", " return 1"); } @Test public void testFunctionLocalVariable() throws Exception { checkError("name 'a' is not defined", "def func2(b):", " c = b", " c = a", "def func1():", " a = 1", " func2(2)"); } @Test public void testFunctionLocalVariableDoesNotEffectGlobalValidationEnv() throws Exception { checkError("name 'a' is not defined", "def func1():", " a = 1", "def func2(b):", " b = a"); } @Test public void testFunctionParameterDoesNotEffectGlobalValidationEnv() throws Exception { checkError("name 'a' is not defined", "def func1(a):", " return a", "def func2():", " b = a"); } @Test public void testLocalValidationEnvironmentsAreSeparated() throws Exception { parse( "def func1():", " a = 1", "def func2():", " a = 'abc'\n"); } @Test public void testBuiltinSymbolsAreReadOnly() throws Exception { checkError("Variable repr is read only", "repr = 1"); } @Test public void testSkylarkGlobalVariablesAreReadonly() throws Exception { checkError("Variable a is read only", "a = 1", "a = 2"); } @Test public void testFunctionDefRecursion() throws Exception { parse( "def func():", " func()\n"); } @Test public void testMutualRecursion() throws Exception { parse( "def foo(i):", " bar(i)", "def bar(i):", " foo(i)", "foo(4)"); } @Test public void testFunctionDefinedBelow() { parse( "def bar(): a = foo() + 'a'", "def foo(): return 1\n"); } @Test public void testFunctionDoesNotExist() { checkError("function 'foo' does not exist", "def bar(): a = foo() + 'a'"); } @Test public void testStructMembersAreImmutable() { checkError("can only assign to variables and tuples, not to 's.x'", "s = struct(x = 'a')", "s.x = 'b'\n"); } @Test public void testStructDictMembersAreImmutable() { checkError("can only assign to variables and tuples, not to 's.x['b']'", "s = struct(x = {'a' : 1})", "s.x['b'] = 2\n"); } @Test public void testTupleLiteralWorksForDifferentTypes() throws Exception { parse("('a', 1)"); } @Test public void testDictLiteralDifferentValueTypeWorks() throws Exception { parse("{'a': 1, 'b': 'c'}"); } @Test public void testNoneAssignment() throws Exception { parse("def func():", " a = None", " a = 2", " a = None\n"); } @Test public void testNoneIsAnyType() throws Exception { parse("None + None"); parse("2 == None"); parse("None > 'a'"); parse("[] in None"); parse("5 * None"); } // Skylark built-in functions specific tests @Test public void testFuncReturningDictAssignmentAsLValue() throws Exception { checkError("can only assign to variables and tuples, not to 'my_dict()['b']'", "def my_dict():", " return {'a': 1}", "def func():", " my_dict()['b'] = 2", " return d\n"); } @Test public void testEmptyLiteralGenericIsSetInLaterConcatWorks() { parse("def func():", " s = {}", " s['a'] = 'b'\n"); } @Test public void testReadOnlyWorksForSimpleBranching() { parse("if 1:", " v = 'a'", "else:", " v = 'b'"); } @Test public void testReadOnlyWorksForNestedBranching() { parse("if 1:", " if 0:", " v = 'a'", " else:", " v = 'b'", "else:", " if 0:", " v = 'c'", " else:", " v = 'd'\n"); } @Test public void testReadOnlyWorksForDifferentLevelBranches() { checkError("Variable v is read only", "if 1:", " if 1:", " v = 'a'", " v = 'b'\n"); } @Test public void testReadOnlyWorksWithinSimpleBranch() { checkError("Variable v is read only", "if 1:", " v = 'a'", "else:", " v = 'b'", " v = 'c'\n"); } @Test public void testReadOnlyWorksWithinNestedBranch() { checkError("Variable v is read only", "if 1:", " v = 'a'", "else:", " if 1:", " v = 'b'", " else:", " v = 'c'", " v = 'd'\n"); } @Test public void testReadOnlyWorksAfterSimpleBranch() { checkError("Variable v is read only", "if 1:", " v = 'a'", "else:", " w = 'a'", "v = 'b'"); } @Test public void testReadOnlyWorksAfterNestedBranch() { checkError("Variable v is read only", "if 1:", " if 1:", " v = 'a'", "v = 'b'"); } @Test public void testReadOnlyWorksAfterNestedBranch2() { checkError("Variable v is read only", "if 1:", " v = 'a'", "else:", " if 0:", " w = 1", "v = 'b'\n"); } @Test public void testModulesReadOnlyInFuncDefBody() { parse("def func():", " cmd_helper = set()"); } @Test public void testBuiltinGlobalFunctionsReadOnlyInFuncDefBody() { parse("def func():", " rule = 'abc'"); } @Test public void testBuiltinGlobalFunctionsReadOnlyAsFuncDefArg() { parse("def func(rule):", " return rule"); } @Test public void testFunctionReturnsFunction() { parse( "def rule(*, implementation): return None", "def impl(ctx): return None", "", "skylark_rule = rule(implementation = impl)", "", "def macro(name):", " skylark_rule(name = name)"); } @Test public void testTypeForBooleanLiterals() { parse("len([1, 2]) == 0 and True"); parse("len([1, 2]) == 0 and False"); } @Test public void testLoadRelativePathOneSegment() throws Exception { parse("load('extension', 'a')\n"); } @Test public void testLoadAbsolutePathMultipleSegments() throws Exception { parse("load('/pkg/extension', 'a')\n"); } @Test public void testLoadRelativePathMultipleSegments() throws Exception { checkError("Path 'pkg/extension.bzl' is not valid. It should either start with " + "a slash or refer to a file in the current directory.", "load('pkg/extension', 'a')\n"); } @Test public void testDollarErrorDoesNotLeak() throws Exception { setFailFast(false); parseFile("def GenerateMapNames():", " a = 2", " b = [3, 4]", " if a not b:", " print(a)"); assertContainsEvent("syntax error at 'b': expected in"); // Parser uses "$error" symbol for error recovery. // It should not be used in error messages. for (Event event : getEventCollector()) { assertThat(event.getMessage()).doesNotContain("$error$"); } } @Test public void testParentWithSkylarkModule() throws Exception { Class emptyListClass = SkylarkList.EMPTY_LIST.getClass(); Class simpleListClass = SkylarkList.list(Arrays.asList(1, 2, 3), SkylarkType.INT) .getClass(); Class tupleClass = SkylarkList.tuple(Arrays.asList(1, "a", "b")).getClass(); assertThat(SkylarkList.class.isAnnotationPresent(SkylarkModule.class)).isTrue(); assertThat(EvalUtils.getParentWithSkylarkModule(SkylarkList.class)) .isEqualTo(SkylarkList.class); assertThat(EvalUtils.getParentWithSkylarkModule(emptyListClass)).isEqualTo(SkylarkList.class); assertThat(EvalUtils.getParentWithSkylarkModule(simpleListClass)).isEqualTo(SkylarkList.class); // TODO(bazel-team): make a tuple not a list anymore. assertThat(EvalUtils.getParentWithSkylarkModule(tupleClass)).isEqualTo(SkylarkList.class); // TODO(bazel-team): fix that? assertThat(ClassObject.class.isAnnotationPresent(SkylarkModule.class)) .isFalse(); assertThat(ClassObject.SkylarkClassObject.class.isAnnotationPresent(SkylarkModule.class)) .isTrue(); assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.SkylarkClassObject.class) == ClassObject.SkylarkClassObject.class).isTrue(); assertThat(EvalUtils.getParentWithSkylarkModule(ClassObject.class)) .isNull(); } @Test public void testSkylarkTypeEquivalence() throws Exception { // All subclasses of SkylarkList are made equivalent Class emptyListClass = SkylarkList.EMPTY_LIST.getClass(); Class simpleListClass = SkylarkList.list(Arrays.asList(1, 2, 3), SkylarkType.INT) .getClass(); Class tupleClass = SkylarkList.tuple(Arrays.asList(1, "a", "b")).getClass(); assertThat(SkylarkType.of(SkylarkList.class)).isEqualTo(SkylarkType.LIST); assertThat(SkylarkType.of(emptyListClass)).isEqualTo(SkylarkType.LIST); assertThat(SkylarkType.of(simpleListClass)).isEqualTo(SkylarkType.LIST); // TODO(bazel-team): make a tuple not a list anymore. assertThat(SkylarkType.of(tupleClass)).isEqualTo(SkylarkType.LIST); // Also for ClassObject assertThat(SkylarkType.of(ClassObject.SkylarkClassObject.class)) .isEqualTo(SkylarkType.STRUCT); // TODO(bazel-team): fix that? assertThat(SkylarkType.of(ClassObject.class)) .isNotEqualTo(SkylarkType.STRUCT); // Also test for these bazel classes, to avoid some regression. // TODO(bazel-team): move to some other place to remove dependency of syntax tests on Artifact? assertThat(SkylarkType.of(Artifact.SpecialArtifact.class)) .isEqualTo(SkylarkType.of(Artifact.class)); assertThat(SkylarkType.of(RuleConfiguredTarget.class)) .isNotEqualTo(SkylarkType.STRUCT); } @Test public void testSkylarkTypeInclusion() throws Exception { assertThat(SkylarkType.INT.includes(SkylarkType.BOTTOM)).isTrue(); assertThat(SkylarkType.BOTTOM.includes(SkylarkType.INT)).isFalse(); assertThat(SkylarkType.TOP.includes(SkylarkType.INT)).isTrue(); SkylarkType combo1 = SkylarkType.Combination.of(SkylarkType.LIST, SkylarkType.INT); assertThat(SkylarkType.LIST.includes(combo1)).isTrue(); SkylarkType union1 = SkylarkType.Union.of( SkylarkType.MAP, SkylarkType.LIST, SkylarkType.STRUCT); assertThat(union1.includes(SkylarkType.MAP)).isTrue(); assertThat(union1.includes(SkylarkType.STRUCT)).isTrue(); assertThat(union1.includes(combo1)).isTrue(); assertThat(union1.includes(SkylarkType.STRING)).isFalse(); SkylarkType union2 = SkylarkType.Union.of( SkylarkType.LIST, SkylarkType.MAP, SkylarkType.STRING, SkylarkType.INT); SkylarkType inter1 = SkylarkType.intersection(union1, union2); assertThat(inter1.includes(SkylarkType.MAP)).isTrue(); assertThat(inter1.includes(SkylarkType.LIST)).isTrue(); assertThat(inter1.includes(combo1)).isTrue(); assertThat(inter1.includes(SkylarkType.INT)).isFalse(); } private void parse(String... lines) { parseFile(lines); assertNoEvents(); } private void checkError(String errorMsg, String... lines) { setFailFast(false); parseFile(lines); assertContainsEvent(errorMsg); } }