• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "gn/operators.h"
6 
7 #include <stdint.h>
8 
9 #include <memory>
10 #include <utility>
11 
12 #include "gn/parse_tree.h"
13 #include "gn/pattern.h"
14 #include "gn/test_with_scope.h"
15 #include "util/test/test.h"
16 
17 namespace {
18 
IsValueIntegerEqualing(const Value & v,int64_t i)19 bool IsValueIntegerEqualing(const Value& v, int64_t i) {
20   if (v.type() != Value::INTEGER)
21     return false;
22   return v.int_value() == i;
23 }
24 
IsValueStringEqualing(const Value & v,const char * s)25 bool IsValueStringEqualing(const Value& v, const char* s) {
26   if (v.type() != Value::STRING)
27     return false;
28   return v.string_value() == s;
29 }
30 
31 // This parse node is for passing to tests. It returns a canned value for
32 // Execute().
33 class TestParseNode : public ParseNode {
34  public:
TestParseNode(const Value & v)35   TestParseNode(const Value& v) : value_(v) {}
36 
Execute(Scope * scope,Err * err) const37   Value Execute(Scope* scope, Err* err) const override { return value_; }
GetRange() const38   LocationRange GetRange() const override { return LocationRange(); }
MakeErrorDescribing(const std::string & msg,const std::string & help) const39   Err MakeErrorDescribing(const std::string& msg,
40                           const std::string& help) const override {
41     return Err(this, msg);
42   }
GetJSONNode() const43   base::Value GetJSONNode() const override {
44     return base::Value();
45   }
46 
47  private:
48   Value value_;
49 };
50 
51 // Sets up a BinaryOpNode for testing.
52 class TestBinaryOpNode : public BinaryOpNode {
53  public:
54   // Input token value string must outlive class.
TestBinaryOpNode(Token::Type op_token_type,const char * op_token_value)55   TestBinaryOpNode(Token::Type op_token_type, const char* op_token_value)
56       : BinaryOpNode(),
57         op_token_ownership_(Location(), op_token_type, op_token_value) {
58     set_op(op_token_ownership_);
59   }
60 
SetLeftToValue(const Value & value)61   void SetLeftToValue(const Value& value) {
62     set_left(std::make_unique<TestParseNode>(value));
63   }
64 
65   // Sets the left-hand side of the operator to an identifier node, this is
66   // used for testing assignments. Input string must outlive class.
SetLeftToIdentifier(const char * identifier)67   void SetLeftToIdentifier(const char* identifier) {
68     left_identifier_token_ownership_ =
69         Token(Location(), Token::IDENTIFIER, identifier);
70     set_left(
71         std::make_unique<IdentifierNode>(left_identifier_token_ownership_));
72   }
73 
SetRightToValue(const Value & value)74   void SetRightToValue(const Value& value) {
75     set_right(std::make_unique<TestParseNode>(value));
76   }
SetRightToListOfValue(const Value & value)77   void SetRightToListOfValue(const Value& value) {
78     Value list(nullptr, Value::LIST);
79     list.list_value().push_back(value);
80     set_right(std::make_unique<TestParseNode>(list));
81   }
SetRightToListOfValue(const Value & value1,const Value & value2)82   void SetRightToListOfValue(const Value& value1, const Value& value2) {
83     Value list(nullptr, Value::LIST);
84     list.list_value().push_back(value1);
85     list.list_value().push_back(value2);
86     set_right(std::make_unique<TestParseNode>(list));
87   }
88 
89  private:
90   // The base class takes the Token by reference, this manages the lifetime.
91   Token op_token_ownership_;
92 
93   // When setting the left to an identifier, this manages the lifetime of
94   // the identifier token.
95   Token left_identifier_token_ownership_;
96 };
97 
98 }  // namespace
99 
TEST(Operators,SourcesAppend)100 TEST(Operators, SourcesAppend) {
101   Err err;
102   TestWithScope setup;
103 
104   // Set up "sources" with an empty list.
105   const char sources[] = "sources";
106   setup.scope()->SetValue(sources, Value(nullptr, Value::LIST), nullptr);
107 
108   // Set up the operator.
109   TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
110   node.SetLeftToIdentifier(sources);
111 
112   // Set up the filter on the scope to remove everything ending with "rm"
113   std::unique_ptr<PatternList> pattern_list = std::make_unique<PatternList>();
114   pattern_list->Append(Pattern("*rm"));
115   setup.scope()->set_sources_assignment_filter(std::move(pattern_list));
116 
117   // Append an integer.
118   node.SetRightToListOfValue(Value(nullptr, static_cast<int64_t>(5)));
119   node.Execute(setup.scope(), &err);
120   EXPECT_FALSE(err.has_error());
121 
122   // Append a string that doesn't match the pattern, it should get appended.
123   const char string1[] = "good";
124   node.SetRightToListOfValue(Value(nullptr, string1));
125   node.Execute(setup.scope(), &err);
126   EXPECT_FALSE(err.has_error());
127 
128   // Append a string that does match the pattern, it should be a no-op.
129   const char string2[] = "foo-rm";
130   node.SetRightToListOfValue(Value(nullptr, string2));
131   node.Execute(setup.scope(), &err);
132   EXPECT_FALSE(err.has_error());
133 
134   // Append a list with the two strings from above.
135   node.SetRightToListOfValue(Value(nullptr, string1), Value(nullptr, string2));
136   node.Execute(setup.scope(), &err);
137   EXPECT_FALSE(err.has_error());
138 
139   // The sources variable in the scope should now have: [ 5, "good", "good" ]
140   const Value* value = setup.scope()->GetValue(sources);
141   ASSERT_TRUE(value);
142   ASSERT_EQ(Value::LIST, value->type());
143   ASSERT_EQ(3u, value->list_value().size());
144   EXPECT_TRUE(IsValueIntegerEqualing(value->list_value()[0], 5));
145   EXPECT_TRUE(IsValueStringEqualing(value->list_value()[1], "good"));
146   EXPECT_TRUE(IsValueStringEqualing(value->list_value()[2], "good"));
147 }
148 
149 // Note that the SourcesAppend test above tests the basic list + list features,
150 // this test handles the other cases.
TEST(Operators,ListAppend)151 TEST(Operators, ListAppend) {
152   Err err;
153   TestWithScope setup;
154 
155   // Set up "foo" with an empty list.
156   const char foo[] = "foo";
157   setup.scope()->SetValue(foo, Value(nullptr, Value::LIST), nullptr);
158 
159   // Set up the operator to append to "foo".
160   TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
161   node.SetLeftToIdentifier(foo);
162 
163   // Append a list with a list, the result should be a nested list.
164   Value inner_list(nullptr, Value::LIST);
165   inner_list.list_value().push_back(Value(nullptr, static_cast<int64_t>(12)));
166   node.SetRightToListOfValue(inner_list);
167 
168   Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
169                                     node.right(), &err);
170   EXPECT_FALSE(err.has_error());
171 
172   // Return from the operator should always be "none", it should update the
173   // value only.
174   EXPECT_EQ(Value::NONE, ret.type());
175 
176   // The value should be updated with "[ [ 12 ] ]"
177   Value result = *setup.scope()->GetValue(foo);
178   ASSERT_EQ(Value::LIST, result.type());
179   ASSERT_EQ(1u, result.list_value().size());
180   ASSERT_EQ(Value::LIST, result.list_value()[0].type());
181   ASSERT_EQ(1u, result.list_value()[0].list_value().size());
182   ASSERT_EQ(Value::INTEGER, result.list_value()[0].list_value()[0].type());
183   ASSERT_EQ(12, result.list_value()[0].list_value()[0].int_value());
184 
185   // Try to append an integer and a string directly (e.g. foo += "hi").
186   // This should fail.
187   const char str_str[] = "\"hi\"";
188   Token str(Location(), Token::STRING, str_str);
189   node.set_right(std::make_unique<LiteralNode>(str));
190   ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
191   EXPECT_TRUE(err.has_error());
192   err = Err();
193 
194   node.SetRightToValue(Value(nullptr, static_cast<int64_t>(12)));
195   ExecuteBinaryOperator(setup.scope(), &node, node.left(), node.right(), &err);
196   EXPECT_TRUE(err.has_error());
197 }
198 
TEST(Operators,ListRemove)199 TEST(Operators, ListRemove) {
200   Err err;
201   TestWithScope setup;
202 
203   const char foo_str[] = "foo";
204   const char bar_str[] = "bar";
205   Value test_list(nullptr, Value::LIST);
206   test_list.list_value().push_back(Value(nullptr, foo_str));
207   test_list.list_value().push_back(Value(nullptr, bar_str));
208   test_list.list_value().push_back(Value(nullptr, foo_str));
209 
210   // Set up "var" with an the test list.
211   const char var[] = "var";
212   setup.scope()->SetValue(var, test_list, nullptr);
213 
214   TestBinaryOpNode node(Token::MINUS_EQUALS, "-=");
215   node.SetLeftToIdentifier(var);
216 
217   // Subtract a list consisting of "foo".
218   node.SetRightToListOfValue(Value(nullptr, foo_str));
219   Value result = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
220                                        node.right(), &err);
221   EXPECT_FALSE(err.has_error());
222 
223   // -= returns an empty value to reduce the possibility of writing confusing
224   // cases like foo = bar += 1.
225   EXPECT_EQ(Value::NONE, result.type());
226 
227   // The "var" variable should have been updated. Both instances of "foo" are
228   // deleted.
229   const Value* new_value = setup.scope()->GetValue(var);
230   ASSERT_TRUE(new_value);
231   ASSERT_EQ(Value::LIST, new_value->type());
232   ASSERT_EQ(1u, new_value->list_value().size());
233   ASSERT_EQ(Value::STRING, new_value->list_value()[0].type());
234   EXPECT_EQ("bar", new_value->list_value()[0].string_value());
235 }
236 
TEST(Operators,ListSubtractWithScope)237 TEST(Operators, ListSubtractWithScope) {
238   Err err;
239   TestWithScope setup;
240 
241   Scope* scope_a = new Scope(setup.settings());
242   Value scopeval_a(nullptr, std::unique_ptr<Scope>(scope_a));
243   scope_a->SetValue("a", Value(nullptr, "foo"), nullptr);
244 
245   Scope* scope_b = new Scope(setup.settings());
246   Value scopeval_b(nullptr, std::unique_ptr<Scope>(scope_b));
247   scope_b->SetValue("b", Value(nullptr, "bar"), nullptr);
248 
249   Value lval(nullptr, Value::LIST);
250   lval.list_value().push_back(scopeval_a);
251   lval.list_value().push_back(scopeval_b);
252 
253   Scope* scope_a_other = new Scope(setup.settings());
254   Value scopeval_a_other(nullptr, std::unique_ptr<Scope>(scope_a_other));
255   scope_a_other->SetValue("a", Value(nullptr, "foo"), nullptr);
256 
257   Value rval(nullptr, Value::LIST);
258   rval.list_value().push_back(scopeval_a_other);
259 
260   TestBinaryOpNode node(Token::MINUS, "-");
261   node.SetLeftToValue(lval);
262   node.SetRightToValue(rval);
263   Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
264                                     node.right(), &err);
265   ASSERT_FALSE(err.has_error());
266   ASSERT_EQ(Value::LIST, ret.type());
267 
268   std::vector<Value> expected;
269   Scope* scope_expected = new Scope(setup.settings());
270   Value scopeval_expected(nullptr, std::unique_ptr<Scope>(scope_expected));
271   scope_expected->SetValue("b", Value(nullptr, "bar"), nullptr);
272   expected.push_back(scopeval_expected);
273   EXPECT_EQ(expected, ret.list_value());
274 }
275 
TEST(Operators,IntegerAdd)276 TEST(Operators, IntegerAdd) {
277   Err err;
278   TestWithScope setup;
279 
280   TestBinaryOpNode node(Token::PLUS, "+");
281   node.SetLeftToValue(Value(nullptr, static_cast<int64_t>(123)));
282   node.SetRightToValue(Value(nullptr, static_cast<int64_t>(456)));
283   Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
284                                     node.right(), &err);
285   ASSERT_FALSE(err.has_error());
286   ASSERT_EQ(Value::INTEGER, ret.type());
287   EXPECT_EQ(579, ret.int_value());
288 }
289 
TEST(Operators,IntegerSubtract)290 TEST(Operators, IntegerSubtract) {
291   Err err;
292   TestWithScope setup;
293 
294   TestBinaryOpNode node(Token::MINUS, "-");
295   node.SetLeftToValue(Value(nullptr, static_cast<int64_t>(123)));
296   node.SetRightToValue(Value(nullptr, static_cast<int64_t>(456)));
297   Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
298                                     node.right(), &err);
299   ASSERT_FALSE(err.has_error());
300   ASSERT_EQ(Value::INTEGER, ret.type());
301   EXPECT_EQ(-333, ret.int_value());
302 }
303 
TEST(Operators,ShortCircuitAnd)304 TEST(Operators, ShortCircuitAnd) {
305   Err err;
306   TestWithScope setup;
307 
308   // Set a && operator with the left to false.
309   TestBinaryOpNode node(Token::BOOLEAN_AND, "&&");
310   node.SetLeftToValue(Value(nullptr, false));
311 
312   // Set right as foo, but don't define a value for it.
313   const char foo[] = "foo";
314   Token identifier_token(Location(), Token::IDENTIFIER, foo);
315   node.set_right(std::make_unique<IdentifierNode>(identifier_token));
316 
317   Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
318                                     node.right(), &err);
319   EXPECT_FALSE(err.has_error());
320 }
321 
TEST(Operators,ShortCircuitOr)322 TEST(Operators, ShortCircuitOr) {
323   Err err;
324   TestWithScope setup;
325 
326   // Set a || operator with the left to true.
327   TestBinaryOpNode node(Token::BOOLEAN_OR, "||");
328   node.SetLeftToValue(Value(nullptr, true));
329 
330   // Set right as foo, but don't define a value for it.
331   const char foo[] = "foo";
332   Token identifier_token(Location(), Token::IDENTIFIER, foo);
333   node.set_right(std::make_unique<IdentifierNode>(identifier_token));
334 
335   Value ret = ExecuteBinaryOperator(setup.scope(), &node, node.left(),
336                                     node.right(), &err);
337   EXPECT_FALSE(err.has_error());
338 }
339 
340 // Overwriting nonempty lists and scopes with other nonempty lists and scopes
341 // should be disallowed.
TEST(Operators,NonemptyOverwriting)342 TEST(Operators, NonemptyOverwriting) {
343   Err err;
344   TestWithScope setup;
345 
346   // Set up "foo" with a nonempty list.
347   const char foo[] = "foo";
348   Value old_value(nullptr, Value::LIST);
349   old_value.list_value().push_back(Value(nullptr, "string"));
350   setup.scope()->SetValue(foo, old_value, nullptr);
351 
352   TestBinaryOpNode node(Token::EQUAL, "=");
353   node.SetLeftToIdentifier(foo);
354 
355   // Assigning a nonempty list should fail.
356   node.SetRightToListOfValue(Value(nullptr, "string"));
357   node.Execute(setup.scope(), &err);
358   ASSERT_TRUE(err.has_error());
359   EXPECT_EQ("Replacing nonempty list.", err.message());
360   err = Err();
361 
362   // Assigning an empty list should succeed.
363   node.SetRightToValue(Value(nullptr, Value::LIST));
364   node.Execute(setup.scope(), &err);
365   ASSERT_FALSE(err.has_error());
366   const Value* new_value = setup.scope()->GetValue(foo);
367   ASSERT_TRUE(new_value);
368   ASSERT_EQ(Value::LIST, new_value->type());
369   ASSERT_TRUE(new_value->list_value().empty());
370 
371   // Set up "foo" with a nonempty scope.
372   const char bar[] = "bar";
373   old_value = Value(nullptr, std::make_unique<Scope>(setup.settings()));
374   old_value.scope_value()->SetValue(bar, Value(nullptr, "bar"), nullptr);
375   setup.scope()->SetValue(foo, old_value, nullptr);
376 
377   // Assigning a nonempty scope should fail (re-use old_value copy).
378   node.SetRightToValue(old_value);
379   node.Execute(setup.scope(), &err);
380   ASSERT_TRUE(err.has_error());
381   EXPECT_EQ("Replacing nonempty scope.", err.message());
382   err = Err();
383 
384   // Assigning an empty list should succeed.
385   node.SetRightToValue(
386       Value(nullptr, std::make_unique<Scope>(setup.settings())));
387   node.Execute(setup.scope(), &err);
388   ASSERT_FALSE(err.has_error());
389   new_value = setup.scope()->GetValue(foo);
390   ASSERT_TRUE(new_value);
391   ASSERT_EQ(Value::SCOPE, new_value->type());
392   ASSERT_FALSE(new_value->scope_value()->HasValues(Scope::SEARCH_CURRENT));
393 }
394 
395 // Tests this case:
396 //  foo = 1
397 //  target(...) {
398 //    foo += 1
399 //
400 // This should mark the outer "foo" as used, and the inner "foo" as unused.
TEST(Operators,PlusEqualsUsed)401 TEST(Operators, PlusEqualsUsed) {
402   Err err;
403   TestWithScope setup;
404 
405   // Outer "foo" definition, it should be unused.
406   const char foo[] = "foo";
407   Value old_value(nullptr, static_cast<int64_t>(1));
408   setup.scope()->SetValue(foo, old_value, nullptr);
409   EXPECT_TRUE(setup.scope()->IsSetButUnused(foo));
410 
411   // Nested scope.
412   Scope nested(setup.scope());
413 
414   // Run "foo += 1".
415   TestBinaryOpNode node(Token::PLUS_EQUALS, "+=");
416   node.SetLeftToIdentifier(foo);
417   node.SetRightToValue(Value(nullptr, static_cast<int64_t>(1)));
418   node.Execute(&nested, &err);
419   ASSERT_FALSE(err.has_error());
420 
421   // Outer foo should be used, inner foo should not be.
422   EXPECT_FALSE(setup.scope()->IsSetButUnused(foo));
423   EXPECT_TRUE(nested.IsSetButUnused(foo));
424 }
425