blob: bb7b0a3d67c94e2089e9662f64bf42c405578293 [file] [log] [blame]
// 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 "gn/functions.h"
#include <memory>
#include <utility>
#include "gn/parse_tree.h"
#include "gn/test_with_scope.h"
#include "gn/value.h"
#include "util/test/test.h"
TEST(Functions, Assert) {
TestWithScope setup;
// Verify cases where the assertion passes.
std::vector<std::string> assert_pass_examples = {
R"gn(assert(true))gn",
R"gn(assert(true, "This message is ignored for passed assertions."))gn",
};
for (const auto& assert_pass_example : assert_pass_examples) {
TestParseInput input(assert_pass_example);
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << assert_pass_example;
}
// Verify case where the assertion fails, with no message.
{
TestParseInput input("assert(false)");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error());
ASSERT_EQ(err.message(), "Assertion failed.");
}
// Verify case where the assertion fails, with a message.
{
TestParseInput input("assert(false, \"What failed\")");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error());
ASSERT_EQ(err.message(), "Assertion failed.");
ASSERT_EQ(err.help_text(), "What failed");
}
// Verify usage errors are detected.
std::vector<std::string> bad_usage_examples = {
// Number of arguments.
R"gn(assert())gn",
R"gn(assert(1, 2, 3))gn",
// Argument types.
R"gn(assert(1))gn",
R"gn(assert("oops"))gn",
R"gn(assert(true, 1))gn",
R"gn(assert(true, []))gn",
};
for (const auto& bad_usage_example : bad_usage_examples) {
TestParseInput input(bad_usage_example);
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << bad_usage_example;
// We are checking for usage errors, not assertion failures.
ASSERT_NE(err.message(), "Assertion failed.") << bad_usage_example;
}
}
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());
// Should also work by pasing an accessor node so you can do
// "defined(def["foo"])" to see if foo is defined on the def scope.
std::unique_ptr<AccessorNode> subscript_accessor =
std::make_unique<AccessorNode>();
subscript_accessor->set_base(defined_token);
subscript_accessor->set_subscript(
std::make_unique<LiteralNode>(Token(Location(), Token::STRING, "foo")));
ListNode args_list_subscript_accessor_defined;
args_list_subscript_accessor_defined.append_item(
std::move(subscript_accessor));
result = functions::RunDefined(setup.scope(), &function_call,
&args_list_subscript_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, StringJoin) {
TestWithScope setup;
// Verify outputs when string_join() is called correctly.
{
TestParseInput input(R"gn(
# No elements in the list and empty separator.
print("<" + string_join("", []) + ">")
# No elements in the list.
print("<" + string_join(" ", []) + ">")
# One element in the list.
print(string_join("|", ["a"]))
# Multiple elements in the list.
print(string_join(" ", ["a", "b", "c"]))
# Multi-character separator.
print(string_join("-.", ["a", "b", "c"]))
# Empty separator.
print(string_join("", ["x", "y", "z"]))
# Empty string list elements.
print(string_join("x", ["", "", ""]))
# Empty string list elements and separator
print(string_join("", ["", "", ""]))
)gn");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
EXPECT_EQ(
"<>\n"
"<>\n"
"a\n"
"a b c\n"
"a-.b-.c\n"
"xyz\n"
"xx\n"
"\n",
setup.print_output()) << setup.print_output();
}
// Verify usage errors are detected.
std::vector<std::string> bad_usage_examples = {
// Number of arguments.
R"gn(string_join())gn",
R"gn(string_join(["oops"]))gn",
R"gn(string_join("kk", [], "oops"))gn",
// Argument types.
R"gn(string_join(1, []))gn",
R"gn(string_join("kk", "oops"))gn",
R"gn(string_join(["oops"], []))gn",
// Non-string elements in list of strings.
R"gn(string_join("kk", [1]))gn",
R"gn(string_join("kk", ["hello", 1]))gn",
R"gn(string_join("kk", ["hello", []]))gn",
};
for (const auto& bad_usage_example : bad_usage_examples) {
TestParseInput input(bad_usage_example);
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << bad_usage_example;
}
}
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, StringSplit) {
TestWithScope setup;
// Verify outputs when string_join() is called correctly.
{
TestParseInput input(R"gn(
# Split on all whitespace: empty string.
print(string_split(""))
# Split on all whitespace: string is only whitespace
print(string_split(" "))
# Split on all whitespace: leading, trailing, runs; one element.
print(string_split("hello"))
print(string_split(" hello"))
print(string_split(" hello "))
print(string_split("hello "))
# Split on all whitespace: leading, trailing, runs; multiple elements.
print(string_split("a b")) # Pre-stripped
print(string_split(" a b")) # Leading whitespace
print(string_split(" a b ")) # Leading & trailing whitespace
print(string_split("a b ")) # Trailing whitespace
print(string_split("a b ")) # Whitespace run between words
print(string_split(" a b cc ddd")) # More & multi-character elements
# Split on string.
print(string_split("", "|")) # Empty string
print(string_split("|", "|")) # Only a separator
print(string_split("||", "|")) # Only separators
print(string_split("ab", "|")) # String is missing separator
print(string_split("a|b", "|")) # Two elements
print(string_split("|a|b", "|")) # Leading separator
print(string_split("a|b|", "|")) # Trailing separator
print(string_split("||x", "|")) # Leading consecutive separators
print(string_split("x||", "|")) # Trailing consecutive separators
print(string_split("a|bb|ccc", "|")) # Multiple elements
print(string_split(".x.x.x.", ".x.")) # Self-overlapping separators 1
print(string_split("x.x.x.", ".x.")) # Self-overlapping separators 2
)gn");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
EXPECT_EQ(
// Split on all whitespace: empty string.
"[]\n"
// Split on all whitespace: string is only whitespace.
"[]\n"
// Split on all whitespace: leading, trailing, runs; one element.
"[\"hello\"]\n"
"[\"hello\"]\n"
"[\"hello\"]\n"
"[\"hello\"]\n"
// Split on all whitespace: leading, trailing, runs; multiple elements.
"[\"a\", \"b\"]\n"
"[\"a\", \"b\"]\n"
"[\"a\", \"b\"]\n"
"[\"a\", \"b\"]\n"
"[\"a\", \"b\"]\n"
"[\"a\", \"b\", \"cc\", \"ddd\"]\n"
// Split on string.
"[\"\"]\n" // Empty string (like Python)
"[\"\", \"\"]\n" // Only a separator
"[\"\", \"\", \"\"]\n" // Only separators
"[\"ab\"]\n" // String is missing separator
"[\"a\", \"b\"]\n" // Two elements
"[\"\", \"a\", \"b\"]\n" // Leading
"[\"a\", \"b\", \"\"]\n" // Trailing
"[\"\", \"\", \"x\"]\n" // Leading consecutive separators
"[\"x\", \"\", \"\"]\n" // Trailing consecutive separators
"[\"a\", \"bb\", \"ccc\"]\n" // Multiple elements
"[\"\", \"x\", \"\"]\n" // Self-overlapping separators 1
"[\"x\", \"x.\"]\n" // Self-overlapping separators 2
,
setup.print_output()) << setup.print_output();
}
// Verify usage errors are detected.
std::vector<std::string> bad_usage_examples = {
// Number of arguments.
R"gn(string_split())gn",
R"gn(string_split("a", "b", "c"))gn",
// Argument types.
R"gn(string_split(1))gn",
R"gn(string_split(["oops"]))gn",
R"gn(string_split("kk", 1))gn",
R"gn(string_split("kk", ["oops"]))gn",
// Empty separator argument.
R"gn(string_split("kk", ""))gn",
};
for (const auto& bad_usage_example : bad_usage_examples) {
TestParseInput input(bad_usage_example);
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_TRUE(err.has_error()) << bad_usage_example;
}
}
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);
}
TEST(Template, PrintStackTraceWithOneTemplate) {
TestWithScope setup;
TestParseInput input(
"template(\"foo\") {\n"
" print(target_name)\n"
" print(invoker.foo_value)\n"
" print_stack_trace()\n"
"}\n"
"foo(\"lala\") {\n"
" foo_value = 42\n"
"}");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message();
EXPECT_EQ(
"lala\n"
"42\n"
"print_stack_trace() initiated at: //test:4 using: //toolchain:default\n"
" foo(\"lala\") //test:6\n"
" print_stack_trace() //test:4\n",
setup.print_output());
}
TEST(Template, PrintStackTraceWithNoTemplates) {
TestWithScope setup;
TestParseInput input("print_stack_trace()\n");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message() << "\n\n" << err.help_text();
EXPECT_EQ(
"print_stack_trace() initiated at: //test:1 using: //toolchain:default\n"
" print_stack_trace() //test:1\n",
setup.print_output());
}
TEST(Template, PrintStackTraceWithNestedTemplates) {
TestWithScope setup;
TestParseInput input(
"template(\"foo\") {\n"
" print(target_name)\n"
" print(invoker.foo_value)\n"
" print_stack_trace()\n"
"}\n"
"template(\"baz\") {\n"
" foo(\"${target_name}.foo\") {\n"
" foo_value = invoker.bar\n"
" }\n"
"}\n"
"baz(\"lala\") {\n"
" bar = 42\n"
"}");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message() << "\n\n" << err.help_text();
EXPECT_EQ(
"lala.foo\n"
"42\n"
"print_stack_trace() initiated at: //test:4 using: //toolchain:default\n"
" baz(\"lala\") //test:11\n"
" foo(\"lala.foo\") //test:7\n"
" print_stack_trace() //test:4\n",
setup.print_output());
}
TEST(Template, PrintStackTraceWithNonTemplateScopes) {
TestWithScope setup;
TestParseInput input(
"template(\"foo\") {\n"
" print(target_name)\n"
" if (defined(invoker.foo_value)) {\n"
" print(invoker.foo_value)\n"
" print_stack_trace()\n"
" }\n"
"}\n"
"template(\"baz\") {\n"
" foo(\"${target_name}.foo\") {\n"
" foo_value = invoker.bar\n"
" }\n"
"}\n"
"baz(\"lala\") {\n"
" bar = 42\n"
"}");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message() << "\n\n" << err.help_text();
EXPECT_EQ(
"lala.foo\n"
"42\n"
"print_stack_trace() initiated at: //test:5 using: //toolchain:default\n"
" baz(\"lala\") //test:13\n"
" foo(\"lala.foo\") //test:9\n"
" print_stack_trace() //test:5\n",
setup.print_output());
}
TEST(Template, PrintStackTraceWithNonTemplateScopesBetweenTemplateInvocations) {
TestWithScope setup;
TestParseInput input(
"template(\"foo\") {\n"
" print(target_name)\n"
" if (defined(invoker.foo_value)) {\n"
" print(invoker.foo_value)\n"
" print_stack_trace()\n"
" }\n"
"}\n"
"template(\"baz\") {\n"
" if (invoker.bar == 42) {\n"
" foo(\"${target_name}.foo\") {\n"
" foo_value = invoker.bar\n"
" }\n"
" }\n"
"}\n"
"baz(\"lala\") {\n"
" bar = 42\n"
"}");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message() << "\n\n" << err.help_text();
EXPECT_EQ(
"lala.foo\n"
"42\n"
"print_stack_trace() initiated at: //test:5 using: //toolchain:default\n"
" baz(\"lala\") //test:15\n"
" foo(\"lala.foo\") //test:10\n"
" print_stack_trace() //test:5\n",
setup.print_output());
}
TEST(Template, PrintStackTraceWithTemplateDefinedWithinATemplate) {
TestWithScope setup;
TestParseInput input(
"template(\"foo\") {\n"
" print(target_name)\n"
" if (defined(invoker.foo_value)) {\n"
" template(\"foo_internal\") {"
" print(target_name)\n"
" print(invoker.foo_internal_value)\n"
" print_stack_trace()\n"
" }\n"
" foo_internal(target_name+\".internal\") {"
" foo_internal_value = invoker.foo_value\n"
" }\n"
" }\n"
"}\n"
"template(\"baz\") {\n"
" if (invoker.bar == 42) {\n"
" foo(\"${target_name}.foo\") {\n"
" foo_value = invoker.bar\n"
" }\n"
" }\n"
"}\n"
"baz(\"lala\") {\n"
" bar = 42\n"
"}");
ASSERT_FALSE(input.has_error());
Err err;
input.parsed()->Execute(setup.scope(), &err);
ASSERT_FALSE(err.has_error()) << err.message() << "\n\n" << err.help_text();
EXPECT_EQ(
"lala.foo\n"
"lala.foo.internal\n"
"42\n"
"print_stack_trace() initiated at: //test:6 using: //toolchain:default\n"
" baz(\"lala\") //test:19\n"
" foo(\"lala.foo\") //test:14\n"
" foo_internal(\"lala.foo.internal\") //test:8\n"
" print_stack_trace() //test:6\n",
setup.print_output());
}