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
41static 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
50static bool exists(const std::string& path) {
51    struct stat st;
52    return stat(path.c_str(), &st) == 0;
53}
54
55
56class IoTest : public ::testing::Test {
57public:
58
59    static void SetUpTestCase();
60
61    static void TearDownTestCase();
62
63protected:
64
65    // A small filesystem we can play around with
66    static const std::vector<std::string> vfs;
67};
68
69const 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
85void 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
92void IoTest::TearDownTestCase() {
93    // Expecting the rmrf to work ;)
94    cb::io::rmrf("fs");
95}
96
97TEST_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
122TEST_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
138TEST_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
166TEST_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
188TEST_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
206TEST_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
215TEST_F(IoTest, removeNonExistentFile) {
216    EXPECT_THROW(cb::io::rmrf("foo"), std::system_error)
217                << "Expected system error for removing non-existent file";
218}
219
220TEST_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
227TEST_F(IoTest, mkdirp) {
228#ifndef WIN32
229    EXPECT_THROW(cb::io::mkdirp("/it/would/suck/if/I/could/create/this"),
230                 std::runtime_error);
231#endif
232    EXPECT_NO_THROW(cb::io::mkdirp("."));
233    EXPECT_NO_THROW(cb::io::mkdirp("/"));
234    EXPECT_NO_THROW(cb::io::mkdirp("foo/bar"));
235    EXPECT_NO_THROW(cb::io::isDirectory("foo/bar"));
236    cb::io::rmrf("foo");
237    EXPECT_FALSE(cb::io::isDirectory("foo/bar"));
238    EXPECT_FALSE(cb::io::isDirectory("foo"));
239}
240
241TEST_F(IoTest, maximizeFileDescriptors) {
242    auto limit = cb::io::maximizeFileDescriptors(32);
243    EXPECT_LE(32u, limit) << "FAIL: I should be able to set it to at least 32";
244
245    limit = cb::io::maximizeFileDescriptors(
246        std::numeric_limits<uint32_t>::max());
247    if (limit != std::numeric_limits<uint32_t>::max()) {
248        // windows don't have a max limit, and that could be for other platforms
249        // as well..
250        EXPECT_EQ(limit, cb::io::maximizeFileDescriptors(limit + 1))
251             << "FAIL: I expected maximizeFileDescriptors to return "
252                      << "the same max limit two times in a row";
253    }
254
255    limit = cb::io::maximizeFileDescriptors(
256        std::numeric_limits<uint64_t>::max());
257    if (limit != std::numeric_limits<uint64_t>::max()) {
258        // windows don't have a max limit, and that could be for other platforms
259        // as well..
260        EXPECT_EQ(limit, cb::io::maximizeFileDescriptors(limit + 1))
261                    << "FAIL: I expected maximizeFileDescriptors to return "
262                    << "the same max limit two times in a row";
263    }
264}
265