• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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