| // Copyright 2014 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 "tools/gn/functions.h" | |
| #include <memory> | |
| #include <utility> | |
| #include "tools/gn/parse_tree.h" | |
| #include "tools/gn/test_with_scope.h" | |
| #include "tools/gn/value.h" | |
| #include "util/test/test.h" | |
| TEST(Functions, Defined) { | |
| TestWithScope setup; | |
| FunctionCallNode function_call; | |
| Err err; | |
| // Test an undefined identifier. | |
| Token undefined_token(Location(), Token::IDENTIFIER, "undef"); | |
| ListNode args_list_identifier_undefined; | |
| args_list_identifier_undefined.append_item( | |
| std::make_unique<IdentifierNode>(undefined_token)); | |
| Value result = functions::RunDefined(setup.scope(), &function_call, | |
| &args_list_identifier_undefined, &err); | |
| ASSERT_EQ(Value::BOOLEAN, result.type()); | |
| EXPECT_FALSE(result.boolean_value()); | |
| // Define a value that's itself a scope value. | |
| const char kDef[] = "def"; // Defined variable name. | |
| setup.scope()->SetValue( | |
| kDef, Value(nullptr, std::make_unique<Scope>(setup.scope())), nullptr); | |
| // Test the defined identifier. | |
| Token defined_token(Location(), Token::IDENTIFIER, kDef); | |
| ListNode args_list_identifier_defined; | |
| args_list_identifier_defined.append_item( | |
| std::make_unique<IdentifierNode>(defined_token)); | |
| result = functions::RunDefined(setup.scope(), &function_call, | |
| &args_list_identifier_defined, &err); | |
| ASSERT_EQ(Value::BOOLEAN, result.type()); | |
| EXPECT_TRUE(result.boolean_value()); | |
| // Should also work by passing an accessor node so you can do | |
| // "defined(def.foo)" to see if foo is defined on the def scope. | |
| std::unique_ptr<AccessorNode> undef_accessor = | |
| std::make_unique<AccessorNode>(); | |
| undef_accessor->set_base(defined_token); | |
| undef_accessor->set_member(std::make_unique<IdentifierNode>(undefined_token)); | |
| ListNode args_list_accessor_defined; | |
| args_list_accessor_defined.append_item(std::move(undef_accessor)); | |
| result = functions::RunDefined(setup.scope(), &function_call, | |
| &args_list_accessor_defined, &err); | |
| ASSERT_EQ(Value::BOOLEAN, result.type()); | |
| EXPECT_FALSE(result.boolean_value()); | |
| } | |
| // Tests that an error is thrown when a {} is supplied to a function that | |
| // doesn't take one. | |
| TEST(Functions, FunctionsWithBlock) { | |
| TestWithScope setup; | |
| Err err; | |
| // No scope to print() is OK. | |
| TestParseInput print_no_scope("print(6)"); | |
| EXPECT_FALSE(print_no_scope.has_error()); | |
| Value result = print_no_scope.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_FALSE(err.has_error()); | |
| // Passing a scope should pass parsing (it doesn't know about what kind of | |
| // function it is) and then throw an error during execution. | |
| TestParseInput print_with_scope("print(foo) {}"); | |
| EXPECT_FALSE(print_with_scope.has_error()); | |
| result = print_with_scope.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| err = Err(); | |
| // defined() is a special function so test it separately. | |
| TestParseInput defined_no_scope("defined(foo)"); | |
| EXPECT_FALSE(defined_no_scope.has_error()); | |
| result = defined_no_scope.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_FALSE(err.has_error()); | |
| // A block to defined should fail. | |
| TestParseInput defined_with_scope("defined(foo) {}"); | |
| EXPECT_FALSE(defined_with_scope.has_error()); | |
| result = defined_with_scope.parsed()->Execute(setup.scope(), &err); | |
| EXPECT_TRUE(err.has_error()); | |
| } | |
| TEST(Functions, SplitList) { | |
| TestWithScope setup; | |
| TestParseInput input( | |
| // Empty input with varying result items. | |
| "out1 = split_list([], 1)\n" | |
| "out2 = split_list([], 3)\n" | |
| "print(\"empty = $out1 $out2\")\n" | |
| // One item input. | |
| "out3 = split_list([1], 1)\n" | |
| "out4 = split_list([1], 2)\n" | |
| "print(\"one = $out3 $out4\")\n" | |
| // Multiple items. | |
| "out5 = split_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 2)\n" | |
| "print(\"many = $out5\")\n" | |
| // Rounding. | |
| "out6 = split_list([1, 2, 3, 4, 5, 6], 4)\n" | |
| "print(\"rounding = $out6\")\n"); | |
| ASSERT_FALSE(input.has_error()); | |
| Err err; | |
| input.parsed()->Execute(setup.scope(), &err); | |
| ASSERT_FALSE(err.has_error()) << err.message(); | |
| EXPECT_EQ( | |
| "empty = [[]] [[], [], []]\n" | |
| "one = [[1]] [[1], []]\n" | |
| "many = [[1, 2, 3, 4, 5], [6, 7, 8, 9]]\n" | |
| "rounding = [[1, 2], [3, 4], [5], [6]]\n", | |
| setup.print_output()); | |
| } | |
| TEST(Functions, StringReplace) { | |
| TestWithScope setup; | |
| TestParseInput input( | |
| // Replace all occurrences of string. | |
| "out1 = string_replace(\"abbcc\", \"b\", \"d\")\n" | |
| "print(out1)\n" | |
| // Replace only the first occurrence. | |
| "out2 = string_replace(\"abbcc\", \"b\", \"d\", 1)\n" | |
| "print(out2)\n" | |
| // Duplicate string to be replaced. | |
| "out3 = string_replace(\"abbcc\", \"b\", \"bb\")\n" | |
| "print(out3)\n" | |
| // Handle overlapping occurrences. | |
| "out4 = string_replace(\"aaa\", \"aa\", \"b\")\n" | |
| "print(out4)\n"); | |
| ASSERT_FALSE(input.has_error()); | |
| Err err; | |
| input.parsed()->Execute(setup.scope(), &err); | |
| ASSERT_FALSE(err.has_error()) << err.message(); | |
| EXPECT_EQ( | |
| "addcc\n" | |
| "adbcc\n" | |
| "abbbbcc\n" | |
| "ba\n", | |
| setup.print_output()); | |
| } | |
| TEST(Functions, DeclareArgs) { | |
| TestWithScope setup; | |
| Err err; | |
| // It is not legal to read the value of an argument declared in a | |
| // declare_args() from inside the call, but outside the call and in | |
| // a separate call should work. | |
| TestParseInput reading_from_same_call(R"( | |
| declare_args() { | |
| foo = true | |
| bar = foo | |
| })"); | |
| reading_from_same_call.parsed()->Execute(setup.scope(), &err); | |
| ASSERT_TRUE(err.has_error()); | |
| TestParseInput reading_from_outside_call(R"( | |
| declare_args() { | |
| foo = true | |
| } | |
| bar = foo | |
| assert(bar) | |
| )"); | |
| err = Err(); | |
| reading_from_outside_call.parsed()->Execute(setup.scope(), &err); | |
| ASSERT_FALSE(err.has_error()); | |
| TestParseInput reading_from_different_call(R"( | |
| declare_args() { | |
| foo = true | |
| } | |
| declare_args() { | |
| bar = foo | |
| } | |
| assert(bar) | |
| )"); | |
| err = Err(); | |
| TestWithScope setup2; | |
| reading_from_different_call.parsed()->Execute(setup2.scope(), &err); | |
| ASSERT_FALSE(err.has_error()); | |
| } | |
| TEST(Functions, NotNeeded) { | |
| TestWithScope setup; | |
| TestParseInput input("not_needed({ a = 1 }, \"*\")"); | |
| ASSERT_FALSE(input.has_error()); | |
| Err err; | |
| input.parsed()->Execute(setup.scope(), &err); | |
| ASSERT_FALSE(err.has_error()) | |
| << err.message() << err.location().Describe(true); | |
| } |