summaryrefslogtreecommitdiffstats
path: root/tools/gn
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gn')
-rw-r--r--tools/gn/BUILD.gn1
-rw-r--r--tools/gn/build_settings.h9
-rw-r--r--tools/gn/functions.cc15
-rw-r--r--tools/gn/template_unittest.cc75
-rw-r--r--tools/gn/test_with_scope.cc22
-rw-r--r--tools/gn/test_with_scope.h44
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_