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 "testing/gtest/include/gtest/gtest.h" 6 #include "tools/gn/operators.h" 7 #include "tools/gn/parse_tree.h" 8 #include "tools/gn/pattern.h" 9 #include "tools/gn/test_with_scope.h" 10 11 namespace { 12 13 bool IsValueIntegerEqualing(const Value& v, int64 i) { 14 if (v.type() != Value::INTEGER) 15 return false; 16 return v.int_value() == i; 17 } 18 19 bool IsValueStringEqualing(const Value& v, const char* s) { 20 if (v.type() != Value::STRING) 21 return false; 22 return v.string_value() == s; 23 } 24 25 // Returns a list populated with a single literal Value corresponding to the 26 // given token. The token must outlive the list (since the list will just 27 // copy the reference). 28 scoped_ptr<ListNode> ListWithLiteral(const Token& token) { 29 scoped_ptr<ListNode> list(new ListNode); 30 list->append_item(scoped_ptr<ParseNode>(new LiteralNode(token))); 31 return list.Pass(); 32 } 33 34 } // namespace 35 36 TEST(Operators, SourcesAppend) { 37 Err err; 38 TestWithScope setup; 39 40 // Set up "sources" with an empty list. 41 const char sources[] = "sources"; 42 setup.scope()->SetValue(sources, Value(NULL, Value::LIST), NULL); 43 44 // Set up the operator. 45 BinaryOpNode node; 46 const char token_value[] = "+="; 47 Token op(Location(), Token::PLUS_EQUALS, token_value); 48 node.set_op(op); 49 50 // Append to the sources variable. 51 Token identifier_token(Location(), Token::IDENTIFIER, sources); 52 node.set_left(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token))); 53 54 // Set up the filter on the scope to remove everything ending with "rm" 55 scoped_ptr<PatternList> pattern_list(new PatternList); 56 pattern_list->Append(Pattern("*rm")); 57 setup.scope()->set_sources_assignment_filter(pattern_list.Pass()); 58 59 // Append an integer. 60 const char integer_value[] = "5"; 61 Token integer(Location(), Token::INTEGER, integer_value); 62 node.set_right(ListWithLiteral(integer).PassAs<ParseNode>()); 63 node.Execute(setup.scope(), &err); 64 EXPECT_FALSE(err.has_error()); 65 66 // Append a string that doesn't match the pattern, it should get appended. 67 const char string_1_value[] = "\"good\""; 68 Token string_1(Location(), Token::STRING, string_1_value); 69 node.set_right(ListWithLiteral(string_1).PassAs<ParseNode>()); 70 node.Execute(setup.scope(), &err); 71 EXPECT_FALSE(err.has_error()); 72 73 // Append a string that does match the pattern, it should be a no-op. 74 const char string_2_value[] = "\"foo-rm\""; 75 Token string_2(Location(), Token::STRING, string_2_value); 76 node.set_right(ListWithLiteral(string_2).PassAs<ParseNode>()); 77 node.Execute(setup.scope(), &err); 78 EXPECT_FALSE(err.has_error()); 79 80 // Append a list with the two strings from above. 81 ListNode list; 82 list.append_item(scoped_ptr<ParseNode>(new LiteralNode(string_1))); 83 list.append_item(scoped_ptr<ParseNode>(new LiteralNode(string_2))); 84 ExecuteBinaryOperator(setup.scope(), &node, node.left(), &list, &err); 85 EXPECT_FALSE(err.has_error()); 86 87 // The sources variable in the scope should now have: [ 5, "good", "good" ] 88 const Value* value = setup.scope()->GetValue(sources); 89 ASSERT_TRUE(value); 90 ASSERT_EQ(Value::LIST, value->type()); 91 ASSERT_EQ(3u, value->list_value().size()); 92 EXPECT_TRUE(IsValueIntegerEqualing(value->list_value()[0], 5)); 93 EXPECT_TRUE(IsValueStringEqualing(value->list_value()[1], "good")); 94 EXPECT_TRUE(IsValueStringEqualing(value->list_value()[2], "good")); 95 } 96 97 // Note that the SourcesAppend test above tests the basic list + list features, 98 // this test handles the other cases. 99 TEST(Operators, ListAppend) { 100 Err err; 101 TestWithScope setup; 102 103 // Set up "foo" with an empty list. 104 const char foo[] = "foo"; 105 setup.scope()->SetValue(foo, Value(NULL, Value::LIST), NULL); 106 107 // Set up the operator. 108 BinaryOpNode node; 109 const char token_value[] = "+="; 110 Token op(Location(), Token::PLUS_EQUALS, token_value); 111 node.set_op(op); 112 113 // Append to the foo variable. 114 Token identifier_token(Location(), Token::IDENTIFIER, foo); 115 node.set_left(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token))); 116 117 // Append a list with a list, the result should be a nested list. 118 scoped_ptr<ListNode> outer_list(new ListNode); 119 const char twelve_str[] = "12"; 120 Token twelve(Location(), Token::INTEGER, twelve_str); 121 outer_list->append_item(ListWithLiteral(twelve).PassAs<ParseNode>()); 122 node.set_right(outer_list.PassAs<ParseNode>()); 123 124 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(), 125 node.right(), &err); 126 EXPECT_FALSE(err.has_error()); 127 128 // Return from the operator should always be "none", it should update the 129 // value only. 130 EXPECT_EQ(Value::NONE, ret.type()); 131 132 // The value should be updated with "[ [ 12 ] ]" 133 Value result = *setup.scope()->GetValue(foo); 134 ASSERT_EQ(Value::LIST, result.type()); 135 ASSERT_EQ(1u, result.list_value().size()); 136 ASSERT_EQ(Value::LIST, result.list_value()[0].type()); 137 ASSERT_EQ(1u, result.list_value()[0].list_value().size()); 138 ASSERT_EQ(Value::INTEGER, result.list_value()[0].list_value()[0].type()); 139 ASSERT_EQ(12, result.list_value()[0].list_value()[0].int_value()); 140 141 // Try to append an integer and a string directly (e.g. foo += "hi"). 142 // This should fail. 143 const char str_str[] = "\"hi\""; 144 Token str(Location(), Token::STRING, str_str); 145 node.set_right(scoped_ptr<ParseNode>(new LiteralNode(str))); 146 ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err); 147 EXPECT_TRUE(err.has_error()); 148 err = Err(); 149 150 node.set_right(scoped_ptr<ParseNode>(new LiteralNode(twelve))); 151 ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err); 152 EXPECT_TRUE(err.has_error()); 153 } 154 155 TEST(Operators, ShortCircuitAnd) { 156 Err err; 157 TestWithScope setup; 158 159 // Set up the operator. 160 BinaryOpNode node; 161 const char token_value[] = "&&"; 162 Token op(Location(), Token::BOOLEAN_AND, token_value); 163 node.set_op(op); 164 165 // Set the left to false. 166 const char false_str[] = "false"; 167 Token false_tok(Location(), Token::FALSE_TOKEN, false_str); 168 node.set_left(scoped_ptr<ParseNode>(new LiteralNode(false_tok))); 169 170 // Set right as foo, but don't define a value for it. 171 const char foo[] = "foo"; 172 Token identifier_token(Location(), Token::IDENTIFIER, foo); 173 node.set_right(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token))); 174 175 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(), 176 node.right(), &err); 177 EXPECT_FALSE(err.has_error()); 178 } 179 180 TEST(Operators, ShortCircuitOr) { 181 Err err; 182 TestWithScope setup; 183 184 // Set up the operator. 185 BinaryOpNode node; 186 const char token_value[] = "||"; 187 Token op(Location(), Token::BOOLEAN_OR, token_value); 188 node.set_op(op); 189 190 // Set the left to false. 191 const char false_str[] = "true"; 192 Token false_tok(Location(), Token::TRUE_TOKEN, false_str); 193 node.set_left(scoped_ptr<ParseNode>(new LiteralNode(false_tok))); 194 195 // Set right as foo, but don't define a value for it. 196 const char foo[] = "foo"; 197 Token identifier_token(Location(), Token::IDENTIFIER, foo); 198 node.set_right(scoped_ptr<ParseNode>(new IdentifierNode(identifier_token))); 199 200 Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(), 201 node.right(), &err); 202 EXPECT_FALSE(err.has_error()); 203 } 204