| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #ifdef _WIN32 |
| #include <io.h> |
| #include <windows.h> |
| #endif |
| |
| #include "disk_interface.h" |
| #include "graph.h" |
| #include "test.h" |
| |
| using namespace std; |
| |
| namespace { |
| |
| struct DiskInterfaceTest : public testing::Test { |
| virtual void SetUp() { |
| // These tests do real disk accesses, so create a temp dir. |
| temp_dir_.CreateAndEnter("Ninja-DiskInterfaceTest"); |
| } |
| |
| virtual void TearDown() { |
| temp_dir_.Cleanup(); |
| } |
| |
| bool Touch(const char* path) { |
| FILE *f = fopen(path, "w"); |
| if (!f) |
| return false; |
| return fclose(f) == 0; |
| } |
| |
| ScopedTempDir temp_dir_; |
| RealDiskInterface disk_; |
| }; |
| |
| TEST_F(DiskInterfaceTest, StatMissingFile) { |
| string err; |
| EXPECT_EQ(0, disk_.Stat("nosuchfile", &err)); |
| EXPECT_EQ("", err); |
| |
| // On Windows, the errno for a file in a nonexistent directory |
| // is different. |
| EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err)); |
| EXPECT_EQ("", err); |
| |
| // On POSIX systems, the errno is different if a component of the |
| // path prefix is not a directory. |
| ASSERT_TRUE(Touch("notadir")); |
| EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile", &err)); |
| EXPECT_EQ("", err); |
| } |
| |
| TEST_F(DiskInterfaceTest, StatBadPath) { |
| string err; |
| #ifdef _WIN32 |
| string bad_path("cc:\\foo"); |
| EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); |
| EXPECT_NE("", err); |
| #else |
| string too_long_name(512, 'x'); |
| EXPECT_EQ(-1, disk_.Stat(too_long_name, &err)); |
| EXPECT_NE("", err); |
| #endif |
| } |
| |
| TEST_F(DiskInterfaceTest, StatExistingFile) { |
| string err; |
| ASSERT_TRUE(Touch("file")); |
| EXPECT_GT(disk_.Stat("file", &err), 1); |
| EXPECT_EQ("", err); |
| } |
| |
| TEST_F(DiskInterfaceTest, StatExistingDir) { |
| string err; |
| ASSERT_TRUE(disk_.MakeDir("subdir")); |
| ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir")); |
| EXPECT_GT(disk_.Stat("..", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat(".", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat("subdir", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1); |
| EXPECT_EQ("", err); |
| |
| EXPECT_EQ(disk_.Stat("subdir", &err), |
| disk_.Stat("subdir/.", &err)); |
| EXPECT_EQ(disk_.Stat("subdir", &err), |
| disk_.Stat("subdir/subsubdir/..", &err)); |
| EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err), |
| disk_.Stat("subdir/subsubdir/.", &err)); |
| } |
| |
| #ifdef _WIN32 |
| TEST_F(DiskInterfaceTest, StatCache) { |
| string err; |
| |
| ASSERT_TRUE(Touch("file1")); |
| ASSERT_TRUE(Touch("fiLE2")); |
| ASSERT_TRUE(disk_.MakeDir("subdir")); |
| ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir")); |
| ASSERT_TRUE(Touch("subdir\\subfile1")); |
| ASSERT_TRUE(Touch("subdir\\SUBFILE2")); |
| ASSERT_TRUE(Touch("subdir\\SUBFILE3")); |
| |
| disk_.AllowStatCache(false); |
| TimeStamp parent_stat_uncached = disk_.Stat("..", &err); |
| disk_.AllowStatCache(true); |
| |
| EXPECT_GT(disk_.Stat("FIle1", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat("file1", &err), 1); |
| EXPECT_EQ("", err); |
| |
| EXPECT_GT(disk_.Stat("subdir/subfile2", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat("sUbdir\\suBFile1", &err), 1); |
| EXPECT_EQ("", err); |
| |
| EXPECT_GT(disk_.Stat("..", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat(".", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat("subdir", &err), 1); |
| EXPECT_EQ("", err); |
| EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1); |
| EXPECT_EQ("", err); |
| |
| #ifndef _MSC_VER // TODO: Investigate why. Also see https://github.com/ninja-build/ninja/pull/1423 |
| EXPECT_EQ(disk_.Stat("subdir", &err), |
| disk_.Stat("subdir/.", &err)); |
| EXPECT_EQ("", err); |
| EXPECT_EQ(disk_.Stat("subdir", &err), |
| disk_.Stat("subdir/subsubdir/..", &err)); |
| #endif |
| EXPECT_EQ("", err); |
| EXPECT_EQ(disk_.Stat("..", &err), parent_stat_uncached); |
| EXPECT_EQ("", err); |
| EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err), |
| disk_.Stat("subdir/subsubdir/.", &err)); |
| EXPECT_EQ("", err); |
| |
| // Test error cases. |
| string bad_path("cc:\\foo"); |
| EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); |
| EXPECT_NE("", err); err.clear(); |
| EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); |
| EXPECT_NE("", err); err.clear(); |
| EXPECT_EQ(0, disk_.Stat("nosuchfile", &err)); |
| EXPECT_EQ("", err); |
| EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err)); |
| EXPECT_EQ("", err); |
| } |
| #endif |
| |
| TEST_F(DiskInterfaceTest, ReadFile) { |
| string err; |
| std::string content; |
| ASSERT_EQ(DiskInterface::NotFound, |
| disk_.ReadFile("foobar", &content, &err)); |
| EXPECT_EQ("", content); |
| EXPECT_NE("", err); // actual value is platform-specific |
| err.clear(); |
| |
| const char* kTestFile = "testfile"; |
| FILE* f = fopen(kTestFile, "wb"); |
| ASSERT_TRUE(f); |
| const char* kTestContent = "test content\nok"; |
| fprintf(f, "%s", kTestContent); |
| ASSERT_EQ(0, fclose(f)); |
| |
| ASSERT_EQ(DiskInterface::Okay, |
| disk_.ReadFile(kTestFile, &content, &err)); |
| EXPECT_EQ(kTestContent, content); |
| EXPECT_EQ("", err); |
| } |
| |
| TEST_F(DiskInterfaceTest, MakeDirs) { |
| string path = "path/with/double//slash/"; |
| EXPECT_TRUE(disk_.MakeDirs(path)); |
| FILE* f = fopen((path + "a_file").c_str(), "w"); |
| EXPECT_TRUE(f); |
| EXPECT_EQ(0, fclose(f)); |
| #ifdef _WIN32 |
| string path2 = "another\\with\\back\\\\slashes\\"; |
| EXPECT_TRUE(disk_.MakeDirs(path2.c_str())); |
| FILE* f2 = fopen((path2 + "a_file").c_str(), "w"); |
| EXPECT_TRUE(f2); |
| EXPECT_EQ(0, fclose(f2)); |
| #endif |
| } |
| |
| TEST_F(DiskInterfaceTest, RemoveFile) { |
| const char* kFileName = "file-to-remove"; |
| ASSERT_TRUE(Touch(kFileName)); |
| EXPECT_EQ(0, disk_.RemoveFile(kFileName)); |
| EXPECT_EQ(1, disk_.RemoveFile(kFileName)); |
| EXPECT_EQ(1, disk_.RemoveFile("does not exist")); |
| #ifdef _WIN32 |
| ASSERT_TRUE(Touch(kFileName)); |
| EXPECT_EQ(0, system((std::string("attrib +R ") + kFileName).c_str())); |
| EXPECT_EQ(0, disk_.RemoveFile(kFileName)); |
| EXPECT_EQ(1, disk_.RemoveFile(kFileName)); |
| #endif |
| } |
| |
| TEST_F(DiskInterfaceTest, RemoveDirectory) { |
| const char* kDirectoryName = "directory-to-remove"; |
| EXPECT_TRUE(disk_.MakeDir(kDirectoryName)); |
| EXPECT_EQ(0, disk_.RemoveFile(kDirectoryName)); |
| EXPECT_EQ(1, disk_.RemoveFile(kDirectoryName)); |
| EXPECT_EQ(1, disk_.RemoveFile("does not exist")); |
| } |
| |
| struct StatTest : public StateTestWithBuiltinRules, |
| public DiskInterface { |
| StatTest() : scan_(&state_, NULL, NULL, this, NULL) {} |
| |
| // DiskInterface implementation. |
| virtual TimeStamp Stat(const string& path, string* err) const; |
| virtual bool WriteFile(const string& path, const string& contents) { |
| assert(false); |
| return true; |
| } |
| virtual bool MakeDir(const string& path) { |
| assert(false); |
| return false; |
| } |
| virtual Status ReadFile(const string& path, string* contents, string* err) { |
| assert(false); |
| return NotFound; |
| } |
| virtual int RemoveFile(const string& path) { |
| assert(false); |
| return 0; |
| } |
| |
| DependencyScan scan_; |
| map<string, TimeStamp> mtimes_; |
| mutable vector<string> stats_; |
| }; |
| |
| TimeStamp StatTest::Stat(const string& path, string* err) const { |
| stats_.push_back(path); |
| map<string, TimeStamp>::const_iterator i = mtimes_.find(path); |
| if (i == mtimes_.end()) |
| return 0; // File not found. |
| return i->second; |
| } |
| |
| TEST_F(StatTest, Simple) { |
| ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, |
| "build out: cat in\n")); |
| |
| Node* out = GetNode("out"); |
| string err; |
| EXPECT_TRUE(out->Stat(this, &err)); |
| EXPECT_EQ("", err); |
| ASSERT_EQ(1u, stats_.size()); |
| scan_.RecomputeDirty(out, NULL, NULL); |
| ASSERT_EQ(2u, stats_.size()); |
| ASSERT_EQ("out", stats_[0]); |
| ASSERT_EQ("in", stats_[1]); |
| } |
| |
| TEST_F(StatTest, TwoStep) { |
| ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, |
| "build out: cat mid\n" |
| "build mid: cat in\n")); |
| |
| Node* out = GetNode("out"); |
| string err; |
| EXPECT_TRUE(out->Stat(this, &err)); |
| EXPECT_EQ("", err); |
| ASSERT_EQ(1u, stats_.size()); |
| scan_.RecomputeDirty(out, NULL, NULL); |
| ASSERT_EQ(3u, stats_.size()); |
| ASSERT_EQ("out", stats_[0]); |
| ASSERT_TRUE(GetNode("out")->dirty()); |
| ASSERT_EQ("mid", stats_[1]); |
| ASSERT_TRUE(GetNode("mid")->dirty()); |
| ASSERT_EQ("in", stats_[2]); |
| } |
| |
| TEST_F(StatTest, Tree) { |
| ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, |
| "build out: cat mid1 mid2\n" |
| "build mid1: cat in11 in12\n" |
| "build mid2: cat in21 in22\n")); |
| |
| Node* out = GetNode("out"); |
| string err; |
| EXPECT_TRUE(out->Stat(this, &err)); |
| EXPECT_EQ("", err); |
| ASSERT_EQ(1u, stats_.size()); |
| scan_.RecomputeDirty(out, NULL, NULL); |
| ASSERT_EQ(1u + 6u, stats_.size()); |
| ASSERT_EQ("mid1", stats_[1]); |
| ASSERT_TRUE(GetNode("mid1")->dirty()); |
| ASSERT_EQ("in11", stats_[2]); |
| } |
| |
| TEST_F(StatTest, Middle) { |
| ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, |
| "build out: cat mid\n" |
| "build mid: cat in\n")); |
| |
| mtimes_["in"] = 1; |
| mtimes_["mid"] = 0; // missing |
| mtimes_["out"] = 1; |
| |
| Node* out = GetNode("out"); |
| string err; |
| EXPECT_TRUE(out->Stat(this, &err)); |
| EXPECT_EQ("", err); |
| ASSERT_EQ(1u, stats_.size()); |
| scan_.RecomputeDirty(out, NULL, NULL); |
| ASSERT_FALSE(GetNode("in")->dirty()); |
| ASSERT_TRUE(GetNode("mid")->dirty()); |
| ASSERT_TRUE(GetNode("out")->dirty()); |
| } |
| |
| } // namespace |