| // 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()); |
| } |