diff options
-rw-r--r-- | tools/gn/BUILD.gn | 1 | ||||
-rw-r--r-- | tools/gn/build_settings.h | 9 | ||||
-rw-r--r-- | tools/gn/functions.cc | 15 | ||||
-rw-r--r-- | tools/gn/template_unittest.cc | 75 | ||||
-rw-r--r-- | tools/gn/test_with_scope.cc | 22 | ||||
-rw-r--r-- | tools/gn/test_with_scope.h | 44 |
6 files changed, 163 insertions, 3 deletions
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn index fe27b90..20aea83 100644 --- a/tools/gn/BUILD.gn +++ b/tools/gn/BUILD.gn @@ -197,6 +197,7 @@ test("gn_unittests") { "string_utils_unittest.cc", "target_generator_unittest.cc", "target_unittest.cc", + "template_unittest.cc", "test_with_scope.cc", "test_with_scope.h", "tokenizer_unittest.cc", diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h index 2e93296..daea82d 100644 --- a/tools/gn/build_settings.h +++ b/tools/gn/build_settings.h @@ -24,6 +24,7 @@ class OutputFile; class BuildSettings { public: typedef base::Callback<void(scoped_ptr<Item>)> ItemDefinedCallback; + typedef base::Callback<void(const std::string&)> PrintCallback; BuildSettings(); BuildSettings(const BuildSettings& other); @@ -83,6 +84,13 @@ class BuildSettings { item_defined_callback_ = cb; } + // Defines a callback that will be used to override the behavior of the + // print function. This is used in tests to collect print output. If the + // callback is is_null() (the default) the output will be printed to the + // console. + const PrintCallback& print_callback() const { return print_callback_; } + void set_print_callback(const PrintCallback& cb) { print_callback_ = cb; } + private: base::FilePath root_path_; std::string root_path_utf8_; @@ -95,6 +103,7 @@ class BuildSettings { Args build_args_; ItemDefinedCallback item_defined_callback_; + PrintCallback print_callback_; BuildSettings& operator=(const BuildSettings& other); // Disallow. }; diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc index ac1c294..618855b 100644 --- a/tools/gn/functions.cc +++ b/tools/gn/functions.cc @@ -532,12 +532,21 @@ Value RunPrint(Scope* scope, const FunctionCallNode* function, const std::vector<Value>& args, Err* err) { + std::string output; for (size_t i = 0; i < args.size(); i++) { if (i != 0) - std::cout << " "; - std::cout << args[i].ToString(false); + output.push_back(' '); + output.append(args[i].ToString(false)); } - std::cout << std::endl; + output.push_back('\n'); + + const BuildSettings::PrintCallback& cb = + scope->settings()->build_settings()->print_callback(); + if (cb.is_null()) + printf("%s", output.c_str()); + else + cb.Run(output); + return Value(); } diff --git a/tools/gn/template_unittest.cc b/tools/gn/template_unittest.cc new file mode 100644 index 0000000..13ec848 --- /dev/null +++ b/tools/gn/template_unittest.cc @@ -0,0 +1,75 @@ +// 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 "testing/gtest/include/gtest/gtest.h" +#include "tools/gn/test_with_scope.h" + +TEST(Template, Basic) { + TestWithScope setup; + TestParseInput input( + "template(\"foo\") {\n" + " print(target_name)\n" + " print(invoker.bar)\n" + "}\n" + "foo(\"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(); + + EXPECT_EQ("lala\n42\n", setup.print_output()); +} + +TEST(Template, UnusedTargetNameShouldThrowError) { + TestWithScope setup; + TestParseInput input( + "template(\"foo\") {\n" + " print(invoker.bar)\n" + "}\n" + "foo(\"lala\") {\n" + " bar = 42\n" + "}"); + ASSERT_FALSE(input.has_error()); + + Err err; + input.parsed()->Execute(setup.scope(), &err); + EXPECT_TRUE(err.has_error()); +} + +TEST(Template, UnusedInvokerShouldThrowError) { + TestWithScope setup; + TestParseInput input( + "template(\"foo\") {\n" + " print(target_name)\n" + "}\n" + "foo(\"lala\") {\n" + " bar = 42\n" + "}"); + ASSERT_FALSE(input.has_error()); + + Err err; + input.parsed()->Execute(setup.scope(), &err); + EXPECT_TRUE(err.has_error()); +} + +TEST(Template, UnusedVarInInvokerShouldThrowError) { + TestWithScope setup; + TestParseInput input( + "template(\"foo\") {\n" + " print(target_name)\n" + " print(invoker.bar)\n" + "}\n" + "foo(\"lala\") {\n" + " bar = 42\n" + " baz = [ \"foo\" ]\n" + "}"); + ASSERT_FALSE(input.has_error()); + + Err err; + input.parsed()->Execute(setup.scope(), &err); + EXPECT_TRUE(err.has_error()); +} diff --git a/tools/gn/test_with_scope.cc b/tools/gn/test_with_scope.cc index 97eb2fb..55fbc2e 100644 --- a/tools/gn/test_with_scope.cc +++ b/tools/gn/test_with_scope.cc @@ -4,12 +4,18 @@ #include "tools/gn/test_with_scope.h" +#include "base/bind.h" +#include "tools/gn/parser.h" +#include "tools/gn/tokenizer.h" + TestWithScope::TestWithScope() : build_settings_(), settings_(&build_settings_, std::string()), toolchain_(&settings_, Label(SourceDir("//toolchain/"), "default")), scope_(&settings_) { build_settings_.SetBuildDir(SourceDir("//out/Debug/")); + build_settings_.set_print_callback( + base::Bind(&TestWithScope::AppendPrintOutput, base::Unretained(this))); settings_.set_toolchain_label(toolchain_.label()); settings_.set_default_toolchain_label(toolchain_.label()); @@ -17,3 +23,19 @@ TestWithScope::TestWithScope() TestWithScope::~TestWithScope() { } + +void TestWithScope::AppendPrintOutput(const std::string& str) { + print_output_.append(str); +} + +TestParseInput::TestParseInput(const std::string& input) + : input_file_(SourceFile("//test")) { + input_file_.SetContents(input); + + tokens_ = Tokenizer::Tokenize(&input_file_, &parse_err_); + if (!parse_err_.has_error()) + parsed_ = Parser::Parse(tokens_, &parse_err_); +} + +TestParseInput::~TestParseInput() { +} diff --git a/tools/gn/test_with_scope.h b/tools/gn/test_with_scope.h index df2c93e..ce7d95c 100644 --- a/tools/gn/test_with_scope.h +++ b/tools/gn/test_with_scope.h @@ -5,11 +5,18 @@ #ifndef TOOLS_GN_TEST_WITH_SCOPE_H_ #define TOOLS_GN_TEST_WITH_SCOPE_H_ +#include <vector> + #include "base/basictypes.h" #include "tools/gn/build_settings.h" +#include "tools/gn/err.h" +#include "tools/gn/input_file.h" +#include "tools/gn/parse_tree.h" #include "tools/gn/scope.h" #include "tools/gn/settings.h" +#include "tools/gn/token.h" #include "tools/gn/toolchain.h" +#include "tools/gn/value.h" // A helper class for setting up a Scope that a test can use. It makes a // toolchain and sets up all the build state. @@ -23,13 +30,50 @@ class TestWithScope { Toolchain* toolchain() { return &toolchain_; } Scope* scope() { return &scope_; } + // This buffer accumulates output from any print() commands executed in the + // context of this test. Note that the implementation of this is not + // threadsafe so don't write tests that call print from multiple threads. + std::string& print_output() { return print_output_; } + private: + void AppendPrintOutput(const std::string& str); + BuildSettings build_settings_; Settings settings_; Toolchain toolchain_; Scope scope_; + std::string print_output_; + DISALLOW_COPY_AND_ASSIGN(TestWithScope); }; +// Helper class to treat some string input as a file. +// +// Instantiate it with the contents you want, be sure to check for error, and +// then you can execute the ParseNode or whatever. +class TestParseInput { + public: + TestParseInput(const std::string& input); + ~TestParseInput(); + + // Indicates whether and what error occurred during tokenizing and parsing. + bool has_error() const { return parse_err_.has_error(); } + const Err& parse_err() const { return parse_err_; } + + const InputFile& input_file() const { return input_file_; } + const std::vector<Token>& tokens() const { return tokens_; } + const ParseNode* parsed() const { return parsed_.get(); } + + private: + InputFile input_file_; + + std::vector<Token> tokens_; + scoped_ptr<ParseNode> parsed_; + + Err parse_err_; + + DISALLOW_COPY_AND_ASSIGN(TestParseInput); +}; + #endif // TOOLS_GN_TEST_WITH_SCOPE_H_ |