summaryrefslogtreecommitdiffstats
path: root/src/build_test.cc
diff options
context:
space:
mode:
authorEvan Martin <martine@danga.com>2010-12-05 00:09:50 (GMT)
committerEvan Martin <martine@danga.com>2010-12-05 00:09:50 (GMT)
commit42d237e7841679b27cd5dcae82d143d86a87a807 (patch)
tree253b0cf88ecf3b0404146a7c7945dd4c660afa04 /src/build_test.cc
parentd0025e234cf3fc0a13f45d179880578285609b59 (diff)
downloadNinja-42d237e7841679b27cd5dcae82d143d86a87a807.zip
Ninja-42d237e7841679b27cd5dcae82d143d86a87a807.tar.gz
Ninja-42d237e7841679b27cd5dcae82d143d86a87a807.tar.bz2
move src into subdir
Diffstat (limited to 'src/build_test.cc')
-rw-r--r--src/build_test.cc488
1 files changed, 488 insertions, 0 deletions
diff --git a/src/build_test.cc b/src/build_test.cc
new file mode 100644
index 0000000..2ded588
--- /dev/null
+++ b/src/build_test.cc
@@ -0,0 +1,488 @@
+#include "build.h"
+
+#include "test.h"
+
+// Though Plan doesn't use State, it's useful to have one around
+// to create Nodes and Edges.
+struct PlanTest : public StateTestWithBuiltinRules {
+ Plan plan_;
+};
+
+TEST_F(PlanTest, Basic) {
+ AssertParse(&state_,
+"build out: cat mid\n"
+"build mid: cat in\n");
+ GetNode("in")->MarkDependentsDirty();
+ string err;
+ EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+ ASSERT_TRUE(plan_.more_to_do());
+
+ Edge* edge = plan_.FindWork();
+ ASSERT_TRUE(edge);
+ ASSERT_EQ("in", edge->inputs_[0]->file_->path_);
+ ASSERT_EQ("mid", edge->outputs_[0]->file_->path_);
+
+ ASSERT_FALSE(plan_.FindWork());
+
+ GetNode("mid")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge);
+ ASSERT_EQ("mid", edge->inputs_[0]->file_->path_);
+ ASSERT_EQ("out", edge->outputs_[0]->file_->path_);
+
+ GetNode("out")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ ASSERT_FALSE(plan_.more_to_do());
+ edge = plan_.FindWork();
+ ASSERT_EQ(NULL, edge);
+}
+
+// Test that two outputs from one rule can be handled as inputs to the next.
+TEST_F(PlanTest, DoubleOutputDirect) {
+ AssertParse(&state_,
+"build out: cat mid1 mid2\n"
+"build mid1 mid2: cat in\n");
+ GetNode("in")->MarkDependentsDirty();
+ string err;
+ EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+ ASSERT_TRUE(plan_.more_to_do());
+
+ Edge* edge;
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat in
+ GetNode("mid1")->dirty_ = false;
+ GetNode("mid2")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat mid1 mid2
+ GetNode("in")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_FALSE(edge); // done
+}
+
+// Test that two outputs from one rule can eventually be routed to another.
+TEST_F(PlanTest, DoubleOutputIndirect) {
+ AssertParse(&state_,
+"build out: cat b1 b2\n"
+"build b1: cat a1\n"
+"build b2: cat a2\n"
+"build a1 a2: cat in\n");
+ GetNode("in")->MarkDependentsDirty();
+ string err;
+ EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+ ASSERT_TRUE(plan_.more_to_do());
+
+ Edge* edge;
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat in
+ GetNode("a1")->dirty_ = false;
+ GetNode("a2")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat a1
+ GetNode("b1")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat a2
+ GetNode("b2")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat b1 b2
+ GetNode("out")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_FALSE(edge); // done
+}
+
+// Test that two edges from one output can both execute.
+TEST_F(PlanTest, DoubleDependent) {
+ AssertParse(&state_,
+"build out: cat a1 a2\n"
+"build a1: cat mid\n"
+"build a2: cat mid\n"
+"build mid: cat in\n");
+ GetNode("in")->MarkDependentsDirty();
+ string err;
+ EXPECT_TRUE(plan_.AddTarget(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+ ASSERT_TRUE(plan_.more_to_do());
+
+ Edge* edge;
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat in
+ GetNode("mid")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat mid
+ GetNode("a1")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat mid
+ GetNode("a2")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_TRUE(edge); // cat a1 a2
+ GetNode("out")->dirty_ = false;
+ plan_.EdgeFinished(edge);
+
+ edge = plan_.FindWork();
+ ASSERT_FALSE(edge); // done
+}
+
+TEST_F(PlanTest, DependencyCycle) {
+ AssertParse(&state_,
+"build out: cat mid\n"
+"build mid: cat in\n"
+"build in: cat pre\n"
+"build pre: cat out\n");
+ GetNode("pre")->MarkDependentsDirty();
+ string err;
+ EXPECT_FALSE(plan_.AddTarget(GetNode("out"), &err));
+ ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err);
+}
+
+struct BuildTest : public StateTestWithBuiltinRules,
+ public CommandRunner,
+ public DiskInterface {
+ BuildTest() : builder_(&state_), now_(1), last_command_(NULL) {
+ builder_.disk_interface_ = this;
+ builder_.command_runner_ = this;
+ AssertParse(&state_,
+"build cat1: cat in1\n"
+"build cat2: cat in1 in2\n"
+"build cat12: cat cat1 cat2\n");
+ }
+
+ // Mark a path dirty.
+ void Dirty(const string& path);
+ // Mark dependents of a path dirty.
+ void Touch(const string& path);
+
+ // CommandRunner impl
+ virtual bool CanRunMore();
+ virtual bool StartCommand(Edge* edge);
+ virtual void WaitForCommands();
+ virtual Edge* NextFinishedCommand(bool* success);
+
+ // DiskInterface
+ virtual int Stat(const string& path) {
+ return now_;
+ }
+ virtual bool MakeDir(const string& path) {
+ directories_made_.push_back(path);
+ return true; // success
+ }
+ virtual string ReadFile(const string& path, string* err) {
+ files_read_.push_back(path);
+ map<string, string>::iterator i = file_contents_.find(path);
+ if (i != file_contents_.end())
+ return i->second;
+ return "";
+ }
+
+ Builder builder_;
+ int now_;
+ map<string, string> file_contents_;
+ vector<string> commands_ran_;
+ vector<string> directories_made_;
+ vector<string> files_read_;
+ Edge* last_command_;
+};
+
+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->file_->mtime_ = 0;
+}
+
+void BuildTest::Touch(const string& path) {
+ Node* node = GetNode(path);
+ assert(node);
+ node->MarkDependentsDirty();
+}
+
+bool BuildTest::CanRunMore() {
+ // Only run one at a time.
+ return last_command_ == NULL;
+}
+
+bool BuildTest::StartCommand(Edge* edge) {
+ assert(!last_command_);
+ commands_ran_.push_back(edge->EvaluateCommand());
+ if (edge->rule_->name_ == "cat" || edge->rule_->name_ == "cc") {
+ for (vector<Node*>::iterator out = edge->outputs_.begin();
+ out != edge->outputs_.end(); ++out) {
+ (*out)->file_->Touch(now_);
+ }
+ last_command_ = edge;
+ return true;
+ } else {
+ printf("unkown command\n");
+ }
+
+ return false;
+}
+
+void BuildTest::WaitForCommands() {
+}
+
+Edge* BuildTest::NextFinishedCommand(bool* success) {
+ if (Edge* edge = last_command_) {
+ *success = true;
+ last_command_ = NULL;
+ return edge;
+ }
+ return NULL;
+}
+
+TEST_F(BuildTest, NoWork) {
+ string err;
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("no work to do", err);
+ EXPECT_EQ(0, commands_ran_.size());
+}
+
+TEST_F(BuildTest, OneStep) {
+ // Given a dirty target with one ready input,
+ // we should rebuild the target.
+ Dirty("cat1");
+ string err;
+ ASSERT_TRUE(builder_.AddTarget("cat1", &err));
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1, commands_ran_.size());
+ EXPECT_EQ("cat in1 > cat1", commands_ran_[0]);
+}
+
+TEST_F(BuildTest, OneStep2) {
+ // Given a target with one dirty input,
+ // we should rebuild the target.
+ Dirty("cat1");
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat1", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ ASSERT_EQ(1, commands_ran_.size());
+ EXPECT_EQ("cat in1 > cat1", commands_ran_[0]);
+}
+
+TEST_F(BuildTest, TwoStep) {
+ // Modifying in1 requires rebuilding both intermediate files
+ // and the final file.
+ Touch("in1");
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("cat12", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(3, commands_ran_.size());
+ EXPECT_EQ("cat in1 > cat1", commands_ran_[0]);
+ EXPECT_EQ("cat in1 in2 > cat2", commands_ran_[1]);
+ EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[2]);
+
+ // Modifying in2 requires rebuilding one intermediate file
+ // and the final file.
+ Touch("in2");
+ ASSERT_TRUE(builder_.AddTarget("cat12", &err));
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(5, commands_ran_.size());
+ EXPECT_EQ("cat in1 in2 > cat2", commands_ran_[3]);
+ EXPECT_EQ("cat cat1 cat2 > cat12", commands_ran_[4]);
+}
+
+TEST_F(BuildTest, Chain) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build c2: cat c1\n"
+"build c3: cat c2\n"
+"build c4: cat c3\n"
+"build c5: cat c4\n"));
+
+ Touch("c1"); // Will recursively dirty all the way to c5.
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("c5", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+ ASSERT_EQ(4, commands_ran_.size());
+
+ err.clear();
+ commands_ran_.clear();
+ EXPECT_FALSE(builder_.AddTarget("c5", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ(0, commands_ran_.size());
+
+ Touch("c3"); // Will recursively dirty through to c5.
+ err.clear();
+ commands_ran_.clear();
+ EXPECT_TRUE(builder_.AddTarget("c5", &err));
+ ASSERT_EQ("", err);
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ(2, commands_ran_.size()); // 3->4, 4->5
+}
+
+TEST_F(BuildTest, MissingInput) {
+ // Input is referenced by build file, but no rule for it.
+ string err;
+ Dirty("in1");
+ EXPECT_FALSE(builder_.AddTarget("cat1", &err));
+ EXPECT_EQ("'in1', needed by 'cat1', missing and no known rule to make it",
+ err);
+}
+
+TEST_F(BuildTest, MissingTarget) {
+ // Target is not referenced by build file.
+ string err;
+ EXPECT_FALSE(builder_.AddTarget("meow", &err));
+ EXPECT_EQ("unknown target: 'meow'", err);
+}
+
+TEST_F(BuildTest, MakeDirs) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build subdir/dir2/file: cat in1\n"));
+
+ Touch("in1");
+ EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err));
+ EXPECT_EQ("", err);
+ now_ = 0; // Make all stat()s return file not found.
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(2, directories_made_.size());
+ EXPECT_EQ("subdir", directories_made_[0]);
+ EXPECT_EQ("subdir/dir2", directories_made_[1]);
+}
+
+TEST_F(BuildTest, DepFileMissing) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cc\n command = cc $in\n depfile = $out.d\n"
+"build foo.o: cc foo.c\n"));
+ Touch("foo.c");
+ EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1, files_read_.size());
+ EXPECT_EQ("foo.o.d", files_read_[0]);
+}
+
+TEST_F(BuildTest, DepFileOK) {
+ string err;
+ int orig_edges = state_.edges_.size();
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cc\n command = cc $in\n depfile = $out.d\n"
+"build foo.o: cc foo.c\n"));
+ Touch("foo.c");
+ Dirty("bar.h"); // Mark bar.h as missing.
+ file_contents_["foo.o.d"] = "foo.o: blah.h bar.h\n";
+ EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1, files_read_.size());
+ EXPECT_EQ("foo.o.d", files_read_[0]);
+
+ // Expect our edge to now have three inputs: foo.c and two headers.
+ ASSERT_EQ(orig_edges + 1, state_.edges_.size());
+ Edge* edge = state_.edges_.back();
+ ASSERT_EQ(3, edge->inputs_.size());
+
+ // Expect the command line we generate to only use the original input.
+ ASSERT_EQ("cc foo.c", edge->EvaluateCommand());
+}
+
+TEST_F(BuildTest, DepFileParseError) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cc\n command = cc $in\n depfile = $out.d\n"
+"build foo.o: cc foo.c\n"));
+ Touch("foo.c");
+ file_contents_["foo.o.d"] = "foo.o blah.h bar.h\n";
+ EXPECT_FALSE(builder_.AddTarget("foo.o", &err));
+ EXPECT_EQ("line 1, col 7: expected ':', got 'blah.h'", err);
+}
+
+TEST_F(BuildTest, OrderOnlyDeps) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule cc\n command = cc $in\n depfile = $out.d\n"
+"build foo.o: cc foo.c | otherfile\n"));
+ Touch("foo.c");
+ file_contents_["foo.o.d"] = "foo.o: blah.h bar.h\n";
+ EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
+
+ Edge* edge = state_.edges_.back();
+ // One explicit, two implicit, one order only.
+ ASSERT_EQ(4, edge->inputs_.size());
+ EXPECT_EQ(2, edge->implicit_deps_);
+ EXPECT_EQ(1, edge->order_only_deps_);
+ // Verify the inputs are in the order we expect
+ // (explicit then implicit then orderonly).
+ EXPECT_EQ("foo.c", edge->inputs_[0]->file_->path_);
+ EXPECT_EQ("blah.h", edge->inputs_[1]->file_->path_);
+ EXPECT_EQ("bar.h", edge->inputs_[2]->file_->path_);
+ EXPECT_EQ("otherfile", edge->inputs_[3]->file_->path_);
+
+ // Expect the command line we generate to only use the original input.
+ ASSERT_EQ("cc foo.c", edge->EvaluateCommand());
+
+ // explicit dep dirty, expect a rebuild.
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1, commands_ran_.size());
+
+ // implicit dep dirty, expect a rebuild.
+ commands_ran_.clear();
+ Touch("blah.h");
+ EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1, commands_ran_.size());
+
+ // order only dep dirty, no rebuild.
+ commands_ran_.clear();
+ Touch("otherfile");
+ // We should fail to even add the depenency on foo.o, because
+ // there's nothing to do.
+ EXPECT_FALSE(builder_.AddTarget("foo.o", &err));
+}
+
+TEST_F(BuildTest, Phony) {
+ string err;
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat bar.cc\n"
+"build all: phony out\n"));
+ Touch("bar.cc");
+
+ EXPECT_TRUE(builder_.AddTarget("all", &err));
+
+ // Only one command to run, because phony runs no command.
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_EQ("", err);
+ ASSERT_EQ(1, commands_ran_.size());
+
+ EXPECT_TRUE(builder_.Build(&err));
+ ASSERT_NE("", err);
+}
+