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