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