• 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 [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