• 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() = default;
58 
59   LoaderImpl::AsyncLoadFileCallback GetCallback();
60 
61   // Sets a given response for a given source file.
62   void AddCannedResponse(const SourceFile& source_file,
63                          const std::string& source);
64 
65   // Returns true if there is/are pending load(s) matching the given file(s).
66   bool HasOnePending(const SourceFile& f) const;
67   bool HasTwoPending(const SourceFile& f1, const SourceFile& f2) const;
68 
69   void IssueAllPending();
70 
71  private:
72   struct CannedResult {
73     std::unique_ptr<InputFile> input_file;
74     std::vector<Token> tokens;
75     std::unique_ptr<ParseNode> root;
76   };
77 
AsyncLoadFile(const LocationRange & origin,const BuildSettings * build_settings,const SourceFile & file_name,const Callback & callback,Err * err)78   bool AsyncLoadFile(const LocationRange& origin,
79                      const BuildSettings* build_settings,
80                      const SourceFile& file_name,
81                      const Callback& callback,
82                      Err* err) {
83     pending_.push_back(std::make_pair(file_name, callback));
84     return true;
85   }
86 
87   using CannedResponseMap = std::map<SourceFile, std::unique_ptr<CannedResult>>;
88   CannedResponseMap canned_responses_;
89 
90   std::vector<std::pair<SourceFile, Callback>> pending_;
91 };
92 
GetCallback()93 LoaderImpl::AsyncLoadFileCallback MockInputFileManager::GetCallback() {
94   return
95       [this](const LocationRange& origin, const BuildSettings* build_settings,
96              const SourceFile& file_name, const Callback& callback, Err* err) {
97         return AsyncLoadFile(origin, build_settings, file_name, callback, err);
98       };
99 }
100 
101 // Sets a given response for a given source file.
AddCannedResponse(const SourceFile & source_file,const std::string & source)102 void MockInputFileManager::AddCannedResponse(const SourceFile& source_file,
103                                              const std::string& source) {
104   std::unique_ptr<CannedResult> canned = std::make_unique<CannedResult>();
105   canned->input_file = std::make_unique<InputFile>(source_file);
106   canned->input_file->SetContents(source);
107 
108   // Tokenize.
109   Err err;
110   canned->tokens = Tokenizer::Tokenize(canned->input_file.get(), &err);
111   EXPECT_FALSE(err.has_error());
112 
113   // Parse.
114   canned->root = Parser::Parse(canned->tokens, &err);
115   EXPECT_FALSE(err.has_error());
116 
117   canned_responses_[source_file] = std::move(canned);
118 }
119 
HasOnePending(const SourceFile & f) const120 bool MockInputFileManager::HasOnePending(const SourceFile& f) const {
121   return pending_.size() == 1u && pending_[0].first == f;
122 }
123 
HasTwoPending(const SourceFile & f1,const SourceFile & f2) const124 bool MockInputFileManager::HasTwoPending(const SourceFile& f1,
125                                          const SourceFile& f2) const {
126   if (pending_.size() != 2u)
127     return false;
128   return pending_[0].first == f1 && pending_[1].first == f2;
129 }
130 
IssueAllPending()131 void MockInputFileManager::IssueAllPending() {
132   BlockNode block(BlockNode::DISCARDS_RESULT);  // Default response.
133 
134   for (const auto& cur : pending_) {
135     CannedResponseMap::const_iterator found = canned_responses_.find(cur.first);
136     if (found == canned_responses_.end())
137       cur.second(&block);
138     else
139       cur.second(found->second->root.get());
140   }
141   pending_.clear();
142 }
143 
144 // LoaderTest ------------------------------------------------------------------
145 
146 class LoaderTest : public TestWithScheduler {
147  public:
LoaderTest()148   LoaderTest() { build_settings_.SetBuildDir(SourceDir("//out/Debug/")); }
149 
150  protected:
151   BuildSettings build_settings_;
152   MockBuilder mock_builder_;
153   MockInputFileManager mock_ifm_;
154 };
155 
156 }  // namespace
157 
158 // -----------------------------------------------------------------------------
159 
TEST_F(LoaderTest,Foo)160 TEST_F(LoaderTest, Foo) {
161   SourceFile build_config("//build/config/BUILDCONFIG.gn");
162   build_settings_.set_build_config_file(build_config);
163 
164   scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
165 
166   // The default toolchain needs to be set by the build config file.
167   mock_ifm_.AddCannedResponse(build_config,
168                               "set_default_toolchain(\"//tc:tc\")");
169 
170   loader->set_async_load_file(mock_ifm_.GetCallback());
171 
172   // Request the root build file be loaded. This should kick off the default
173   // build config loading.
174   SourceFile root_build("//BUILD.gn");
175   loader->Load(root_build, LocationRange(), Label());
176   EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
177 
178   // Completing the build config load should kick off the root build file load.
179   mock_ifm_.IssueAllPending();
180   MsgLoop::Current()->RunUntilIdleForTesting();
181   EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
182 
183   // Load the root build file.
184   mock_ifm_.IssueAllPending();
185   MsgLoop::Current()->RunUntilIdleForTesting();
186 
187   // Schedule some other file to load in another toolchain.
188   Label second_tc(SourceDir("//tc2/"), "tc2");
189   SourceFile second_file("//foo/BUILD.gn");
190   loader->Load(second_file, LocationRange(), second_tc);
191   EXPECT_TRUE(mock_ifm_.HasOnePending(SourceFile("//tc2/BUILD.gn")));
192 
193   // Running the toolchain file should schedule the build config file to load
194   // for that toolchain.
195   mock_ifm_.IssueAllPending();
196   MsgLoop::Current()->RunUntilIdleForTesting();
197 
198   // We have to tell it we have a toolchain definition now (normally the
199   // builder would do this).
200   const Settings* default_settings = loader->GetToolchainSettings(Label());
201   Toolchain second_tc_object(default_settings, second_tc);
202   loader->ToolchainLoaded(&second_tc_object);
203   EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
204 
205   // Scheduling a second file to load in that toolchain should not make it
206   // pending yet (it's waiting for the build config).
207   SourceFile third_file("//bar/BUILD.gn");
208   loader->Load(third_file, LocationRange(), second_tc);
209   EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
210 
211   // Running the build config file should make our third file pending.
212   mock_ifm_.IssueAllPending();
213   MsgLoop::Current()->RunUntilIdleForTesting();
214   EXPECT_TRUE(mock_ifm_.HasTwoPending(second_file, third_file));
215 
216   EXPECT_FALSE(scheduler().is_failed());
217 }
218 
TEST_F(LoaderTest,BuildDependencyFilesAreCollected)219 TEST_F(LoaderTest, BuildDependencyFilesAreCollected) {
220   SourceFile build_config("//build/config/BUILDCONFIG.gn");
221   SourceFile root_build("//BUILD.gn");
222   build_settings_.set_build_config_file(build_config);
223   build_settings_.set_item_defined_callback(
224       [builder = &mock_builder_](std::unique_ptr<Item> item) {
225         builder->OnItemDefined(std::move(item));
226       });
227 
228   scoped_refptr<LoaderImpl> loader(new LoaderImpl(&build_settings_));
229   mock_ifm_.AddCannedResponse(build_config,
230                               "set_default_toolchain(\"//tc:tc\")");
231   mock_ifm_.AddCannedResponse(SourceFile("//test.gni"), "concurrent_jobs = 1");
232   std::string root_build_content =
233       "executable(\"a\") { sources = [ \"a.cc\" ] }\n"
234       "config(\"b\") { configs = [\"//t:t\"] }\n"
235       "toolchain(\"c\") {}\n"
236       "pool(\"d\") { depth = 1 }";
237   mock_ifm_.AddCannedResponse(root_build, root_build_content);
238 
239   loader->set_async_load_file(mock_ifm_.GetCallback());
240 
241   // Request the root build file be loaded. This should kick off the default
242   // build config loading.
243   loader->Load(root_build, LocationRange(), Label());
244   EXPECT_TRUE(mock_ifm_.HasOnePending(build_config));
245 
246   // Completing the build config load should kick off the root build file load.
247   mock_ifm_.IssueAllPending();
248   MsgLoop::Current()->RunUntilIdleForTesting();
249   EXPECT_TRUE(mock_ifm_.HasOnePending(root_build));
250 
251   // Completing the root build file should define a target which must have
252   // set of source files hashes.
253   mock_ifm_.IssueAllPending();
254   MsgLoop::Current()->RunUntilIdleForTesting();
255 
256   std::vector<const Item*> items = mock_builder_.GetAllItems();
257   EXPECT_TRUE(items[0]->AsTarget());
258   EXPECT_TRUE(ItemContainsBuildDependencyFile(items[0], root_build));
259   EXPECT_TRUE(items[1]->AsConfig());
260   EXPECT_TRUE(ItemContainsBuildDependencyFile(items[1], root_build));
261   EXPECT_TRUE(items[2]->AsToolchain());
262   EXPECT_TRUE(ItemContainsBuildDependencyFile(items[2], root_build));
263   EXPECT_TRUE(items[3]->AsPool());
264   EXPECT_TRUE(ItemContainsBuildDependencyFile(items[3], root_build));
265 
266   EXPECT_FALSE(scheduler().is_failed());
267 }
268