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
27 using namespace std;
28
29 namespace {
30
31 struct DiskInterfaceTest : public testing::Test {
SetUp__anon11574::DiskInterfaceTest32 virtual void SetUp() {
33 // These tests do real disk accesses, so create a temp dir.
34 temp_dir_.CreateAndEnter("Ninja-DiskInterfaceTest");
35 }
36
TearDown__anon11574::DiskInterfaceTest37 virtual void TearDown() {
38 temp_dir_.Cleanup();
39 }
40
Touch__anon11574::DiskInterfaceTest41 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
TEST_F(DiskInterfaceTest, StatMissingFile)52 TEST_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
TEST_F(DiskInterfaceTest, StatMissingFileWithCache)69 TEST_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
TEST_F(DiskInterfaceTest, StatBadPath)80 TEST_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
TEST_F(DiskInterfaceTest, StatExistingFile)93 TEST_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
TEST_F(DiskInterfaceTest, StatExistingFileWithLongPath)101 TEST_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\
107 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\
108 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\
109 xxxxxxxxxxxxxxxxxxxxx";
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
TEST_F(DiskInterfaceTest, StatExistingDir)118 TEST_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
TEST_F(DiskInterfaceTest, StatCache)140 TEST_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
TEST_F(DiskInterfaceTest, ReadFile)201 TEST_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
TEST_F(DiskInterfaceTest, MakeDirs)223 TEST_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
TEST_F(DiskInterfaceTest, RemoveFile)238 TEST_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
TEST_F(DiskInterfaceTest, RemoveDirectory)252 TEST_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
260 struct StatTest : public StateTestWithBuiltinRules,
261 public DiskInterface {
StatTest__anon11574::StatTest262 StatTest() : scan_(&state_, NULL, NULL, this, NULL) {}
263
264 // DiskInterface implementation.
265 virtual TimeStamp Stat(const string& path, string* err) const;
WriteFile__anon11574::StatTest266 virtual bool WriteFile(const string& path, const string& contents) {
267 assert(false);
268 return true;
269 }
MakeDir__anon11574::StatTest270 virtual bool MakeDir(const string& path) {
271 assert(false);
272 return false;
273 }
ReadFile__anon11574::StatTest274 virtual Status ReadFile(const string& path, string* contents, string* err) {
275 assert(false);
276 return NotFound;
277 }
RemoveFile__anon11574::StatTest278 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
Stat(const string& path, string* err) const288 TimeStamp 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
TEST_F(StatTest, Simple)296 TEST_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
TEST_F(StatTest, TwoStep)311 TEST_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
TEST_F(StatTest, Tree)330 TEST_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
TEST_F(StatTest, Middle)348 TEST_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