| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "tools/gn/analyzer.h" |
| |
| #include <tools/gn/c_tool.h> |
| #include "tools/gn/build_settings.h" |
| #include "tools/gn/builder.h" |
| #include "tools/gn/config.h" |
| #include "tools/gn/general_tool.h" |
| #include "tools/gn/loader.h" |
| #include "tools/gn/pool.h" |
| #include "tools/gn/settings.h" |
| #include "tools/gn/source_file.h" |
| #include "tools/gn/substitution_list.h" |
| #include "tools/gn/target.h" |
| #include <tools/gn/tool.h> |
| #include "tools/gn/toolchain.h" |
| #include "util/test/test.h" |
| |
| namespace gn_analyzer_unittest { |
| |
| class MockLoader : public Loader { |
| public: |
| MockLoader() = default; |
| |
| void Load(const SourceFile& file, |
| const LocationRange& origin, |
| const Label& toolchain_name) override {} |
| void ToolchainLoaded(const Toolchain* toolchain) override {} |
| Label GetDefaultToolchain() const override { |
| return Label(SourceDir("//tc/"), "default"); |
| } |
| const Settings* GetToolchainSettings(const Label& label) const override { |
| return nullptr; |
| } |
| |
| private: |
| ~MockLoader() override = default; |
| }; |
| |
| class AnalyzerTest : public testing::Test { |
| public: |
| AnalyzerTest() |
| : loader_(new MockLoader), |
| builder_(loader_.get()), |
| settings_(&build_settings_, std::string()) { |
| build_settings_.SetBuildDir(SourceDir("//out/")); |
| settings_.set_toolchain_label(Label(SourceDir("//tc/"), "default")); |
| settings_.set_default_toolchain_label(settings_.toolchain_label()); |
| tc_dir_ = settings_.toolchain_label().dir(); |
| tc_name_ = settings_.toolchain_label().name(); |
| } |
| |
| // Ownership of the target will be transfered to the builder, so no leaks. |
| Target* MakeTarget(const std::string& dir, const std::string& name) { |
| Label label(SourceDir(dir), name, tc_dir_, tc_name_); |
| Target* target = new Target(&settings_, label); |
| |
| return target; |
| } |
| |
| // Ownership of the config will be transfered to the builder, so no leaks. |
| Config* MakeConfig(const std::string& dir, const std::string& name) { |
| Label label(SourceDir(dir), name, tc_dir_, tc_name_); |
| Config* config = new Config(&settings_, label); |
| |
| return config; |
| } |
| |
| // Ownership of the pool will be transfered to the builder, so no leaks. |
| Pool* MakePool(const std::string& dir, const std::string& name) { |
| Label label(SourceDir(dir), name, tc_dir_, tc_name_); |
| Pool* pool = new Pool(&settings_, label); |
| |
| return pool; |
| } |
| |
| void RunAnalyzerTest(const std::string& input, |
| const std::string& expected_output) { |
| Analyzer analyzer(builder_, SourceFile("//build/config/BUILDCONFIG.gn"), |
| SourceFile("//.gn"), |
| {SourceFile("//out/debug/args.gn"), |
| SourceFile("//build/default_args.gn")}); |
| Err err; |
| std::string actual_output = analyzer.Analyze(input, &err); |
| EXPECT_EQ(err.has_error(), false); |
| EXPECT_EQ(expected_output, actual_output); |
| } |
| |
| protected: |
| scoped_refptr<MockLoader> loader_; |
| Builder builder_; |
| BuildSettings build_settings_; |
| Settings settings_; |
| SourceDir tc_dir_; |
| std::string tc_name_; |
| }; |
| |
| // Tests that a target is marked as affected if its sources are modified. |
| TEST_F(AnalyzerTest, TargetRefersToSources) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/file_name.cc" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| t->sources().push_back(SourceFile("//dir/file_name.cc")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/file_name.cc" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that a target is marked as affected if its public headers are modified. |
| TEST_F(AnalyzerTest, TargetRefersToPublicHeaders) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/header_name.h" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| t->public_headers().push_back(SourceFile("//dir/header_name.h")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/header_name.h" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that a target is marked as affected if its inputs are modified. |
| TEST_F(AnalyzerTest, TargetRefersToInputs) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/extra_input.cc" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| SourceFile extra_input(SourceFile("//dir/extra_input.cc")); |
| t->config_values().inputs().push_back(extra_input); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/extra_input.cc" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| |
| t->config_values().inputs().clear(); |
| Config* c = MakeConfig("//dir", "config_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(c)); |
| c->own_values().inputs().push_back(extra_input); |
| t->configs().push_back(LabelConfigPair(c)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/extra_input.cc" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that a target is marked as affected if its data are modified. |
| TEST_F(AnalyzerTest, TargetRefersToData) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/data.html" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| t->data().push_back("//dir/data.html"); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/data.html" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that a target is marked as affected if the target is an action and its |
| // action script is modified. |
| TEST_F(AnalyzerTest, TargetRefersToActionScript) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| t->set_output_type(Target::ACTION); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/script.py" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| t->action_values().set_script(SourceFile("//dir/script.py")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/script.py" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that a target is marked as affected if its build dependency files are |
| // modified. |
| TEST_F(AnalyzerTest, TargetRefersToBuildDependencyFiles) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| t->build_dependency_files().insert(SourceFile("//dir/BUILD.gn")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that if a target is marked as affected, then it propagates to dependent |
| // test_targets. |
| TEST_F(AnalyzerTest, AffectedTargetpropagatesToDependentTargets) { |
| Target* t1 = MakeTarget("//dir", "target_name1"); |
| Target* t2 = MakeTarget("//dir", "target_name2"); |
| Target* t3 = MakeTarget("//dir", "target_name3"); |
| t1->private_deps().push_back(LabelTargetPair(t2)); |
| t2->private_deps().push_back(LabelTargetPair(t3)); |
| builder_.ItemDefined(std::unique_ptr<Item>(t1)); |
| builder_.ItemDefined(std::unique_ptr<Item>(t2)); |
| builder_.ItemDefined(std::unique_ptr<Item>(t3)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name1", "//dir:target_name2" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| t3->build_dependency_files().insert(SourceFile("//dir/BUILD.gn")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name1", "//dir:target_name2" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name1","//dir:target_name2"])" |
| "}"); |
| } |
| |
| // Tests that if a config is marked as affected, then it propagates to dependent |
| // test_targets. |
| TEST_F(AnalyzerTest, AffectedConfigpropagatesToDependentTargets) { |
| Config* c = MakeConfig("//dir", "config_name"); |
| Target* t = MakeTarget("//dir", "target_name"); |
| t->configs().push_back(LabelConfigPair(c)); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| builder_.ItemDefined(std::unique_ptr<Item>(c)); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| c->build_dependency_files().insert(SourceFile("//dir/BUILD.gn")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that if toolchain is marked as affected, then it propagates to |
| // dependent test_targets. |
| TEST_F(AnalyzerTest, AffectedToolchainpropagatesToDependentTargets) { |
| Target* target = MakeTarget("//dir", "target_name"); |
| target->set_output_type(Target::EXECUTABLE); |
| Toolchain* toolchain = new Toolchain(&settings_, settings_.toolchain_label()); |
| |
| // The tool is not used anywhere, but is required to construct the dependency |
| // between a target and the toolchain. |
| std::unique_ptr<Tool> fake_tool = Tool::CreateTool(CTool::kCToolLink); |
| fake_tool->set_outputs( |
| SubstitutionList::MakeForTest("//out/debug/output.txt")); |
| toolchain->SetTool(std::move(fake_tool)); |
| builder_.ItemDefined(std::unique_ptr<Item>(target)); |
| builder_.ItemDefined(std::unique_ptr<Item>(toolchain)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//tc/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| toolchain->build_dependency_files().insert(SourceFile("//tc/BUILD.gn")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//tc/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that if a pool is marked as affected, then it propagates to dependent |
| // targets. |
| TEST_F(AnalyzerTest, AffectedPoolpropagatesToDependentTargets) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| t->set_output_type(Target::ACTION); |
| Pool* p = MakePool("//dir", "pool_name"); |
| t->action_values().set_pool(LabelPtrPair<Pool>(p)); |
| |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| builder_.ItemDefined(std::unique_ptr<Item>(p)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| |
| p->build_dependency_files().insert(SourceFile("//dir/BUILD.gn")); |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["all"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Tests that when dependency was found, the "compile_targets" in the output is |
| // not "all". |
| TEST_F(AnalyzerTest, CompileTargetsAllWasPruned) { |
| Target* t1 = MakeTarget("//dir", "target_name1"); |
| Target* t2 = MakeTarget("//dir", "target_name2"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t1)); |
| builder_.ItemDefined(std::unique_ptr<Item>(t2)); |
| t2->build_dependency_files().insert(SourceFile("//dir/BUILD.gn")); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [] |
| })", |
| "{" |
| R"("compile_targets":["//dir:target_name2"],)" |
| R"/("status":"Found dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| } |
| |
| // Tests that output is "No dependency" when no dependency is found. |
| TEST_F(AnalyzerTest, NoDependency) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//dir/BUILD.gn" ], |
| "additional_compile_targets": [ "all" ], |
| "test_targets": [] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"/("status":"No dependency",)/" |
| R"("test_targets":[])" |
| "}"); |
| } |
| |
| // Tests that output is "No dependency" when no files or targets are provided. |
| TEST_F(AnalyzerTest, NoFilesNoTargets) { |
| RunAnalyzerTest( |
| R"({ |
| "files": [], |
| "additional_compile_targets": [], |
| "test_targets": [] |
| })", |
| "{" |
| R"("compile_targets":[],)" |
| R"("status":"No dependency",)" |
| R"("test_targets":[])" |
| "}"); |
| } |
| |
| // Tests that output displays proper error message when given files aren't |
| // source-absolute or absolute path. |
| TEST_F(AnalyzerTest, FilesArentSourceAbsolute) { |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "a.cc" ], |
| "additional_compile_targets": [], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("error":)" |
| R"("\"a.cc\" is not a source-absolute or absolute path.",)" |
| R"("invalid_targets":[])" |
| "}"); |
| } |
| |
| // Tests that output displays proper error message when input is illy-formed. |
| TEST_F(AnalyzerTest, WrongInputFields) { |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//a.cc" ], |
| "compile_targets": [], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("error":)" |
| R"("Input does not have a key named )" |
| R"(\"additional_compile_targets\" with a list value.",)" |
| R"("invalid_targets":[])" |
| "}"); |
| } |
| |
| // Bails out early with "Found dependency (all)" if dot file is modified. |
| TEST_F(AnalyzerTest, DotFileWasModified) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//.gn" ], |
| "additional_compile_targets": [], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["//dir:target_name"],)" |
| R"/("status":"Found dependency (all)",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Bails out early with "Found dependency (all)" if master build config file is |
| // modified. |
| TEST_F(AnalyzerTest, BuildConfigFileWasModified) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//build/config/BUILDCONFIG.gn" ], |
| "additional_compile_targets": [], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["//dir:target_name"],)" |
| R"/("status":"Found dependency (all)",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| // Bails out early with "Found dependency (all)" if a build args dependency file |
| // is modified. |
| TEST_F(AnalyzerTest, BuildArgsDependencyFileWasModified) { |
| Target* t = MakeTarget("//dir", "target_name"); |
| builder_.ItemDefined(std::unique_ptr<Item>(t)); |
| |
| RunAnalyzerTest( |
| R"({ |
| "files": [ "//build/default_args.gn" ], |
| "additional_compile_targets": [], |
| "test_targets": [ "//dir:target_name" ] |
| })", |
| "{" |
| R"("compile_targets":["//dir:target_name"],)" |
| R"/("status":"Found dependency (all)",)/" |
| R"("test_targets":["//dir:target_name"])" |
| "}"); |
| } |
| |
| } // namespace gn_analyzer_unittest |