Open source all the tests under lib/syntax/.

--
MOS_MIGRATED_REVID=87244284
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
new file mode 100644
index 0000000..e698728
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
@@ -0,0 +1,876 @@
+// 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.syntax;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.DictionaryLiteral.DictionaryEntryLiteral;
+
+import java.util.List;
+
+/**
+ *  Tests of parser behaviour.
+ *
+ */
+public class ParserTest extends AbstractParserTestCase {
+
+  private static String getText(String text, ASTNode node) {
+    return text.substring(node.getLocation().getStartOffset(),
+                          node.getLocation().getEndOffset());
+  }
+
+  // helper func for testListLiterals:
+  private static int getIntElem(DictionaryEntryLiteral entry, boolean key) {
+    return ((IntegerLiteral) (key ? entry.getKey() : entry.getValue())).getValue();
+  }
+
+  // helper func for testListLiterals:
+  private static DictionaryEntryLiteral getElem(DictionaryLiteral list, int index) {
+    return list.getEntries().get(index);
+  }
+
+  // helper func for testListLiterals:
+  private static int getIntElem(ListLiteral list, int index) {
+    return ((IntegerLiteral) list.getElements().get(index)).getValue();
+  }
+
+  // helper func for testListLiterals:
+  private static Expression getElem(ListLiteral list, int index) {
+    return list.getElements().get(index);
+  }
+
+  // helper func for testing arguments:
+  private static Expression getArg(FuncallExpression f, int index) {
+    return f.getArguments().get(index).getValue();
+  }
+
+  public void testPrecedence1() throws Exception {
+    BinaryOperatorExpression e =
+      (BinaryOperatorExpression) parseExpr("'%sx' % 'foo' + 'bar'");
+
+    assertEquals(Operator.PLUS, e.getOperator());
+  }
+
+  public void testPrecedence2() throws Exception {
+    BinaryOperatorExpression e =
+      (BinaryOperatorExpression) parseExpr("('%sx' % 'foo') + 'bar'");
+    assertEquals(Operator.PLUS, e.getOperator());
+  }
+
+  public void testPrecedence3() throws Exception {
+    BinaryOperatorExpression e =
+      (BinaryOperatorExpression) parseExpr("'%sx' % ('foo' + 'bar')");
+    assertEquals(Operator.PERCENT, e.getOperator());
+  }
+
+  public void testPrecedence4() throws Exception {
+    BinaryOperatorExpression e =
+        (BinaryOperatorExpression) parseExpr("1 + - (2 - 3)");
+    assertEquals(Operator.PLUS, e.getOperator());
+  }
+
+  public void testUnaryMinusExpr() throws Exception {
+    FuncallExpression e = (FuncallExpression) parseExpr("-5");
+    FuncallExpression e2 = (FuncallExpression) parseExpr("- 5");
+
+    assertEquals("-", e.getFunction().getName());
+    assertEquals("-", e2.getFunction().getName());
+
+    assertThat(e.getArguments()).hasSize(1);
+    assertEquals(1, e.getNumPositionalArguments());
+
+    IntegerLiteral arg0 = (IntegerLiteral) e.getArguments().get(0).getValue();
+    assertEquals(5, (int) arg0.getValue());
+  }
+
+  public void testFuncallExpr() throws Exception {
+    FuncallExpression e = (FuncallExpression) parseExpr("foo(1, 2, bar=wiz)");
+
+    Ident ident = e.getFunction();
+    assertEquals("foo", ident.getName());
+
+    assertThat(e.getArguments()).hasSize(3);
+    assertEquals(2, e.getNumPositionalArguments());
+
+    IntegerLiteral arg0 = (IntegerLiteral) e.getArguments().get(0).getValue();
+    assertEquals(1, (int) arg0.getValue());
+
+    IntegerLiteral arg1 = (IntegerLiteral) e.getArguments().get(1).getValue();
+    assertEquals(2, (int) arg1.getValue());
+
+    Argument.Passed arg2 = e.getArguments().get(2);
+    assertEquals("bar", arg2.getName());
+    Ident arg2val = (Ident) arg2.getValue();
+    assertEquals("wiz", arg2val.getName());
+  }
+
+  public void testMethCallExpr() throws Exception {
+    FuncallExpression e =
+      (FuncallExpression) parseExpr("foo.foo(1, 2, bar=wiz)");
+
+    Ident ident = e.getFunction();
+    assertEquals("foo", ident.getName());
+
+    assertThat(e.getArguments()).hasSize(3);
+    assertEquals(2, e.getNumPositionalArguments());
+
+    IntegerLiteral arg0 = (IntegerLiteral) e.getArguments().get(0).getValue();
+    assertEquals(1, (int) arg0.getValue());
+
+    IntegerLiteral arg1 = (IntegerLiteral) e.getArguments().get(1).getValue();
+    assertEquals(2, (int) arg1.getValue());
+
+    Argument.Passed arg2 = e.getArguments().get(2);
+    assertEquals("bar", arg2.getName());
+    Ident arg2val = (Ident) arg2.getValue();
+    assertEquals("wiz", arg2val.getName());
+  }
+
+  public void testChainedMethCallExpr() throws Exception {
+    FuncallExpression e =
+      (FuncallExpression) parseExpr("foo.replace().split(1)");
+
+    Ident ident = e.getFunction();
+    assertEquals("split", ident.getName());
+
+    assertThat(e.getArguments()).hasSize(1);
+    assertEquals(1, e.getNumPositionalArguments());
+
+    IntegerLiteral arg0 = (IntegerLiteral) e.getArguments().get(0).getValue();
+    assertEquals(1, (int) arg0.getValue());
+  }
+
+  public void testPropRefExpr() throws Exception {
+    DotExpression e = (DotExpression) parseExpr("foo.foo");
+
+    Ident ident = e.getField();
+    assertEquals("foo", ident.getName());
+  }
+
+  public void testStringMethExpr() throws Exception {
+    FuncallExpression e = (FuncallExpression) parseExpr("'foo'.foo()");
+
+    Ident ident = e.getFunction();
+    assertEquals("foo", ident.getName());
+
+    assertThat(e.getArguments()).isEmpty();
+  }
+
+  public void testStringLiteralOptimizationValue() throws Exception {
+    StringLiteral l = (StringLiteral) parseExpr("'abc' + 'def'");
+    assertEquals("abcdef", l.value);
+  }
+
+  public void testStringLiteralOptimizationToString() throws Exception {
+    StringLiteral l = (StringLiteral) parseExpr("'abc' + 'def'");
+    assertEquals("'abcdef'", l.toString());
+  }
+
+  public void testStringLiteralOptimizationLocation() throws Exception {
+    StringLiteral l = (StringLiteral) parseExpr("'abc' + 'def'");
+    assertEquals(0, l.getLocation().getStartOffset());
+    assertEquals(13, l.getLocation().getEndOffset());
+  }
+
+  public void testStringLiteralOptimizationDifferentQuote() throws Exception {
+    assertThat(parseExpr("'abc' + \"def\"")).isInstanceOf(BinaryOperatorExpression.class);
+  }
+
+  public void testSubstring() throws Exception {
+    FuncallExpression e = (FuncallExpression) parseExpr("'FOO.CC'[:].lower()[1:]");
+    assertEquals("$substring", e.getFunction().getName());
+    assertThat(e.getArguments()).hasSize(2);
+
+    e = (FuncallExpression) parseExpr("'FOO.CC'.lower()[1:].startswith('oo')");
+    assertEquals("startswith", e.getFunction().getName());
+    assertThat(e.getArguments()).hasSize(1);
+
+    e = (FuncallExpression) parseExpr("'FOO.CC'[1:][:2]");
+    assertEquals("$substring", e.getFunction().getName());
+    assertThat(e.getArguments()).hasSize(2);
+  }
+
+  private void assertLocation(int start, int end, Location location)
+      throws Exception {
+    int actualStart = location.getStartOffset();
+    int actualEnd = location.getEndOffset();
+
+    if (actualStart != start || actualEnd != end) {
+      fail("Expected location = [" + start + ", " + end + "), found ["
+          + actualStart + ", " + actualEnd + ")");
+    }
+  }
+
+  public void testErrorRecovery() throws Exception {
+    syntaxEvents.setFailFast(false);
+
+    String expr = "f(1, [x for foo foo foo], 3)";
+    FuncallExpression e = (FuncallExpression) parseExpr(expr);
+
+    syntaxEvents.assertContainsEvent("syntax error at 'foo'");
+
+    // Test that the actual parameters are: (1, $error$, 3):
+
+    Ident ident = e.getFunction();
+    assertEquals("f", ident.getName());
+
+    assertThat(e.getArguments()).hasSize(3);
+    assertEquals(3, e.getNumPositionalArguments());
+
+    IntegerLiteral arg0 = (IntegerLiteral) e.getArguments().get(0).getValue();
+    assertEquals(1, (int) arg0.getValue());
+
+    Argument.Passed arg1 = e.getArguments().get(1);
+    Ident arg1val = ((Ident) arg1.getValue());
+    assertEquals("$error$", arg1val.getName());
+
+    assertLocation(5, 24, arg1val.getLocation());
+    assertEquals("[x for foo foo foo]", expr.substring(5, 24));
+    assertEquals(25, arg1val.getLocation().getEndLineAndColumn().getColumn());
+
+    IntegerLiteral arg2 = (IntegerLiteral) e.getArguments().get(2).getValue();
+    assertEquals(3, (int) arg2.getValue());
+  }
+
+  public void testDoesntGetStuck() throws Exception {
+    syntaxEvents.setFailFast(false);
+
+    // Make sure the parser does not get stuck when trying
+    // to parse an expression containing a syntax error.
+    // This usually results in OutOfMemoryError because the
+    // parser keeps filling up the error log.
+    // We need to make sure that we will always advance
+    // in the token stream.
+    parseExpr("f(1, ], 3)");
+    parseExpr("f(1, ), 3)");
+    parseExpr("[ ) for v in 3)");
+
+    syntaxEvents.assertContainsEvent(""); // "" matches any;
+                                          // i.e. there were some events
+  }
+
+  public void testSecondaryLocation() {
+    String expr = "f(1 % 2)";
+    FuncallExpression call = (FuncallExpression) parseExpr(expr);
+    Argument.Passed arg = call.getArguments().get(0);
+    assertTrue(arg.getLocation().getEndOffset() < call.getLocation().getEndOffset());
+  }
+
+  public void testPrimaryLocation() {
+    String expr = "f(1 + 2)";
+    FuncallExpression call = (FuncallExpression) parseExpr(expr);
+    Argument.Passed arg = call.getArguments().get(0);
+    assertTrue(arg.getLocation().getEndOffset() < call.getLocation().getEndOffset());
+  }
+
+  public void testAssignLocation() {
+    String expr = "a = b;c = d\n";
+    List<Statement> statements = parseFile(expr);
+    Statement statement = statements.get(0);
+    assertEquals(5, statement.getLocation().getEndOffset());
+  }
+
+  public void testAssign() {
+    String expr = "list[0] = 5; dict['key'] = value\n";
+    List<Statement> statements = parseFile(expr);
+    assertThat(statements).hasSize(2);
+  }
+
+  public void testInvalidAssign() {
+    syntaxEvents.setFailFast(false);
+    parseExpr("1 + (b = c)");
+    syntaxEvents.assertContainsEvent("syntax error");
+    syntaxEvents.collector().clear();
+  }
+
+  public void testAugmentedAssign() throws Exception {
+    assertEquals("[x = x + 1\n]", parseFile("x += 1").toString());
+  }
+
+  public void testPrettyPrintFunctions() throws Exception {
+    assertEquals("[x[1:3]\n]", parseFile("x[1:3]").toString());
+    assertEquals("[str[42]\n]", parseFile("str[42]").toString());
+    assertEquals("[ctx.new_file(['hello'])\n]", parseFile("ctx.new_file('hello')").toString());
+    assertEquals("[new_file(['hello'])\n]", parseFile("new_file('hello')").toString());
+  }
+
+  public void testFuncallLocation() {
+    String expr = "a(b);c = d\n";
+    List<Statement> statements = parseFile(expr);
+    Statement statement = statements.get(0);
+    assertEquals(4, statement.getLocation().getEndOffset());
+  }
+
+  public void testSpecialFuncallLocation() throws Exception {
+    List<Statement> statements = parseFile("-x\n");
+    assertLocation(0, 3, statements.get(0).getLocation());
+
+    statements = parseFile("arr[15]\n");
+    assertLocation(0, 8, statements.get(0).getLocation());
+
+    statements = parseFile("str[1:12]\n");
+    assertLocation(0, 10, statements.get(0).getLocation());
+  }
+
+  public void testListPositions() throws Exception {
+    String expr = "[0,f(1),2]";
+    ListLiteral list = (ListLiteral) parseExpr(expr);
+    assertEquals("[0,f(1),2]", getText(expr, list));
+    assertEquals("0",    getText(expr, getElem(list, 0)));
+    assertEquals("f(1)", getText(expr, getElem(list, 1)));
+    assertEquals("2",    getText(expr, getElem(list, 2)));
+  }
+
+  public void testDictPositions() throws Exception {
+    String expr = "{1:2,2:f(1),3:4}";
+    DictionaryLiteral list = (DictionaryLiteral) parseExpr(expr);
+    assertEquals("{1:2,2:f(1),3:4}", getText(expr, list));
+    assertEquals("1:2",    getText(expr, getElem(list, 0)));
+    assertEquals("2:f(1)", getText(expr, getElem(list, 1)));
+    assertEquals("3:4",    getText(expr, getElem(list, 2)));
+  }
+
+  public void testArgumentPositions() throws Exception {
+    String stmt = "f(0,g(1,2),2)";
+    FuncallExpression f = (FuncallExpression) parseExpr(stmt);
+    assertEquals(stmt, getText(stmt, f));
+    assertEquals("0",    getText(stmt, getArg(f, 0)));
+    assertEquals("g(1,2)", getText(stmt, getArg(f, 1)));
+    assertEquals("2",    getText(stmt, getArg(f, 2)));
+  }
+
+  public void testListLiterals1() throws Exception {
+    ListLiteral list = (ListLiteral) parseExpr("[0,1,2]");
+    assertFalse(list.isTuple());
+    assertThat(list.getElements()).hasSize(3);
+    assertFalse(list.isTuple());
+    for (int i = 0; i < 3; ++i) {
+      assertEquals(i, getIntElem(list, i));
+    }
+  }
+
+  public void testTupleLiterals2() throws Exception {
+    ListLiteral tuple = (ListLiteral) parseExpr("(0,1,2)");
+    assertTrue(tuple.isTuple());
+    assertThat(tuple.getElements()).hasSize(3);
+    assertTrue(tuple.isTuple());
+    for (int i = 0; i < 3; ++i) {
+      assertEquals(i, getIntElem(tuple, i));
+    }
+  }
+
+  public void testTupleLiterals3() throws Exception {
+    ListLiteral emptyTuple = (ListLiteral) parseExpr("()");
+    assertTrue(emptyTuple.isTuple());
+    assertThat(emptyTuple.getElements()).isEmpty();
+  }
+
+  public void testTupleLiterals4() throws Exception {
+    ListLiteral singletonTuple = (ListLiteral) parseExpr("(42,)");
+    assertTrue(singletonTuple.isTuple());
+    assertThat(singletonTuple.getElements()).hasSize(1);
+    assertEquals(42, getIntElem(singletonTuple, 0));
+  }
+
+  public void testTupleLiterals5() throws Exception {
+    IntegerLiteral intLit = (IntegerLiteral) parseExpr("(42)"); // not a tuple!
+    assertEquals(42, (int) intLit.getValue());
+  }
+
+  public void testListLiterals6() throws Exception {
+    ListLiteral emptyList = (ListLiteral) parseExpr("[]");
+    assertFalse(emptyList.isTuple());
+    assertThat(emptyList.getElements()).isEmpty();
+  }
+
+  public void testListLiterals7() throws Exception {
+    ListLiteral singletonList = (ListLiteral) parseExpr("[42,]");
+    assertFalse(singletonList.isTuple());
+    assertThat(singletonList.getElements()).hasSize(1);
+    assertEquals(42, getIntElem(singletonList, 0));
+  }
+
+  public void testListLiterals8() throws Exception {
+    ListLiteral singletonList = (ListLiteral) parseExpr("[42]"); // a singleton
+    assertFalse(singletonList.isTuple());
+    assertThat(singletonList.getElements()).hasSize(1);
+    assertEquals(42, getIntElem(singletonList, 0));
+  }
+
+  public void testDictionaryLiterals() throws Exception {
+    DictionaryLiteral dictionaryList =
+      (DictionaryLiteral) parseExpr("{1:42}"); // a singleton dictionary
+    assertThat(dictionaryList.getEntries()).hasSize(1);
+    DictionaryEntryLiteral tuple = getElem(dictionaryList, 0);
+    assertEquals(1, getIntElem(tuple, true));
+    assertEquals(42, getIntElem(tuple, false));
+  }
+
+  public void testDictionaryLiterals1() throws Exception {
+    DictionaryLiteral dictionaryList =
+      (DictionaryLiteral) parseExpr("{}"); // an empty dictionary
+    assertThat(dictionaryList.getEntries()).isEmpty();
+  }
+
+  public void testDictionaryLiterals2() throws Exception {
+    DictionaryLiteral dictionaryList =
+      (DictionaryLiteral) parseExpr("{1:42,}"); // a singleton dictionary
+    assertThat(dictionaryList.getEntries()).hasSize(1);
+    DictionaryEntryLiteral tuple = getElem(dictionaryList, 0);
+    assertEquals(1, getIntElem(tuple, true));
+    assertEquals(42, getIntElem(tuple, false));
+  }
+
+  public void testDictionaryLiterals3() throws Exception {
+    DictionaryLiteral dictionaryList = (DictionaryLiteral) parseExpr("{1:42,2:43,3:44}");
+    assertThat(dictionaryList.getEntries()).hasSize(3);
+    for (int i = 0; i < 3; i++) {
+      DictionaryEntryLiteral tuple = getElem(dictionaryList, i);
+      assertEquals(i + 1, getIntElem(tuple, true));
+      assertEquals(i + 42, getIntElem(tuple, false));
+    }
+  }
+
+  public void testListLiterals9() throws Exception {
+    ListLiteral singletonList =
+      (ListLiteral) parseExpr("[ abi + opt_level + \'/include\' ]");
+    assertFalse(singletonList.isTuple());
+    assertThat(singletonList.getElements()).hasSize(1);
+  }
+
+  public void testListComprehensionSyntax() throws Exception {
+    syntaxEvents.setFailFast(false);
+
+    parseExpr("[x for");
+    syntaxEvents.assertContainsEvent("syntax error at 'newline'");
+    syntaxEvents.collector().clear();
+
+    parseExpr("[x for x");
+    syntaxEvents.assertContainsEvent("syntax error at 'newline'");
+    syntaxEvents.collector().clear();
+
+    parseExpr("[x for x in");
+    syntaxEvents.assertContainsEvent("syntax error at 'newline'");
+    syntaxEvents.collector().clear();
+
+    parseExpr("[x for x in []");
+    syntaxEvents.assertContainsEvent("syntax error at 'newline'");
+    syntaxEvents.collector().clear();
+
+    parseExpr("[x for x for y in ['a']]");
+    syntaxEvents.assertContainsEvent("syntax error at 'for'");
+    syntaxEvents.collector().clear();
+  }
+
+  public void testListComprehension() throws Exception {
+    ListComprehension list =
+      (ListComprehension) parseExpr(
+          "['foo/%s.java' % x "
+          + "for x in []]");
+    assertThat(list.getLists()).hasSize(1);
+
+    list = (ListComprehension) parseExpr("['foo/%s.java' % x "
+        + "for x in ['bar', 'wiz', 'quux']]");
+    assertThat(list.getLists()).hasSize(1);
+
+    list = (ListComprehension) parseExpr("['%s/%s.java' % (x, y) "
+        + "for x in ['foo', 'bar'] for y in ['baz', 'wiz', 'quux']]");
+    assertThat(list.getLists()).hasSize(2);
+  }
+
+  public void testParserContainsErrorsIfSyntaxException() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseExpr("'foo' %%");
+    syntaxEvents.assertContainsEvent("syntax error at '%'");
+  }
+
+  public void testParserDoesNotContainErrorsIfSuccess() throws Exception {
+    parseExpr("'foo'");
+  }
+
+  public void testParserContainsErrors() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseStmt("+");
+    syntaxEvents.assertContainsEvent("syntax error at '+'");
+  }
+
+  public void testSemicolonAndNewline() throws Exception {
+    List<Statement> stmts = parseFile(
+      "foo='bar'; foo(bar)" + '\n'
+      + "" + '\n'
+      + "foo='bar'; foo(bar)"
+    );
+    assertThat(stmts).hasSize(4);
+  }
+
+  public void testSemicolonAndNewline2() throws Exception {
+    syntaxEvents.setFailFast(false);
+    List<Statement> stmts = parseFile(
+      "foo='foo' error(bar)" + '\n'
+      + "" + '\n'
+    );
+    syntaxEvents.assertContainsEvent("syntax error at 'error'");
+    assertThat(stmts).hasSize(2);
+  }
+
+  public void testExprAsStatement() throws Exception {
+    List<Statement> stmts = parseFile(
+      "li = []\n"
+      + "li.append('a.c')\n"
+      + "\"\"\" string comment \"\"\"\n"
+      + "foo(bar)"
+    );
+    assertThat(stmts).hasSize(4);
+  }
+
+  public void testParseBuildFileWithSingeRule() throws Exception {
+    List<Statement> stmts = parseFile(
+      "genrule(name = 'foo'," + '\n'
+      + "   srcs = ['input.csv']," + '\n'
+      + "   outs = [ 'result.txt'," + '\n'
+      + "           'result.log']," + '\n'
+      + "   cmd = 'touch result.txt result.log')" + '\n'
+      );
+    assertThat(stmts).hasSize(1);
+  }
+
+  public void testParseBuildFileWithMultipleRules() throws Exception {
+    List<Statement> stmts = parseFile(
+      "genrule(name = 'foo'," + '\n'
+      + "   srcs = ['input.csv']," + '\n'
+      + "   outs = [ 'result.txt'," + '\n'
+      + "           'result.log']," + '\n'
+      + "   cmd = 'touch result.txt result.log')" + '\n'
+      + "" + '\n'
+      + "genrule(name = 'bar'," + '\n'
+      + "   srcs = ['input.csv']," + '\n'
+      + "   outs = [ 'graph.svg']," + '\n'
+      + "   cmd = 'touch graph.svg')" + '\n'
+      );
+    assertThat(stmts).hasSize(2);
+  }
+
+  public void testParseBuildFileWithComments() throws Exception {
+    Parser.ParseResult result = parseFileWithComments(
+      "# Test BUILD file" + '\n'
+      + "# with multi-line comment" + '\n'
+      + "" + '\n'
+      + "genrule(name = 'foo'," + '\n'
+      + "   srcs = ['input.csv']," + '\n'
+      + "   outs = [ 'result.txt'," + '\n'
+      + "           'result.log']," + '\n'
+      + "   cmd = 'touch result.txt result.log')" + '\n'
+      );
+    assertThat(result.statements).hasSize(1);
+    assertThat(result.comments).hasSize(2);
+  }
+
+  public void testParseBuildFileWithManyComments() throws Exception {
+    Parser.ParseResult result = parseFileWithComments(
+      "# 1" + '\n'
+      + "# 2" + '\n'
+      + "" + '\n'
+      + "# 4 " + '\n'
+      + "# 5" + '\n'
+      + "#" + '\n' // 6 - find empty comment for syntax highlighting
+      + "# 7 " + '\n'
+      + "# 8" + '\n'
+      + "genrule(name = 'foo'," + '\n'
+      + "   srcs = ['input.csv']," + '\n'
+      + "   # 11" + '\n'
+      + "   outs = [ 'result.txt'," + '\n'
+      + "           'result.log'], # 13" + '\n'
+      + "   cmd = 'touch result.txt result.log')" + '\n'
+      + "# 15" + '\n'
+      );
+    assertThat(result.statements).hasSize(1); // Single genrule
+    StringBuilder commentLines = new StringBuilder();
+    for (Comment comment : result.comments) {
+      // Comments start and end on the same line
+      assertEquals(comment.getLocation().getStartLineAndColumn().getLine() + " ends on "
+          + comment.getLocation().getEndLineAndColumn().getLine(),
+          comment.getLocation().getStartLineAndColumn().getLine(),
+          comment.getLocation().getEndLineAndColumn().getLine());
+      commentLines.append('(');
+      commentLines.append(comment.getLocation().getStartLineAndColumn().getLine());
+      commentLines.append(',');
+      commentLines.append(comment.getLocation().getStartLineAndColumn().getColumn());
+      commentLines.append(") ");
+    }
+    assertWithMessage("Found: " + commentLines)
+        .that(result.comments.size()).isEqualTo(10); // One per '#'
+  }
+
+  public void testMissingComma() throws Exception {
+    syntaxEvents.setFailFast(false);
+    // Regression test.
+    // Note: missing comma after name='foo'
+    parseFile("genrule(name = 'foo'\n"
+              + "      srcs = ['in'])");
+    syntaxEvents.assertContainsEvent("syntax error at 'srcs'");
+  }
+
+  public void testDoubleSemicolon() throws Exception {
+    syntaxEvents.setFailFast(false);
+    // Regression test.
+    parseFile("x = 1; ; x = 2;");
+    syntaxEvents.assertContainsEvent("syntax error at ';'");
+  }
+
+  public void testFunctionDefinitionErrorRecovery() throws Exception {
+    // Parser skips over entire function definitions, and reports a meaningful
+    // error.
+    syntaxEvents.setFailFast(false);
+    List<Statement> stmts = parseFile(
+        "x = 1;\n"
+        + "def foo(x, y, **z):\n"
+        + "  # a comment\n"
+        + "  x = 2\n"
+        + "  foo(bar)\n"
+        + "  return z\n"
+        + "x = 3");
+    assertThat(stmts).hasSize(2);
+  }
+
+  public void testFunctionDefinitionIgnored() throws Exception {
+    // Parser skips over entire function definitions without reporting error,
+    // when parsePython is set to true.
+    List<Statement> stmts = parseFile(
+        "x = 1;\n"
+        + "def foo(x, y, **z):\n"
+        + "  # a comment\n"
+        + "  if true:"
+        + "    x = 2\n"
+        + "  foo(bar)\n"
+        + "  return z\n"
+        + "x = 3", true /* parsePython */);
+    assertThat(stmts).hasSize(2);
+
+    stmts = parseFile(
+        "x = 1;\n"
+        + "def foo(x, y, **z): return x\n"
+        + "x = 3", true /* parsePython */);
+    assertThat(stmts).hasSize(2);
+  }
+
+  public void testMissingBlock() throws Exception {
+    syntaxEvents.setFailFast(false);
+    List<Statement> stmts = parseFile(
+        "x = 1;\n"
+        + "def foo(x):\n"
+        + "x = 2;\n",
+        true /* parsePython */);
+    assertThat(stmts).hasSize(2);
+    syntaxEvents.assertContainsEvent("expected an indented block");
+  }
+
+  public void testInvalidDef() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile(
+        "x = 1;\n"
+        + "def foo(x)\n"
+        + "x = 2;\n",
+        true /* parsePython */);
+    syntaxEvents.assertContainsEvent("syntax error at 'EOF'");
+  }
+
+  public void testSkipIfBlock() throws Exception {
+    // Skip over 'if' blocks, when parsePython is set
+    List<Statement> stmts = parseFile(
+        "x = 1;\n"
+        + "if x == 1:\n"
+        + "  foo(x)\n"
+        + "else:\n"
+        + "  bar(x)\n"
+        + "x = 3;\n",
+        true /* parsePython */);
+    assertThat(stmts).hasSize(2);
+  }
+
+  public void testSkipIfBlockFail() throws Exception {
+    // Do not parse 'if' blocks, when parsePython is not set
+    syntaxEvents.setFailFast(false);
+    List<Statement> stmts = parseFile(
+        "x = 1;\n"
+        + "if x == 1:\n"
+        + "  x = 2\n"
+        + "x = 3;\n",
+        false /* no parsePython */);
+    assertThat(stmts).hasSize(2);
+    syntaxEvents.assertContainsEvent("This Python-style construct is not supported");
+  }
+
+  public void testForLoopMultipleVariablesFail() throws Exception {
+    // For loops with multiple variables are not allowed, when parsePython is not set
+    syntaxEvents.setFailFast(false);
+    List<Statement> stmts = parseFile(
+        "[ i for i, j, k in [(1, 2, 3)] ]\n",
+        false /* no parsePython */);
+    assertThat(stmts).hasSize(1);
+    syntaxEvents.assertContainsEvent("For loops with multiple variables are not yet supported.");
+  }
+
+  public void testForLoopMultipleVariables() throws Exception {
+    // For loops with multiple variables is ok, when parsePython is set
+    List<Statement> stmts1 = parseFile(
+        "[ i for i, j, k in [(1, 2, 3)] ]\n",
+        true /* parsePython */);
+    assertThat(stmts1).hasSize(1);
+
+    List<Statement> stmts2 = parseFile(
+        "[ i for i, j in [(1, 2, 3)] ]\n",
+        true /* parsePython */);
+    assertThat(stmts2).hasSize(1);
+
+    List<Statement> stmts3 = parseFile(
+        "[ i for (i, j, k) in [(1, 2, 3)] ]\n",
+        true /* parsePython */);
+    assertThat(stmts3).hasSize(1);
+  }
+
+  public void testForLoopBadSyntax() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile(
+        "[1 for (a, b, c in var]\n",
+        false /* no parsePython */);
+    syntaxEvents.assertContainsEvent("syntax error");
+  }
+
+  public void testForLoopBadSyntax2() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile(
+        "[1 for () in var]\n",
+        false /* no parsePython */);
+    syntaxEvents.assertContainsEvent("syntax error");
+  }
+
+  public void testFunCallBadSyntax() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile("f(1,\n");
+    syntaxEvents.assertContainsEvent("syntax error");
+  }
+
+  public void testFunCallBadSyntax2() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile("f(1, 5, ,)\n");
+    syntaxEvents.assertContainsEvent("syntax error");
+  }
+
+  public void testLoadOneSymbol() throws Exception {
+    List<Statement> statements = parseFileForSkylark(
+        "load('/foo/bar/file', 'fun_test')\n");
+    LoadStatement stmt = (LoadStatement) statements.get(0);
+    assertEquals("/foo/bar/file.bzl", stmt.getImportPath().toString());
+    assertThat(stmt.getSymbols()).hasSize(1);
+  }
+
+  public void testLoadMultipleSymbols() throws Exception {
+    List<Statement> statements = parseFileForSkylark(
+        "load('file', 'foo', 'bar')\n");
+    LoadStatement stmt = (LoadStatement) statements.get(0);
+    assertEquals("file.bzl", stmt.getImportPath().toString());
+    assertThat(stmt.getSymbols()).hasSize(2);
+  }
+
+  public void testLoadSyntaxError() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark("load(non_quoted, 'a')\n");
+    syntaxEvents.assertContainsEvent("syntax error");
+  }
+
+  public void testLoadSyntaxError2() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark("load('non_quoted', a)\n");
+    syntaxEvents.assertContainsEvent("syntax error");
+  }
+
+  public void testLoadNotAtTopLevel() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark("if 1: load(8)\n");
+    syntaxEvents.assertContainsEvent("function 'load' does not exist");
+  }
+
+  public void testParseErrorNotComparison() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile("2 < not 3");
+    syntaxEvents.assertContainsEvent("syntax error at 'not'");
+  }
+
+  public void testNotWithArithmeticOperatorsBadSyntax() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile("0 + not 0");
+    syntaxEvents.assertContainsEvent("syntax error at 'not'");
+  }
+
+  public void testOptionalArgBeforeMandatoryArgInFuncDef() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark("def func(a, b = 'a', c):\n  return 0\n");
+    syntaxEvents.assertContainsEvent(
+        "a mandatory positional parameter must not follow an optional parameter");
+  }
+
+  public void testKwargBeforePositionalArg() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark(
+        "def func(a, b): return a + b\n"
+        + "func(**{'b': 1}, 'a')");
+    syntaxEvents.assertContainsEvent("unexpected tokens after kwarg");
+  }
+
+  public void testDuplicateKwarg() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark(
+        "def func(a, b): return a + b\n"
+        + "func(**{'b': 1}, **{'a': 2})");
+    syntaxEvents.assertContainsEvent("unexpected tokens after kwarg");
+  }
+
+  public void testUnnamedStar() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark(
+        "def func(a, b1=2, b2=3, *, c1, c2, d=4): return a + b1 + b2 + c1 + c2 + d\n");
+    syntaxEvents.assertContainsEvent("no star, star-star or named-only parameters (for now)");
+  }
+
+  public void testTopLevelForFails() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark("for i in []: 0\n");
+    syntaxEvents.assertContainsEvent(
+        "for loops are not allowed on top-level. Put it into a function");
+  }
+
+  public void testNestedFunctionFails() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark(
+          "def func(a):\n"
+        + "  def bar(): return 0\n"
+        + "  return bar()\n");
+    syntaxEvents.assertContainsEvent(
+        "nested functions are not allowed. Move the function to top-level");
+  }
+
+  public void testIncludeFailureSkylark() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFileForSkylark("include('//foo:bar')");
+    syntaxEvents.assertContainsEvent("function 'include' does not exist");
+  }
+
+  public void testIncludeFailure() throws Exception {
+    syntaxEvents.setFailFast(false);
+    parseFile("include('nonexistent')\n");
+    syntaxEvents.assertContainsEvent("Invalid label 'nonexistent'");
+  }
+}