|  | // Copyright (c) 2013 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 <functional> | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "gn/build_settings.h" | 
|  | #include "gn/err.h" | 
|  | #include "gn/loader.h" | 
|  | #include "gn/parse_tree.h" | 
|  | #include "gn/parser.h" | 
|  | #include "gn/scheduler.h" | 
|  | #include "gn/test_with_scheduler.h" | 
|  | #include "gn/tokenizer.h" | 
|  | #include "util/msg_loop.h" | 
|  | #include "util/test/test.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | bool ItemContainsBuildDependencyFile(const Item* item, | 
|  | const SourceFile& source_file) { | 
|  | const auto& build_dependency_files = item->build_dependency_files(); | 
|  | return build_dependency_files.end() != | 
|  | build_dependency_files.find(source_file); | 
|  | } | 
|  |  | 
|  | class MockBuilder { | 
|  | public: | 
|  | void OnItemDefined(std::unique_ptr<Item> item); | 
|  | std::vector<const Item*> GetAllItems() const; | 
|  |  | 
|  | private: | 
|  | std::vector<std::unique_ptr<Item>> items_; | 
|  | }; | 
|  |  | 
|  | void MockBuilder::OnItemDefined(std::unique_ptr<Item> item) { | 
|  | items_.push_back(std::move(item)); | 
|  | } | 
|  |  | 
|  | std::vector<const Item*> MockBuilder::GetAllItems() const { | 
|  | std::vector<const Item*> result; | 
|  | for (const auto& item : items_) { | 
|  | result.push_back(item.get()); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | class MockInputFileManager { | 
|  | public: | 
|  | using Callback = std::function<void(const ParseNode*)>; | 
|  |  | 
|  | MockInputFileManager(); | 
|  | ~MockInputFileManager(); | 
|  |  | 
|  | LoaderImpl::AsyncLoadFileCallback GetAsyncCallback(); | 
|  |  | 
|  | // Sets a given response for a given source file. | 
|  | void AddCannedResponse(const SourceFile& source_file, | 
|  | const std::string& source); | 
|  |  | 
|  | // Returns true if there is/are pending load(s) matching the given file(s). | 
|  | bool HasOnePending(const SourceFile& f) const; | 
|  | bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const; | 
|  |  | 
|  | void IssueAllPending(); | 
|  |  | 
|  | private: | 
|  | struct CannedResult { | 
|  | std::unique_ptr<InputFile> input_file; | 
|  | std::vector<Token> tokens; | 
|  | std::unique_ptr<ParseNode> root; | 
|  | }; | 
|  |  | 
|  | InputFileManager::SyncLoadFileCallback GetSyncCallback(); | 
|  |  | 
|  | bool AsyncLoadFile(const LocationRange& origin, | 
|  | const BuildSettings* build_settings, | 
|  | const SourceFile& file_name, | 
|  | const Callback& callback, | 
|  | Err* err) { | 
|  | pending_.push_back(std::make_pair(file_name, callback)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | using CannedResponseMap = std::map<SourceFile, std::unique_ptr<CannedResult>>; | 
|  | CannedResponseMap canned_responses_; | 
|  |  | 
|  | std::vector<std::pair<SourceFile, Callback>> pending_; | 
|  | }; | 
|  |  | 
|  | MockInputFileManager::MockInputFileManager() { | 
|  | g_scheduler->input_file_manager()->set_load_file_callback(GetSyncCallback()); | 
|  | } | 
|  |  | 
|  | MockInputFileManager::~MockInputFileManager() { | 
|  | g_scheduler->input_file_manager()->set_load_file_callback(nullptr); | 
|  | } | 
|  |  | 
|  | LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetAsyncCallback() { | 
|  | return | 
|  | [this](const LocationRange& origin, const BuildSettings* build_settings, | 
|  | const SourceFile& file_name, const Callback& callback, Err* err) { | 
|  | return AsyncLoadFile(origin, build_settings, file_name, callback, err); | 
|  | }; | 
|  | } | 
|  |  | 
|  | InputFileManager::SyncLoadFileCallback MockInputFileManager::GetSyncCallback() { | 
|  | return | 
|  | [this](const SourceFile& file_name, InputFile* file) { | 
|  | CannedResponseMap::const_iterator found = canned_responses_.find(file_name); | 
|  | if (found == canned_responses_.end()) | 
|  | return false; | 
|  | file->SetContents(found->second->input_file->contents()); | 
|  | return true; | 
|  | }; | 
|  | } | 
|  |  | 
|  | // Sets a given response for a given source file. | 
|  | void MockInputFileManager::AddCannedResponse(const SourceFile& source_file, | 
|  | const std::string& source) { | 
|  | std::unique_ptr<CannedResult> canned = std::make_unique<CannedResult>(); | 
|  | canned->input_file = std::make_unique<InputFile>(source_file); | 
|  | canned->input_file->SetContents(source); | 
|  |  | 
|  | // Tokenize. | 
|  | Err err; | 
|  | canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err); | 
|  | EXPECT_FALSE(err.has_error()); | 
|  |  | 
|  | // Parse. | 
|  | canned->root = Parser::Parse(canned->tokens, &err); | 
|  | if (err.has_error()) | 
|  | err.PrintToStdout(); | 
|  | EXPECT_FALSE(err.has_error()); | 
|  |  | 
|  | canned_responses_[source_file] = std::move(canned); | 
|  | } | 
|  |  | 
|  | bool MockInputFileManager::HasOnePending(const SourceFile& f) const { | 
|  | return pending_.size() == 1u && pending_[0].first == f; | 
|  | } | 
|  |  | 
|  | bool MockInputFileManager::HasTwoPending(const SourceFile& f1, | 
|  | const SourceFile& f2) const { | 
|  | if (pending_.size() != 2u) | 
|  | return false; | 
|  | return pending_[0].first == f1 && pending_[1].first == f2; | 
|  | } | 
|  |  | 
|  | void MockInputFileManager::IssueAllPending() { | 
|  | BlockNode block(BlockNode::DISCARDS_RESULT);  // Default response. | 
|  |  | 
|  | for (const auto& cur : pending_) { | 
|  | CannedResponseMap::const_iterator found = canned_responses_.find(cur.first); | 
|  | if (found == canned_responses_.end()) | 
|  | cur.second(&block); | 
|  | else | 
|  | cur.second(found->second->root.get()); | 
|  | } | 
|  | pending_.clear(); | 
|  | } | 
|  |  | 
|  | // LoaderTest ------------------------------------------------------------------ | 
|  |  | 
|  | class LoaderTest : public TestWithScheduler { | 
|  | public: | 
|  | LoaderTest() { build_settings_.SetBuildDir(SourceDir("//out/Debug/")); } | 
|  |  | 
|  | protected: | 
|  | BuildSettings build_settings_; | 
|  | MockBuilder mock_builder_; | 
|  | MockInputFileManager mock_ifm_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // ----------------------------------------------------------------------------- | 
|  |  | 
|  | TEST_F(LoaderTest, Foo) { | 
|  | SourceFile build_config("//build/config/BUILDCONFIG.gn"); | 
|  | build_settings_.set_build_config_file(build_config); | 
|  |  | 
|  | scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_)); | 
|  |  | 
|  | // The default toolchain needs to be set by the build config file. | 
|  | mock_ifm_.AddCannedResponse(build_config, | 
|  | "set_default_toolchain(\"//tc:tc\")"); | 
|  |  | 
|  | loader->set_async_load_file(mock_ifm_.GetAsyncCallback()); | 
|  |  | 
|  | // Request the root build file be loaded. This should kick off the default | 
|  | // build config loading. | 
|  | SourceFile root_build("//BUILD.gn"); | 
|  | loader->Load(root_build, LocationRange(), Label()); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Completing the build config load should kick off the root build file load. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(root_build)); | 
|  |  | 
|  | // Load the root build file. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  |  | 
|  | // Schedule some other file to load in another toolchain. | 
|  | Label second_tc(SourceDir("//tc2/"), "tc2"); | 
|  | SourceFile second_file("//foo/BUILD.gn"); | 
|  | loader->Load(second_file, LocationRange(), second_tc); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn"))); | 
|  |  | 
|  | // Running the toolchain file should schedule the build config file to load | 
|  | // for that toolchain. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  |  | 
|  | // We have to tell it we have a toolchain definition now (normally the | 
|  | // builder would do this). | 
|  | const Settings* default_settings = loader->GetToolchainSettings(Label()); | 
|  | Toolchain second_tc_object(default_settings, second_tc); | 
|  | loader->ToolchainLoaded(&second_tc_object); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Scheduling a second file to load in that toolchain should not make it | 
|  | // pending yet (it's waiting for the build config). | 
|  | SourceFile third_file("//bar/BUILD.gn"); | 
|  | loader->Load(third_file, LocationRange(), second_tc); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Running the build config file should make our third file pending. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  | EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file)); | 
|  |  | 
|  | EXPECT_FALSE(scheduler().is_failed()); | 
|  | } | 
|  |  | 
|  | TEST_F(LoaderTest, BuildDependencyFilesAreCollected) { | 
|  | SourceFile build_config("//build/config/BUILDCONFIG.gn"); | 
|  | SourceFile root_build("//BUILD.gn"); | 
|  | build_settings_.set_build_config_file(build_config); | 
|  | build_settings_.set_item_defined_callback( | 
|  | [builder = &mock_builder_](std::unique_ptr<Item> item) { | 
|  | builder->OnItemDefined(std::move(item)); | 
|  | }); | 
|  |  | 
|  | scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_)); | 
|  | mock_ifm_.AddCannedResponse(build_config, | 
|  | "set_default_toolchain(\"//tc:tc\")"); | 
|  | std::string root_build_content = | 
|  | "executable(\"a\") { sources = [ \"a.cc\" ] }\n" | 
|  | "config(\"b\") { configs = [\"//t:t\"] }\n" | 
|  | "toolchain(\"c\") {}\n" | 
|  | "pool(\"d\") { depth = 1 }"; | 
|  | mock_ifm_.AddCannedResponse(root_build, root_build_content); | 
|  |  | 
|  | loader->set_async_load_file(mock_ifm_.GetAsyncCallback()); | 
|  |  | 
|  | // Request the root build file be loaded. This should kick off the default | 
|  | // build config loading. | 
|  | loader->Load(root_build, LocationRange(), Label()); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Completing the build config load should kick off the root build file load. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(root_build)); | 
|  |  | 
|  | // Completing the root build file should define a target which must have | 
|  | // set of source files hashes. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  |  | 
|  | std::vector<const Item*> items = mock_builder_.GetAllItems(); | 
|  | EXPECT_TRUE(items[0]->AsTarget()); | 
|  | EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build)); | 
|  | EXPECT_TRUE(items[1]->AsConfig()); | 
|  | EXPECT_TRUE(ItemContainsBuildDependencyFile(items[1], root_build)); | 
|  | EXPECT_TRUE(items[2]->AsToolchain()); | 
|  | EXPECT_TRUE(ItemContainsBuildDependencyFile(items[2], root_build)); | 
|  | EXPECT_TRUE(items[3]->AsPool()); | 
|  | EXPECT_TRUE(ItemContainsBuildDependencyFile(items[3], root_build)); | 
|  |  | 
|  | EXPECT_FALSE(scheduler().is_failed()); | 
|  | } | 
|  |  | 
|  | TEST_F(LoaderTest, TemplateBuildDependencyFilesAreCollected) { | 
|  | SourceFile build_config("//build/config/BUILDCONFIG.gn"); | 
|  | SourceFile root_build("//BUILD.gn"); | 
|  | build_settings_.set_build_config_file(build_config); | 
|  | build_settings_.set_item_defined_callback( | 
|  | [builder = &mock_builder_](std::unique_ptr<Item> item) { | 
|  | builder->OnItemDefined(std::move(item)); | 
|  | }); | 
|  |  | 
|  | scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_)); | 
|  | mock_ifm_.AddCannedResponse(build_config, | 
|  | "set_default_toolchain(\"//tc:tc\")"); | 
|  | mock_ifm_.AddCannedResponse( | 
|  | SourceFile("//test.gni"), | 
|  | "template(\"tmpl\") {\n" | 
|  | "  executable(target_name) { sources = invoker.sources }\n" | 
|  | "}\n"); | 
|  | mock_ifm_.AddCannedResponse(root_build, | 
|  | "import(\"//test.gni\")\n" | 
|  | "tmpl(\"a\") {sources = [ \"a.cc\" ]}\n"); | 
|  |  | 
|  | loader->set_async_load_file(mock_ifm_.GetAsyncCallback()); | 
|  |  | 
|  | // Request the root build file be loaded. This should kick off the default | 
|  | // build config loading. | 
|  | loader->Load(root_build, LocationRange(), Label()); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Completing the build config load should kick off the root build file load. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(root_build)); | 
|  |  | 
|  | // Completing the root build file should define a target which must have | 
|  | // set of source files hashes. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  |  | 
|  | std::vector<const Item*> items = mock_builder_.GetAllItems(); | 
|  | EXPECT_TRUE(items[0]->AsTarget()); | 
|  | // Ensure the target as a dep on BUILD.gn even though it was defined via | 
|  | // a template. | 
|  | EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build)); | 
|  |  | 
|  | EXPECT_FALSE(scheduler().is_failed()); | 
|  | } | 
|  |  | 
|  | TEST_F(LoaderTest, NonDefaultBuildFileName) { | 
|  | std::string new_name = "BUILD.more.gn"; | 
|  |  | 
|  | SourceFile build_config("//build/config/BUILDCONFIG.gn"); | 
|  | build_settings_.set_build_config_file(build_config); | 
|  |  | 
|  | scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_)); | 
|  | loader->set_build_file_extension("more"); | 
|  |  | 
|  | // The default toolchain needs to be set by the build config file. | 
|  | mock_ifm_.AddCannedResponse(build_config, | 
|  | "set_default_toolchain(\"//tc:tc\")"); | 
|  |  | 
|  | loader->set_async_load_file(mock_ifm_.GetAsyncCallback()); | 
|  |  | 
|  | // Request the root build file be loaded. This should kick off the default | 
|  | // build config loading. | 
|  | SourceFile root_build("//" + new_name); | 
|  | loader->Load(root_build, LocationRange(), Label()); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Completing the build config load should kick off the root build file load. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(root_build)); | 
|  |  | 
|  | // Load the root build file. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  |  | 
|  | // Schedule some other file to load in another toolchain. | 
|  | Label second_tc(SourceDir("//tc2/"), "tc2"); | 
|  | SourceFile second_file("//foo/" + new_name); | 
|  | loader->Load(second_file, LocationRange(), second_tc); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/" + new_name))); | 
|  |  | 
|  | // Running the toolchain file should schedule the build config file to load | 
|  | // for that toolchain. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  |  | 
|  | // We have to tell it we have a toolchain definition now (normally the | 
|  | // builder would do this). | 
|  | const Settings* default_settings = loader->GetToolchainSettings(Label()); | 
|  | Toolchain second_tc_object(default_settings, second_tc); | 
|  | loader->ToolchainLoaded(&second_tc_object); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); | 
|  |  | 
|  | // Running the build config file should make our second file pending. | 
|  | mock_ifm_.IssueAllPending(); | 
|  | MsgLoop::Current()->RunUntilIdleForTesting(); | 
|  | EXPECT_TRUE(mock_ifm_.HasOnePending(second_file)); | 
|  |  | 
|  | EXPECT_FALSE(scheduler().is_failed()); | 
|  | } |