| // Copyright 2014 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 <ostream> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "tools/gn/config.h" |
| #include "tools/gn/header_checker.h" |
| #include "tools/gn/scheduler.h" |
| #include "tools/gn/target.h" |
| #include "tools/gn/test_with_scheduler.h" |
| #include "tools/gn/test_with_scope.h" |
| #include "util/test/test.h" |
| |
| namespace { |
| |
| class HeaderCheckerTest : public TestWithScheduler { |
| public: |
| HeaderCheckerTest() |
| : a_(setup_.settings(), Label(SourceDir("//a/"), "a")), |
| b_(setup_.settings(), Label(SourceDir("//b/"), "b")), |
| c_(setup_.settings(), Label(SourceDir("//c/"), "c")), |
| d_(setup_.settings(), Label(SourceDir("//d/"), "d")) { |
| a_.set_output_type(Target::SOURCE_SET); |
| b_.set_output_type(Target::SOURCE_SET); |
| c_.set_output_type(Target::SOURCE_SET); |
| d_.set_output_type(Target::SOURCE_SET); |
| |
| Err err; |
| a_.SetToolchain(setup_.toolchain(), &err); |
| b_.SetToolchain(setup_.toolchain(), &err); |
| c_.SetToolchain(setup_.toolchain(), &err); |
| d_.SetToolchain(setup_.toolchain(), &err); |
| |
| a_.public_deps().push_back(LabelTargetPair(&b_)); |
| b_.public_deps().push_back(LabelTargetPair(&c_)); |
| |
| // Start with all public visibility. |
| a_.visibility().SetPublic(); |
| b_.visibility().SetPublic(); |
| c_.visibility().SetPublic(); |
| d_.visibility().SetPublic(); |
| |
| d_.OnResolved(&err); |
| c_.OnResolved(&err); |
| b_.OnResolved(&err); |
| a_.OnResolved(&err); |
| |
| targets_.push_back(&a_); |
| targets_.push_back(&b_); |
| targets_.push_back(&c_); |
| targets_.push_back(&d_); |
| } |
| |
| protected: |
| TestWithScope setup_; |
| |
| // Some headers that are automatically set up with a public dependency chain. |
| // a -> b -> c. D is unconnected. |
| Target a_; |
| Target b_; |
| Target c_; |
| Target d_; |
| |
| std::vector<const Target*> targets_; |
| }; |
| |
| } // namespace |
| |
| void PrintTo(const SourceFile& source_file, ::std::ostream* os) { |
| *os << source_file.value(); |
| } |
| |
| TEST_F(HeaderCheckerTest, IsDependencyOf) { |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| |
| // Add a target P ("private") that privately depends on C, and hook up the |
| // chain so that A -> P -> C. A will depend on C via two different paths. |
| Err err; |
| Target p(setup_.settings(), Label(SourceDir("//p/"), "p")); |
| p.set_output_type(Target::SOURCE_SET); |
| p.SetToolchain(setup_.toolchain(), &err); |
| EXPECT_FALSE(err.has_error()); |
| p.private_deps().push_back(LabelTargetPair(&c_)); |
| p.visibility().SetPublic(); |
| p.OnResolved(&err); |
| |
| a_.public_deps().push_back(LabelTargetPair(&p)); |
| |
| // A does not depend on itself. |
| bool is_permitted = false; |
| HeaderChecker::Chain chain; |
| EXPECT_FALSE(checker->IsDependencyOf(&a_, &a_, &chain, &is_permitted)); |
| |
| // A depends publicly on B. |
| chain.clear(); |
| is_permitted = false; |
| EXPECT_TRUE(checker->IsDependencyOf(&b_, &a_, &chain, &is_permitted)); |
| ASSERT_EQ(2u, chain.size()); |
| EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[0]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[1]); |
| EXPECT_TRUE(is_permitted); |
| |
| // A indirectly depends on C. The "public" dependency path through B should |
| // be identified. |
| chain.clear(); |
| is_permitted = false; |
| EXPECT_TRUE(checker->IsDependencyOf(&c_, &a_, &chain, &is_permitted)); |
| ASSERT_EQ(3u, chain.size()); |
| EXPECT_EQ(HeaderChecker::ChainLink(&c_, true), chain[0]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[1]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]); |
| EXPECT_TRUE(is_permitted); |
| |
| // C does not depend on A. |
| chain.clear(); |
| is_permitted = false; |
| EXPECT_FALSE(checker->IsDependencyOf(&a_, &c_, &chain, &is_permitted)); |
| EXPECT_TRUE(chain.empty()); |
| EXPECT_FALSE(is_permitted); |
| |
| // Remove the B -> C public dependency, leaving P's private dep on C the only |
| // path from A to C. This should now be found. |
| chain.clear(); |
| EXPECT_EQ(&c_, b_.public_deps()[0].ptr); // Validate it's the right one. |
| b_.public_deps().erase(b_.public_deps().begin()); |
| EXPECT_TRUE(checker->IsDependencyOf(&c_, &a_, &chain, &is_permitted)); |
| EXPECT_EQ(3u, chain.size()); |
| EXPECT_EQ(HeaderChecker::ChainLink(&c_, false), chain[0]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&p, true), chain[1]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]); |
| EXPECT_FALSE(is_permitted); |
| |
| // P privately depends on C. That dependency should be OK since it's only |
| // one hop. |
| chain.clear(); |
| is_permitted = false; |
| EXPECT_TRUE(checker->IsDependencyOf(&c_, &p, &chain, &is_permitted)); |
| ASSERT_EQ(2u, chain.size()); |
| EXPECT_EQ(HeaderChecker::ChainLink(&c_, false), chain[0]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&p, true), chain[1]); |
| EXPECT_TRUE(is_permitted); |
| } |
| |
| TEST_F(HeaderCheckerTest, CheckInclude) { |
| InputFile input_file(SourceFile("//some_file.cc")); |
| input_file.SetContents(std::string()); |
| LocationRange range; // Dummy value. |
| |
| // Add a disconnected target d with a header to check that you have to have |
| // to depend on a target listing a header. |
| SourceFile d_header("//d_header.h"); |
| d_.sources().push_back(SourceFile(d_header)); |
| |
| // Add a header on B and say everything in B is public. |
| SourceFile b_public("//b_public.h"); |
| b_.sources().push_back(b_public); |
| c_.set_all_headers_public(true); |
| |
| // Add a public and private header on C. |
| SourceFile c_public("//c_public.h"); |
| SourceFile c_private("//c_private.h"); |
| c_.sources().push_back(c_private); |
| c_.public_headers().push_back(c_public); |
| c_.set_all_headers_public(false); |
| |
| // Create another toolchain. |
| Settings other_settings(setup_.build_settings(), "other/"); |
| Toolchain other_toolchain(&other_settings, |
| Label(SourceDir("//toolchain/"), "other")); |
| TestWithScope::SetupToolchain(&other_toolchain); |
| other_settings.set_toolchain_label(other_toolchain.label()); |
| other_settings.set_default_toolchain_label(setup_.toolchain()->label()); |
| |
| // Add a target in the other toolchain with a header in it that is not |
| // connected to any targets in the main toolchain. |
| Target otc(&other_settings, |
| Label(SourceDir("//p/"), "otc", other_toolchain.label().dir(), |
| other_toolchain.label().name())); |
| otc.set_output_type(Target::SOURCE_SET); |
| Err err; |
| EXPECT_TRUE(otc.SetToolchain(&other_toolchain, &err)); |
| otc.visibility().SetPublic(); |
| targets_.push_back(&otc); |
| |
| SourceFile otc_header("//otc_header.h"); |
| otc.sources().push_back(otc_header); |
| EXPECT_TRUE(otc.OnResolved(&err)); |
| |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| |
| std::set<std::pair<const Target*, const Target*>> no_dependency_cache; |
| // A file in target A can't include a header from D because A has no |
| // dependency on D. |
| std::vector<Err> errors; |
| checker->CheckInclude(&a_, input_file, d_header, range, &no_dependency_cache, |
| &errors); |
| EXPECT_GT(errors.size(), 0); |
| |
| // A can include the public header in B. |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&a_, input_file, b_public, range, &no_dependency_cache, |
| &errors); |
| EXPECT_EQ(errors.size(), 0); |
| |
| // Check A depending on the public and private headers in C. |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&a_, input_file, c_public, range, &no_dependency_cache, |
| &errors); |
| EXPECT_EQ(errors.size(), 0); |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&a_, input_file, c_private, range, &no_dependency_cache, |
| &errors); |
| EXPECT_GT(errors.size(), 0); |
| |
| // A can depend on a random file unknown to the build. |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&a_, input_file, SourceFile("//random.h"), range, |
| &no_dependency_cache, &errors); |
| EXPECT_EQ(errors.size(), 0); |
| |
| // A can depend on a file present only in another toolchain even with no |
| // dependency path. |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&a_, input_file, otc_header, range, |
| &no_dependency_cache, &errors); |
| EXPECT_EQ(errors.size(), 0); |
| } |
| |
| // A public chain of dependencies should always be identified first, even if |
| // it is longer than a private one. |
| TEST_F(HeaderCheckerTest, PublicFirst) { |
| // Now make a A -> Z -> D private dependency chain (one shorter than the |
| // public one to get to D). |
| Target z(setup_.settings(), Label(SourceDir("//a/"), "a")); |
| z.set_output_type(Target::SOURCE_SET); |
| Err err; |
| EXPECT_TRUE(z.SetToolchain(setup_.toolchain(), &err)); |
| z.private_deps().push_back(LabelTargetPair(&d_)); |
| EXPECT_TRUE(z.OnResolved(&err)); |
| targets_.push_back(&z); |
| |
| a_.private_deps().push_back(LabelTargetPair(&z)); |
| |
| // Check that D can be found from A, but since it's private, it will be |
| // marked as not permitted. |
| bool is_permitted = false; |
| HeaderChecker::Chain chain; |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted)); |
| |
| EXPECT_FALSE(is_permitted); |
| ASSERT_EQ(3u, chain.size()); |
| EXPECT_EQ(HeaderChecker::ChainLink(&d_, false), chain[0]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&z, false), chain[1]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[2]); |
| |
| // Hook up D to the existing public A -> B -> C chain to make a long one, and |
| // search for D again. |
| c_.public_deps().push_back(LabelTargetPair(&d_)); |
| checker = new HeaderChecker(setup_.build_settings(), targets_); |
| chain.clear(); |
| EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted)); |
| |
| // This should have found the long public one. |
| EXPECT_TRUE(is_permitted); |
| ASSERT_EQ(4u, chain.size()); |
| EXPECT_EQ(HeaderChecker::ChainLink(&d_, true), chain[0]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&c_, true), chain[1]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&b_, true), chain[2]); |
| EXPECT_EQ(HeaderChecker::ChainLink(&a_, true), chain[3]); |
| } |
| |
| // Checks that the allow_circular_includes_from list works. |
| TEST_F(HeaderCheckerTest, CheckIncludeAllowCircular) { |
| InputFile input_file(SourceFile("//some_file.cc")); |
| input_file.SetContents(std::string()); |
| LocationRange range; // Dummy value. |
| |
| // Add an include file to A. |
| SourceFile a_public("//a_public.h"); |
| a_.sources().push_back(a_public); |
| |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| |
| // A depends on B. So B normally can't include headers from A. |
| std::vector<Err> errors; |
| std::set<std::pair<const Target*, const Target*>> no_dependency_cache; |
| checker->CheckInclude(&b_, input_file, a_public, range, &no_dependency_cache, |
| &errors); |
| EXPECT_GT(errors.size(), 0); |
| |
| // Add an allow_circular_includes_from on A that lists B. |
| a_.allow_circular_includes_from().insert(b_.label()); |
| |
| // Now the include from B to A should be allowed. |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&b_, input_file, a_public, range, &no_dependency_cache, |
| &errors); |
| EXPECT_EQ(errors.size(), 0); |
| } |
| |
| TEST_F(HeaderCheckerTest, SourceFileForInclude) { |
| using base::FilePath; |
| const std::vector<SourceDir> kIncludeDirs = { |
| SourceDir("/c/custom_include/"), SourceDir("//"), SourceDir("//subdir")}; |
| a_.sources().push_back(SourceFile("//lib/header1.h")); |
| b_.sources().push_back(SourceFile("/c/custom_include/header2.h")); |
| |
| InputFile dummy_input_file(SourceFile("//some_file.cc")); |
| dummy_input_file.SetContents(std::string()); |
| LocationRange dummy_range; |
| |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| { |
| Err err; |
| SourceFile source_file = checker->SourceFileForInclude( |
| "lib/header1.h", kIncludeDirs, dummy_input_file, dummy_range, &err); |
| EXPECT_FALSE(err.has_error()); |
| EXPECT_EQ(SourceFile("//lib/header1.h"), source_file); |
| } |
| |
| { |
| Err err; |
| SourceFile source_file = checker->SourceFileForInclude( |
| "header2.h", kIncludeDirs, dummy_input_file, dummy_range, &err); |
| EXPECT_FALSE(err.has_error()); |
| EXPECT_EQ(SourceFile("/c/custom_include/header2.h"), source_file); |
| } |
| } |
| |
| TEST_F(HeaderCheckerTest, SourceFileForInclude_FileNotFound) { |
| using base::FilePath; |
| const char kFileContents[] = "Some dummy contents"; |
| const std::vector<SourceDir> kIncludeDirs = {SourceDir("//")}; |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| |
| Err err; |
| InputFile input_file(SourceFile("//input.cc")); |
| input_file.SetContents(std::string(kFileContents)); |
| const int kLineNumber = 10; |
| const int kColumnNumber = 16; |
| const int kLength = 8; |
| const int kByteNumber = 100; |
| LocationRange range( |
| Location(&input_file, kLineNumber, kColumnNumber, kByteNumber), |
| Location(&input_file, kLineNumber, kColumnNumber + kLength, kByteNumber)); |
| |
| SourceFile source_file = checker->SourceFileForInclude( |
| "header.h", kIncludeDirs, input_file, range, &err); |
| EXPECT_TRUE(source_file.is_null()); |
| EXPECT_FALSE(err.has_error()); |
| } |
| |
| TEST_F(HeaderCheckerTest, Friend) { |
| // Note: we have a public dependency chain A -> B -> C set up already. |
| InputFile input_file(SourceFile("//some_file.cc")); |
| input_file.SetContents(std::string()); |
| LocationRange range; // Dummy value. |
| |
| // Add a private header on C. |
| SourceFile c_private("//c_private.h"); |
| c_.sources().push_back(c_private); |
| c_.set_all_headers_public(false); |
| |
| // List A as a friend of C. |
| Err err; |
| c_.friends().push_back( |
| LabelPattern::GetPattern(SourceDir("//"), Value(nullptr, "//a:*"), &err)); |
| ASSERT_FALSE(err.has_error()); |
| |
| // Must be after setting everything up for it to find the files. |
| scoped_refptr<HeaderChecker> checker( |
| new HeaderChecker(setup_.build_settings(), targets_)); |
| |
| // B should not be allowed to include C's private header. |
| std::vector<Err> errors; |
| std::set<std::pair<const Target*, const Target*>> no_dependency_cache; |
| checker->CheckInclude(&b_, input_file, c_private, range, &no_dependency_cache, |
| &errors); |
| EXPECT_GT(errors.size(), 0); |
| |
| // A should be able to because of the friend declaration. |
| errors.clear(); |
| no_dependency_cache.clear(); |
| checker->CheckInclude(&a_, input_file, c_private, range, &no_dependency_cache, |
| &errors); |
| EXPECT_EQ(errors.size(), 0); |
| } |