text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "testing/gtest/include/gtest/gtest.h"
  #include "tools/gn/operators.h"
  #include "tools/gn/parse_tree.h"
  #include "tools/gn/pattern.h"
  #include "tools/gn/test_with_scope.h"
  
  namespace {
  
  bool IsValueIntegerEqualing(const Value& v, int64 i) {
    if (v.type() != Value::INTEGER)
      return false;
    return v.int_value() == i;
  }
  
  bool IsValueStringEqualing(const Value& v, const char* s) {
    if (v.type() != Value::STRING)
      return false;
    return v.string_value() == s;
  }
  
  // Returns a list populated with a single literal Value corresponding to the
  // given token. The token must outlive the list (since the list will just
  // copy the reference).
  scoped_ptr<ListNode> ListWithLiteral(const Token& token) {
    scoped_ptr<ListNode> list(new ListNode);
    list->append_item(scoped_ptr<ParseNode>(new LiteralNode(token)));
    return list.Pass();
  }
  
  }  // namespace
  
  TEST(Operators, SourcesAppend) {
    Err err;
    TestWithScope setup;
  
    // Set up "sources" with an empty list.
    const char sources[] = "sources";
    setup.scope()->SetValue(sources, Value(NULL, Value::LIST), NULL);
  
    // Set up the operator.
    BinaryOpNode node;
    const char token_value[] = "+=";
    Token op(Location(), Token::PLUS_EQUALS, token_value);
    node.set_op(op);
  
    // Append to the sources variable.
    Token identifier_token(Location(), Token::IDENTIFIER, sources);
    node.set_left(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token)));
  
    // Set up the filter on the scope to remove everything ending with "rm"
    scoped_ptr<PatternList> pattern_list(new PatternList);
    pattern_list->Append(Pattern("*rm"));
    setup.scope()->set_sources_assignment_filter(pattern_list.Pass());
  
    // Append an integer.
    const char integer_value[] = "5";
    Token integer(Location(), Token::INTEGER, integer_value);
    node.set_right(ListWithLiteral(integer));
    node.Execute(setup.scope(), &err);
    EXPECT_FALSE(err.has_error());
  
    // Append a string that doesn't match the pattern, it should get appended.
    const char string_1_value[] = "\"good\"";
    Token string_1(Location(), Token::STRING, string_1_value);
    node.set_right(ListWithLiteral(string_1));
    node.Execute(setup.scope(), &err);
    EXPECT_FALSE(err.has_error());
  
    // Append a string that does match the pattern, it should be a no-op.
    const char string_2_value[] = "\"foo-rm\"";
    Token string_2(Location(), Token::STRING, string_2_value);
    node.set_right(ListWithLiteral(string_2));
    node.Execute(setup.scope(), &err);
    EXPECT_FALSE(err.has_error());
  
    // Append a list with the two strings from above.
    ListNode list;
    list.append_item(scoped_ptr<ParseNode>(new LiteralNode(string_1)));
    list.append_item(scoped_ptr<ParseNode>(new LiteralNode(string_2)));
    ExecuteBinaryOperator(setup.scope(), &node, node.left(), &list, &err);
    EXPECT_FALSE(err.has_error());
  
    // The sources variable in the scope should now have: [ 5, "good", "good" ]
    const Value* value = setup.scope()->GetValue(sources);
    ASSERT_TRUE(value);
    ASSERT_EQ(Value::LIST, value->type());
    ASSERT_EQ(3u, value->list_value().size());
    EXPECT_TRUE(IsValueIntegerEqualing(value->list_value()[0], 5));
    EXPECT_TRUE(IsValueStringEqualing(value->list_value()[1], "good"));
    EXPECT_TRUE(IsValueStringEqualing(value->list_value()[2], "good"));
  }
  
  // Note that the SourcesAppend test above tests the basic list + list features,
  // this test handles the other cases.
  TEST(Operators, ListAppend) {
    Err err;
    TestWithScope setup;
  
    // Set up "foo" with an empty list.
    const char foo[] = "foo";
    setup.scope()->SetValue(foo, Value(NULL, Value::LIST), NULL);
  
    // Set up the operator.
    BinaryOpNode node;
    const char token_value[] = "+=";
    Token op(Location(), Token::PLUS_EQUALS, token_value);
    node.set_op(op);
  
    // Append to the foo variable.
    Token identifier_token(Location(), Token::IDENTIFIER, foo);
    node.set_left(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token)));
  
    // Append a list with a list, the result should be a nested list.
    scoped_ptr<ListNode> outer_list(new ListNode);
    const char twelve_str[] = "12";
    Token twelve(Location(), Token::INTEGER, twelve_str);
    outer_list->append_item(ListWithLiteral(twelve));
    node.set_right(outer_list.Pass());
  
    Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
                                      node.right(), &err);
    EXPECT_FALSE(err.has_error());
  
    // Return from the operator should always be "none", it should update the
    // value only.
    EXPECT_EQ(Value::NONE, ret.type());
  
    // The value should be updated with "[ [ 12 ] ]"
    Value result = *setup.scope()->GetValue(foo);
    ASSERT_EQ(Value::LIST, result.type());
    ASSERT_EQ(1u, result.list_value().size());
    ASSERT_EQ(Value::LIST, result.list_value()[0].type());
    ASSERT_EQ(1u, result.list_value()[0].list_value().size());
    ASSERT_EQ(Value::INTEGER, result.list_value()[0].list_value()[0].type());
    ASSERT_EQ(12, result.list_value()[0].list_value()[0].int_value());
  
    // Try to append an integer and a string directly (e.g. foo += "hi").
    // This should fail.
    const char str_str[] = "\"hi\"";
    Token str(Location(), Token::STRING, str_str);
    node.set_right(scoped_ptr<ParseNode>(new LiteralNode(str)));
    ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
    EXPECT_TRUE(err.has_error());
    err = Err();
  
    node.set_right(scoped_ptr<ParseNode>(new LiteralNode(twelve)));
    ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
    EXPECT_TRUE(err.has_error());
  }
  
  TEST(Operators, ShortCircuitAnd) {
    Err err;
    TestWithScope setup;
  
    // Set up the operator.
    BinaryOpNode node;
    const char token_value[] = "&&";
    Token op(Location(), Token::BOOLEAN_AND, token_value);
    node.set_op(op);
  
    // Set the left to false.
    const char false_str[] = "false";
    Token false_tok(Location(), Token::FALSE_TOKEN, false_str);
    node.set_left(scoped_ptr<ParseNode>(new LiteralNode(false_tok)));
  
    // Set right as foo, but don't define a value for it.
    const char foo[] = "foo";
    Token identifier_token(Location(), Token::IDENTIFIER, foo);
    node.set_right(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token)));
  
    Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
                                      node.right(), &err);
    EXPECT_FALSE(err.has_error());
  }
  
  TEST(Operators, ShortCircuitOr) {
    Err err;
    TestWithScope setup;
  
    // Set up the operator.
    BinaryOpNode node;
    const char token_value[] = "||";
    Token op(Location(), Token::BOOLEAN_OR, token_value);
    node.set_op(op);
  
    // Set the left to false.
    const char false_str[] = "true";
    Token false_tok(Location(), Token::TRUE_TOKEN, false_str);
    node.set_left(scoped_ptr<ParseNode>(new LiteralNode(false_tok)));
  
    // Set right as foo, but don't define a value for it.
    const char foo[] = "foo";
    Token identifier_token(Location(), Token::IDENTIFIER, foo);
    node.set_right(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token)));
  
    Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
                                      node.right(), &err);
    EXPECT_FALSE(err.has_error());
  }
  