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 <functional>
6 #include <map>
7 #include <memory>
8 #include <utility>
9 #include <vector>
10
11 #include "gn/build_settings.h"
12 #include "gn/err.h"
13 #include "gn/loader.h"
14 #include "gn/parse_tree.h"
15 #include "gn/parser.h"
16 #include "gn/scheduler.h"
17 #include "gn/test_with_scheduler.h"
18 #include "gn/tokenizer.h"
19 #include "util/msg_loop.h"
20 #include "util/test/test.h"
21
22 namespace {
23
ItemContainsBuildDependencyFile(const Item * item,const SourceFile & source_file)24 bool ItemContainsBuildDependencyFile(const Item* item,
25 const SourceFile& source_file) {
26 const auto& build_dependency_files = item->build_dependency_files();
27 return build_dependency_files.end() !=
28 build_dependency_files.find(source_file);
29 }
30
31 class MockBuilder {
32 public:
33 void OnItemDefined(std::unique_ptr<Item> item);
34 std::vector<const Item*> GetAllItems() const;
35
36 private:
37 std::vector<std::unique_ptr<Item>> items_;
38 };
39
OnItemDefined(std::unique_ptr<Item> item)40 void MockBuilder::OnItemDefined(std::unique_ptr<Item> item) {
41 items_.push_back(std::move(item));
42 }
43
GetAllItems() const44 std::vector<const Item*> MockBuilder::GetAllItems() const {
45 std::vector<const Item*> result;
46 for (const auto& item : items_) {
47 result.push_back(item.get());
48 }
49
50 return result;
51 }
52
53 class MockInputFileManager {
54 public:
55 using Callback = std::function<void(const ParseNode*)>;
56
57 MockInputFileManager();
58 ~MockInputFileManager();
59
60 LoaderImpl::AsyncLoadFileCallback GetAsyncCallback();
61
62 // Sets a given response for a given source file.
63 void AddCannedResponse(const SourceFile& source_file,
64 const std::string& source);
65
66 // Returns true if there is/are pending load(s) matching the given file(s).
67 bool HasOnePending(const SourceFile& f) const;
68 bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const;
69
70 void IssueAllPending();
71
72 private:
73 struct CannedResult {
74 std::unique_ptr<InputFile> input_file;
75 std::vector<Token> tokens;
76 std::unique_ptr<ParseNode> root;
77 };
78
79 InputFileManager::SyncLoadFileCallback GetSyncCallback();
80
AsyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,const Callback & callback,Err * err)81 bool AsyncLoadFile(const LocationRange& origin,
82 const BuildSettings* build_settings,
83 const SourceFile& file_name,
84 const Callback& callback,
85 Err* err) {
86 pending_.push_back(std::make_pair(file_name, callback));
87 return true;
88 }
89
90 using CannedResponseMap = std::map<SourceFile, std::unique_ptr<CannedResult>>;
91 CannedResponseMap canned_responses_;
92
93 std::vector<std::pair<SourceFile, Callback>> pending_;
94 };
95
MockInputFileManager()96 MockInputFileManager::MockInputFileManager() {
97 g_scheduler->input_file_manager()->set_load_file_callback(GetSyncCallback());
98 }
99
~MockInputFileManager()100 MockInputFileManager::~MockInputFileManager() {
101 g_scheduler->input_file_manager()->set_load_file_callback(nullptr);
102 }
103
GetAsyncCallback()104 LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetAsyncCallback() {
105 return
106 [this](const LocationRange& origin, const BuildSettings* build_settings,
107 const SourceFile& file_name, const Callback& callback, Err* err) {
108 return AsyncLoadFile(origin, build_settings, file_name, callback, err);
109 };
110 }
111
GetSyncCallback()112 InputFileManager::SyncLoadFileCallback MockInputFileManager::GetSyncCallback() {
113 return
114 [this](const SourceFile& file_name, InputFile* file) {
115 CannedResponseMap::const_iterator found = canned_responses_.find(file_name);
116 if (found == canned_responses_.end())
117 return false;
118 file->SetContents(found->second->input_file->contents());
119 return true;
120 };
121 }
122
123 // Sets a given response for a given source file.
AddCannedResponse(const SourceFile & source_file,const std::string & source)124 void MockInputFileManager::AddCannedResponse(const SourceFile& source_file,
125 const std::string& source) {
126 std::unique_ptr<CannedResult> canned = std::make_unique<CannedResult>();
127 canned->input_file = std::make_unique<InputFile>(source_file);
128 canned->input_file->SetContents(source);
129
130 // Tokenize.
131 Err err;
132 canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err);
133 EXPECT_FALSE(err.has_error());
134
135 // Parse.
136 canned->root = Parser::Parse(canned->tokens, &err);
137 if (err.has_error())
138 err.PrintToStdout();
139 EXPECT_FALSE(err.has_error());
140
141 canned_responses_[source_file] = std::move(canned);
142 }
143
HasOnePending(const SourceFile & f) const144 bool MockInputFileManager::HasOnePending(const SourceFile& f) const {
145 return pending_.size() == 1u && pending_[0].first == f;
146 }
147
HasTwoPending(const SourceFile & f1,const SourceFile & f2) const148 bool MockInputFileManager::HasTwoPending(const SourceFile& f1,
149 const SourceFile& f2) const {
150 if (pending_.size() != 2u)
151 return false;
152 return pending_[0].first == f1 && pending_[1].first == f2;
153 }
154
IssueAllPending()155 void MockInputFileManager::IssueAllPending() {
156 BlockNode block(BlockNode::DISCARDS_RESULT); // Default response.
157
158 for (const auto& cur : pending_) {
159 CannedResponseMap::const_iterator found = canned_responses_.find(cur.first);
160 if (found == canned_responses_.end())
161 cur.second(&block);
162 else
163 cur.second(found->second->root.get());
164 }
165 pending_.clear();
166 }
167
168 // LoaderTest ------------------------------------------------------------------
169
170 class LoaderTest : public TestWithScheduler {
171 public:
LoaderTest()172 LoaderTest() { build_settings_.SetBuildDir(SourceDir("//out/Debug/")); }
173
174 protected:
175 BuildSettings build_settings_;
176 MockBuilder mock_builder_;
177 MockInputFileManager mock_ifm_;
178 };
179
180 } // namespace
181
182 // -----------------------------------------------------------------------------
183
TEST_F(LoaderTest,Foo)184 TEST_F(LoaderTest, Foo) {
185 SourceFile build_config("//build/config/BUILDCONFIG.gn");
186 build_settings_.set_build_config_file(build_config);
187
188 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
189
190 // The default toolchain needs to be set by the build config file.
191 mock_ifm_.AddCannedResponse(build_config,
192 "set_default_toolchain(\"//tc:tc\")");
193
194 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
195
196 // Request the root build file be loaded. This should kick off the default
197 // build config loading.
198 SourceFile root_build("//BUILD.gn");
199 loader->Load(root_build, LocationRange(), Label());
200 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
201
202 // Completing the build config load should kick off the root build file load.
203 mock_ifm_.IssueAllPending();
204 MsgLoop::Current()->RunUntilIdleForTesting();
205 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
206
207 // Load the root build file.
208 mock_ifm_.IssueAllPending();
209 MsgLoop::Current()->RunUntilIdleForTesting();
210
211 // Schedule some other file to load in another toolchain.
212 Label second_tc(SourceDir("//tc2/"), "tc2");
213 SourceFile second_file("//foo/BUILD.gn");
214 loader->Load(second_file, LocationRange(), second_tc);
215 EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn")));
216
217 // Running the toolchain file should schedule the build config file to load
218 // for that toolchain.
219 mock_ifm_.IssueAllPending();
220 MsgLoop::Current()->RunUntilIdleForTesting();
221
222 // We have to tell it we have a toolchain definition now (normally the
223 // builder would do this).
224 const Settings* default_settings = loader->GetToolchainSettings(Label());
225 Toolchain second_tc_object(default_settings, second_tc);
226 loader->ToolchainLoaded(&second_tc_object);
227 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
228
229 // Scheduling a second file to load in that toolchain should not make it
230 // pending yet (it's waiting for the build config).
231 SourceFile third_file("//bar/BUILD.gn");
232 loader->Load(third_file, LocationRange(), second_tc);
233 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
234
235 // Running the build config file should make our third file pending.
236 mock_ifm_.IssueAllPending();
237 MsgLoop::Current()->RunUntilIdleForTesting();
238 EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
239
240 EXPECT_FALSE(scheduler().is_failed());
241 }
242
TEST_F(LoaderTest,BuildDependencyFilesAreCollected)243 TEST_F(LoaderTest, BuildDependencyFilesAreCollected) {
244 SourceFile build_config("//build/config/BUILDCONFIG.gn");
245 SourceFile root_build("//BUILD.gn");
246 build_settings_.set_build_config_file(build_config);
247 build_settings_.set_item_defined_callback(
248 [builder = &mock_builder_](std::unique_ptr<Item> item) {
249 builder->OnItemDefined(std::move(item));
250 });
251
252 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
253 mock_ifm_.AddCannedResponse(build_config,
254 "set_default_toolchain(\"//tc:tc\")");
255 std::string root_build_content =
256 "executable(\"a\") { sources = [ \"a.cc\" ] }\n"
257 "config(\"b\") { configs = [\"//t:t\"] }\n"
258 "toolchain(\"c\") {}\n"
259 "pool(\"d\") { depth = 1 }";
260 mock_ifm_.AddCannedResponse(root_build, root_build_content);
261
262 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
263
264 // Request the root build file be loaded. This should kick off the default
265 // build config loading.
266 loader->Load(root_build, LocationRange(), Label());
267 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
268
269 // Completing the build config load should kick off the root build file load.
270 mock_ifm_.IssueAllPending();
271 MsgLoop::Current()->RunUntilIdleForTesting();
272 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
273
274 // Completing the root build file should define a target which must have
275 // set of source files hashes.
276 mock_ifm_.IssueAllPending();
277 MsgLoop::Current()->RunUntilIdleForTesting();
278
279 std::vector<const Item*> items = mock_builder_.GetAllItems();
280 EXPECT_TRUE(items[0]->AsTarget());
281 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
282 EXPECT_TRUE(items[1]->AsConfig());
283 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[1], root_build));
284 EXPECT_TRUE(items[2]->AsToolchain());
285 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[2], root_build));
286 EXPECT_TRUE(items[3]->AsPool());
287 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[3], root_build));
288
289 EXPECT_FALSE(scheduler().is_failed());
290 }
291
TEST_F(LoaderTest,TemplateBuildDependencyFilesAreCollected)292 TEST_F(LoaderTest, TemplateBuildDependencyFilesAreCollected) {
293 SourceFile build_config("//build/config/BUILDCONFIG.gn");
294 SourceFile root_build("//BUILD.gn");
295 build_settings_.set_build_config_file(build_config);
296 build_settings_.set_item_defined_callback(
297 [builder = &mock_builder_](std::unique_ptr<Item> item) {
298 builder->OnItemDefined(std::move(item));
299 });
300
301 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
302 mock_ifm_.AddCannedResponse(build_config,
303 "set_default_toolchain(\"//tc:tc\")");
304 mock_ifm_.AddCannedResponse(
305 SourceFile("//test.gni"),
306 "template(\"tmpl\") {\n"
307 " executable(target_name) { sources = invoker.sources }\n"
308 "}\n");
309 mock_ifm_.AddCannedResponse(root_build,
310 "import(\"//test.gni\")\n"
311 "tmpl(\"a\") {sources = [ \"a.cc\" ]}\n");
312
313 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
314
315 // Request the root build file be loaded. This should kick off the default
316 // build config loading.
317 loader->Load(root_build, LocationRange(), Label());
318 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
319
320 // Completing the build config load should kick off the root build file load.
321 mock_ifm_.IssueAllPending();
322 MsgLoop::Current()->RunUntilIdleForTesting();
323 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
324
325 // Completing the root build file should define a target which must have
326 // set of source files hashes.
327 mock_ifm_.IssueAllPending();
328 MsgLoop::Current()->RunUntilIdleForTesting();
329
330 std::vector<const Item*> items = mock_builder_.GetAllItems();
331 EXPECT_TRUE(items[0]->AsTarget());
332 // Ensure the target as a dep on BUILD.gn even though it was defined via
333 // a template.
334 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
335
336 EXPECT_FALSE(scheduler().is_failed());
337 }
338
TEST_F(LoaderTest,NonDefaultBuildFileName)339 TEST_F(LoaderTest, NonDefaultBuildFileName) {
340 std::string new_name = "BUILD.more.gn";
341
342 SourceFile build_config("//build/config/BUILDCONFIG.gn");
343 build_settings_.set_build_config_file(build_config);
344
345 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
346 loader->set_build_file_extension("more");
347
348 // The default toolchain needs to be set by the build config file.
349 mock_ifm_.AddCannedResponse(build_config,
350 "set_default_toolchain(\"//tc:tc\")");
351
352 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
353
354 // Request the root build file be loaded. This should kick off the default
355 // build config loading.
356 SourceFile root_build("//" + new_name);
357 loader->Load(root_build, LocationRange(), Label());
358 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
359
360 // Completing the build config load should kick off the root build file load.
361 mock_ifm_.IssueAllPending();
362 MsgLoop::Current()->RunUntilIdleForTesting();
363 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
364
365 // Load the root build file.
366 mock_ifm_.IssueAllPending();
367 MsgLoop::Current()->RunUntilIdleForTesting();
368
369 // Schedule some other file to load in another toolchain.
370 Label second_tc(SourceDir("//tc2/"), "tc2");
371 SourceFile second_file("//foo/" + new_name);
372 loader->Load(second_file, LocationRange(), second_tc);
373 EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/" + new_name)));
374
375 // Running the toolchain file should schedule the build config file to load
376 // for that toolchain.
377 mock_ifm_.IssueAllPending();
378 MsgLoop::Current()->RunUntilIdleForTesting();
379
380 // We have to tell it we have a toolchain definition now (normally the
381 // builder would do this).
382 const Settings* default_settings = loader->GetToolchainSettings(Label());
383 Toolchain second_tc_object(default_settings, second_tc);
384 loader->ToolchainLoaded(&second_tc_object);
385 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
386
387 // Running the build config file should make our second file pending.
388 mock_ifm_.IssueAllPending();
389 MsgLoop::Current()->RunUntilIdleForTesting();
390 EXPECT_TRUE(mock_ifm_.HasOnePending(second_file));
391
392 EXPECT_FALSE(scheduler().is_failed());
393 }
394