// Copyright 2017 The Bazel Authors. 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.devtools.build.lib.syntax.Parser.ParsingLevel.LOCAL_LEVEL; import static com.google.devtools.build.lib.syntax.Parser.ParsingLevel.TOP_LEVEL; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.syntax.util.EvaluationTestCase; import java.io.IOException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests the {@code toString} and pretty printing methods for {@link ASTNode} subclasses. */ @RunWith(JUnit4.class) public class ASTPrettyPrintTest extends EvaluationTestCase { private String join(String... lines) { return Joiner.on("\n").join(lines); } /** * Asserts that the given node's pretty print at a given indent level matches the given string. */ private void assertPrettyMatches(ASTNode node, int indentLevel, String expected) { StringBuilder prettyBuilder = new StringBuilder(); try { node.prettyPrint(prettyBuilder, indentLevel); } catch (IOException e) { // Impossible for StringBuilder. throw new AssertionError(e); } assertThat(prettyBuilder.toString()).isEqualTo(expected); } /** Asserts that the given node's pretty print with no indent matches the given string. */ private void assertPrettyMatches(ASTNode node, String expected) { assertPrettyMatches(node, 0, expected); } /** Asserts that the given node's pretty print with one indent matches the given string. */ private void assertIndentedPrettyMatches(ASTNode node, String expected) { assertPrettyMatches(node, 1, expected); } /** Asserts that the given node's {@code toString} matches the given string. */ private void assertTostringMatches(ASTNode node, String expected) { assertThat(node.toString()).isEqualTo(expected); } /** * Parses the given string as an expression, and asserts that its pretty print matches the given * string. */ private void assertExprPrettyMatches(String source, String expected) { Expression node = parseExpression(source); assertPrettyMatches(node, expected); } /** * Parses the given string as an expression, and asserts that its {@code toString} matches the * given string. */ private void assertExprTostringMatches(String source, String expected) { Expression node = parseExpression(source); assertThat(node.toString()).isEqualTo(expected); } /** * Parses the given string as an expression, and asserts that both its pretty print and {@code * toString} return the original string. */ private void assertExprBothRoundTrip(String source) { assertExprPrettyMatches(source, source); assertExprTostringMatches(source, source); } /** * Parses the given string as a statement, and asserts that its pretty print with one indent * matches the given string. */ private void assertStmtIndentedPrettyMatches( Parser.ParsingLevel parsingLevel, String source, String expected) { Statement node = parseStatement(parsingLevel, source); assertIndentedPrettyMatches(node, expected); } /** * Parses the given string as an statement, and asserts that its {@code toString} matches the * given string. */ private void assertStmtTostringMatches( Parser.ParsingLevel parsingLevel, String source, String expected) { Statement node = parseStatement(parsingLevel, source); assertThat(node.toString()).isEqualTo(expected); } // Expressions. @Test public void abstractComprehension() { // Covers DictComprehension and ListComprehension. assertExprBothRoundTrip("[z for y in x if True for z in y]"); assertExprBothRoundTrip("{z: x for y in x if True for z in y}"); } @Test public void binaryOperatorExpression() { assertExprPrettyMatches("1 + 2", "(1 + 2)"); assertExprTostringMatches("1 + 2", "1 + 2"); assertExprPrettyMatches("1 + (2 * 3)", "(1 + (2 * 3))"); assertExprTostringMatches("1 + (2 * 3)", "1 + 2 * 3"); } @Test public void conditionalExpression() { assertExprBothRoundTrip("1 if True else 2"); } @Test public void dictionaryLiteral() { assertExprBothRoundTrip("{1: \"a\", 2: \"b\"}"); } @Test public void dotExpression() { assertExprBothRoundTrip("o.f"); } @Test public void funcallExpression() { assertExprBothRoundTrip("f()"); assertExprBothRoundTrip("f(a)"); assertExprBothRoundTrip("f(a, b = B, *c, d = D, **e)"); assertExprBothRoundTrip("o.f()"); } @Test public void identifier() { assertExprBothRoundTrip("foo"); } @Test public void indexExpression() { assertExprBothRoundTrip("a[i]"); } @Test public void integerLiteral() { assertExprBothRoundTrip("5"); } @Test public void listLiteralShort() { assertExprBothRoundTrip("[]"); assertExprBothRoundTrip("[5]"); assertExprBothRoundTrip("[5, 6]"); assertExprBothRoundTrip("()"); assertExprBothRoundTrip("(5,)"); assertExprBothRoundTrip("(5, 6)"); } @Test public void listLiteralLong() { // List literals with enough elements to trigger the abbreviated toString() format. assertExprPrettyMatches("[1, 2, 3, 4, 5, 6]", "[1, 2, 3, 4, 5, 6]"); assertExprTostringMatches("[1, 2, 3, 4, 5, 6]", "[1, 2, 3, 4, <2 more arguments>]"); assertExprPrettyMatches("(1, 2, 3, 4, 5, 6)", "(1, 2, 3, 4, 5, 6)"); assertExprTostringMatches("(1, 2, 3, 4, 5, 6)", "(1, 2, 3, 4, <2 more arguments>)"); } @Test public void listLiteralNested() { // Make sure that the inner list doesn't get abbreviated when the outer list is printed using // prettyPrint(). assertExprPrettyMatches( "[1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6]", "[1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6]"); // It doesn't matter as much what toString does. This case demonstrates an apparent bug in how // Printer#printList abbreviates the nested contents. We can keep this test around to help // monitor changes in the buggy behavior or eventually fix it. assertExprTostringMatches( "[1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6]", "[1, 2, 3, [10, 20, 30, 40, <2 more argu...<2 more arguments>], <3 more arguments>]"); } @Test public void sliceExpression() { assertExprBothRoundTrip("a[b:c:d]"); assertExprBothRoundTrip("a[b:c]"); assertExprBothRoundTrip("a[b:]"); assertExprBothRoundTrip("a[:c:d]"); assertExprBothRoundTrip("a[:c]"); assertExprBothRoundTrip("a[::d]"); assertExprBothRoundTrip("a[:]"); } @Test public void stringLiteral() { assertExprBothRoundTrip("\"foo\""); assertExprBothRoundTrip("\"quo\\\"ted\""); } @Test public void unaryOperatorExpression() { assertExprPrettyMatches("not True", "not (True)"); assertExprTostringMatches("not True", "not True"); assertExprPrettyMatches("-5", "-(5)"); assertExprTostringMatches("-5", "-5"); } // Statements. @Test public void assignmentStatement() { assertStmtIndentedPrettyMatches(LOCAL_LEVEL, "x = y", " x = y\n"); assertStmtTostringMatches(LOCAL_LEVEL, "x = y", "x = y\n"); } @Test public void augmentedAssignmentStatement() { assertStmtIndentedPrettyMatches(LOCAL_LEVEL, "x += y", " x += y\n"); assertStmtTostringMatches(LOCAL_LEVEL, "x += y", "x += y\n"); } @Test public void expressionStatement() { assertStmtIndentedPrettyMatches(LOCAL_LEVEL, "5", " 5\n"); assertStmtTostringMatches(LOCAL_LEVEL, "5", "5\n"); } @Test public void functionDefStatement() { assertStmtIndentedPrettyMatches( TOP_LEVEL, join("def f(x):", " print(x)"), join(" def f(x):", " print(x)", "")); assertStmtTostringMatches( TOP_LEVEL, join("def f(x):", " print(x)"), "def f(x): ...\n"); assertStmtIndentedPrettyMatches( TOP_LEVEL, join("def f(a, b=B, *c, d=D, **e):", " print(x)"), join(" def f(a, b=B, *c, d=D, **e):", " print(x)", "")); assertStmtTostringMatches( TOP_LEVEL, join("def f(a, b=B, *c, d=D, **e):", " print(x)"), "def f(a, b = B, *c, d = D, **e): ...\n"); assertStmtIndentedPrettyMatches( TOP_LEVEL, join("def f():", " pass"), join(" def f():", " pass", "")); assertStmtTostringMatches( TOP_LEVEL, join("def f():", " pass"), "def f(): ...\n"); } @Test public void flowStatement() { // The parser would complain if we tried to construct them from source. ASTNode breakNode = new FlowStatement(FlowStatement.Kind.BREAK); assertIndentedPrettyMatches(breakNode, " break\n"); assertTostringMatches(breakNode, "break\n"); ASTNode continueNode = new FlowStatement(FlowStatement.Kind.CONTINUE); assertIndentedPrettyMatches(continueNode, " continue\n"); assertTostringMatches(continueNode, "continue\n"); } @Test public void forStatement() { assertStmtIndentedPrettyMatches( LOCAL_LEVEL, join("for x in y:", " print(x)"), join(" for x in y:", " print(x)", "")); assertStmtTostringMatches( LOCAL_LEVEL, join("for x in y:", " print(x)"), "for x in y: ...\n"); assertStmtIndentedPrettyMatches( LOCAL_LEVEL, join("for x in y:", " pass"), join(" for x in y:", " pass", "")); assertStmtTostringMatches( LOCAL_LEVEL, join("for x in y:", " pass"), "for x in y: ...\n"); } @Test public void ifStatement() { assertStmtIndentedPrettyMatches( LOCAL_LEVEL, join("if True:", " print(x)"), join(" if True:", " print(x)", "")); assertStmtTostringMatches( LOCAL_LEVEL, join("if True:", " print(x)"), "if True: ...\n"); assertStmtIndentedPrettyMatches( LOCAL_LEVEL, join("if True:", " print(x)", "elif False:", " print(y)", "else:", " print(z)"), join(" if True:", " print(x)", " elif False:", " print(y)", " else:", " print(z)", "")); assertStmtTostringMatches( LOCAL_LEVEL, join("if True:", " print(x)", "elif False:", " print(y)", "else:", " print(z)"), "if True: ...\n"); } @Test public void loadStatement() { // load("foo.bzl", a="A", "B") ASTNode loadStatement = new LoadStatement( new StringLiteral("foo.bzl"), ImmutableMap.of(new Identifier("a"), "A", new Identifier("B"), "B")); assertIndentedPrettyMatches( loadStatement, " load(\"foo.bzl\", a=\"A\", \"B\")\n"); assertTostringMatches( loadStatement, "load(\"foo.bzl\", a=\"A\", \"B\")\n"); } @Test public void returnStatement() { assertIndentedPrettyMatches( new ReturnStatement(new StringLiteral("foo")), " return \"foo\"\n"); assertTostringMatches( new ReturnStatement(new StringLiteral("foo")), "return \"foo\"\n"); assertIndentedPrettyMatches(new ReturnStatement(new Identifier("None")), " return None\n"); assertTostringMatches(new ReturnStatement(new Identifier("None")), "return None\n"); assertIndentedPrettyMatches(new ReturnStatement(null), " return\n"); assertTostringMatches(new ReturnStatement(null), "return\n"); } // Miscellaneous. @Test public void buildFileAST() { ASTNode node = parseBuildFileASTWithoutValidation("print(x)\nprint(y)"); assertIndentedPrettyMatches( node, join(" print(x)", " print(y)", "")); assertTostringMatches( node, ""); } @Test public void comment() { Comment node = new Comment("foo"); assertIndentedPrettyMatches(node, " # foo"); assertTostringMatches(node, "foo"); } /* Not tested explicitly because they're covered implicitly by tests for other nodes: * - LValue * - DictionaryEntryLiteral * - passed arguments / formal parameters * - ConditionalStatements */ }