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 [this](const SourceFile& file_name, InputFile* file) {
114 CannedResponseMap::const_iterator found = canned_responses_.find(file_name);
115 if (found == canned_responses_.end())
116 return false;
117 file->SetContents(found->second->input_file->contents());
118 return true;
119 };
120 }
121
122 // Sets a given response for a given source file.
AddCannedResponse(const SourceFile & source_file,const std::string & source)123 void MockInputFileManager::AddCannedResponse(const SourceFile& source_file,
124 const std::string& source) {
125 std::unique_ptr<CannedResult> canned = std::make_unique<CannedResult>();
126 canned->input_file = std::make_unique<InputFile>(source_file);
127 canned->input_file->SetContents(source);
128
129 // Tokenize.
130 Err err;
131 canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err);
132 EXPECT_FALSE(err.has_error());
133
134 // Parse.
135 canned->root = Parser::Parse(canned->tokens, &err);
136 if (err.has_error())
137 err.PrintToStdout();
138 EXPECT_FALSE(err.has_error());
139
140 canned_responses_[source_file] = std::move(canned);
141 }
142
HasOnePending(const SourceFile & f) const143 bool MockInputFileManager::HasOnePending(const SourceFile& f) const {
144 return pending_.size() == 1u && pending_[0].first == f;
145 }
146
HasTwoPending(const SourceFile & f1,const SourceFile & f2) const147 bool MockInputFileManager::HasTwoPending(const SourceFile& f1,
148 const SourceFile& f2) const {
149 if (pending_.size() != 2u)
150 return false;
151 return pending_[0].first == f1 && pending_[1].first == f2;
152 }
153
IssueAllPending()154 void MockInputFileManager::IssueAllPending() {
155 BlockNode block(BlockNode::DISCARDS_RESULT); // Default response.
156
157 for (const auto& cur : pending_) {
158 CannedResponseMap::const_iterator found = canned_responses_.find(cur.first);
159 if (found == canned_responses_.end())
160 cur.second(&block);
161 else
162 cur.second(found->second->root.get());
163 }
164 pending_.clear();
165 }
166
167 // LoaderTest ------------------------------------------------------------------
168
169 class LoaderTest : public TestWithScheduler {
170 public:
LoaderTest()171 LoaderTest() { build_settings_.SetBuildDir(SourceDir("//out/Debug/")); }
172
173 protected:
174 BuildSettings build_settings_;
175 MockBuilder mock_builder_;
176 MockInputFileManager mock_ifm_;
177 };
178
179 } // namespace
180
181 // -----------------------------------------------------------------------------
182
TEST_F(LoaderTest,Foo)183 TEST_F(LoaderTest, Foo) {
184 SourceFile build_config("//build/config/BUILDCONFIG.gn");
185 build_settings_.set_build_config_file(build_config);
186
187 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
188
189 // The default toolchain needs to be set by the build config file.
190 mock_ifm_.AddCannedResponse(build_config,
191 "set_default_toolchain(\"//tc:tc\")");
192
193 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
194
195 // Request the root build file be loaded. This should kick off the default
196 // build config loading.
197 SourceFile root_build("//BUILD.gn");
198 loader->Load(root_build, LocationRange(), Label());
199 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
200
201 // Completing the build config load should kick off the root build file load.
202 mock_ifm_.IssueAllPending();
203 MsgLoop::Current()->RunUntilIdleForTesting();
204 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
205
206 // Load the root build file.
207 mock_ifm_.IssueAllPending();
208 MsgLoop::Current()->RunUntilIdleForTesting();
209
210 // Schedule some other file to load in another toolchain.
211 Label second_tc(SourceDir("//tc2/"), "tc2");
212 SourceFile second_file("//foo/BUILD.gn");
213 loader->Load(second_file, LocationRange(), second_tc);
214 EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn")));
215
216 // Running the toolchain file should schedule the build config file to load
217 // for that toolchain.
218 mock_ifm_.IssueAllPending();
219 MsgLoop::Current()->RunUntilIdleForTesting();
220
221 // We have to tell it we have a toolchain definition now (normally the
222 // builder would do this).
223 const Settings* default_settings = loader->GetToolchainSettings(Label());
224 Toolchain second_tc_object(default_settings, second_tc);
225 loader->ToolchainLoaded(&second_tc_object);
226 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
227
228 // Scheduling a second file to load in that toolchain should not make it
229 // pending yet (it's waiting for the build config).
230 SourceFile third_file("//bar/BUILD.gn");
231 loader->Load(third_file, LocationRange(), second_tc);
232 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
233
234 // Running the build config file should make our third file pending.
235 mock_ifm_.IssueAllPending();
236 MsgLoop::Current()->RunUntilIdleForTesting();
237 EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
238
239 EXPECT_FALSE(scheduler().is_failed());
240 }
241
TEST_F(LoaderTest,BuildDependencyFilesAreCollected)242 TEST_F(LoaderTest, BuildDependencyFilesAreCollected) {
243 SourceFile build_config("//build/config/BUILDCONFIG.gn");
244 SourceFile root_build("//BUILD.gn");
245 build_settings_.set_build_config_file(build_config);
246 build_settings_.set_item_defined_callback(
247 [builder = &mock_builder_](std::unique_ptr<Item> item) {
248 builder->OnItemDefined(std::move(item));
249 });
250
251 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
252 mock_ifm_.AddCannedResponse(build_config,
253 "set_default_toolchain(\"//tc:tc\")");
254 std::string root_build_content =
255 "executable(\"a\") { sources = [ \"a.cc\" ] }\n"
256 "config(\"b\") { configs = [\"//t:t\"] }\n"
257 "toolchain(\"c\") {}\n"
258 "pool(\"d\") { depth = 1 }";
259 mock_ifm_.AddCannedResponse(root_build, root_build_content);
260
261 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
262
263 // Request the root build file be loaded. This should kick off the default
264 // build config loading.
265 loader->Load(root_build, LocationRange(), Label());
266 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
267
268 // Completing the build config load should kick off the root build file load.
269 mock_ifm_.IssueAllPending();
270 MsgLoop::Current()->RunUntilIdleForTesting();
271 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
272
273 // Completing the root build file should define a target which must have
274 // set of source files hashes.
275 mock_ifm_.IssueAllPending();
276 MsgLoop::Current()->RunUntilIdleForTesting();
277
278 std::vector<const Item*> items = mock_builder_.GetAllItems();
279 EXPECT_TRUE(items[0]->AsTarget());
280 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
281 EXPECT_TRUE(items[1]->AsConfig());
282 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[1], root_build));
283 EXPECT_TRUE(items[2]->AsToolchain());
284 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[2], root_build));
285 EXPECT_TRUE(items[3]->AsPool());
286 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[3], root_build));
287
288 EXPECT_FALSE(scheduler().is_failed());
289 }
290
TEST_F(LoaderTest,TemplateBuildDependencyFilesAreCollected)291 TEST_F(LoaderTest, TemplateBuildDependencyFilesAreCollected) {
292 SourceFile build_config("//build/config/BUILDCONFIG.gn");
293 SourceFile root_build("//BUILD.gn");
294 build_settings_.set_build_config_file(build_config);
295 build_settings_.set_item_defined_callback(
296 [builder = &mock_builder_](std::unique_ptr<Item> item) {
297 builder->OnItemDefined(std::move(item));
298 });
299
300 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
301 mock_ifm_.AddCannedResponse(build_config,
302 "set_default_toolchain(\"//tc:tc\")");
303 mock_ifm_.AddCannedResponse(
304 SourceFile("//test.gni"),
305 "template(\"tmpl\") {\n"
306 " executable(target_name) { sources = invoker.sources }\n"
307 "}\n");
308 mock_ifm_.AddCannedResponse(root_build,
309 "import(\"//test.gni\")\n"
310 "tmpl(\"a\") {sources = [ \"a.cc\" ]}\n");
311
312 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
313
314 // Request the root build file be loaded. This should kick off the default
315 // build config loading.
316 loader->Load(root_build, LocationRange(), Label());
317 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
318
319 // Completing the build config load should kick off the root build file load.
320 mock_ifm_.IssueAllPending();
321 MsgLoop::Current()->RunUntilIdleForTesting();
322 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
323
324 // Completing the root build file should define a target which must have
325 // set of source files hashes.
326 mock_ifm_.IssueAllPending();
327 MsgLoop::Current()->RunUntilIdleForTesting();
328
329 std::vector<const Item*> items = mock_builder_.GetAllItems();
330 EXPECT_TRUE(items[0]->AsTarget());
331 // Ensure the target as a dep on BUILD.gn even though it was defined via
332 // a template.
333 EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
334
335 EXPECT_FALSE(scheduler().is_failed());
336 }
337
TEST_F(LoaderTest,NonDefaultBuildFileName)338 TEST_F(LoaderTest, NonDefaultBuildFileName) {
339 std::string new_name = "BUILD.more.gn";
340
341 SourceFile build_config("//build/config/BUILDCONFIG.gn");
342 build_settings_.set_build_config_file(build_config);
343
344 scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
345 loader->set_build_file_extension("more");
346
347 // The default toolchain needs to be set by the build config file.
348 mock_ifm_.AddCannedResponse(build_config,
349 "set_default_toolchain(\"//tc:tc\")");
350
351 loader->set_async_load_file(mock_ifm_.GetAsyncCallback());
352
353 // Request the root build file be loaded. This should kick off the default
354 // build config loading.
355 SourceFile root_build("//" + new_name);
356 loader->Load(root_build, LocationRange(), Label());
357 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
358
359 // Completing the build config load should kick off the root build file load.
360 mock_ifm_.IssueAllPending();
361 MsgLoop::Current()->RunUntilIdleForTesting();
362 EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
363
364 // Load the root build file.
365 mock_ifm_.IssueAllPending();
366 MsgLoop::Current()->RunUntilIdleForTesting();
367
368 // Schedule some other file to load in another toolchain.
369 Label second_tc(SourceDir("//tc2/"), "tc2");
370 SourceFile second_file("//foo/" + new_name);
371 loader->Load(second_file, LocationRange(), second_tc);
372 EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/" + new_name)));
373
374 // Running the toolchain file should schedule the build config file to load
375 // for that toolchain.
376 mock_ifm_.IssueAllPending();
377 MsgLoop::Current()->RunUntilIdleForTesting();
378
379 // We have to tell it we have a toolchain definition now (normally the
380 // builder would do this).
381 const Settings* default_settings = loader->GetToolchainSettings(Label());
382 Toolchain second_tc_object(default_settings, second_tc);
383 loader->ToolchainLoaded(&second_tc_object);
384 EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
385
386 // Running the build config file should make our second file pending.
387 mock_ifm_.IssueAllPending();
388 MsgLoop::Current()->RunUntilIdleForTesting();
389 EXPECT_TRUE(mock_ifm_.HasOnePending(second_file));
390
391 EXPECT_FALSE(scheduler().is_failed());
392 }
393