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