Add symlink tests
Track how symlinks work in the build graph with "stat" today. These will
be updated in the next change that switches to lstat.
Test: run ninja_tests (the build-tools build does this)
Change-Id: Ic1674bef202c198b6c707fc8a36edc2d89c27ee7
diff --git a/src/graph_test.cc b/src/graph_test.cc
index 78364f3..5dbe3ea 100644
--- a/src/graph_test.cc
+++ b/src/graph_test.cc
@@ -335,6 +335,90 @@
ASSERT_EQ("dependency cycle: a -> a [-w phonycycle=err]", err);
}
+TEST_F(GraphTest, OutputSymlinkSourceUpdate) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build other: cat\n"
+"build sym: cat\n"
+"build out: cat | sym\n"));
+
+ fs_.Create("out", "");
+ fs_.CreateSymlink("sym", "other");
+ fs_.Tick();
+ fs_.Create("other", "");
+
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+}
+
+TEST_F(GraphTest, OutputSymlinkDangling) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build sym: cat\n"
+"build out: cat | sym\n"));
+
+ fs_.CreateSymlink("sym", "dangling");
+ fs_.Create("out", "");
+
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+}
+
+TEST_F(GraphTest, InputSymlink) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat sym\n"));
+
+ fs_.Create("out", "");
+ fs_.CreateSymlink("sym", "in");
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+}
+
+TEST_F(GraphTest, InputSymlinkUpdate) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat sym\n"));
+
+ fs_.Create("out", "");
+ fs_.Create("in", "");
+ fs_.Tick();
+ fs_.CreateSymlink("sym", "in");
+
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+
+ // This can be incorrect if the destination of the symlink changed to
+ // a file with an equal or older timestamp.
+ EXPECT_FALSE(GetNode("out")->dirty());
+}
+
+TEST_F(GraphTest, InputSymlinkDangling) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build out: cat sym\n"));
+
+ fs_.Create("out", "");
+ fs_.CreateSymlink("sym", "in");
+
+ string err;
+ EXPECT_TRUE(scan_.RecomputeDirty(GetNode("out"), &err));
+ ASSERT_EQ("", err);
+
+ EXPECT_TRUE(GetNode("out")->dirty());
+}
+
+
+// TODO: tests around directory timestamps
+
TEST_F(GraphTest, DependencyCycle) {
AssertParse(&state_,
"build out: cat mid\n"
diff --git a/src/test.cc b/src/test.cc
index 5b0ab30..415b598 100644
--- a/src/test.cc
+++ b/src/test.cc
@@ -175,9 +175,20 @@
files_created_.insert(path);
}
+void VirtualFileSystem::CreateSymlink(const string& path,
+ const string& dest) {
+ files_[path].mtime = now_;
+ files_[path].contents = dest;
+ files_[path].is_symlink = true;
+ files_created_.insert(path);
+}
+
TimeStamp VirtualFileSystem::Stat(const string& path, string* err) const {
FileMap::const_iterator i = files_.find(path);
if (i != files_.end()) {
+ if (i->second.is_symlink) {
+ return Stat(i->second.contents, err);
+ }
*err = i->second.stat_error;
return i->second.mtime;
}
@@ -216,6 +227,9 @@
files_read_.push_back(path);
FileMap::iterator i = files_.find(path);
if (i != files_.end()) {
+ if (i->second.is_symlink) {
+ return LoadFile(i->second.contents, result, err);
+ }
std::string& contents = i->second.contents;
*result = std::unique_ptr<HeapLoadedFile>(new HeapLoadedFile(path,
contents));
diff --git a/src/test.h b/src/test.h
index affab62..e97f4c4 100644
--- a/src/test.h
+++ b/src/test.h
@@ -137,6 +137,9 @@
/// "Create" a file with contents.
void Create(const string& path, const string& contents);
+ /// "Create" a symlink pointing to another path.
+ void CreateSymlink(const string& path, const string& dest);
+
/// Tick "time" forwards; subsequent file operations will be newer than
/// previous ones.
int Tick() {
@@ -159,6 +162,7 @@
int mtime;
string stat_error; // If mtime is -1.
string contents;
+ bool is_symlink = false;
};
vector<string> directories_made_;