Open source all the tests under lib/syntax/.

--
MOS_MIGRATED_REVID=87244284
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
new file mode 100644
index 0000000..723d72c
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
@@ -0,0 +1,576 @@
+// 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 com.google.common.base.Joiner;
+
+/**
+ * Tests for the validation process of Skylark files.
+ */
+public class ValidationTests extends AbstractParserTestCase {
+
+  public void testIncompatibleLiteralTypesStringInt() {
+    checkError("bad variable 'a': int is incompatible with string at /some/file.txt",
+        "def foo():\n",
+        "  a = '1'",
+        "  a = 1");
+  }
+
+  public void testIncompatibleLiteralTypesDictString() {
+    checkError("bad variable 'a': int is incompatible with dict at /some/file.txt",
+        "def foo():\n",
+        "  a = {1 : 'x'}",
+        "  a = 1");
+  }
+
+  public void testIncompatibleLiteralTypesInIf() {
+    checkError("bad variable 'a': int is incompatible with string at /some/file.txt",
+        "def foo():\n",
+        "  if 1:",
+        "    a = 'a'",
+        "  else:",
+        "    a = 1");
+  }
+
+  public void testAssignmentNotValidLValue() {
+    checkError("can only assign to variables, not to ''a''", "'a' = 1");
+  }
+
+  public void testForNotIterable() throws Exception {
+    checkError("type 'int' is not iterable",
+          "def func():\n"
+        + "  for i in 5: a = i\n");
+  }
+
+  public void testForIterableWithUknownArgument() throws Exception {
+    parse("def func(x=None):\n"
+        + "  for i in x: a = i\n");
+  }
+
+  public void testForNotIterableBinaryExpression() throws Exception {
+    checkError("type 'int' is not iterable",
+          "def func():\n"
+        + "  for i in 1 + 1: a = i\n");
+  }
+
+  public void testOptionalArgument() throws Exception {
+    checkError("type 'int' is not iterable",
+          "def func(x=5):\n"
+        + "  for i in x: a = i\n");
+  }
+
+  public void testOptionalArgumentHasError() throws Exception {
+    checkError("unsupported operand type(s) for +: 'int' and 'string'",
+          "def func(x=5+'a'):\n"
+        + "  return 0\n");
+  }
+
+  public void testTopLevelForStatement() throws Exception {
+    checkError("'For' is not allowed as a top level statement", "for i in [1,2,3]: a = i\n");
+  }
+
+  public void testReturnOutsideFunction() throws Exception {
+    checkError("Return statements must be inside a function", "return 2\n");
+  }
+
+  public void testTwoReturnTypes() throws Exception {
+    checkError("bad return type of foo: string is incompatible with int at /some/file.txt:3:5",
+        "def foo(x):",
+        "  if x:",
+        "    return 1",
+        "  else:",
+        "    return 'a'");
+  }
+
+  public void testTwoFunctionsWithTheSameName() throws Exception {
+    checkError("function foo already exists",
+        "def foo():",
+        "  return 1",
+        "def foo(x, y):",
+        "  return 1");
+  }
+
+  public void testDynamicTypeCheck() throws Exception {
+    checkError("bad variable 'a': string is incompatible with int at /some/file.txt:2:3",
+        "def foo():",
+        "  a = 1",
+        "  a = '1'");
+  }
+
+  public void testFunctionLocalVariable() throws Exception {
+    checkError("name 'a' is not defined",
+        "def func2(b):",
+        "  c = b",
+        "  c = a",
+        "def func1():",
+        "  a = 1",
+        "  func2(2)");
+  }
+
+  public void testFunctionLocalVariableDoesNotEffectGlobalValidationEnv() throws Exception {
+    checkError("name 'a' is not defined",
+        "def func1():",
+        "  a = 1",
+        "def func2(b):",
+        "  b = a");
+  }
+
+  public void testFunctionParameterDoesNotEffectGlobalValidationEnv() throws Exception {
+    checkError("name 'a' is not defined",
+        "def func1(a):",
+        "  return a",
+        "def func2():",
+        "  b = a");
+  }
+
+  public void testLocalValidationEnvironmentsAreSeparated() throws Exception {
+    parse(
+          "def func1():\n"
+        + "  a = 1\n"
+        + "def func2():\n"
+        + "  a = 'abc'\n");
+  }
+
+  public void testListComprehensionNotIterable() throws Exception {
+    checkError("type 'int' is not iterable",
+        "[i for i in 1 for j in [2]]");
+  }
+
+  public void testListComprehensionNotIterable2() throws Exception {
+    checkError("type 'int' is not iterable",
+        "[i for i in [1] for j in 123]");
+  }
+
+  public void testListIsNotComparable() {
+    checkError("list is not comparable", "['a'] > 1");
+  }
+
+  public void testStringCompareToInt() {
+    checkError("bad comparison: int is incompatible with string", "'a' > 1");
+  }
+
+  public void testInOnInt() {
+    checkError("operand 'in' only works on strings, dictionaries, "
+        + "lists, sets or tuples, not on a(n) int", "1 in 2");
+  }
+
+  public void testUnsupportedOperator() {
+    checkError("unsupported operand type(s) for -: 'string' and 'int'", "'a' - 1");
+  }
+
+  public void testBuiltinSymbolsAreReadOnly() throws Exception {
+    checkError("Variable rule is read only", "rule = 1");
+  }
+
+  public void testSkylarkGlobalVariablesAreReadonly() throws Exception {
+    checkError("Variable a is read only",
+        "a = 1\n"
+        + "a = 2");
+  }
+
+  public void testFunctionDefRecursion() throws Exception {
+    checkError("function 'func' does not exist",
+        "def func():\n"
+      + "  func()\n");
+  }
+
+  public void testMutualRecursion() throws Exception {
+    checkError("function 'bar' does not exist",
+        "def foo(i):\n"
+      + "  bar(i)\n"
+      + "def bar(i):\n"
+      + "  foo(i)\n"
+      + "foo(4)");
+  }
+
+  public void testFunctionReturnValue() {
+    checkError("unsupported operand type(s) for +: 'int' and 'string'",
+          "def foo(): return 1\n"
+        + "a = foo() + 'a'\n");
+  }
+
+  public void testFunctionReturnValueInFunctionDef() {
+    checkError("unsupported operand type(s) for +: 'int' and 'string'",
+          "def foo(): return 1\n"
+        + "def bar(): a = foo() + 'a'\n");
+  }
+
+  public void testFunctionDoesNotExistInFunctionDef() {
+    checkError("function 'foo' does not exist",
+          "def bar(): a = foo() + 'a'\n"
+        + "def foo(): return 1\n");
+  }
+
+  public void testStructMembersAreImmutable() {
+    checkError("can only assign to variables, not to 's.x'",
+        "s = struct(x = 'a')\n"
+      + "s.x = 'b'\n");
+  }
+
+  public void testStructDictMembersAreImmutable() {
+    checkError("can only assign to variables, not to 's.x['b']'",
+        "s = struct(x = {'a' : 1})\n"
+      + "s.x['b'] = 2\n");
+  }
+
+  public void testTupleAssign() throws Exception {
+    checkError("unsupported operand type(s) for +: 'list' and 'dict'",
+        "d = (1, 2)\n"
+      + "d[0] = 2\n");
+  }
+
+  public void testAssignOnNonCollection() throws Exception {
+    checkError("unsupported operand type(s) for +: 'string' and 'dict'",
+        "d = 'abc'\n"
+      + "d[0] = 2");
+  }
+
+  public void testNsetBadRightOperand() throws Exception {
+    checkError("can only concatenate nested sets with other nested sets or list of items, "
+        + "not 'string'", "set() + 'a'");
+  }
+
+  public void testNsetBadItemType() throws Exception {
+    checkError("bad nested set: incompatible generic variable types int with string",
+        "(set() + ['a']) + [1]");
+  }
+
+  public void testNsetBadNestedItemType() throws Exception {
+    checkError("bad nested set: incompatible generic variable types int with string",
+        "(set() + ['b']) + (set() + [1])");
+  }
+
+  public void testTypeInferenceForMethodLibraryFunction() throws Exception {
+    checkError("bad variable 'l': string is incompatible with int at /some/file.txt:2:3",
+          "def foo():\n"
+        + "  l = len('abc')\n"
+        + "  l = 'a'");
+  }
+
+  public void testListLiteralBadTypes() throws Exception {
+    checkError("bad list literal: int is incompatible with string at /some/file.txt:1:1",
+        "['a', 1]");
+  }
+
+  public void testTupleLiteralWorksForDifferentTypes() throws Exception {
+    parse("('a', 1)");
+  }
+
+  public void testDictLiteralBadKeyTypes() throws Exception {
+    checkError("bad dict literal: int is incompatible with string at /some/file.txt:1:1",
+        "{'a': 1, 1: 2}");
+  }
+
+  public void testDictLiteralDifferentValueTypeWorks() throws Exception {
+    parse("{'a': 1, 'b': 'c'}");
+  }
+
+  public void testListConcatBadTypes() throws Exception {
+    checkError("bad list concatenation: incompatible generic variable types int with string",
+        "['a'] + [1]");
+  }
+
+  public void testDictConcatBadKeyTypes() throws Exception {
+    checkError("bad dict concatenation: incompatible generic variable types int with string",
+        "{'a': 1} + {1: 2}");
+  }
+
+  public void testDictLiteralBadKeyType() throws Exception {
+    checkError("Dict cannot contain composite type 'list' as key", "{['a']: 1}");
+  }
+
+  public void testAndTypeInfer() throws Exception {
+    checkError("unsupported operand type(s) for +: 'string' and 'int'", "('a' and 'b') + 1");
+  }
+
+  public void testOrTypeInfer() throws Exception {
+    checkError("unsupported operand type(s) for +: 'string' and 'int'", "('' or 'b') + 1");
+  }
+
+  public void testAndDifferentTypes() throws Exception {
+    checkError("bad and operator: int is incompatible with string at /some/file.txt:1:1",
+        "'ab' and 3");
+  }
+
+  public void testOrDifferentTypes() throws Exception {
+    checkError("bad or operator: int is incompatible with string at /some/file.txt:1:1",
+        "'ab' or 3");
+  }
+
+  public void testOrNone() throws Exception {
+    parse("a = None or 3");
+  }
+
+  public void testNoneAssignment() throws Exception {
+    parse("def func():\n"
+        + "  a = None\n"
+        + "  a = 2\n"
+        + "  a = None\n");
+  }
+
+  public void testNoneAssignmentError() throws Exception {
+    checkError("bad variable 'a': string is incompatible with int at /some/file.txt",
+          "def func():\n"
+        + "  a = None\n"
+        + "  a = 2\n"
+        + "  a = None\n"
+        + "  a = 'b'\n");
+  }
+
+  public void testDictComprehensionNotOnList() throws Exception {
+    checkError("Dict comprehension elements must be a list", "{k : k for k in 'abc'}");
+  }
+
+  public void testTypeInferenceForUserDefinedFunction() throws Exception {
+    checkError("bad variable 'a': string is incompatible with int at /some/file.txt",
+          "def func():\n"
+        + "  return 'a'\n"
+        + "def foo():\n"
+        + "  a = 1\n"
+        + "  a = func()\n");
+  }
+
+  public void testCallingNonFunction() {
+    checkError("a is not a function",
+        "a = '1':\n"
+      + "a()\n");
+  }
+
+  public void testFuncallArgument() {
+    checkError("unsupported operand type(s) for +: 'int' and 'string'",
+        "def foo(x): return x\n"
+      + "a = foo(1 + 'a')");
+  }
+
+  // Skylark built-in functions specific tests
+
+  public void testTypeInferenceForSkylarkBuiltinGlobalFunction() throws Exception {
+    checkError("bad variable 'a': string is incompatible with function at /some/file.txt:3:3",
+          "def impl(ctx): return None\n"
+        + "def foo():\n"
+        + "  a = rule(impl)\n"
+        + "  a = 'a'\n");
+  }
+
+  public void testTypeInferenceForSkylarkBuiltinObjectFunction() throws Exception {
+    checkError("bad variable 'a': string is incompatible with Attribute at /some/file.txt",
+        "def foo():\n"
+        + "  a = attr.int()\n"
+        + "  a = 'a'\n");
+  }
+
+  public void testFuncReturningDictAssignmentAsLValue() throws Exception {
+    checkError("can only assign to variables, not to 'dict([])['b']'",
+          "def dict():\n"
+        + "  return {'a': 1}\n"
+        + "def func():\n"
+        + "  dict()['b'] = 2\n"
+        + "  return d\n");
+  }
+
+  public void testListIndexAsLValue() {
+    checkError("unsupported operand type(s) for +: 'list' and 'dict'",
+        "def func():\n"
+      + "  l = [1]\n"
+      + "  l[0] = 2\n"
+      + "  return l\n");
+  }
+
+  public void testStringIndexAsLValue() {
+    checkError("unsupported operand type(s) for +: 'string' and 'dict'",
+        "def func():\n"
+      + "  s = 'abc'\n"
+      + "  s[0] = 'd'\n"
+      + "  return s\n");
+  }
+
+  public void testEmptyLiteralGenericIsSetInLaterConcatWorks() {
+    parse("def func():\n"
+        + "  s = {}\n"
+        + "  s['a'] = 'b'\n");
+  }
+
+  public void testTypeIsInferredForStructs() {
+    checkError("unsupported operand type(s) for +: 'struct' and 'string'",
+        "(struct(a = 1) + struct(b = 1)) + 'x'");
+  }
+
+  public void testReadOnlyWorksForSimpleBranching() {
+    parse("if 1:\n"
+        + "  v = 'a'\n"
+        + "else:\n"
+        + "  v = 'b'");
+  }
+
+  public void testReadOnlyWorksForNestedBranching() {
+    parse("if 1:\n"
+        + "  if 0:\n"
+        + "    v = 'a'\n"
+        + "  else:\n"
+        + "    v = 'b'\n"
+        + "else:\n"
+        + "  if 0:\n"
+        + "    v = 'c'\n"
+        + "  else:\n"
+        + "    v = 'd'\n");
+  }
+
+  public void testTypeCheckWorksForSimpleBranching() {
+    checkError("bad variable 'v': int is incompatible with string at /some/file.txt:2:3",
+          "if 1:\n"
+        + "  v = 'a'\n"
+        + "else:\n"
+        + "  v = 1");
+  }
+
+  public void testTypeCheckWorksForNestedBranching() {
+    checkError("bad variable 'v': int is incompatible with string at /some/file.txt:5:5",
+        "if 1:\n"
+      + "  v = 'a'\n"
+      + "else:\n"
+      + "  if 0:\n"
+      + "    v = 'b'\n"
+      + "  else:\n"
+      + "    v = 1\n");
+  }
+
+  public void testTypeCheckWorksForDifferentLevelBranches() {
+    checkError("bad variable 'v': int is incompatible with string at /some/file.txt:2:3",
+        "if 1:\n"
+      + "  v = 'a'\n"
+      + "else:\n"
+      + "  if 0:\n"
+      + "    v = 1\n");
+  }
+
+  public void testReadOnlyWorksForDifferentLevelBranches() {
+    checkError("Variable v is read only",
+        "if 1:\n"
+      + "  if 1:\n"
+      + "    v = 'a'\n"
+      + "  v = 'b'\n");
+  }
+
+  public void testReadOnlyWorksWithinSimpleBranch() {
+    checkError("Variable v is read only",
+        "if 1:\n"
+      + "  v = 'a'\n"
+      + "else:\n"
+      + "  v = 'b'\n"
+      + "  v = 'c'\n");
+  }
+
+  public void testReadOnlyWorksWithinNestedBranch() {
+    checkError("Variable v is read only",
+        "if 1:\n"
+      + "  v = 'a'\n"
+      + "else:\n"
+      + "  if 1:\n"
+      + "    v = 'b'\n"
+      + "  else:\n"
+      + "    v = 'c'\n"
+      + "    v = 'd'\n");
+  }
+
+  public void testReadOnlyWorksAfterSimpleBranch() {
+    checkError("Variable v is read only",
+        "if 1:\n"
+      + "  v = 'a'\n"
+      + "else:\n"
+      + "  w = 'a'\n"
+      + "v = 'b'");
+  }
+
+  public void testReadOnlyWorksAfterNestedBranch() {
+    checkError("Variable v is read only",
+        "if 1:\n"
+      + "  if 1:\n"
+      + "    v = 'a'\n"
+      + "v = 'b'");
+  }
+
+  public void testReadOnlyWorksAfterNestedBranch2() {
+    checkError("Variable v is read only",
+        "if 1:\n"
+      + "  v = 'a'\n"
+      + "else:\n"
+      + "  if 0:\n"
+      + "    w = 1\n"
+      + "v = 'b'\n");
+  }
+
+  public void testModulesReadOnlyInFuncDefBody() {
+    checkError("Variable cmd_helper is read only",
+        "def func():",
+        "  cmd_helper = set()");
+  }
+
+  public void testBuiltinGlobalFunctionsReadOnlyInFuncDefBody() {
+    checkError("Variable rule is read only",
+        "def func():",
+        "  rule = 'abc'");
+  }
+
+  public void testBuiltinGlobalFunctionsReadOnlyAsFuncDefArg() {
+    checkError("Variable rule is read only",
+        "def func(rule):",
+        "  return rule");
+  }
+
+  public void testFilesModulePlusStringErrorMessage() throws Exception {
+    checkError("unsupported operand type(s) for +: 'cmd_helper (a language module)' and 'string'",
+        "cmd_helper += 'a'");
+  }
+
+  public void testFunctionReturnsFunction() {
+    parse(
+        "def impl(ctx):",
+        "  return None",
+        "",
+        "skylark_rule = rule(implementation = impl)",
+        "",
+        "def macro(name):",
+        "  skylark_rule(name = name)");
+  }
+
+  public void testTypeForBooleanLiterals() {
+    parse("len([1, 2]) == 0 and True");
+    parse("len([1, 2]) == 0 and False");
+  }
+
+  public void testLoadRelativePathOneSegment() throws Exception {
+    parse("load('extension', 'a')\n");
+  }
+
+  public void testLoadAbsolutePathMultipleSegments() throws Exception {
+    parse("load('/pkg/extension', 'a')\n");
+  }
+
+  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");
+  }
+
+  private void parse(String... lines) {
+    parseFileForSkylark(Joiner.on("\n").join(lines));
+    syntaxEvents.assertNoEvents();
+  }
+
+  private void checkError(String errorMsg, String... lines) {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark(Joiner.on("\n").join(lines));
+    syntaxEvents.assertContainsEvent(errorMsg);
+  }
+}