diff options
Diffstat (limited to 'src/build_test.cc')
-rw-r--r-- | src/build_test.cc | 662 |
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(); +} |