summaryrefslogtreecommitdiffstats
path: root/src/build_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/build_test.cc')
-rw-r--r--src/build_test.cc662
1 files changed, 463 insertions, 199 deletions
diff --git a/src/build_test.cc b/src/build_test.cc
index 40a82ed..90c328a 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -15,6 +15,7 @@
#include "build.h"
#include "build_log.h"
+#include "deps_log.h"
#include "graph.h"
#include "test.h"
@@ -23,6 +24,26 @@
// to create Nodes and Edges.
struct PlanTest : public StateTestWithBuiltinRules {
Plan plan_;
+
+ /// Because FindWork does not return Edges in any sort of predictable order,
+ // provide a means to get available Edges in order and in a format which is
+ // easy to write tests around.
+ void FindWorkSorted(deque<Edge*>* ret, int count) {
+ struct CompareEdgesByOutput {
+ static bool cmp(const Edge* a, const Edge* b) {
+ return a->outputs_[0]->path() < b->outputs_[0]->path();
+ }
+ };
+
+ for (int i = 0; i < count; ++i) {
+ ASSERT_TRUE(plan_.more_to_do());
+ Edge* edge = plan_.FindWork();
+ ASSERT_TRUE(edge);
+ ret->push_back(edge);
+ }
+ ASSERT_FALSE(plan_.FindWork());
+ sort(ret->begin(), ret->end(), CompareEdgesByOutput::cmp);
+ }
};
TEST_F(PlanTest, Basic) {
@@ -241,8 +262,8 @@ TEST_F(PlanTest, PoolsWithDepthTwo) {
));
// Mark all the out* nodes dirty
for (int i = 0; i < 3; ++i) {
- GetNode("out" + string(1, '1' + i))->MarkDirty();
- GetNode("outb" + string(1, '1' + i))->MarkDirty();
+ GetNode("out" + string(1, '1' + static_cast<char>(i)))->MarkDirty();
+ GetNode("outb" + string(1, '1' + static_cast<char>(i)))->MarkDirty();
}
GetNode("allTheThings")->MarkDirty();
@@ -250,27 +271,21 @@ TEST_F(PlanTest, PoolsWithDepthTwo) {
EXPECT_TRUE(plan_.AddTarget(GetNode("allTheThings"), &err));
ASSERT_EQ("", err);
- // Grab the first 4 edges, out1 out2 outb1 outb2
deque<Edge*> edges;
+ FindWorkSorted(&edges, 5);
+
for (int i = 0; i < 4; ++i) {
- ASSERT_TRUE(plan_.more_to_do());
- Edge* edge = plan_.FindWork();
- ASSERT_TRUE(edge);
+ Edge *edge = edges[i];
ASSERT_EQ("in", edge->inputs_[0]->path());
string base_name(i < 2 ? "out" : "outb");
ASSERT_EQ(base_name + string(1, '1' + (i % 2)), edge->outputs_[0]->path());
- edges.push_back(edge);
}
// outb3 is exempt because it has an empty pool
- ASSERT_TRUE(plan_.more_to_do());
- Edge* edge = plan_.FindWork();
+ Edge* edge = edges[4];
ASSERT_TRUE(edge);
ASSERT_EQ("in", edge->inputs_[0]->path());
ASSERT_EQ("outb3", edge->outputs_[0]->path());
- edges.push_back(edge);
-
- ASSERT_FALSE(plan_.FindWork());
// finish out1
plan_.EdgeFinished(edges.front());
@@ -292,11 +307,11 @@ TEST_F(PlanTest, PoolsWithDepthTwo) {
plan_.EdgeFinished(*it);
}
- Edge* final = plan_.FindWork();
- ASSERT_TRUE(final);
- ASSERT_EQ("allTheThings", final->outputs_[0]->path());
+ Edge* last = plan_.FindWork();
+ ASSERT_TRUE(last);
+ ASSERT_EQ("allTheThings", last->outputs_[0]->path());
- plan_.EdgeFinished(final);
+ plan_.EdgeFinished(last);
ASSERT_FALSE(plan_.more_to_do());
ASSERT_FALSE(plan_.FindWork());
@@ -333,25 +348,28 @@ TEST_F(PlanTest, PoolWithRedundantEdges) {
Edge* edge = NULL;
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
+ deque<Edge*> initial_edges;
+ FindWorkSorted(&initial_edges, 2);
+
+ edge = initial_edges[1]; // Foo first
ASSERT_EQ("foo.cpp", edge->outputs_[0]->path());
plan_.EdgeFinished(edge);
edge = plan_.FindWork();
ASSERT_TRUE(edge);
+ ASSERT_FALSE(plan_.FindWork());
ASSERT_EQ("foo.cpp", edge->inputs_[0]->path());
ASSERT_EQ("foo.cpp", edge->inputs_[1]->path());
ASSERT_EQ("foo.cpp.obj", edge->outputs_[0]->path());
plan_.EdgeFinished(edge);
- edge = plan_.FindWork();
- ASSERT_TRUE(edge);
+ edge = initial_edges[0]; // Now for bar
ASSERT_EQ("bar.cpp", edge->outputs_[0]->path());
plan_.EdgeFinished(edge);
edge = plan_.FindWork();
ASSERT_TRUE(edge);
+ ASSERT_FALSE(plan_.FindWork());
ASSERT_EQ("bar.cpp", edge->inputs_[0]->path());
ASSERT_EQ("bar.cpp", edge->inputs_[1]->path());
ASSERT_EQ("bar.cpp.obj", edge->outputs_[0]->path());
@@ -359,6 +377,7 @@ TEST_F(PlanTest, PoolWithRedundantEdges) {
edge = plan_.FindWork();
ASSERT_TRUE(edge);
+ ASSERT_FALSE(plan_.FindWork());
ASSERT_EQ("foo.cpp.obj", edge->inputs_[0]->path());
ASSERT_EQ("bar.cpp.obj", edge->inputs_[1]->path());
ASSERT_EQ("libfoo.a", edge->outputs_[0]->path());
@@ -366,6 +385,7 @@ TEST_F(PlanTest, PoolWithRedundantEdges) {
edge = plan_.FindWork();
ASSERT_TRUE(edge);
+ ASSERT_FALSE(plan_.FindWork());
ASSERT_EQ("libfoo.a", edge->inputs_[0]->path());
ASSERT_EQ("all", edge->outputs_[0]->path());
plan_.EdgeFinished(edge);
@@ -375,20 +395,40 @@ TEST_F(PlanTest, PoolWithRedundantEdges) {
ASSERT_FALSE(plan_.more_to_do());
}
+/// Fake implementation of CommandRunner, useful for tests.
+struct FakeCommandRunner : public CommandRunner {
+ explicit FakeCommandRunner(VirtualFileSystem* fs) :
+ last_command_(NULL), fs_(fs) {}
+
+ // CommandRunner impl
+ virtual bool CanRunMore();
+ virtual bool StartCommand(Edge* edge);
+ virtual bool WaitForCommand(Result* result);
+ virtual vector<Edge*> GetActiveEdges();
+ virtual void Abort();
+
+ vector<string> commands_ran_;
+ Edge* last_command_;
+ VirtualFileSystem* fs_;
+};
+
+struct BuildTest : public StateTestWithBuiltinRules {
+ BuildTest() : config_(MakeConfig()), command_runner_(&fs_),
+ builder_(&state_, config_, NULL, NULL, &fs_),
+ status_(config_) {
+ }
+
+ virtual void SetUp() {
+ StateTestWithBuiltinRules::SetUp();
-struct BuildTest : public StateTestWithBuiltinRules,
- public CommandRunner {
- BuildTest() : config_(MakeConfig()),
- builder_(&state_, config_, NULL, &fs_),
- now_(1), last_command_(NULL), status_(config_) {
- builder_.command_runner_.reset(this);
+ builder_.command_runner_.reset(&command_runner_);
AssertParse(&state_,
"build cat1: cat in1\n"
"build cat2: cat in1 in2\n"
"build cat12: cat cat1 cat2\n");
- fs_.Create("in1", now_, "");
- fs_.Create("in2", now_, "");
+ fs_.Create("in1", "");
+ fs_.Create("in2", "");
}
~BuildTest() {
@@ -398,13 +438,6 @@ struct BuildTest : public StateTestWithBuiltinRules,
// Mark a path dirty.
void Dirty(const string& path);
- // CommandRunner impl
- virtual bool CanRunMore();
- virtual bool StartCommand(Edge* edge);
- virtual Edge* WaitForCommand(ExitStatus* status, string* output);
- virtual vector<Edge*> GetActiveEdges();
- virtual void Abort();
-
BuildConfig MakeConfig() {
BuildConfig config;
config.verbosity = BuildConfig::QUIET;
@@ -412,31 +445,19 @@ struct BuildTest : public StateTestWithBuiltinRules,
}
BuildConfig config_;
+ FakeCommandRunner command_runner_;
VirtualFileSystem fs_;
Builder builder_;
- int now_;
- vector<string> commands_ran_;
- Edge* last_command_;
BuildStatus status_;
};
-void BuildTest::Dirty(const string& path) {
- Node* node = GetNode(path);
- node->MarkDirty();
-
- // If it's an input file, mark that we've already stat()ed it and
- // it's missing.
- if (!node->in_edge())
- node->MarkMissing();
-}
-
-bool BuildTest::CanRunMore() {
+bool FakeCommandRunner::CanRunMore() {
// Only run one at a time.
return last_command_ == NULL;
}
-bool BuildTest::StartCommand(Edge* edge) {
+bool FakeCommandRunner::StartCommand(Edge* edge) {
assert(!last_command_);
commands_ran_.push_back(edge->EvaluateCommand());
if (edge->rule().name() == "cat" ||
@@ -446,7 +467,7 @@ bool BuildTest::StartCommand(Edge* edge) {
edge->rule().name() == "touch-interrupt") {
for (vector<Node*>::iterator out = edge->outputs_.begin();
out != edge->outputs_.end(); ++out) {
- fs_.Create((*out)->path(), now_, "");
+ fs_->Create((*out)->path(), "");
}
} else if (edge->rule().name() == "true" ||
edge->rule().name() == "fail" ||
@@ -461,36 +482,48 @@ bool BuildTest::StartCommand(Edge* edge) {
return true;
}
-Edge* BuildTest::WaitForCommand(ExitStatus* status, string* /* output */) {
- if (Edge* edge = last_command_) {
- if (edge->rule().name() == "interrupt" ||
- edge->rule().name() == "touch-interrupt") {
- *status = ExitInterrupted;
- return NULL;
- }
+bool FakeCommandRunner::WaitForCommand(Result* result) {
+ if (!last_command_)
+ return false;
- if (edge->rule().name() == "fail")
- *status = ExitFailure;
- else
- *status = ExitSuccess;
- last_command_ = NULL;
- return edge;
+ Edge* edge = last_command_;
+ result->edge = edge;
+
+ if (edge->rule().name() == "interrupt" ||
+ edge->rule().name() == "touch-interrupt") {
+ result->status = ExitInterrupted;
+ return true;
}
- *status = ExitFailure;
- return NULL;
+
+ if (edge->rule().name() == "fail")
+ result->status = ExitFailure;
+ else
+ result->status = ExitSuccess;
+ last_command_ = NULL;
+ return true;
}
-vector<Edge*> BuildTest::GetActiveEdges() {
+vector<Edge*> FakeCommandRunner::GetActiveEdges() {
vector<Edge*> edges;
if (last_command_)
edges.push_back(last_command_);
return edges;
}
-void BuildTest::Abort() {
+void FakeCommandRunner::Abort() {
last_command_ = NULL;
}
+void BuildTest::Dirty(const string& path) {
+ Node* node = GetNode(path);
+ node->MarkDirty();
+
+ // If it's an input file, mark that we've already stat()ed it and
+ // it's missing.
+ if (!node->in_edge())
+ node->MarkMissing();
+}
+
TEST_F(BuildTest, NoWork) {
string err;
EXPECT_TRUE(builder_.AlreadyUpToDate());
@@ -506,8 +539,8 @@ TEST_F(BuildTest, OneStep) {
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
- EXPECT_EQ("cat in1 > cat1", commands_ran_[0]);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in1 > cat1", command_runner_.commands_ran_[0]);
}
TEST_F(BuildTest, OneStep2) {
@@ -520,8 +553,8 @@ TEST_F(BuildTest, OneStep2) {
EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
- EXPECT_EQ("cat in1 > cat1", commands_ran_[0]);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in1 > cat1", command_runner_.commands_ran_[0]);
}
TEST_F(BuildTest, TwoStep) {
@@ -530,29 +563,29 @@ TEST_F(BuildTest, TwoStep) {
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
- ASSERT_EQ(3u, commands_ran_.size());
+ ASSERT_EQ(3u, command_runner_.commands_ran_.size());
// Depending on how the pointers work out, we could've ran
// the first two commands in either order.
- EXPECT_TRUE((commands_ran_[0] == "cat in1 > cat1" &&
- commands_ran_[1] == "cat in1 in2 > cat2") ||
- (commands_ran_[1] == "cat in1 > cat1" &&
- commands_ran_[0] == "cat in1 in2 > cat2"));
+ EXPECT_TRUE((command_runner_.commands_ran_[0] == "cat in1 > cat1" &&
+ command_runner_.commands_ran_[1] == "cat in1 in2 > cat2") ||
+ (command_runner_.commands_ran_[1] == "cat in1 > cat1" &&
+ command_runner_.commands_ran_[0] == "cat in1 in2 > cat2"));
- EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[2]);
+ EXPECT_EQ("cat cat1 cat2 > cat12", command_runner_.commands_ran_[2]);
- now_++;
+ fs_.Tick();
// Modifying in2 requires rebuilding one intermediate file
// and the final file.
- fs_.Create("in2", now_, "");
+ fs_.Create("in2", "");
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("cat12", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(5u, commands_ran_.size());
- EXPECT_EQ("cat in1 in2 > cat2", commands_ran_[3]);
- EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[4]);
+ ASSERT_EQ(5u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("cat in1 in2 > cat2", command_runner_.commands_ran_[3]);
+ EXPECT_EQ("cat cat1 cat2 > cat12", command_runner_.commands_ran_[4]);
}
TEST_F(BuildTest, TwoOutputs) {
@@ -561,15 +594,15 @@ TEST_F(BuildTest, TwoOutputs) {
" command = touch $out\n"
"build out1 out2: touch in.txt\n"));
- fs_.Create("in.txt", now_, "");
+ fs_.Create("in.txt", "");
string err;
EXPECT_TRUE(builder_.AddTarget("out1", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
- EXPECT_EQ("touch out1 out2", commands_ran_[0]);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ("touch out1 out2", command_runner_.commands_ran_[0]);
}
// Test case from
@@ -581,8 +614,9 @@ TEST_F(BuildTest, MultiOutIn) {
"build in1 otherfile: touch in\n"
"build out: touch in | in1\n"));
- fs_.Create("in", now_, "");
- fs_.Create("in1", ++now_, "");
+ fs_.Create("in", "");
+ fs_.Tick();
+ fs_.Create("in1", "");
string err;
EXPECT_TRUE(builder_.AddTarget("out", &err));
@@ -598,33 +632,33 @@ TEST_F(BuildTest, Chain) {
"build c4: cat c3\n"
"build c5: cat c4\n"));
- fs_.Create("c1", now_, "");
+ fs_.Create("c1", "");
string err;
EXPECT_TRUE(builder_.AddTarget("c5", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
- ASSERT_EQ(4u, commands_ran_.size());
+ ASSERT_EQ(4u, command_runner_.commands_ran_.size());
err.clear();
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("c5", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.AlreadyUpToDate());
- now_++;
+ fs_.Tick();
- fs_.Create("c3", now_, "");
+ fs_.Create("c3", "");
err.clear();
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("c5", &err));
ASSERT_EQ("", err);
EXPECT_FALSE(builder_.AlreadyUpToDate());
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, commands_ran_.size()); // 3->4, 4->5
+ ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // 3->4, 4->5
}
TEST_F(BuildTest, MissingInput) {
@@ -657,7 +691,6 @@ TEST_F(BuildTest, MakeDirs) {
#endif
EXPECT_EQ("", err);
- now_ = 0; // Make all stat()s return file not found.
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
ASSERT_EQ(2u, fs_.directories_made_.size());
@@ -674,7 +707,7 @@ TEST_F(BuildTest, DepFileMissing) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule cc\n command = cc $in\n depfile = $out.d\n"
"build foo.o: cc foo.c\n"));
- fs_.Create("foo.c", now_, "");
+ fs_.Create("foo.c", "");
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
ASSERT_EQ("", err);
@@ -690,9 +723,9 @@ TEST_F(BuildTest, DepFileOK) {
"build foo.o: cc foo.c\n"));
Edge* edge = state_.edges_.back();
- fs_.Create("foo.c", now_, "");
+ fs_.Create("foo.c", "");
GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing.
- fs_.Create("foo.o.d", now_, "foo.o: blah.h bar.h\n");
+ fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
ASSERT_EQ("", err);
ASSERT_EQ(1u, fs_.files_read_.size());
@@ -713,8 +746,8 @@ TEST_F(BuildTest, DepFileParseError) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"rule cc\n command = cc $in\n depfile = $out.d\n"
"build foo.o: cc foo.c\n"));
- fs_.Create("foo.c", now_, "");
- fs_.Create("foo.o.d", now_, "randomtext\n");
+ fs_.Create("foo.c", "");
+ fs_.Create("foo.o.d", "randomtext\n");
EXPECT_FALSE(builder_.AddTarget("foo.o", &err));
EXPECT_EQ("expected depfile 'foo.o.d' to mention 'foo.o', got 'randomtext'",
err);
@@ -727,9 +760,9 @@ TEST_F(BuildTest, OrderOnlyDeps) {
"build foo.o: cc foo.c || otherfile\n"));
Edge* edge = state_.edges_.back();
- fs_.Create("foo.c", now_, "");
- fs_.Create("otherfile", now_, "");
- fs_.Create("foo.o.d", now_, "foo.o: blah.h bar.h\n");
+ fs_.Create("foo.c", "");
+ fs_.Create("otherfile", "");
+ fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
ASSERT_EQ("", err);
@@ -750,25 +783,31 @@ TEST_F(BuildTest, OrderOnlyDeps) {
// explicit dep dirty, expect a rebuild.
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
- now_++;
+ fs_.Tick();
+
+ // Recreate the depfile, as it should have been deleted by the build.
+ fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
// implicit dep dirty, expect a rebuild.
- fs_.Create("blah.h", now_, "");
- fs_.Create("bar.h", now_, "");
- commands_ran_.clear();
+ fs_.Create("blah.h", "");
+ fs_.Create("bar.h", "");
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+
+ fs_.Tick();
- now_++;
+ // Recreate the depfile, as it should have been deleted by the build.
+ fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
// order only dep dirty, no rebuild.
- fs_.Create("otherfile", now_, "");
- commands_ran_.clear();
+ fs_.Create("otherfile", "");
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_EQ("", err);
@@ -776,12 +815,12 @@ TEST_F(BuildTest, OrderOnlyDeps) {
// implicit dep missing, expect rebuild.
fs_.RemoveFile("bar.h");
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
}
TEST_F(BuildTest, RebuildOrderOnlyDeps) {
@@ -792,17 +831,17 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) {
"build oo.h: cc oo.h.in\n"
"build foo.o: cc foo.c || oo.h\n"));
- fs_.Create("foo.c", now_, "");
- fs_.Create("oo.h.in", now_, "");
+ fs_.Create("foo.c", "");
+ fs_.Create("oo.h.in", "");
// foo.o and order-only dep dirty, build both.
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(2u, commands_ran_.size());
+ ASSERT_EQ(2u, command_runner_.commands_ran_.size());
// all clean, no rebuild.
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_EQ("", err);
@@ -810,25 +849,25 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) {
// order-only dep missing, build it only.
fs_.RemoveFile("oo.h");
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
- ASSERT_EQ("cc oo.h.in", commands_ran_[0]);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]);
- now_++;
+ fs_.Tick();
// order-only dep dirty, build it only.
- fs_.Create("oo.h.in", now_, "");
- commands_ran_.clear();
+ fs_.Create("oo.h.in", "");
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
- ASSERT_EQ("cc oo.h.in", commands_ran_[0]);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+ ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]);
}
TEST_F(BuildTest, Phony) {
@@ -836,7 +875,7 @@ TEST_F(BuildTest, Phony) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build out: cat bar.cc\n"
"build all: phony out\n"));
- fs_.Create("bar.cc", now_, "");
+ fs_.Create("bar.cc", "");
EXPECT_TRUE(builder_.AddTarget("all", &err));
ASSERT_EQ("", err);
@@ -845,7 +884,7 @@ TEST_F(BuildTest, Phony) {
EXPECT_FALSE(builder_.AlreadyUpToDate());
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
}
TEST_F(BuildTest, PhonyNoWork) {
@@ -853,8 +892,8 @@ TEST_F(BuildTest, PhonyNoWork) {
ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
"build out: cat bar.cc\n"
"build all: phony out\n"));
- fs_.Create("bar.cc", now_, "");
- fs_.Create("out", now_, "");
+ fs_.Create("bar.cc", "");
+ fs_.Create("out", "");
EXPECT_TRUE(builder_.AddTarget("all", &err));
ASSERT_EQ("", err);
@@ -872,7 +911,7 @@ TEST_F(BuildTest, Fail) {
ASSERT_EQ("", err);
EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
ASSERT_EQ("subcommand failed", err);
}
@@ -893,7 +932,7 @@ TEST_F(BuildTest, SwallowFailures) {
ASSERT_EQ("", err);
EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(3u, commands_ran_.size());
+ ASSERT_EQ(3u, command_runner_.commands_ran_.size());
ASSERT_EQ("subcommands failed", err);
}
@@ -914,7 +953,7 @@ TEST_F(BuildTest, SwallowFailuresLimit) {
ASSERT_EQ("", err);
EXPECT_FALSE(builder_.Build(&err));
- ASSERT_EQ(3u, commands_ran_.size());
+ ASSERT_EQ(3u, command_runner_.commands_ran_.size());
ASSERT_EQ("cannot make progress due to previous errors", err);
}
@@ -934,8 +973,8 @@ TEST_F(BuildWithLogTest, NotInLogButOnDisk) {
// Create input/output that would be considered up to date when
// not considering the command line hash.
- fs_.Create("in", now_, "");
- fs_.Create("out1", now_, "");
+ fs_.Create("in", "");
+ fs_.Create("out1", "");
string err;
// Because it's not in the log, it should not be up-to-date until
@@ -943,7 +982,7 @@ TEST_F(BuildWithLogTest, NotInLogButOnDisk) {
EXPECT_TRUE(builder_.AddTarget("out1", &err));
EXPECT_FALSE(builder_.AlreadyUpToDate());
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out1", &err));
@@ -963,13 +1002,13 @@ TEST_F(BuildWithLogTest, RestatTest) {
"build out2: true out1\n"
"build out3: cat out2\n"));
- fs_.Create("out1", now_, "");
- fs_.Create("out2", now_, "");
- fs_.Create("out3", now_, "");
+ fs_.Create("out1", "");
+ fs_.Create("out2", "");
+ fs_.Create("out3", "");
- now_++;
+ fs_.Tick();
- fs_.Create("in", now_, "");
+ fs_.Create("in", "");
// Do a pre-build so that there's commands in the log for the outputs,
// otherwise, the lack of an entry in the build log will cause out3 to rebuild
@@ -979,39 +1018,39 @@ TEST_F(BuildWithLogTest, RestatTest) {
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
- now_++;
+ fs_.Tick();
- fs_.Create("in", now_, "");
+ fs_.Create("in", "");
// "cc" touches out1, so we should build out2. But because "true" does not
// touch out2, we should cancel the build of out3.
EXPECT_TRUE(builder_.AddTarget("out3", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, commands_ran_.size());
+ ASSERT_EQ(2u, command_runner_.commands_ran_.size());
// If we run again, it should be a no-op, because the build log has recorded
// that we've already built out2 with an input timestamp of 2 (from out1).
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out3", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.AlreadyUpToDate());
- now_++;
+ fs_.Tick();
- fs_.Create("in", now_, "");
+ fs_.Create("in", "");
// The build log entry should not, however, prevent us from rebuilding out2
// if out1 changes.
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out3", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, commands_ran_.size());
+ ASSERT_EQ(2u, command_runner_.commands_ran_.size());
}
TEST_F(BuildWithLogTest, RestatMissingFile) {
@@ -1028,8 +1067,8 @@ TEST_F(BuildWithLogTest, RestatMissingFile) {
"build out1: true in\n"
"build out2: cc out1\n"));
- fs_.Create("in", now_, "");
- fs_.Create("out2", now_, "");
+ fs_.Create("in", "");
+ fs_.Create("out2", "");
// Do a pre-build so that there's commands in the log for the outputs,
// otherwise, the lack of an entry in the build log will cause out2 to rebuild
@@ -1039,12 +1078,12 @@ TEST_F(BuildWithLogTest, RestatMissingFile) {
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
ASSERT_EQ("", err);
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
- now_++;
- fs_.Create("in", now_, "");
- fs_.Create("out2", now_, "");
+ fs_.Tick();
+ fs_.Create("in", "");
+ fs_.Create("out2", "");
// Run a build, expect only the first command to run.
// It doesn't touch its output (due to being the "true" command), so
@@ -1052,7 +1091,7 @@ TEST_F(BuildWithLogTest, RestatMissingFile) {
EXPECT_TRUE(builder_.AddTarget("out2", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
}
// Test scenario, in which an input file is removed, but output isn't changed
@@ -1069,21 +1108,21 @@ TEST_F(BuildWithLogTest, RestatMissingInput) {
"build out2: cc out1\n"));
// Create all necessary files
- fs_.Create("in", now_, "");
+ fs_.Create("in", "");
// The implicit dependencies and the depfile itself
// are newer than the output
- TimeStamp restat_mtime = ++now_;
- fs_.Create("out1.d", now_, "out1: will.be.deleted restat.file\n");
- fs_.Create("will.be.deleted", now_, "");
- fs_.Create("restat.file", now_, "");
+ TimeStamp restat_mtime = fs_.Tick();
+ fs_.Create("out1.d", "out1: will.be.deleted restat.file\n");
+ fs_.Create("will.be.deleted", "");
+ fs_.Create("restat.file", "");
// Run the build, out1 and out2 get built
string err;
EXPECT_TRUE(builder_.AddTarget("out2", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, commands_ran_.size());
+ ASSERT_EQ(2u, command_runner_.commands_ran_.size());
// See that an entry in the logfile is created, capturing
// the right mtime
@@ -1096,12 +1135,12 @@ TEST_F(BuildWithLogTest, RestatMissingInput) {
fs_.RemoveFile("will.be.deleted");
// Trigger the build again - only out1 gets built
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out2", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
// Check that the logfile entry remains correctly set
log_entry = build_log_.LookupByOutput("out1");
@@ -1127,13 +1166,13 @@ TEST_F(BuildDryRun, AllCommandsShown) {
"build out2: true out1\n"
"build out3: cat out2\n"));
- fs_.Create("out1", now_, "");
- fs_.Create("out2", now_, "");
- fs_.Create("out3", now_, "");
+ fs_.Create("out1", "");
+ fs_.Create("out2", "");
+ fs_.Create("out3", "");
- now_++;
+ fs_.Tick();
- fs_.Create("in", now_, "");
+ fs_.Create("in", "");
// "cc" touches out1, so we should build out2. But because "true" does not
// touch out2, we should cancel the build of out3.
@@ -1141,7 +1180,7 @@ TEST_F(BuildDryRun, AllCommandsShown) {
EXPECT_TRUE(builder_.AddTarget("out3", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(3u, commands_ran_.size());
+ ASSERT_EQ(3u, command_runner_.commands_ran_.size());
}
// Test that RSP files are created when & where appropriate and deleted after
@@ -1158,13 +1197,13 @@ TEST_F(BuildTest, RspFileSuccess)
" rspfile = out2.rsp\n"
" long_command = Some very long command\n"));
- fs_.Create("out1", now_, "");
- fs_.Create("out2", now_, "");
- fs_.Create("out3", now_, "");
+ fs_.Create("out1", "");
+ fs_.Create("out2", "");
+ fs_.Create("out3", "");
- now_++;
+ fs_.Tick();
- fs_.Create("in", now_, "");
+ fs_.Create("in", "");
string err;
EXPECT_TRUE(builder_.AddTarget("out1", &err));
@@ -1176,7 +1215,7 @@ TEST_F(BuildTest, RspFileSuccess)
size_t files_removed = fs_.files_removed_.size();
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(2u, commands_ran_.size()); // cat + cat_rsp
+ ASSERT_EQ(2u, command_runner_.commands_ran_.size()); // cat + cat_rsp
// The RSP file was created
ASSERT_EQ(files_created + 1, fs_.files_created_.size());
@@ -1198,9 +1237,9 @@ TEST_F(BuildTest, RspFileFailure) {
" rspfile = out.rsp\n"
" long_command = Another very long command\n"));
- fs_.Create("out", now_, "");
- now_++;
- fs_.Create("in", now_, "");
+ fs_.Create("out", "");
+ fs_.Tick();
+ fs_.Create("in", "");
string err;
EXPECT_TRUE(builder_.AddTarget("out", &err));
@@ -1211,7 +1250,7 @@ TEST_F(BuildTest, RspFileFailure) {
EXPECT_FALSE(builder_.Build(&err));
ASSERT_EQ("subcommand failed", err);
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
// The RSP file was created
ASSERT_EQ(files_created + 1, fs_.files_created_.size());
@@ -1237,9 +1276,9 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
" rspfile = out.rsp\n"
" long_command = Original very long command\n"));
- fs_.Create("out", now_, "");
- now_++;
- fs_.Create("in", now_, "");
+ fs_.Create("out", "");
+ fs_.Tick();
+ fs_.Create("in", "");
string err;
EXPECT_TRUE(builder_.AddTarget("out", &err));
@@ -1247,10 +1286,10 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
// 1. Build for the 1st time (-> populate log)
EXPECT_TRUE(builder_.Build(&err));
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
// 2. Build again (no change)
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
@@ -1265,12 +1304,12 @@ TEST_F(BuildWithLogTest, RspFileCmdLineChange) {
log_entry->command_hash));
log_entry->command_hash++; // Change the command hash to something else.
// Now expect the target to be rebuilt
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
- EXPECT_EQ(1u, commands_ran_.size());
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
}
TEST_F(BuildTest, InterruptCleanup) {
@@ -1282,11 +1321,11 @@ TEST_F(BuildTest, InterruptCleanup) {
"build out1: interrupt in1\n"
"build out2: touch-interrupt in2\n"));
- fs_.Create("out1", now_, "");
- fs_.Create("out2", now_, "");
- now_++;
- fs_.Create("in1", now_, "");
- fs_.Create("in2", now_, "");
+ fs_.Create("out1", "");
+ fs_.Create("out2", "");
+ fs_.Tick();
+ fs_.Create("in1", "");
+ fs_.Create("in2", "");
// An untouched output of an interrupted command should be retained.
string err;
@@ -1295,7 +1334,7 @@ TEST_F(BuildTest, InterruptCleanup) {
EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("interrupted by user", err);
builder_.Cleanup();
- EXPECT_EQ(now_-1, fs_.Stat("out1"));
+ EXPECT_GT(fs_.Stat("out1"), 0);
err = "";
// A touched output of an interrupted command should be deleted.
@@ -1312,8 +1351,8 @@ TEST_F(BuildTest, PhonyWithNoInputs) {
"build nonexistent: phony\n"
"build out1: cat || nonexistent\n"
"build out2: cat nonexistent\n"));
- fs_.Create("out1", now_, "");
- fs_.Create("out2", now_, "");
+ fs_.Create("out1", "");
+ fs_.Create("out2", "");
// out1 should be up to date even though its input is dirty, because its
// order-only dependency has nothing to do.
@@ -1324,13 +1363,31 @@ TEST_F(BuildTest, PhonyWithNoInputs) {
// out2 should still be out of date though, because its input is dirty.
err.clear();
- commands_ran_.clear();
+ command_runner_.commands_ran_.clear();
state_.Reset();
EXPECT_TRUE(builder_.AddTarget("out2", &err));
ASSERT_EQ("", err);
EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
- ASSERT_EQ(1u, commands_ran_.size());
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+}
+
+TEST_F(BuildTest, DepsGccWithEmptyDepfileErrorsOut) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cc\n"
+" command = cc\n"
+" deps = gcc\n"
+"build out: cc\n"));
+ Dirty("out");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ EXPECT_FALSE(builder_.AlreadyUpToDate());
+
+ EXPECT_FALSE(builder_.Build(&err));
+ ASSERT_EQ("subcommand failed", err);
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
}
TEST_F(BuildTest, StatusFormatReplacePlaceholder) {
@@ -1338,3 +1395,210 @@ TEST_F(BuildTest, StatusFormatReplacePlaceholder) {
status_.FormatProgressStatus("[%%/s%s/t%t/r%r/u%u/f%f]"));
}
+TEST_F(BuildTest, FailedDepsParse) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build bad_deps.o: cat in1\n"
+" deps = gcc\n"
+" depfile = in1.d\n"));
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("bad_deps.o", &err));
+ ASSERT_EQ("", err);
+
+ // These deps will fail to parse, as they should only have one
+ // path to the left of the colon.
+ fs_.Create("in1.d", "AAA BBB");
+
+ EXPECT_FALSE(builder_.Build(&err));
+ EXPECT_EQ("subcommand failed", err);
+}
+
+/// Tests of builds involving deps logs necessarily must span
+/// multiple builds. We reuse methods on BuildTest but not the
+/// builder_ it sets up, because we want pristine objects for
+/// each build.
+struct BuildWithDepsLogTest : public BuildTest {
+ BuildWithDepsLogTest() {}
+
+ virtual void SetUp() {
+ BuildTest::SetUp();
+
+ temp_dir_.CreateAndEnter("BuildWithDepsLogTest");
+ }
+
+ virtual void TearDown() {
+ temp_dir_.Cleanup();
+ }
+
+ ScopedTempDir temp_dir_;
+
+ /// Shadow parent class builder_ so we don't accidentally use it.
+ void* builder_;
+};
+
+/// Run a straightforwad build where the deps log is used.
+TEST_F(BuildWithDepsLogTest, Straightforward) {
+ string err;
+ // Note: in1 was created by the superclass SetUp().
+ const char* manifest =
+ "build out: cat in1\n"
+ " deps = gcc\n"
+ " depfile = in1.d\n";
+ {
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ // Run the build once, everything should be ok.
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+ ASSERT_EQ("", err);
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ fs_.Create("in1.d", "out: in2");
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ // The deps file should have been removed.
+ EXPECT_EQ(0, fs_.Stat("in1.d"));
+ // Recreate it for the next step.
+ fs_.Create("in1.d", "out: in2");
+ deps_log.Close();
+ builder.command_runner_.release();
+ }
+
+ {
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ // Touch the file only mentioned in the deps.
+ fs_.Tick();
+ fs_.Create("in2", "");
+
+ // Run the build again.
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ command_runner_.commands_ran_.clear();
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ // We should have rebuilt the output due to in2 being
+ // out of date.
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+
+ builder.command_runner_.release();
+ }
+}
+
+/// Verify that obsolete dependency info causes a rebuild.
+/// 1) Run a successful build where everything has time t, record deps.
+/// 2) Move input/output to time t+1 -- despite files in alignment,
+/// should still need to rebuild due to deps at older time.
+TEST_F(BuildWithDepsLogTest, ObsoleteDeps) {
+ string err;
+ // Note: in1 was created by the superclass SetUp().
+ const char* manifest =
+ "build out: cat in1\n"
+ " deps = gcc\n"
+ " depfile = in1.d\n";
+ {
+ // Run an ordinary build that gathers dependencies.
+ fs_.Create("in1", "");
+ fs_.Create("in1.d", "out: ");
+
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ // Run the build once, everything should be ok.
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+ ASSERT_EQ("", err);
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ deps_log.Close();
+ builder.command_runner_.release();
+ }
+
+ // Push all files one tick forward so that only the deps are out
+ // of date.
+ fs_.Tick();
+ fs_.Create("in1", "");
+ fs_.Create("out", "");
+
+ // The deps file should have been removed, so no need to timestamp it.
+ EXPECT_EQ(0, fs_.Stat("in1.d"));
+
+ {
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ DepsLog deps_log;
+ ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err));
+ ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err));
+
+ Builder builder(&state, config_, NULL, &deps_log, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ command_runner_.commands_ran_.clear();
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+
+ // Recreate the deps file here because the build expects them to exist.
+ fs_.Create("in1.d", "out: ");
+
+ EXPECT_TRUE(builder.Build(&err));
+ EXPECT_EQ("", err);
+
+ // We should have rebuilt the output due to the deps being
+ // out of date.
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+
+ builder.command_runner_.release();
+ }
+}
+
+TEST_F(BuildWithDepsLogTest, DepsIgnoredInDryRun) {
+ const char* manifest =
+ "build out: cat in1\n"
+ " deps = gcc\n"
+ " depfile = in1.d\n";
+
+ fs_.Create("out", "");
+ fs_.Tick();
+ fs_.Create("in1", "");
+
+ State state;
+ ASSERT_NO_FATAL_FAILURE(AddCatRule(&state));
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest));
+
+ // The deps log is NULL in dry runs.
+ config_.dry_run = true;
+ Builder builder(&state, config_, NULL, NULL, &fs_);
+ builder.command_runner_.reset(&command_runner_);
+ command_runner_.commands_ran_.clear();
+
+ string err;
+ EXPECT_TRUE(builder.AddTarget("out", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder.Build(&err));
+ ASSERT_EQ(1u, command_runner_.commands_ran_.size());
+
+ builder.command_runner_.release();
+}