1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2016 Couchbase, Inc.
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include <gtest/gtest.h>
18 
19 #include <algorithm>
20 #include <cerrno>
21 #include <cstdlib>
22 #include <cstring>
23 #include <limits>
24 #include <platform/dirutils.h>
25 #include <stdio.h>
26 #include <string>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <vector>
30 #include <system_error>
31 
32 #ifdef WIN32
33 #include <windows.h>
34 #include <direct.h>
35 #define mkdir(a, b) _mkdir(a)
36 #define PATH_SEPARATOR "\\"
37 #else
38 #define PATH_SEPARATOR "/"
39 #endif
40 
CreateDirectory(const std::string& dir)41 static bool CreateDirectory(const std::string& dir) {
42     if (mkdir(dir.c_str(), S_IXUSR | S_IWUSR | S_IRUSR) != 0) {
43         if (errno != EEXIST) {
44             return false;
45         }
46     }
47     return true;
48 }
49 
exists(const std::string& path)50 static bool exists(const std::string& path) {
51     struct stat st;
52     return stat(path.c_str(), &st) == 0;
53 }
54 
55 
56 class IoTest : public ::testing::Test {
57 public:
58 
59     static void SetUpTestCase();
60 
61     static void TearDownTestCase();
62 
63 protected:
64 
65     // A small filesystem we can play around with
66     static const std::vector<std::string> vfs;
67 };
68 
69 const std::vector<std::string> IoTest::vfs = {
70     {"fs"},
71     {"fs/d1"},
72     {"fs/d2"},
73     {"fs/e2"},
74     {"fs/f2c"},
75     {"fs/g2"},
76     {"fs/d3"},
77     {"fs/1"},
78     {"fs/2"},
79     {"fs/2c"},
80     {"fs/2d"},
81     {"fs/3"},
82     {"fs/d1/d1"}
83 };
84 
SetUpTestCase()85 void IoTest::SetUpTestCase() {
86     for (const auto& file : vfs) {
87         ASSERT_TRUE(CreateDirectory(file)) << "failed to create directory";
88         ASSERT_TRUE(exists(file)) << "directory " << file << " does not exists";
89     }
90 }
91 
TearDownTestCase()92 void IoTest::TearDownTestCase() {
93     // Expecting the rmrf to work ;)
94     cb::io::rmrf("fs");
95 }
96 
TEST_F(IoTest, dirname)97 TEST_F(IoTest, dirname) {
98     // Check the simple case
99     EXPECT_EQ("foo", cb::io::dirname("foo\\bar"));
100     EXPECT_EQ("foo", cb::io::dirname("foo/bar"));
101 
102     // Make sure that we remove double an empty chunk
103     EXPECT_EQ("foo", cb::io::dirname("foo\\\\bar"));
104     EXPECT_EQ("foo", cb::io::dirname("foo//bar"));
105 
106     // Make sure that we handle the case without a directory
107     EXPECT_EQ(".", cb::io::dirname("bar"));
108     EXPECT_EQ(".", cb::io::dirname(""));
109 
110     // Absolute directories
111     EXPECT_EQ("\\", cb::io::dirname("\\bar"));
112     EXPECT_EQ("\\", cb::io::dirname("\\\\bar"));
113     EXPECT_EQ("/", cb::io::dirname("/bar"));
114     EXPECT_EQ("/", cb::io::dirname("//bar"));
115 
116     // Test that we work with multiple directories
117     EXPECT_EQ("1/2/3/4/5", cb::io::dirname("1/2/3/4/5/6"));
118     EXPECT_EQ("1\\2\\3\\4\\5", cb::io::dirname("1\\2\\3\\4\\5\\6"));
119     EXPECT_EQ("1/2\\4/5", cb::io::dirname("1/2\\4/5\\6"));
120 }
121 
TEST_F(IoTest, basename)122 TEST_F(IoTest, basename) {
123     EXPECT_EQ("bar", cb::io::basename("foo\\bar"));
124     EXPECT_EQ("bar", cb::io::basename("foo/bar"));
125     EXPECT_EQ("bar", cb::io::basename("foo\\\\bar"));
126     EXPECT_EQ("bar", cb::io::basename("foo//bar"));
127     EXPECT_EQ("bar", cb::io::basename("bar"));
128     EXPECT_EQ("", cb::io::basename(""));
129     EXPECT_EQ("bar", cb::io::basename("\\bar"));
130     EXPECT_EQ("bar", cb::io::basename("\\\\bar"));
131     EXPECT_EQ("bar", cb::io::basename("/bar"));
132     EXPECT_EQ("bar", cb::io::basename("//bar"));
133     EXPECT_EQ("6", cb::io::basename("1/2/3/4/5/6"));
134     EXPECT_EQ("6", cb::io::basename("1\\2\\3\\4\\5\\6"));
135     EXPECT_EQ("6", cb::io::basename("1/2\\4/5\\6"));
136 }
137 
TEST_F(IoTest, findFilesWithPrefix)138 TEST_F(IoTest, findFilesWithPrefix) {
139     auto vec = cb::io::findFilesWithPrefix("fs");
140     EXPECT_EQ(1u, vec.size());
141 
142     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
143                                    "." PATH_SEPARATOR "fs"));
144 
145 
146     vec = cb::io::findFilesWithPrefix("fs", "d");
147     EXPECT_EQ(3u, vec.size());
148 
149     // We don't know the order of the files in the result..
150     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
151                                    "fs" PATH_SEPARATOR "d1"));
152     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
153                                    "fs" PATH_SEPARATOR "d2"));
154     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
155                                    "fs" PATH_SEPARATOR "d3"));
156 
157     vec = cb::io::findFilesWithPrefix("fs", "1");
158     EXPECT_EQ(1u, vec.size());
159     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
160                                    "fs" PATH_SEPARATOR "1"));
161 
162     vec = cb::io::findFilesWithPrefix("fs", "");
163     EXPECT_EQ(vfs.size() - 2, vec.size());
164 }
165 
TEST_F(IoTest, findFilesContaining)166 TEST_F(IoTest, findFilesContaining) {
167     auto vec = cb::io::findFilesContaining("fs", "");
168     EXPECT_EQ(vfs.size() - 2, vec.size());
169 
170     vec = cb::io::findFilesContaining("fs", "2");
171     EXPECT_EQ(7u, vec.size());
172     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
173                                    "fs" PATH_SEPARATOR "d2"));
174     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
175                                    "fs" PATH_SEPARATOR "e2"));
176     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
177                                    "fs" PATH_SEPARATOR "f2c"));
178     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
179                                    "fs" PATH_SEPARATOR "g2"));
180     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
181                                    "fs" PATH_SEPARATOR "2"));
182     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
183                                    "fs" PATH_SEPARATOR "2c"));
184     EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
185                                    "fs" PATH_SEPARATOR "2d"));
186 }
187 
TEST_F(IoTest, mktemp)188 TEST_F(IoTest, mktemp) {
189     auto filename = cb::io::mktemp("foo");
190     EXPECT_FALSE(filename.empty())
191                 << "FAIL: Expected to create tempfile without mask";
192     EXPECT_TRUE(cb::io::isFile(filename));
193     EXPECT_NO_THROW(cb::io::rmrf(filename));
194     EXPECT_FALSE(cb::io::isFile(filename));
195     EXPECT_FALSE(cb::io::isDirectory(filename));
196 
197     filename = cb::io::mktemp("barXXXXXX");
198     EXPECT_FALSE(filename.empty())
199                 << "FAIL: Expected to create tempfile mask";
200     EXPECT_TRUE(cb::io::isFile(filename));
201     EXPECT_NO_THROW(cb::io::rmrf(filename));
202     EXPECT_FALSE(cb::io::isFile(filename));
203     EXPECT_FALSE(cb::io::isDirectory(filename));
204 }
205 
TEST_F(IoTest, isFileAndIsDirectory)206 TEST_F(IoTest, isFileAndIsDirectory) {
207     EXPECT_FALSE(cb::io::isFile("."));
208     EXPECT_TRUE(cb::io::isDirectory("."));
209     auto filename = cb::io::mktemp("plainfile");
210     EXPECT_TRUE(cb::io::isFile(filename));
211     EXPECT_FALSE(cb::io::isDirectory(filename));
212     EXPECT_NO_THROW(cb::io::rmrf(filename));
213 }
214 
TEST_F(IoTest, removeNonExistentFile)215 TEST_F(IoTest, removeNonExistentFile) {
216     EXPECT_THROW(cb::io::rmrf("foo"), std::system_error)
217                 << "Expected system error for removing non-existent file";
218 }
219 
TEST_F(IoTest, getcwd)220 TEST_F(IoTest, getcwd) {
221     auto cwd = cb::io::getcwd();
222     // I can't really determine the correct value here, but it shouldn't be
223     // empty ;-)
224     ASSERT_FALSE(cwd.empty());
225 }
226 
TEST_F(IoTest, mkdirp)227 TEST_F(IoTest, mkdirp) {
228     std::string path{"/it/would/suck/if/I/could/create/this"};
229 
230 #ifdef WIN32
231     // Try to use an invalid drive on Windows
232     bool found = false;
233     for (int ii = 'D'; ii < 'Z'; ++ii) {
234         std::string drive;
235         drive.push_back((char)ii);
236         drive.push_back(':');
237         if (!cb::io::isDirectory(drive)) {
238             found = true;
239             path = drive + path;
240             break;
241         }
242     }
243     if (!found) {
244         FAIL() << "Failed to locate an unused drive";
245     }
246 #endif
247     EXPECT_THROW(cb::io::mkdirp(path), std::runtime_error);
248 
249     EXPECT_NO_THROW(cb::io::mkdirp("."));
250     EXPECT_NO_THROW(cb::io::mkdirp("/"));
251     EXPECT_NO_THROW(cb::io::mkdirp("foo/bar"));
252     EXPECT_NO_THROW(cb::io::isDirectory("foo/bar"));
253     cb::io::rmrf("foo");
254     EXPECT_FALSE(cb::io::isDirectory("foo/bar"));
255     EXPECT_FALSE(cb::io::isDirectory("foo"));
256 }
257 
TEST_F(IoTest, maximizeFileDescriptors)258 TEST_F(IoTest, maximizeFileDescriptors) {
259     auto limit = cb::io::maximizeFileDescriptors(32);
260     EXPECT_LE(32u, limit) << "FAIL: I should be able to set it to at least 32";
261 
262     limit = cb::io::maximizeFileDescriptors(
263         std::numeric_limits<uint32_t>::max());
264     if (limit != std::numeric_limits<uint32_t>::max()) {
265         // windows don't have a max limit, and that could be for other platforms
266         // as well..
267         EXPECT_EQ(limit, cb::io::maximizeFileDescriptors(limit + 1))
268              << "FAIL: I expected maximizeFileDescriptors to return "
269                       << "the same max limit two times in a row";
270     }
271 
272     limit = cb::io::maximizeFileDescriptors(
273         std::numeric_limits<uint64_t>::max());
274     if (limit != std::numeric_limits<uint64_t>::max()) {
275         // windows don't have a max limit, and that could be for other platforms
276         // as well..
277         EXPECT_EQ(limit, cb::io::maximizeFileDescriptors(limit + 1))
278                     << "FAIL: I expected maximizeFileDescriptors to return "
279                     << "the same max limit two times in a row";
280     }
281 }
282