blob: 8279061945a17179e75f9b71f067c8cf05800d9a [file] [log] [blame]
// Copyright (c) 2013 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 <algorithm>
#include <memory>
#include "gn/builder.h"
#include "gn/config.h"
#include "gn/loader.h"
#include "gn/target.h"
#include "gn/test_with_scheduler.h"
#include "gn/test_with_scope.h"
#include "gn/toolchain.h"
#include "util/test/test.h"
namespace gn_builder_unittest {
class MockLoader : public Loader {
public:
MockLoader() = default;
// Loader implementation:
void Load(const SourceFile& file,
const LocationRange& origin,
const Label& toolchain_name) override {
files_.push_back(file);
}
void ToolchainLoaded(const Toolchain* toolchain) override {}
Label GetDefaultToolchain() const override { return Label(); }
const Settings* GetToolchainSettings(const Label& label) const override {
return nullptr;
}
SourceFile BuildFileForLabel(const Label& label) const override {
return SourceFile(label.dir().value() + "BUILD.gn");
}
bool HasLoadedNone() const { return files_.empty(); }
// Returns true if one/two loads have been requested and they match the given
// file(s). This will clear the records so it will be empty for the next call.
bool HasLoadedOne(const SourceFile& file) {
if (files_.size() != 1u) {
files_.clear();
return false;
}
bool match = (files_[0] == file);
files_.clear();
return match;
}
bool HasLoadedTwo(const SourceFile& a, const SourceFile& b) {
if (files_.size() != 2u) {
files_.clear();
return false;
}
bool match = ((files_[0] == a && files_[1] == b) ||
(files_[0] == b && files_[1] == a));
files_.clear();
return match;
}
bool HasLoadedOnce(const SourceFile& f) {
return count(files_.begin(), files_.end(), f) == 1;
}
private:
~MockLoader() override = default;
std::vector<SourceFile> files_;
};
class BuilderTest : public TestWithScheduler {
public:
BuilderTest()
: loader_(new MockLoader),
builder_(loader_.get()),
settings_(&build_settings_, std::string()),
scope_(&settings_) {
build_settings_.SetBuildDir(SourceDir("//out/"));
settings_.set_toolchain_label(Label(SourceDir("//tc/"), "default"));
settings_.set_default_toolchain_label(settings_.toolchain_label());
}
Toolchain* DefineToolchain() {
Toolchain* tc = new Toolchain(&settings_, settings_.toolchain_label());
TestWithScope::SetupToolchain(tc);
builder_.ItemDefined(std::unique_ptr<Item>(tc));
return tc;
}
protected:
scoped_refptr<MockLoader> loader_;
Builder builder_;
BuildSettings build_settings_;
Settings settings_;
Scope scope_;
};
TEST_F(BuilderTest, BasicDeps) {
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
// Construct a dependency chain: A -> B -> C. Define A first with a
// forward-reference to B, then C, then B to test the different orders that
// the dependencies are hooked up.
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
// The builder will take ownership of the pointers.
Target* a = new Target(&settings_, a_label);
a->public_deps().push_back(LabelTargetPair(b_label));
a->set_output_type(Target::EXECUTABLE);
builder_.ItemDefined(std::unique_ptr<Item>(a));
// Should have requested that B and the toolchain is loaded.
EXPECT_TRUE(loader_->HasLoadedTwo(SourceFile("//tc/BUILD.gn"),
SourceFile("//b/BUILD.gn")));
// Define the toolchain.
DefineToolchain();
BuilderRecord* toolchain_record =
builder_.GetRecord(settings_.toolchain_label());
ASSERT_TRUE(toolchain_record);
EXPECT_EQ(BuilderRecord::ITEM_TOOLCHAIN, toolchain_record->type());
// A should be unresolved with an item
BuilderRecord* a_record = builder_.GetRecord(a_label);
EXPECT_TRUE(a_record->item());
EXPECT_FALSE(a_record->resolved());
EXPECT_FALSE(a_record->can_resolve());
// B should be unresolved, have no item, and no deps.
BuilderRecord* b_record = builder_.GetRecord(b_label);
EXPECT_FALSE(b_record->item());
EXPECT_FALSE(b_record->resolved());
EXPECT_FALSE(b_record->can_resolve());
EXPECT_TRUE(b_record->all_deps().empty());
// A should have two deps: B and the toolchain. Only B should be unresolved.
EXPECT_EQ(2u, a_record->all_deps().size());
EXPECT_TRUE(a_record->all_deps().contains(toolchain_record));
EXPECT_TRUE(a_record->all_deps().contains(b_record));
std::vector<const BuilderRecord*> a_unresolved =
a_record->GetSortedUnresolvedDeps();
EXPECT_EQ(1u, a_unresolved.size());
EXPECT_EQ(a_unresolved[0], b_record);
// B should be marked as having A waiting on it.
EXPECT_EQ(1u, b_record->waiting_on_resolution().size());
EXPECT_TRUE(b_record->waiting_on_resolution().contains(a_record));
// Add the C target.
Target* c = new Target(&settings_, c_label);
c->set_output_type(Target::STATIC_LIBRARY);
c->visibility().SetPublic();
builder_.ItemDefined(std::unique_ptr<Item>(c));
// C only depends on the already-loaded toolchain so we shouldn't have
// requested anything else.
EXPECT_TRUE(loader_->HasLoadedNone());
// Add the B target.
Target* b = new Target(&settings_, b_label);
a->public_deps().push_back(LabelTargetPair(c_label));
b->set_output_type(Target::SHARED_LIBRARY);
b->visibility().SetPublic();
builder_.ItemDefined(std::unique_ptr<Item>(b));
scheduler().Run();
// B depends only on the already-loaded C and toolchain so we shouldn't have
// requested anything else.
EXPECT_TRUE(loader_->HasLoadedNone());
// All targets should now be resolved.
BuilderRecord* c_record = builder_.GetRecord(c_label);
EXPECT_TRUE(a_record->resolved());
EXPECT_TRUE(b_record->resolved());
EXPECT_TRUE(c_record->resolved());
EXPECT_TRUE(a_record->GetSortedUnresolvedDeps().empty());
EXPECT_TRUE(b_record->GetSortedUnresolvedDeps().empty());
EXPECT_TRUE(c_record->GetSortedUnresolvedDeps().empty());
EXPECT_TRUE(a_record->waiting_on_resolution().empty());
EXPECT_TRUE(b_record->waiting_on_resolution().empty());
EXPECT_TRUE(c_record->waiting_on_resolution().empty());
}
TEST_F(BuilderTest, SortedUnresolvedDeps) {
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
// Construct a dependency graph with:
// A -> B
// A -> D
// A -> C
//
// Ensure that the unresolved list of A is always [B, C, D]
//
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
Label d_label(SourceDir("//d/"), "d", toolchain_dir, toolchain_name);
BuilderRecord* a_record = builder_.GetOrCreateRecordForTesting(a_label);
BuilderRecord* b_record = builder_.GetOrCreateRecordForTesting(b_label);
BuilderRecord* c_record = builder_.GetOrCreateRecordForTesting(c_label);
BuilderRecord* d_record = builder_.GetOrCreateRecordForTesting(d_label);
a_record->AddDep(b_record);
a_record->AddDep(d_record);
a_record->AddDep(c_record);
std::vector<const BuilderRecord*> a_unresolved =
a_record->GetSortedUnresolvedDeps();
EXPECT_EQ(3u, a_unresolved.size()) << a_unresolved.size();
EXPECT_EQ(b_record, a_unresolved[0]);
EXPECT_EQ(c_record, a_unresolved[1]);
EXPECT_EQ(d_record, a_unresolved[2]);
}
// Tests that the "should generate" flag is set and propagated properly.
TEST_F(BuilderTest, ShouldGenerate) {
DefineToolchain();
// Define a secondary toolchain.
Settings settings2(&build_settings_, "secondary/");
Label toolchain_label2(SourceDir("//tc/"), "secondary");
settings2.set_toolchain_label(toolchain_label2);
Toolchain* tc2 = new Toolchain(&settings2, toolchain_label2);
TestWithScope::SetupToolchain(tc2);
builder_.ItemDefined(std::unique_ptr<Item>(tc2));
// Construct a dependency chain: A -> B. A is in the default toolchain, B
// is not.
Label a_label(SourceDir("//foo/"), "a", settings_.toolchain_label().dir(),
"a");
Label b_label(SourceDir("//foo/"), "b", toolchain_label2.dir(),
toolchain_label2.name());
// First define B.
Target* b = new Target(&settings2, b_label);
b->visibility().SetPublic();
b->set_output_type(Target::EXECUTABLE);
builder_.ItemDefined(std::unique_ptr<Item>(b));
// B should not be marked generated by default.
BuilderRecord* b_record = builder_.GetRecord(b_label);
EXPECT_FALSE(b_record->should_generate());
// Define A with a dependency on B.
Target* a = new Target(&settings_, a_label);
a->public_deps().push_back(LabelTargetPair(b_label));
a->set_output_type(Target::EXECUTABLE);
builder_.ItemDefined(std::unique_ptr<Item>(a));
scheduler().Run();
// A should have the generate bit set since it's in the default toolchain.
BuilderRecord* a_record = builder_.GetRecord(a_label);
EXPECT_TRUE(a_record->should_generate());
// It should have gotten pushed to B.
EXPECT_TRUE(b_record->should_generate());
}
// Test that "gen_deps" forces targets to be generated.
TEST_F(BuilderTest, GenDeps) {
DefineToolchain();
// Define another toolchain
Settings settings2(&build_settings_, "alternate/");
Label alt_tc(SourceDir("//tc/"), "alternate");
settings2.set_toolchain_label(alt_tc);
Toolchain* tc2 = new Toolchain(&settings2, alt_tc);
TestWithScope::SetupToolchain(tc2);
builder_.ItemDefined(std::unique_ptr<Item>(tc2));
// Construct the dependency chain A -> B -gen-> C -gen-> D where A is the only
// target in the default toolchain. This should cause all 4 targets to be
// generated.
Label a_label(SourceDir("//a/"), "a", settings_.toolchain_label().dir(),
settings_.toolchain_label().name());
Label b_label(SourceDir("//b/"), "b", alt_tc.dir(), alt_tc.name());
Label c_label(SourceDir("//c/"), "c", alt_tc.dir(), alt_tc.name());
Label d_label(SourceDir("//d/"), "d", alt_tc.dir(), alt_tc.name());
Target* c = new Target(&settings2, c_label);
c->set_output_type(Target::EXECUTABLE);
c->gen_deps().push_back(LabelTargetPair(d_label));
builder_.ItemDefined(std::unique_ptr<Item>(c));
Target* b = new Target(&settings2, b_label);
b->set_output_type(Target::EXECUTABLE);
b->visibility().SetPublic(); // Allow 'a' to depend on 'b'
b->gen_deps().push_back(LabelTargetPair(c_label));
builder_.ItemDefined(std::unique_ptr<Item>(b));
Target* a = new Target(&settings_, a_label);
a->set_output_type(Target::EXECUTABLE);
a->private_deps().push_back(LabelTargetPair(b_label));
builder_.ItemDefined(std::unique_ptr<Item>(a));
// At this point, "should generate" should have propogated to C which should
// request for D to be loaded
EXPECT_TRUE(loader_->HasLoadedOnce(SourceFile("//d/BUILD.gn")));
Target* d = new Target(&settings2, d_label);
d->set_output_type(Target::EXECUTABLE);
builder_.ItemDefined(std::unique_ptr<Item>(d));
scheduler().Run();
BuilderRecord* a_record = builder_.GetRecord(a_label);
BuilderRecord* b_record = builder_.GetRecord(b_label);
BuilderRecord* c_record = builder_.GetRecord(c_label);
BuilderRecord* d_record = builder_.GetRecord(d_label);
EXPECT_TRUE(a_record->should_generate());
EXPECT_TRUE(b_record->should_generate());
EXPECT_TRUE(c_record->should_generate());
EXPECT_TRUE(d_record->should_generate());
}
// Test that circular dependencies between gen_deps and deps are allowed
TEST_F(BuilderTest, GenDepsCircle) {
DefineToolchain();
Settings settings2(&build_settings_, "alternate/");
Label alt_tc(SourceDir("//tc/"), "alternate");
settings2.set_toolchain_label(alt_tc);
Toolchain* tc2 = new Toolchain(&settings2, alt_tc);
TestWithScope::SetupToolchain(tc2);
builder_.ItemDefined(std::unique_ptr<Item>(tc2));
// A is in the default toolchain and lists B as a gen_dep
// B is in an alternate toolchain and lists A as a normal dep
Label a_label(SourceDir("//a/"), "a", settings_.toolchain_label().dir(),
settings_.toolchain_label().name());
Label b_label(SourceDir("//b/"), "b", alt_tc.dir(), alt_tc.name());
Target* a = new Target(&settings_, a_label);
a->gen_deps().push_back(LabelTargetPair(b_label));
a->set_output_type(Target::EXECUTABLE);
a->visibility().SetPublic(); // Allow 'b' to depend on 'a'
builder_.ItemDefined(std::unique_ptr<Item>(a));
Target* b = new Target(&settings2, b_label);
b->private_deps().push_back(LabelTargetPair(a_label));
b->set_output_type(Target::EXECUTABLE);
builder_.ItemDefined(std::unique_ptr<Item>(b));
scheduler().Run();
Err err;
EXPECT_TRUE(builder_.CheckForBadItems(&err));
BuilderRecord* b_record = builder_.GetRecord(b_label);
EXPECT_TRUE(b_record->should_generate());
}
// Tests that configs applied to a config get loaded (bug 536844).
TEST_F(BuilderTest, ConfigLoad) {
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
// Construct a dependency chain: A -> B -> C. Define A first with a
// forward-reference to B, then C, then B to test the different orders that
// the dependencies are hooked up.
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
// The builder will take ownership of the pointers.
Config* a = new Config(&settings_, a_label);
a->configs().push_back(LabelConfigPair(b_label));
builder_.ItemDefined(std::unique_ptr<Item>(a));
// Should have requested that B is loaded.
EXPECT_TRUE(loader_->HasLoadedOne(SourceFile("//b/BUILD.gn")));
}
// Tests that "validations" dependencies behave correctly:
// 1. They trigger the loading of the validated target's build file (simulating
// cross-directory deps).
// 2. The validator waits for the validation target to be DEFINED, not RESOLVED.
// 3. This allows cycles (A validates B, B depends on A) to resolve without
// error.
TEST_F(BuilderTest, Validations) {
DefineToolchain();
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
// Define A with validatation B.
auto a = std::make_unique<Target>(&settings_, a_label);
Target* a_ptr = a.get();
a_ptr->set_output_type(Target::ACTION);
a_ptr->visibility().SetPublic();
a_ptr->validations().push_back(LabelTargetPair(b_label));
builder_.ItemDefined(std::move(a));
// Should have requested that B is loaded.
EXPECT_TRUE(loader_->HasLoadedOne(SourceFile("//b/BUILD.gn")));
// A should NOT be resolved yet (waiting for B definition).
BuilderRecord* a_record = builder_.GetRecord(a_label);
EXPECT_TRUE(a_record);
EXPECT_FALSE(a_record->resolved());
// Define B. B depends on A.
auto b = std::make_unique<Target>(&settings_, b_label);
Target* b_ptr = b.get();
b_ptr->set_output_type(Target::ACTION);
b_ptr->visibility().SetPublic();
b_ptr->private_deps().push_back(LabelTargetPair(a_label));
builder_.ItemDefined(std::move(b));
scheduler().Run();
// Now both should be resolved.
EXPECT_TRUE(a_record->resolved());
BuilderRecord* b_record = builder_.GetRecord(b_label);
EXPECT_TRUE(b_record->resolved());
// There should be no cycle.
Err err;
EXPECT_TRUE(builder_.CheckForBadItems(&err));
EXPECT_FALSE(err.has_error()) << "CheckForBadItems error: " << err.message();
// A should have B in its validations.
ASSERT_EQ(1u, a_ptr->validations().size());
EXPECT_EQ(b_ptr, a_ptr->validations()[0].ptr);
}
// Tests that validation dependencies block writing until resolved.
TEST_F(BuilderTest, ValidationsBlockWriting) {
DefineToolchain();
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
// Define A. A lists B in validations.
auto a = std::make_unique<Target>(&settings_, a_label);
a->set_output_type(Target::ACTION);
a->validations().push_back(LabelTargetPair(b_label));
a->visibility().SetPublic();
builder_.ItemDefined(std::move(a));
// Define B. B depends on C.
auto b = std::make_unique<Target>(&settings_, b_label);
b->set_output_type(Target::ACTION);
b->private_deps().push_back(LabelTargetPair(c_label));
b->visibility().SetPublic();
builder_.ItemDefined(std::move(b));
// C is unresolved (waiting on definition).
// B is unresolved (waiting on C).
// A should be RESOLVED (because B is defined, and validations only wait on
// definition for resolution). BUT A should be blocked from WRITING because B
// is not resolved.
BuilderRecord* a_record = builder_.GetRecord(a_label);
BuilderRecord* b_record = builder_.GetRecord(b_label);
scheduler().Run();
EXPECT_TRUE(a_record->resolved());
EXPECT_FALSE(b_record->resolved());
// Check that B knows A is waiting on it for writing.
EXPECT_TRUE(b_record->waiting_on_resolution_for_writing().contains(a_record));
}
// Tests that a target can validate a target that depends on it.
// A -> validations -> B
// B -> deps -> A
TEST_F(BuilderTest, ValidationsWithCycle) {
DefineToolchain();
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
// Define A. A lists B in validations.
auto a = std::make_unique<Target>(&settings_, a_label);
a->set_output_type(Target::ACTION);
a->validations().push_back(LabelTargetPair(b_label));
a->visibility().SetPublic();
builder_.ItemDefined(std::move(a));
// Define B. B depends on A.
auto b = std::make_unique<Target>(&settings_, b_label);
b->set_output_type(Target::ACTION);
b->private_deps().push_back(LabelTargetPair(a_label));
b->visibility().SetPublic();
builder_.ItemDefined(std::move(b));
BuilderRecord* a_record = builder_.GetRecord(a_label);
BuilderRecord* b_record = builder_.GetRecord(b_label);
scheduler().Run();
// Both should be resolved.
EXPECT_TRUE(a_record->resolved());
EXPECT_TRUE(b_record->resolved());
// There should be no errors (cycle detection passed).
Err err;
EXPECT_TRUE(builder_.CheckForBadItems(&err));
EXPECT_FALSE(err.has_error());
}
// Tests that if a validation resolves, the target does NOT write if it
// is still waiting on other dependencies to resolve.
// A -> deps -> B
// A -> validations -> C
// Sequence: C resolves. A should NOT write. B resolves. A writes.
TEST_F(BuilderTest, ValidationsPrematureWrite) {
DefineToolchain();
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
// Define A. A depends on B and has validation C.
auto a = std::make_unique<Target>(&settings_, a_label);
a->set_output_type(Target::ACTION);
a->private_deps().push_back(LabelTargetPair(b_label));
a->validations().push_back(LabelTargetPair(c_label));
a->visibility().SetPublic();
builder_.ItemDefined(std::move(a));
// Define C. C is standalone.
auto c = std::make_unique<Target>(&settings_, c_label);
c->set_output_type(Target::ACTION);
c->visibility().SetPublic();
builder_.ItemDefined(std::move(c));
// Track written targets.
std::vector<const BuilderRecord*> written;
builder_.set_resolved_and_generated_callback(
[&written](const BuilderRecord* record) { written.push_back(record); });
// C should resolve. A is waiting on B.
scheduler().Run();
BuilderRecord* a_record = builder_.GetRecord(a_label);
BuilderRecord* c_record = builder_.GetRecord(c_label);
EXPECT_TRUE(c_record->resolved());
EXPECT_FALSE(a_record->resolved());
// Only C should be written.
ASSERT_EQ(1u, written.size());
EXPECT_EQ(c_record, written[0]);
// Define B.
auto b = std::make_unique<Target>(&settings_, b_label);
b->set_output_type(Target::ACTION);
b->visibility().SetPublic();
builder_.ItemDefined(std::move(b));
BuilderRecord* b_record = builder_.GetRecord(b_label);
scheduler().Run();
EXPECT_TRUE(a_record->resolved());
// Order should be C, B, A.
ASSERT_EQ(3u, written.size());
EXPECT_EQ(c_record, written[0]);
EXPECT_EQ(b_record, written[1]);
EXPECT_EQ(a_record, written[2]);
}
// Tests that RecursiveSetShouldGenerate does not trigger a write callback
// if the target is waiting on validations (can_write() is false).
TEST_F(BuilderTest, RecursiveShouldGenerateWithValidations) {
DefineToolchain();
SourceDir toolchain_dir = settings_.toolchain_label().dir();
std::string toolchain_name = settings_.toolchain_label().name();
// Set root patterns to something that doesn't match A, B, etc.
// This ensures they are not generated by default.
std::vector<LabelPattern> dummy_patterns;
dummy_patterns.push_back(
LabelPattern(LabelPattern::MATCH, SourceDir("//c/"), "c", Label()));
build_settings_.SetRootPatterns(std::move(dummy_patterns));
Label a_label(SourceDir("//a/"), "a", toolchain_dir, toolchain_name);
Label b_label(SourceDir("//b/"), "b", toolchain_dir, toolchain_name);
Label c_label(SourceDir("//c/"), "c", toolchain_dir, toolchain_name);
Label e_label(SourceDir("//e/"), "e", toolchain_dir, toolchain_name);
// Define A with validation B.
auto a = std::make_unique<Target>(&settings_, a_label);
a->set_output_type(Target::GROUP);
a->validations().push_back(LabelTargetPair(b_label));
a->visibility().SetPublic();
builder_.ItemDefined(std::move(a));
BuilderRecord* a_record = builder_.GetRecord(a_label);
// Define B with dependency E delay resolution.
auto b = std::make_unique<Target>(&settings_, b_label);
b->set_output_type(Target::ACTION);
b->private_deps().push_back(LabelTargetPair(e_label));
b->visibility().SetPublic();
builder_.ItemDefined(std::move(b));
BuilderRecord* b_record = builder_.GetRecord(b_label);
// Track written targets.
std::vector<const BuilderRecord*> written;
builder_.set_resolved_and_generated_callback(
[&written](const BuilderRecord* record) { written.push_back(record); });
// A resolves (validations don't block resolution).
// B waits for E.
scheduler().Run();
EXPECT_TRUE(a_record->resolved());
EXPECT_FALSE(b_record->resolved());
EXPECT_FALSE(a_record->can_write());
EXPECT_FALSE(a_record->should_generate());
EXPECT_TRUE(written.empty());
// Define C depending on A.
// C will generate because root pattern matches.
auto c = std::make_unique<Target>(&settings_, c_label);
c->set_output_type(Target::GROUP);
c->public_deps().push_back(LabelTargetPair(a_label));
c->visibility().SetPublic();
builder_.ItemDefined(std::move(c));
BuilderRecord* c_record = builder_.GetRecord(c_label);
scheduler().Run();
EXPECT_TRUE(c_record->should_generate());
EXPECT_TRUE(a_record->should_generate());
// Only C should be written now.
std::vector<const BuilderRecord*> expected_c = {c_record};
EXPECT_EQ(expected_c, written);
// Define E to cause B to resolve and write A.
auto e = std::make_unique<Target>(&settings_, e_label);
e->set_output_type(Target::ACTION);
e->visibility().SetPublic();
builder_.ItemDefined(std::move(e));
BuilderRecord* e_record = builder_.GetRecord(e_label);
scheduler().Run();
// B writes (resolved), then A writes (unblocked).
// C was already written.
std::vector<const BuilderRecord*> expected_final = {c_record, e_record,
b_record, a_record};
EXPECT_EQ(expected_final, written);
}
} // namespace gn_builder_unittest