1// Copyright (c) 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <map> 6#include <utility> 7#include <vector> 8 9#include "base/bind.h" 10#include "base/memory/linked_ptr.h" 11#include "base/message_loop/message_loop.h" 12#include "testing/gtest/include/gtest/gtest.h" 13#include "tools/gn/build_settings.h" 14#include "tools/gn/err.h" 15#include "tools/gn/loader.h" 16#include "tools/gn/parse_tree.h" 17#include "tools/gn/parser.h" 18#include "tools/gn/scheduler.h" 19#include "tools/gn/tokenizer.h" 20 21namespace { 22 23class MockInputFileManager { 24 public: 25 typedef base::Callback<void(const ParseNode*)> Callback; 26 27 MockInputFileManager() { 28 } 29 30 LoaderImpl::AsyncLoadFileCallback GetCallback(); 31 32 // Sets a given response for a given source file. 33 void AddCannedResponse(const SourceFile& source_file, 34 const std::string& source); 35 36 // Returns true if there is/are pending load(s) matching the given file(s). 37 bool HasOnePending(const SourceFile& f) const; 38 bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const; 39 40 void IssueAllPending(); 41 42 private: 43 struct CannedResult { 44 scoped_ptr<InputFile> input_file; 45 std::vector<Token> tokens; 46 scoped_ptr<ParseNode> root; 47 }; 48 49 bool AsyncLoadFile(const LocationRange& origin, 50 const BuildSettings* build_settings, 51 const SourceFile& file_name, 52 const Callback& callback, 53 Err* err) { 54 pending_.push_back(std::make_pair(file_name, callback)); 55 return true; 56 } 57 58 // Owning pointers. 59 typedef std::map<SourceFile, linked_ptr<CannedResult> > CannedResponseMap; 60 CannedResponseMap canned_responses_; 61 62 std::vector< std::pair<SourceFile, Callback> > pending_; 63}; 64 65LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetCallback() { 66 return base::Bind(&MockInputFileManager::AsyncLoadFile, 67 base::Unretained(this)); 68} 69 70// Sets a given response for a given source file. 71void MockInputFileManager::AddCannedResponse(const SourceFile& source_file, 72 const std::string& source) { 73 CannedResult* canned = new CannedResult; 74 canned->input_file.reset(new InputFile(source_file)); 75 canned->input_file->SetContents(source); 76 77 // Tokenize. 78 Err err; 79 canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err); 80 EXPECT_FALSE(err.has_error()); 81 82 // Parse. 83 canned->root = Parser::Parse(canned->tokens, &err).Pass(); 84 EXPECT_FALSE(err.has_error()); 85 86 canned_responses_[source_file] = linked_ptr<CannedResult>(canned); 87} 88 89bool MockInputFileManager::HasOnePending(const SourceFile& f) const { 90 return pending_.size() == 1u && pending_[0].first == f; 91} 92 93bool MockInputFileManager::HasTwoPending(const SourceFile& f1, 94 const SourceFile& f2) const { 95 if (pending_.size() != 2u) 96 return false; 97 return pending_[0].first == f1 && pending_[1].first == f2; 98} 99 100void MockInputFileManager::IssueAllPending() { 101 BlockNode block(false); // Default response. 102 103 for (size_t i = 0; i < pending_.size(); i++) { 104 CannedResponseMap::const_iterator found = 105 canned_responses_.find(pending_[i].first); 106 if (found == canned_responses_.end()) 107 pending_[i].second.Run(&block); 108 else 109 pending_[i].second.Run(found->second->root.get()); 110 } 111 pending_.clear(); 112} 113 114// LoaderTest ------------------------------------------------------------------ 115 116class LoaderTest : public testing::Test { 117 public: 118 LoaderTest() { 119 build_settings_.SetBuildDir(SourceDir("//out/Debug/")); 120 } 121 virtual ~LoaderTest() { 122 } 123 124 protected: 125 Scheduler scheduler_; 126 BuildSettings build_settings_; 127 MockInputFileManager mock_ifm_; 128}; 129 130} // namespace 131 132// ----------------------------------------------------------------------------- 133 134TEST_F(LoaderTest, Foo) { 135 SourceFile build_config("//build/config/BUILDCONFIG.gn"); 136 build_settings_.set_build_config_file(build_config); 137 138 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_)); 139 140 // The default toolchain needs to be set by the build config file. 141 mock_ifm_.AddCannedResponse(build_config, 142 "set_default_toolchain(\"//tc:tc\")"); 143 144 loader->set_async_load_file(mock_ifm_.GetCallback()); 145 146 // Request the root build file be loaded. This should kick off the default 147 // build config loading. 148 SourceFile root_build("//BUILD.gn"); 149 loader->Load(root_build, Label()); 150 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); 151 152 // Completing the build config load should kick off the root build file load. 153 mock_ifm_.IssueAllPending(); 154 scheduler_.main_loop()->RunUntilIdle(); 155 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build)); 156 157 // Load the root build file. 158 mock_ifm_.IssueAllPending(); 159 scheduler_.main_loop()->RunUntilIdle(); 160 161 // Schedule some other file to load in another toolchain. 162 Label second_tc(SourceDir("//tc2/"), "tc2"); 163 SourceFile second_file("//foo/BUILD.gn"); 164 loader->Load(second_file, second_tc); 165 EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn"))); 166 167 // Running the toolchain file should schedule the build config file to load 168 // for that toolchain. 169 mock_ifm_.IssueAllPending(); 170 scheduler_.main_loop()->RunUntilIdle(); 171 172 // We have to tell it we have a toolchain definition now (normally the 173 // builder would do this). 174 const Settings* default_settings = loader->GetToolchainSettings(Label()); 175 Toolchain second_tc_object(default_settings, second_tc); 176 loader->ToolchainLoaded(&second_tc_object); 177 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); 178 179 // Scheduling a second file to load in that toolchain should not make it 180 // pending yet (it's waiting for the build config). 181 SourceFile third_file("//bar/BUILD.gn"); 182 loader->Load(third_file, second_tc); 183 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config)); 184 185 // Running the build config file should make our third file pending. 186 mock_ifm_.IssueAllPending(); 187 scheduler_.main_loop()->RunUntilIdle(); 188 EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file)); 189 190 EXPECT_FALSE(scheduler_.is_failed()); 191} 192