1// Copyright 2011 Google Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15#include <assert.h> 16#include <stdio.h> 17#ifdef _WIN32 18#include <io.h> 19#include <windows.h> 20#include <direct.h> 21#endif 22 23#include "disk_interface.h" 24#include "graph.h" 25#include "test.h" 26 27using namespace std; 28 29namespace { 30 31struct DiskInterfaceTest : public testing::Test { 32 virtual void SetUp() { 33 // These tests do real disk accesses, so create a temp dir. 34 temp_dir_.CreateAndEnter("Ninja-DiskInterfaceTest"); 35 } 36 37 virtual void TearDown() { 38 temp_dir_.Cleanup(); 39 } 40 41 bool Touch(const char* path) { 42 FILE *f = fopen(path, "w"); 43 if (!f) 44 return false; 45 return fclose(f) == 0; 46 } 47 48 ScopedTempDir temp_dir_; 49 RealDiskInterface disk_; 50}; 51 52TEST_F(DiskInterfaceTest, StatMissingFile) { 53 string err; 54 EXPECT_EQ(0, disk_.Stat("nosuchfile", &err)); 55 EXPECT_EQ("", err); 56 57 // On Windows, the errno for a file in a nonexistent directory 58 // is different. 59 EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err)); 60 EXPECT_EQ("", err); 61 62 // On POSIX systems, the errno is different if a component of the 63 // path prefix is not a directory. 64 ASSERT_TRUE(Touch("notadir")); 65 EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile", &err)); 66 EXPECT_EQ("", err); 67} 68 69TEST_F(DiskInterfaceTest, StatMissingFileWithCache) { 70 disk_.AllowStatCache(true); 71 string err; 72 73 // On Windows, the errno for FindFirstFileExA, which is used when the stat 74 // cache is enabled, is different when the directory name is not a directory. 75 ASSERT_TRUE(Touch("notadir")); 76 EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile", &err)); 77 EXPECT_EQ("", err); 78} 79 80TEST_F(DiskInterfaceTest, StatBadPath) { 81 string err; 82#ifdef _WIN32 83 string bad_path("cc:\\foo"); 84 EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); 85 EXPECT_NE("", err); 86#else 87 string too_long_name(512, 'x'); 88 EXPECT_EQ(-1, disk_.Stat(too_long_name, &err)); 89 EXPECT_NE("", err); 90#endif 91} 92 93TEST_F(DiskInterfaceTest, StatExistingFile) { 94 string err; 95 ASSERT_TRUE(Touch("file")); 96 EXPECT_GT(disk_.Stat("file", &err), 1); 97 EXPECT_EQ("", err); 98} 99 100#ifdef _WIN32 101TEST_F(DiskInterfaceTest, StatExistingFileWithLongPath) { 102 string err; 103 char currentdir[32767]; 104 _getcwd(currentdir, sizeof(currentdir)); 105 const string filename = string(currentdir) + 106"\\filename_with_256_characters_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\ 107xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\ 108xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\ 109xxxxxxxxxxxxxxxxxxxxx"; 110 const string prefixed = "\\\\?\\" + filename; 111 ASSERT_TRUE(Touch(prefixed.c_str())); 112 EXPECT_GT(disk_.Stat(disk_.AreLongPathsEnabled() ? 113 filename : prefixed, &err), 1); 114 EXPECT_EQ("", err); 115} 116#endif 117 118TEST_F(DiskInterfaceTest, StatExistingDir) { 119 string err; 120 ASSERT_TRUE(disk_.MakeDir("subdir")); 121 ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir")); 122 EXPECT_GT(disk_.Stat("..", &err), 1); 123 EXPECT_EQ("", err); 124 EXPECT_GT(disk_.Stat(".", &err), 1); 125 EXPECT_EQ("", err); 126 EXPECT_GT(disk_.Stat("subdir", &err), 1); 127 EXPECT_EQ("", err); 128 EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1); 129 EXPECT_EQ("", err); 130 131 EXPECT_EQ(disk_.Stat("subdir", &err), 132 disk_.Stat("subdir/.", &err)); 133 EXPECT_EQ(disk_.Stat("subdir", &err), 134 disk_.Stat("subdir/subsubdir/..", &err)); 135 EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err), 136 disk_.Stat("subdir/subsubdir/.", &err)); 137} 138 139#ifdef _WIN32 140TEST_F(DiskInterfaceTest, StatCache) { 141 string err; 142 143 ASSERT_TRUE(Touch("file1")); 144 ASSERT_TRUE(Touch("fiLE2")); 145 ASSERT_TRUE(disk_.MakeDir("subdir")); 146 ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir")); 147 ASSERT_TRUE(Touch("subdir\\subfile1")); 148 ASSERT_TRUE(Touch("subdir\\SUBFILE2")); 149 ASSERT_TRUE(Touch("subdir\\SUBFILE3")); 150 151 disk_.AllowStatCache(false); 152 TimeStamp parent_stat_uncached = disk_.Stat("..", &err); 153 disk_.AllowStatCache(true); 154 155 EXPECT_GT(disk_.Stat("FIle1", &err), 1); 156 EXPECT_EQ("", err); 157 EXPECT_GT(disk_.Stat("file1", &err), 1); 158 EXPECT_EQ("", err); 159 160 EXPECT_GT(disk_.Stat("subdir/subfile2", &err), 1); 161 EXPECT_EQ("", err); 162 EXPECT_GT(disk_.Stat("sUbdir\\suBFile1", &err), 1); 163 EXPECT_EQ("", err); 164 165 EXPECT_GT(disk_.Stat("..", &err), 1); 166 EXPECT_EQ("", err); 167 EXPECT_GT(disk_.Stat(".", &err), 1); 168 EXPECT_EQ("", err); 169 EXPECT_GT(disk_.Stat("subdir", &err), 1); 170 EXPECT_EQ("", err); 171 EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1); 172 EXPECT_EQ("", err); 173 174#ifndef _MSC_VER // TODO: Investigate why. Also see https://github.com/ninja-build/ninja/pull/1423 175 EXPECT_EQ(disk_.Stat("subdir", &err), 176 disk_.Stat("subdir/.", &err)); 177 EXPECT_EQ("", err); 178 EXPECT_EQ(disk_.Stat("subdir", &err), 179 disk_.Stat("subdir/subsubdir/..", &err)); 180#endif 181 EXPECT_EQ("", err); 182 EXPECT_EQ(disk_.Stat("..", &err), parent_stat_uncached); 183 EXPECT_EQ("", err); 184 EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err), 185 disk_.Stat("subdir/subsubdir/.", &err)); 186 EXPECT_EQ("", err); 187 188 // Test error cases. 189 string bad_path("cc:\\foo"); 190 EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); 191 EXPECT_NE("", err); err.clear(); 192 EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); 193 EXPECT_NE("", err); err.clear(); 194 EXPECT_EQ(0, disk_.Stat("nosuchfile", &err)); 195 EXPECT_EQ("", err); 196 EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err)); 197 EXPECT_EQ("", err); 198} 199#endif 200 201TEST_F(DiskInterfaceTest, ReadFile) { 202 string err; 203 std::string content; 204 ASSERT_EQ(DiskInterface::NotFound, 205 disk_.ReadFile("foobar", &content, &err)); 206 EXPECT_EQ("", content); 207 EXPECT_NE("", err); // actual value is platform-specific 208 err.clear(); 209 210 const char* kTestFile = "testfile"; 211 FILE* f = fopen(kTestFile, "wb"); 212 ASSERT_TRUE(f); 213 const char* kTestContent = "test content\nok"; 214 fprintf(f, "%s", kTestContent); 215 ASSERT_EQ(0, fclose(f)); 216 217 ASSERT_EQ(DiskInterface::Okay, 218 disk_.ReadFile(kTestFile, &content, &err)); 219 EXPECT_EQ(kTestContent, content); 220 EXPECT_EQ("", err); 221} 222 223TEST_F(DiskInterfaceTest, MakeDirs) { 224 string path = "path/with/double//slash/"; 225 EXPECT_TRUE(disk_.MakeDirs(path)); 226 FILE* f = fopen((path + "a_file").c_str(), "w"); 227 EXPECT_TRUE(f); 228 EXPECT_EQ(0, fclose(f)); 229#ifdef _WIN32 230 string path2 = "another\\with\\back\\\\slashes\\"; 231 EXPECT_TRUE(disk_.MakeDirs(path2)); 232 FILE* f2 = fopen((path2 + "a_file").c_str(), "w"); 233 EXPECT_TRUE(f2); 234 EXPECT_EQ(0, fclose(f2)); 235#endif 236} 237 238TEST_F(DiskInterfaceTest, RemoveFile) { 239 const char* kFileName = "file-to-remove"; 240 ASSERT_TRUE(Touch(kFileName)); 241 EXPECT_EQ(0, disk_.RemoveFile(kFileName)); 242 EXPECT_EQ(1, disk_.RemoveFile(kFileName)); 243 EXPECT_EQ(1, disk_.RemoveFile("does not exist")); 244#ifdef _WIN32 245 ASSERT_TRUE(Touch(kFileName)); 246 EXPECT_EQ(0, system((std::string("attrib +R ") + kFileName).c_str())); 247 EXPECT_EQ(0, disk_.RemoveFile(kFileName)); 248 EXPECT_EQ(1, disk_.RemoveFile(kFileName)); 249#endif 250} 251 252TEST_F(DiskInterfaceTest, RemoveDirectory) { 253 const char* kDirectoryName = "directory-to-remove"; 254 EXPECT_TRUE(disk_.MakeDir(kDirectoryName)); 255 EXPECT_EQ(0, disk_.RemoveFile(kDirectoryName)); 256 EXPECT_EQ(1, disk_.RemoveFile(kDirectoryName)); 257 EXPECT_EQ(1, disk_.RemoveFile("does not exist")); 258} 259 260struct StatTest : public StateTestWithBuiltinRules, 261 public DiskInterface { 262 StatTest() : scan_(&state_, NULL, NULL, this, NULL) {} 263 264 // DiskInterface implementation. 265 virtual TimeStamp Stat(const string& path, string* err) const; 266 virtual bool WriteFile(const string& path, const string& contents) { 267 assert(false); 268 return true; 269 } 270 virtual bool MakeDir(const string& path) { 271 assert(false); 272 return false; 273 } 274 virtual Status ReadFile(const string& path, string* contents, string* err) { 275 assert(false); 276 return NotFound; 277 } 278 virtual int RemoveFile(const string& path) { 279 assert(false); 280 return 0; 281 } 282 283 DependencyScan scan_; 284 map<string, TimeStamp> mtimes_; 285 mutable vector<string> stats_; 286}; 287 288TimeStamp StatTest::Stat(const string& path, string* err) const { 289 stats_.push_back(path); 290 map<string, TimeStamp>::const_iterator i = mtimes_.find(path); 291 if (i == mtimes_.end()) 292 return 0; // File not found. 293 return i->second; 294} 295 296TEST_F(StatTest, Simple) { 297 ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, 298"build out: cat in\n")); 299 300 Node* out = GetNode("out"); 301 string err; 302 EXPECT_TRUE(out->Stat(this, &err)); 303 EXPECT_EQ("", err); 304 ASSERT_EQ(1u, stats_.size()); 305 scan_.RecomputeDirty(out, NULL, NULL); 306 ASSERT_EQ(2u, stats_.size()); 307 ASSERT_EQ("out", stats_[0]); 308 ASSERT_EQ("in", stats_[1]); 309} 310 311TEST_F(StatTest, TwoStep) { 312 ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, 313"build out: cat mid\n" 314"build mid: cat in\n")); 315 316 Node* out = GetNode("out"); 317 string err; 318 EXPECT_TRUE(out->Stat(this, &err)); 319 EXPECT_EQ("", err); 320 ASSERT_EQ(1u, stats_.size()); 321 scan_.RecomputeDirty(out, NULL, NULL); 322 ASSERT_EQ(3u, stats_.size()); 323 ASSERT_EQ("out", stats_[0]); 324 ASSERT_TRUE(GetNode("out")->dirty()); 325 ASSERT_EQ("mid", stats_[1]); 326 ASSERT_TRUE(GetNode("mid")->dirty()); 327 ASSERT_EQ("in", stats_[2]); 328} 329 330TEST_F(StatTest, Tree) { 331 ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, 332"build out: cat mid1 mid2\n" 333"build mid1: cat in11 in12\n" 334"build mid2: cat in21 in22\n")); 335 336 Node* out = GetNode("out"); 337 string err; 338 EXPECT_TRUE(out->Stat(this, &err)); 339 EXPECT_EQ("", err); 340 ASSERT_EQ(1u, stats_.size()); 341 scan_.RecomputeDirty(out, NULL, NULL); 342 ASSERT_EQ(1u + 6u, stats_.size()); 343 ASSERT_EQ("mid1", stats_[1]); 344 ASSERT_TRUE(GetNode("mid1")->dirty()); 345 ASSERT_EQ("in11", stats_[2]); 346} 347 348TEST_F(StatTest, Middle) { 349 ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, 350"build out: cat mid\n" 351"build mid: cat in\n")); 352 353 mtimes_["in"] = 1; 354 mtimes_["mid"] = 0; // missing 355 mtimes_["out"] = 1; 356 357 Node* out = GetNode("out"); 358 string err; 359 EXPECT_TRUE(out->Stat(this, &err)); 360 EXPECT_EQ("", err); 361 ASSERT_EQ(1u, stats_.size()); 362 scan_.RecomputeDirty(out, NULL, NULL); 363 ASSERT_FALSE(GetNode("in")->dirty()); 364 ASSERT_TRUE(GetNode("mid")->dirty()); 365 ASSERT_TRUE(GetNode("out")->dirty()); 366} 367 368} // namespace 369