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