1 // Copyright 2016 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 "gn/analyzer.h"
6
7 #include "gn/c_tool.h"
8 #include "gn/build_settings.h"
9 #include "gn/builder.h"
10 #include "gn/config.h"
11 #include "gn/general_tool.h"
12 #include "gn/loader.h"
13 #include "gn/pool.h"
14 #include "gn/settings.h"
15 #include "gn/source_file.h"
16 #include "gn/substitution_list.h"
17 #include "gn/target.h"
18 #include "gn/tool.h"
19 #include "gn/toolchain.h"
20 #include "util/test/test.h"
21
22 namespace gn_analyzer_unittest {
23
24 class MockLoader : public Loader {
25 public:
26 MockLoader() = default;
27
Load(const SourceFile & file,const LocationRange & origin,const Label & toolchain_name)28 void Load(const SourceFile& file,
29 const LocationRange& origin,
30 const Label& toolchain_name) override {}
ToolchainLoaded(const Toolchain * toolchain)31 void ToolchainLoaded(const Toolchain* toolchain) override {}
GetDefaultToolchain() const32 Label GetDefaultToolchain() const override {
33 return Label(SourceDir("//tc/"), "default");
34 }
GetToolchainSettings(const Label & label) const35 const Settings* GetToolchainSettings(const Label& label) const override {
36 return nullptr;
37 }
38
39 private:
40 ~MockLoader() override = default;
41 };
42
43 class AnalyzerTest : public testing::Test {
44 public:
AnalyzerTest()45 AnalyzerTest()
46 : loader_(new MockLoader),
47 builder_(loader_.get()),
48 settings_(&build_settings_, std::string()) {
49 build_settings_.SetBuildDir(SourceDir("//out/"));
50 settings_.set_toolchain_label(Label(SourceDir("//tc/"), "default"));
51 settings_.set_default_toolchain_label(settings_.toolchain_label());
52 tc_dir_ = settings_.toolchain_label().dir();
53 tc_name_ = settings_.toolchain_label().name();
54 }
55
MakeTarget(const std::string & dir,const std::string & name)56 std::unique_ptr<Target> MakeTarget(const std::string& dir,
57 const std::string& name) {
58 Label label(SourceDir(dir), name, tc_dir_, tc_name_);
59 return std::make_unique<Target>(&settings_, label);
60 }
61
MakeConfig(const std::string & dir,const std::string & name)62 std::unique_ptr<Config> MakeConfig(const std::string& dir,
63 const std::string& name) {
64 Label label(SourceDir(dir), name, tc_dir_, tc_name_);
65 return std::make_unique<Config>(&settings_, label);
66 }
67
MakePool(const std::string & dir,const std::string & name)68 std::unique_ptr<Pool> MakePool(const std::string& dir,
69 const std::string& name) {
70 Label label(SourceDir(dir), name, tc_dir_, tc_name_);
71 return std::make_unique<Pool>(&settings_, label);
72 }
73
RunAnalyzerTest(const std::string & input,const std::string & expected_output)74 void RunAnalyzerTest(const std::string& input,
75 const std::string& expected_output) {
76 Analyzer analyzer(builder_, SourceFile("//build/config/BUILDCONFIG.gn"),
77 SourceFile("//.gn"),
78 {SourceFile("//out/debug/args.gn"),
79 SourceFile("//build/default_args.gn")});
80 Err err;
81 std::string actual_output = analyzer.Analyze(input, &err);
82 EXPECT_EQ(err.has_error(), false);
83 EXPECT_EQ(expected_output, actual_output);
84 }
85
86 protected:
87 scoped_refptr<MockLoader> loader_;
88 Builder builder_;
89 BuildSettings build_settings_;
90 Settings settings_;
91 SourceDir tc_dir_;
92 std::string tc_name_;
93 };
94
95 // Tests that a target is marked as affected if its sources are modified.
TEST_F(AnalyzerTest,TargetRefersToSources)96 TEST_F(AnalyzerTest, TargetRefersToSources) {
97 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
98 Target* t_raw = t.get();
99 builder_.ItemDefined(std::move(t));
100 RunAnalyzerTest(
101 R"({
102 "files": [ "//dir/file_name.cc" ],
103 "additional_compile_targets": [ "all" ],
104 "test_targets": [ "//dir:target_name" ]
105 })",
106 "{"
107 R"("compile_targets":[],)"
108 R"/("status":"No dependency",)/"
109 R"("test_targets":[])"
110 "}");
111
112 t_raw->sources().push_back(SourceFile("//dir/file_name.cc"));
113 RunAnalyzerTest(
114 R"({
115 "files": [ "//dir/file_name.cc" ],
116 "additional_compile_targets": [ "all" ],
117 "test_targets": [ "//dir:target_name" ]
118 })",
119 "{"
120 R"("compile_targets":["all"],)"
121 R"/("status":"Found dependency",)/"
122 R"("test_targets":["//dir:target_name"])"
123 "}");
124 }
125
126 // Tests that a target is marked as affected if its public headers are modified.
TEST_F(AnalyzerTest,TargetRefersToPublicHeaders)127 TEST_F(AnalyzerTest, TargetRefersToPublicHeaders) {
128 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
129 Target* t_raw = t.get();
130 builder_.ItemDefined(std::move(t));
131 RunAnalyzerTest(
132 R"({
133 "files": [ "//dir/header_name.h" ],
134 "additional_compile_targets": [ "all" ],
135 "test_targets": [ "//dir:target_name" ]
136 })",
137 "{"
138 R"("compile_targets":[],)"
139 R"/("status":"No dependency",)/"
140 R"("test_targets":[])"
141 "}");
142
143 t_raw->public_headers().push_back(SourceFile("//dir/header_name.h"));
144 RunAnalyzerTest(
145 R"({
146 "files": [ "//dir/header_name.h" ],
147 "additional_compile_targets": [ "all" ],
148 "test_targets": [ "//dir:target_name" ]
149 })",
150 "{"
151 R"("compile_targets":["all"],)"
152 R"/("status":"Found dependency",)/"
153 R"("test_targets":["//dir:target_name"])"
154 "}");
155 }
156
157 // Tests that a target is marked as affected if its inputs are modified.
TEST_F(AnalyzerTest,TargetRefersToInputs)158 TEST_F(AnalyzerTest, TargetRefersToInputs) {
159 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
160 Target* t_raw = t.get();
161 builder_.ItemDefined(std::move(t));
162 RunAnalyzerTest(
163 R"({
164 "files": [ "//dir/extra_input.cc" ],
165 "additional_compile_targets": [ "all" ],
166 "test_targets": [ "//dir:target_name" ]
167 })",
168 "{"
169 R"("compile_targets":[],)"
170 R"/("status":"No dependency",)/"
171 R"("test_targets":[])"
172 "}");
173
174 SourceFile extra_input(SourceFile("//dir/extra_input.cc"));
175 t_raw->config_values().inputs().push_back(extra_input);
176 RunAnalyzerTest(
177 R"({
178 "files": [ "//dir/extra_input.cc" ],
179 "additional_compile_targets": [ "all" ],
180 "test_targets": [ "//dir:target_name" ]
181 })",
182 "{"
183 R"("compile_targets":["all"],)"
184 R"/("status":"Found dependency",)/"
185 R"("test_targets":["//dir:target_name"])"
186 "}");
187
188 t_raw->config_values().inputs().clear();
189 std::unique_ptr<Config> c = MakeConfig("//dir", "config_name");
190 c->own_values().inputs().push_back(extra_input);
191 t_raw->configs().push_back(LabelConfigPair(c.get()));
192 builder_.ItemDefined(std::move(c));
193
194 RunAnalyzerTest(
195 R"({
196 "files": [ "//dir/extra_input.cc" ],
197 "additional_compile_targets": [ "all" ],
198 "test_targets": [ "//dir:target_name" ]
199 })",
200 "{"
201 R"("compile_targets":["all"],)"
202 R"/("status":"Found dependency",)/"
203 R"("test_targets":["//dir:target_name"])"
204 "}");
205 }
206
207 // Tests that a target is marked as affected if its data are modified.
TEST_F(AnalyzerTest,TargetRefersToData)208 TEST_F(AnalyzerTest, TargetRefersToData) {
209 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
210 Target* t_raw = t.get();
211 builder_.ItemDefined(std::move(t));
212 RunAnalyzerTest(
213 R"({
214 "files": [ "//dir/data.html" ],
215 "additional_compile_targets": [ "all" ],
216 "test_targets": [ "//dir:target_name" ]
217 })",
218 "{"
219 R"("compile_targets":[],)"
220 R"/("status":"No dependency",)/"
221 R"("test_targets":[])"
222 "}");
223
224 t_raw->data().push_back("//dir/data.html");
225 RunAnalyzerTest(
226 R"({
227 "files": [ "//dir/data.html" ],
228 "additional_compile_targets": [ "all" ],
229 "test_targets": [ "//dir:target_name" ]
230 })",
231 "{"
232 R"("compile_targets":["all"],)"
233 R"/("status":"Found dependency",)/"
234 R"("test_targets":["//dir:target_name"])"
235 "}");
236 }
237
238 // Tests that a target is marked as affected if the target is an action and its
239 // action script is modified.
TEST_F(AnalyzerTest,TargetRefersToActionScript)240 TEST_F(AnalyzerTest, TargetRefersToActionScript) {
241 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
242 Target* t_raw = t.get();
243 t->set_output_type(Target::ACTION);
244 builder_.ItemDefined(std::move(t));
245 RunAnalyzerTest(
246 R"({
247 "files": [ "//dir/script.py" ],
248 "additional_compile_targets": [ "all" ],
249 "test_targets": [ "//dir:target_name" ]
250 })",
251 "{"
252 R"("compile_targets":[],)"
253 R"/("status":"No dependency",)/"
254 R"("test_targets":[])"
255 "}");
256
257 t_raw->action_values().set_script(SourceFile("//dir/script.py"));
258 RunAnalyzerTest(
259 R"({
260 "files": [ "//dir/script.py" ],
261 "additional_compile_targets": [ "all" ],
262 "test_targets": [ "//dir:target_name" ]
263 })",
264 "{"
265 R"("compile_targets":["all"],)"
266 R"/("status":"Found dependency",)/"
267 R"("test_targets":["//dir:target_name"])"
268 "}");
269 }
270
271 // Tests that a target is marked as affected if its build dependency files are
272 // modified.
TEST_F(AnalyzerTest,TargetRefersToBuildDependencyFiles)273 TEST_F(AnalyzerTest, TargetRefersToBuildDependencyFiles) {
274 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
275 Target* t_raw = t.get();
276 builder_.ItemDefined(std::move(t));
277 RunAnalyzerTest(
278 R"({
279 "files": [ "//dir/BUILD.gn" ],
280 "additional_compile_targets": [ "all" ],
281 "test_targets": [ "//dir:target_name" ]
282 })",
283 "{"
284 R"("compile_targets":[],)"
285 R"/("status":"No dependency",)/"
286 R"("test_targets":[])"
287 "}");
288
289 t_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
290 RunAnalyzerTest(
291 R"({
292 "files": [ "//dir/BUILD.gn" ],
293 "additional_compile_targets": [ "all" ],
294 "test_targets": [ "//dir:target_name" ]
295 })",
296 "{"
297 R"("compile_targets":["all"],)"
298 R"/("status":"Found dependency",)/"
299 R"("test_targets":["//dir:target_name"])"
300 "}");
301 }
302
303 // Tests that if a target is marked as affected, then it propagates to dependent
304 // test_targets.
TEST_F(AnalyzerTest,AffectedTargetpropagatesToDependentTargets)305 TEST_F(AnalyzerTest, AffectedTargetpropagatesToDependentTargets) {
306 std::unique_ptr<Target> t1 = MakeTarget("//dir", "target_name1");
307 std::unique_ptr<Target> t2 = MakeTarget("//dir", "target_name2");
308 std::unique_ptr<Target> t3 = MakeTarget("//dir", "target_name3");
309 t1->private_deps().push_back(LabelTargetPair(t2.get()));
310 t2->private_deps().push_back(LabelTargetPair(t3.get()));
311 builder_.ItemDefined(std::move(t1));
312 builder_.ItemDefined(std::move(t2));
313
314 Target* t3_raw = t3.get();
315 builder_.ItemDefined(std::move(t3));
316
317 RunAnalyzerTest(
318 R"({
319 "files": [ "//dir/BUILD.gn" ],
320 "additional_compile_targets": [ "all" ],
321 "test_targets": [ "//dir:target_name1", "//dir:target_name2" ]
322 })",
323 "{"
324 R"("compile_targets":[],)"
325 R"/("status":"No dependency",)/"
326 R"("test_targets":[])"
327 "}");
328
329 t3_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
330 RunAnalyzerTest(
331 R"({
332 "files": [ "//dir/BUILD.gn" ],
333 "additional_compile_targets": [ "all" ],
334 "test_targets": [ "//dir:target_name1", "//dir:target_name2" ]
335 })",
336 "{"
337 R"("compile_targets":["all"],)"
338 R"/("status":"Found dependency",)/"
339 R"("test_targets":["//dir:target_name1","//dir:target_name2"])"
340 "}");
341 }
342
343 // Tests that if a config is marked as affected, then it propagates to dependent
344 // test_targets.
TEST_F(AnalyzerTest,AffectedConfigpropagatesToDependentTargets)345 TEST_F(AnalyzerTest, AffectedConfigpropagatesToDependentTargets) {
346 std::unique_ptr<Config> c = MakeConfig("//dir", "config_name");
347 Config* c_raw = c.get();
348 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
349 t->configs().push_back(LabelConfigPair(c.get()));
350 builder_.ItemDefined(std::move(t));
351 builder_.ItemDefined(std::move(c));
352 RunAnalyzerTest(
353 R"({
354 "files": [ "//dir/BUILD.gn" ],
355 "additional_compile_targets": [ "all" ],
356 "test_targets": [ "//dir:target_name" ]
357 })",
358 "{"
359 R"("compile_targets":[],)"
360 R"/("status":"No dependency",)/"
361 R"("test_targets":[])"
362 "}");
363
364 c_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
365 RunAnalyzerTest(
366 R"({
367 "files": [ "//dir/BUILD.gn" ],
368 "additional_compile_targets": [ "all" ],
369 "test_targets": [ "//dir:target_name" ]
370 })",
371 "{"
372 R"("compile_targets":["all"],)"
373 R"/("status":"Found dependency",)/"
374 R"("test_targets":["//dir:target_name"])"
375 "}");
376 }
377
378 // Tests that if toolchain is marked as affected, then it propagates to
379 // dependent test_targets.
TEST_F(AnalyzerTest,AffectedToolchainpropagatesToDependentTargets)380 TEST_F(AnalyzerTest, AffectedToolchainpropagatesToDependentTargets) {
381 std::unique_ptr<Target> target = MakeTarget("//dir", "target_name");
382 target->set_output_type(Target::EXECUTABLE);
383 Toolchain* toolchain = new Toolchain(&settings_, settings_.toolchain_label());
384
385 // The tool is not used anywhere, but is required to construct the dependency
386 // between a target and the toolchain.
387 std::unique_ptr<Tool> fake_tool = Tool::CreateTool(CTool::kCToolLink);
388 fake_tool->set_outputs(
389 SubstitutionList::MakeForTest("//out/debug/output.txt"));
390 toolchain->SetTool(std::move(fake_tool));
391 builder_.ItemDefined(std::move(target));
392 builder_.ItemDefined(std::unique_ptr<Item>(toolchain));
393
394 RunAnalyzerTest(
395 R"({
396 "files": [ "//tc/BUILD.gn" ],
397 "additional_compile_targets": [ "all" ],
398 "test_targets": [ "//dir:target_name" ]
399 })",
400 "{"
401 R"("compile_targets":[],)"
402 R"/("status":"No dependency",)/"
403 R"("test_targets":[])"
404 "}");
405
406 toolchain->build_dependency_files().insert(SourceFile("//tc/BUILD.gn"));
407 RunAnalyzerTest(
408 R"({
409 "files": [ "//tc/BUILD.gn" ],
410 "additional_compile_targets": [ "all" ],
411 "test_targets": [ "//dir:target_name" ]
412 })",
413 "{"
414 R"("compile_targets":["all"],)"
415 R"/("status":"Found dependency",)/"
416 R"("test_targets":["//dir:target_name"])"
417 "}");
418 }
419
420 // Tests that if a pool is marked as affected, then it propagates to dependent
421 // targets.
TEST_F(AnalyzerTest,AffectedPoolpropagatesToDependentTargets)422 TEST_F(AnalyzerTest, AffectedPoolpropagatesToDependentTargets) {
423 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
424 t->set_output_type(Target::ACTION);
425 std::unique_ptr<Pool> p = MakePool("//dir", "pool_name");
426 Pool* p_raw = p.get();
427 t->action_values().set_pool(LabelPtrPair<Pool>(p.get()));
428
429 builder_.ItemDefined(std::move(t));
430 builder_.ItemDefined(std::move(p));
431
432 RunAnalyzerTest(
433 R"({
434 "files": [ "//dir/BUILD.gn" ],
435 "additional_compile_targets": [ "all" ],
436 "test_targets": [ "//dir:target_name" ]
437 })",
438 "{"
439 R"("compile_targets":[],)"
440 R"/("status":"No dependency",)/"
441 R"("test_targets":[])"
442 "}");
443
444 p_raw->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
445 RunAnalyzerTest(
446 R"({
447 "files": [ "//dir/BUILD.gn" ],
448 "additional_compile_targets": [ "all" ],
449 "test_targets": [ "//dir:target_name" ]
450 })",
451 "{"
452 R"("compile_targets":["all"],)"
453 R"/("status":"Found dependency",)/"
454 R"("test_targets":["//dir:target_name"])"
455 "}");
456 }
457
458 // Tests that when dependency was found, the "compile_targets" in the output is
459 // not "all".
TEST_F(AnalyzerTest,CompileTargetsAllWasPruned)460 TEST_F(AnalyzerTest, CompileTargetsAllWasPruned) {
461 std::unique_ptr<Target> t1 = MakeTarget("//dir", "target_name1");
462 std::unique_ptr<Target> t2 = MakeTarget("//dir", "target_name2");
463 t2->build_dependency_files().insert(SourceFile("//dir/BUILD.gn"));
464 builder_.ItemDefined(std::move(t1));
465 builder_.ItemDefined(std::move(t2));
466
467 RunAnalyzerTest(
468 R"({
469 "files": [ "//dir/BUILD.gn" ],
470 "additional_compile_targets": [ "all" ],
471 "test_targets": []
472 })",
473 "{"
474 R"("compile_targets":["//dir:target_name2"],)"
475 R"/("status":"Found dependency",)/"
476 R"("test_targets":[])"
477 "}");
478 }
479
480 // Tests that output is "No dependency" when no dependency is found.
TEST_F(AnalyzerTest,NoDependency)481 TEST_F(AnalyzerTest, NoDependency) {
482 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
483 builder_.ItemDefined(std::move(t));
484
485 RunAnalyzerTest(
486 R"({
487 "files": [ "//dir/BUILD.gn" ],
488 "additional_compile_targets": [ "all" ],
489 "test_targets": []
490 })",
491 "{"
492 R"("compile_targets":[],)"
493 R"/("status":"No dependency",)/"
494 R"("test_targets":[])"
495 "}");
496 }
497
498 // Tests that output is "No dependency" when no files or targets are provided.
TEST_F(AnalyzerTest,NoFilesNoTargets)499 TEST_F(AnalyzerTest, NoFilesNoTargets) {
500 RunAnalyzerTest(
501 R"({
502 "files": [],
503 "additional_compile_targets": [],
504 "test_targets": []
505 })",
506 "{"
507 R"("compile_targets":[],)"
508 R"("status":"No dependency",)"
509 R"("test_targets":[])"
510 "}");
511 }
512
513 // Tests that output displays proper error message when given files aren't
514 // source-absolute or absolute path.
TEST_F(AnalyzerTest,FilesArentSourceAbsolute)515 TEST_F(AnalyzerTest, FilesArentSourceAbsolute) {
516 RunAnalyzerTest(
517 R"({
518 "files": [ "a.cc" ],
519 "additional_compile_targets": [],
520 "test_targets": [ "//dir:target_name" ]
521 })",
522 "{"
523 R"("error":)"
524 R"("\"a.cc\" is not a source-absolute or absolute path.",)"
525 R"("invalid_targets":[])"
526 "}");
527 }
528
529 // Tests that output displays proper error message when input is ill-formed.
TEST_F(AnalyzerTest,WrongInputFields)530 TEST_F(AnalyzerTest, WrongInputFields) {
531 RunAnalyzerTest(
532 R"({
533 "files": [ "//a.cc" ],
534 "compile_targets": [],
535 "test_targets": [ "//dir:target_name" ]
536 })",
537 "{"
538 R"("error":)"
539 R"("Input does not have a key named )"
540 R"(\"additional_compile_targets\" with a list value.",)"
541 R"("invalid_targets":[])"
542 "}");
543 }
544
545 // Bails out early with "Found dependency (all)" if dot file is modified.
TEST_F(AnalyzerTest,DotFileWasModified)546 TEST_F(AnalyzerTest, DotFileWasModified) {
547 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
548 builder_.ItemDefined(std::move(t));
549
550 RunAnalyzerTest(
551 R"({
552 "files": [ "//.gn" ],
553 "additional_compile_targets": [],
554 "test_targets": [ "//dir:target_name" ]
555 })",
556 "{"
557 R"("compile_targets":["//dir:target_name"],)"
558 R"/("status":"Found dependency (all)",)/"
559 R"("test_targets":["//dir:target_name"])"
560 "}");
561 }
562
563 // Bails out early with "Found dependency (all)" if master build config file is
564 // modified.
TEST_F(AnalyzerTest,BuildConfigFileWasModified)565 TEST_F(AnalyzerTest, BuildConfigFileWasModified) {
566 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
567 builder_.ItemDefined(std::move(t));
568
569 RunAnalyzerTest(
570 R"({
571 "files": [ "//build/config/BUILDCONFIG.gn" ],
572 "additional_compile_targets": [],
573 "test_targets": [ "//dir:target_name" ]
574 })",
575 "{"
576 R"("compile_targets":["//dir:target_name"],)"
577 R"/("status":"Found dependency (all)",)/"
578 R"("test_targets":["//dir:target_name"])"
579 "}");
580 }
581
582 // Bails out early with "Found dependency (all)" if a build args dependency file
583 // is modified.
TEST_F(AnalyzerTest,BuildArgsDependencyFileWasModified)584 TEST_F(AnalyzerTest, BuildArgsDependencyFileWasModified) {
585 std::unique_ptr<Target> t = MakeTarget("//dir", "target_name");
586 builder_.ItemDefined(std::move(t));
587
588 RunAnalyzerTest(
589 R"({
590 "files": [ "//build/default_args.gn" ],
591 "additional_compile_targets": [],
592 "test_targets": [ "//dir:target_name" ]
593 })",
594 "{"
595 R"("compile_targets":["//dir:target_name"],)"
596 R"/("status":"Found dependency (all)",)/"
597 R"("test_targets":["//dir:target_name"])"
598 "}");
599 }
600
601 } // namespace gn_analyzer_unittest
602