Home | History | Annotate | Download | only in gn
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <iostream>
      6 #include <sstream>
      7 
      8 #include "testing/gtest/include/gtest/gtest.h"
      9 #include "tools/gn/input_file.h"
     10 #include "tools/gn/parser.h"
     11 #include "tools/gn/tokenizer.h"
     12 
     13 namespace {
     14 
     15 bool GetTokens(const InputFile* input, std::vector<Token>* result) {
     16   result->clear();
     17   Err err;
     18   *result = Tokenizer::Tokenize(input, &err);
     19   return !err.has_error();
     20 }
     21 
     22 bool IsIdentifierEqual(const ParseNode* node, const char* val) {
     23   if (!node)
     24     return false;
     25   const IdentifierNode* ident = node->AsIdentifier();
     26   if (!ident)
     27     return false;
     28   return ident->value().value() == val;
     29 }
     30 
     31 bool IsLiteralEqual(const ParseNode* node, const char* val) {
     32   if (!node)
     33     return false;
     34   const LiteralNode* lit = node->AsLiteral();
     35   if (!lit)
     36     return false;
     37   return lit->value().value() == val;
     38 }
     39 
     40 // Returns true if the given node as a simple assignment to a given value.
     41 bool IsAssignment(const ParseNode* node, const char* ident, const char* value) {
     42   if (!node)
     43     return false;
     44   const BinaryOpNode* binary = node->AsBinaryOp();
     45   if (!binary)
     46     return false;
     47   return binary->op().IsOperatorEqualTo("=") &&
     48          IsIdentifierEqual(binary->left(), ident) &&
     49          IsLiteralEqual(binary->right(), value);
     50 }
     51 
     52 // Returns true if the given node is a block with one assignment statement.
     53 bool IsBlockWithAssignment(const ParseNode* node,
     54                            const char* ident, const char* value) {
     55   if (!node)
     56     return false;
     57   const BlockNode* block = node->AsBlock();
     58   if (!block)
     59     return false;
     60   if (block->statements().size() != 1)
     61     return false;
     62   return IsAssignment(block->statements()[0], ident, value);
     63 }
     64 
     65 void DoParserPrintTest(const char* input, const char* expected) {
     66   std::vector<Token> tokens;
     67   InputFile input_file(SourceFile("/test"));
     68   input_file.SetContents(input);
     69   ASSERT_TRUE(GetTokens(&input_file, &tokens));
     70 
     71   Err err;
     72   scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
     73   ASSERT_TRUE(result);
     74 
     75   std::ostringstream collector;
     76   result->Print(collector, 0);
     77 
     78   EXPECT_EQ(expected, collector.str());
     79 }
     80 
     81 // Expects the tokenizer or parser to identify an error at the given line and
     82 // character.
     83 void DoParserErrorTest(const char* input, int err_line, int err_char) {
     84   InputFile input_file(SourceFile("/test"));
     85   input_file.SetContents(input);
     86 
     87   Err err;
     88   std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
     89   if (!err.has_error()) {
     90     scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
     91     ASSERT_FALSE(result);
     92     ASSERT_TRUE(err.has_error());
     93   }
     94 
     95   EXPECT_EQ(err_line, err.location().line_number());
     96   EXPECT_EQ(err_char, err.location().char_offset());
     97 }
     98 
     99 }  // namespace
    100 
    101 TEST(Parser, BinaryOp) {
    102   std::vector<Token> tokens;
    103 
    104   // Simple set expression.
    105   InputFile expr_input(SourceFile("/test"));
    106   expr_input.SetContents("a=2");
    107   ASSERT_TRUE(GetTokens(&expr_input, &tokens));
    108   Err err;
    109   Parser set(tokens, &err);
    110   scoped_ptr<ParseNode> expr = set.ParseExpression();
    111   ASSERT_TRUE(expr);
    112 
    113   const BinaryOpNode* binary_op = expr->AsBinaryOp();
    114   ASSERT_TRUE(binary_op);
    115 
    116   EXPECT_TRUE(binary_op->left()->AsIdentifier());
    117 
    118   EXPECT_TRUE(binary_op->op().type() == Token::OPERATOR);
    119   EXPECT_TRUE(binary_op->op().value() == "=");
    120 
    121   EXPECT_TRUE(binary_op->right()->AsLiteral());
    122 }
    123 
    124 TEST(Parser, Condition) {
    125   std::vector<Token> tokens;
    126 
    127   InputFile cond_input(SourceFile("/test"));
    128   cond_input.SetContents("if(1) { a = 2 }");
    129   ASSERT_TRUE(GetTokens(&cond_input, &tokens));
    130   Err err;
    131   Parser simple_if(tokens, &err);
    132   scoped_ptr<ConditionNode> cond = simple_if.ParseCondition();
    133   ASSERT_TRUE(cond);
    134 
    135   EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1"));
    136   EXPECT_FALSE(cond->if_false());  // No else block.
    137   EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2"));
    138 
    139   // Now try a complicated if/else if/else one.
    140   InputFile complex_if_input(SourceFile("/test"));
    141   complex_if_input.SetContents(
    142       "if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }");
    143   ASSERT_TRUE(GetTokens(&complex_if_input, &tokens));
    144   Parser complex_if(tokens, &err);
    145   cond = complex_if.ParseCondition();
    146   ASSERT_TRUE(cond);
    147 
    148   EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1"));
    149   EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2"));
    150 
    151   ASSERT_TRUE(cond->if_false());
    152   const ConditionNode* nested_cond = cond->if_false()->AsConditionNode();
    153   ASSERT_TRUE(nested_cond);
    154   EXPECT_TRUE(IsLiteralEqual(nested_cond->condition(), "0"));
    155   EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_true(), "a", "3"));
    156   EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_false(), "a", "4"));
    157 }
    158 
    159 TEST(Parser, FunctionCall) {
    160   const char* input = "foo(a, 1, 2,) bar()";
    161   const char* expected =
    162       "BLOCK\n"
    163       " FUNCTION(foo)\n"
    164       "  LIST\n"
    165       "   IDENTIFIER(a)\n"
    166       "   LITERAL(1)\n"
    167       "   LITERAL(2)\n"
    168       " FUNCTION(bar)\n"
    169       "  LIST\n";
    170   DoParserPrintTest(input, expected);
    171 }
    172 
    173 TEST(Parser, ParenExpression) {
    174   const char* input = "(foo(1)) + (a + b)";
    175   const char* expected =
    176       "BLOCK\n"
    177       " BINARY(+)\n"
    178       "  FUNCTION(foo)\n"
    179       "   LIST\n"
    180       "    LITERAL(1)\n"
    181       "  BINARY(+)\n"
    182       "   IDENTIFIER(a)\n"
    183       "   IDENTIFIER(b)\n";
    184   DoParserPrintTest(input, expected);
    185   DoParserErrorTest("(a +", 1, 4);
    186 }
    187 
    188 TEST(Parser, UnaryOp) {
    189   std::vector<Token> tokens;
    190 
    191   InputFile ident_input(SourceFile("/test"));
    192   ident_input.SetContents("!foo");
    193   ASSERT_TRUE(GetTokens(&ident_input, &tokens));
    194   Err err;
    195   Parser ident(tokens, &err);
    196   scoped_ptr<UnaryOpNode> op = ident.ParseUnaryOp();
    197 
    198   ASSERT_TRUE(op);
    199   EXPECT_TRUE(op->op().type() == Token::OPERATOR);
    200   EXPECT_TRUE(op->op().value() == "!");
    201 }
    202 
    203 TEST(Parser, CompleteFunction) {
    204   const char* input =
    205       "cc_test(\"foo\") {\n"
    206       "  sources = [\n"
    207       "    \"foo.cc\",\n"
    208       "    \"foo.h\"\n"
    209       "  ]\n"
    210       "  dependencies = [\n"
    211       "    \"base\"\n"
    212       "  ]\n"
    213       "}\n";
    214   const char* expected =
    215       "BLOCK\n"
    216       " FUNCTION(cc_test)\n"
    217       "  LIST\n"
    218       "   LITERAL(\"foo\")\n"
    219       "  BLOCK\n"
    220       "   BINARY(=)\n"
    221       "    IDENTIFIER(sources)\n"
    222       "    LIST\n"
    223       "     LITERAL(\"foo.cc\")\n"
    224       "     LITERAL(\"foo.h\")\n"
    225       "   BINARY(=)\n"
    226       "    IDENTIFIER(dependencies)\n"
    227       "    LIST\n"
    228       "     LITERAL(\"base\")\n";
    229   DoParserPrintTest(input, expected);
    230 }
    231 
    232 TEST(Parser, FunctionWithConditional) {
    233   const char* input =
    234       "cc_test(\"foo\") {\n"
    235       "  sources = [\"foo.cc\"]\n"
    236       "  if (OS == \"mac\") {\n"
    237       "    sources += \"bar.cc\"\n"
    238       "  } else if (OS == \"win\") {\n"
    239       "    sources -= [\"asd.cc\", \"foo.cc\"]\n"
    240       "  } else {\n"
    241       "    dependencies += [\"bar.cc\"]\n"
    242       "  }\n"
    243       "}\n";
    244   const char* expected =
    245       "BLOCK\n"
    246       " FUNCTION(cc_test)\n"
    247       "  LIST\n"
    248       "   LITERAL(\"foo\")\n"
    249       "  BLOCK\n"
    250       "   BINARY(=)\n"
    251       "    IDENTIFIER(sources)\n"
    252       "    LIST\n"
    253       "     LITERAL(\"foo.cc\")\n"
    254       "   CONDITION\n"
    255       "    BINARY(==)\n"
    256       "     IDENTIFIER(OS)\n"
    257       "     LITERAL(\"mac\")\n"
    258       "    BLOCK\n"
    259       "     BINARY(+=)\n"
    260       "      IDENTIFIER(sources)\n"
    261       "      LITERAL(\"bar.cc\")\n"
    262       "    CONDITION\n"
    263       "     BINARY(==)\n"
    264       "      IDENTIFIER(OS)\n"
    265       "      LITERAL(\"win\")\n"
    266       "     BLOCK\n"
    267       "      BINARY(-=)\n"
    268       "       IDENTIFIER(sources)\n"
    269       "       LIST\n"
    270       "        LITERAL(\"asd.cc\")\n"
    271       "        LITERAL(\"foo.cc\")\n"
    272       "     BLOCK\n"
    273       "      BINARY(+=)\n"
    274       "       IDENTIFIER(dependencies)\n"
    275       "       LIST\n"
    276       "        LITERAL(\"bar.cc\")\n";
    277   DoParserPrintTest(input, expected);
    278 }
    279 
    280 TEST(Parser, NestedBlocks) {
    281   const char* input = "{cc_test(\"foo\") {{foo=1}{}}}";
    282   const char* expected =
    283       "BLOCK\n"
    284       " BLOCK\n"
    285       "  FUNCTION(cc_test)\n"
    286       "   LIST\n"
    287       "    LITERAL(\"foo\")\n"
    288       "   BLOCK\n"
    289       "    BLOCK\n"
    290       "     BINARY(=)\n"
    291       "      IDENTIFIER(foo)\n"
    292       "      LITERAL(1)\n"
    293       "    BLOCK\n";
    294   DoParserPrintTest(input, expected);
    295 }
    296 
    297 TEST(Parser, List) {
    298   const char* input = "[] a = [1,asd,] b = [1, 2+3 - foo]";
    299   const char* expected =
    300       "BLOCK\n"
    301       " LIST\n"
    302       " BINARY(=)\n"
    303       "  IDENTIFIER(a)\n"
    304       "  LIST\n"
    305       "   LITERAL(1)\n"
    306       "   IDENTIFIER(asd)\n"
    307       " BINARY(=)\n"
    308       "  IDENTIFIER(b)\n"
    309       "  LIST\n"
    310       "   LITERAL(1)\n"
    311       "   BINARY(+)\n"
    312       "    LITERAL(2)\n"
    313       "    BINARY(-)\n"
    314       "     LITERAL(3)\n"
    315       "     IDENTIFIER(foo)\n";
    316   DoParserPrintTest(input, expected);
    317 
    318   DoParserErrorTest("[a, 2+,]", 1, 7);
    319   DoParserErrorTest("[,]", 1, 2);
    320   DoParserErrorTest("[a,,]", 1, 4);
    321 }
    322 
    323 TEST(Parser, UnterminatedBlock) {
    324   DoParserErrorTest("hello {", 1, 7);
    325 }
    326 
    327 TEST(Parser, BadlyTerminatedNumber) {
    328   DoParserErrorTest("1234z", 1, 5);
    329 }
    330