blob: 424369646144a4ae81185da9cf1e7c98a1d13adc [file] [log] [blame]
// 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 "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);
}