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
31#ifdef WIN32
32#define NOMINMAX
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};
83
84void IoTest::SetUpTestCase() {
85    for (const auto& file : vfs) {
86        ASSERT_TRUE(CreateDirectory(file)) << "failed to create directory";
87        ASSERT_TRUE(exists(file)) << "directory " << file << " does not exists";
88    }
89}
90
91void IoTest::TearDownTestCase() {
92    // Expecting the rmrf to work ;)
93    cb::io::rmrf("fs");
94}
95
96TEST_F(IoTest, dirname) {
97    // Check the simple case
98    EXPECT_EQ("foo", cb::io::dirname("foo\\bar"));
99    EXPECT_EQ("foo", cb::io::dirname("foo/bar"));
100
101    // Make sure that we remove double an empty chunk
102    EXPECT_EQ("foo", cb::io::dirname("foo\\\\bar"));
103    EXPECT_EQ("foo", cb::io::dirname("foo//bar"));
104
105    // Make sure that we handle the case without a directory
106    EXPECT_EQ(".", cb::io::dirname("bar"));
107    EXPECT_EQ(".", cb::io::dirname(""));
108
109    // Absolute directories
110    EXPECT_EQ("\\", cb::io::dirname("\\bar"));
111    EXPECT_EQ("\\", cb::io::dirname("\\\\bar"));
112    EXPECT_EQ("/", cb::io::dirname("/bar"));
113    EXPECT_EQ("/", cb::io::dirname("//bar"));
114
115    // Test that we work with multiple directories
116    EXPECT_EQ("1/2/3/4/5", cb::io::dirname("1/2/3/4/5/6"));
117    EXPECT_EQ("1\\2\\3\\4\\5", cb::io::dirname("1\\2\\3\\4\\5\\6"));
118    EXPECT_EQ("1/2\\4/5", cb::io::dirname("1/2\\4/5\\6"));
119}
120
121TEST_F(IoTest, basename) {
122    EXPECT_EQ("bar", cb::io::basename("foo\\bar"));
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("bar"));
127    EXPECT_EQ("", cb::io::basename(""));
128    EXPECT_EQ("bar", cb::io::basename("\\bar"));
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("6", cb::io::basename("1/2/3/4/5/6"));
133    EXPECT_EQ("6", cb::io::basename("1\\2\\3\\4\\5\\6"));
134    EXPECT_EQ("6", cb::io::basename("1/2\\4/5\\6"));
135}
136
137TEST_F(IoTest, findFilesWithPrefix) {
138    auto vec = cb::io::findFilesWithPrefix("fs");
139    EXPECT_EQ(1, vec.size());
140
141    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
142                                   "." PATH_SEPARATOR "fs"));
143
144
145    vec = cb::io::findFilesWithPrefix("fs", "d");
146    EXPECT_EQ(3, vec.size());
147
148    // We don't know the order of the files in the result..
149    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
150                                   "fs" PATH_SEPARATOR "d1"));
151    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
152                                   "fs" PATH_SEPARATOR "d2"));
153    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
154                                   "fs" PATH_SEPARATOR "d3"));
155
156    vec = cb::io::findFilesWithPrefix("fs", "1");
157    EXPECT_EQ(1, vec.size());
158    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
159                                   "fs" PATH_SEPARATOR "1"));
160
161    vec = cb::io::findFilesWithPrefix("fs", "");
162    EXPECT_EQ(vfs.size() - 1, vec.size());
163}
164
165TEST_F(IoTest, findFilesContaining) {
166    auto vec = cb::io::findFilesContaining("fs", "");
167    EXPECT_EQ(vfs.size() - 1, vec.size());
168
169    vec = cb::io::findFilesContaining("fs", "2");
170    EXPECT_EQ(7, vec.size());
171    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
172                                   "fs" PATH_SEPARATOR "d2"));
173    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
174                                   "fs" PATH_SEPARATOR "e2"));
175    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
176                                   "fs" PATH_SEPARATOR "f2c"));
177    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
178                                   "fs" PATH_SEPARATOR "g2"));
179    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
180                                   "fs" PATH_SEPARATOR "2"));
181    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
182                                   "fs" PATH_SEPARATOR "2c"));
183    EXPECT_NE(vec.end(), std::find(vec.begin(), vec.end(),
184                                   "fs" PATH_SEPARATOR "2d"));
185}
186
187TEST_F(IoTest, mktemp) {
188    auto filename = cb::io::mktemp("foo");
189    EXPECT_FALSE(filename.empty())
190                << "FAIL: Expected to create tempfile without mask";
191    EXPECT_TRUE(cb::io::isFile(filename));
192    EXPECT_TRUE(cb::io::rmrf(filename));
193    EXPECT_FALSE(cb::io::isFile(filename));
194    EXPECT_FALSE(cb::io::isDirectory(filename));
195
196    filename = cb::io::mktemp("barXXXXXX");
197    EXPECT_FALSE(filename.empty())
198                << "FAIL: Expected to create tempfile mask";
199    EXPECT_TRUE(cb::io::isFile(filename));
200    EXPECT_TRUE(cb::io::rmrf(filename));
201    EXPECT_FALSE(cb::io::isFile(filename));
202    EXPECT_FALSE(cb::io::isDirectory(filename));
203}
204
205TEST_F(IoTest, isFileAndIsDirectory) {
206    EXPECT_FALSE(cb::io::isFile("."));
207    EXPECT_TRUE(cb::io::isDirectory("."));
208    auto filename = cb::io::mktemp("plainfile");
209    EXPECT_TRUE(cb::io::isFile(filename));
210    EXPECT_FALSE(cb::io::isDirectory(filename));
211    EXPECT_TRUE(cb::io::rmrf(filename));
212}
213
214TEST_F(IoTest, getcwd) {
215    auto cwd = cb::io::getcwd();
216    // I can't really determine the correct value here, but it shouldn't be
217    // empty ;-)
218    ASSERT_FALSE(cwd.empty());
219}
220
221TEST_F(IoTest, mkdirp) {
222#ifndef WIN32
223    EXPECT_FALSE(cb::io::mkdirp("/it/would/suck/if/I/could/create/this"));
224#endif
225    EXPECT_TRUE(cb::io::mkdirp("."));
226    EXPECT_TRUE(cb::io::mkdirp("/"));
227    EXPECT_TRUE(cb::io::mkdirp("foo/bar"));
228    EXPECT_TRUE(cb::io::isDirectory("foo/bar"));
229    cb::io::rmrf("foo");
230    EXPECT_FALSE(cb::io::isDirectory("foo/bar"));
231    EXPECT_FALSE(cb::io::isDirectory("foo"));
232}
233
234TEST_F(IoTest, maximizeFileDescriptors) {
235    auto limit = cb::io::maximizeFileDescriptors(32);
236    EXPECT_LE(32, limit) << "FAIL: I should be able to set it to at least 32";
237
238    limit = cb::io::maximizeFileDescriptors(
239        std::numeric_limits<uint32_t>::max());
240    if (limit != std::numeric_limits<uint32_t>::max()) {
241        // windows don't have a max limit, and that could be for other platforms
242        // as well..
243        EXPECT_EQ(limit, cb::io::maximizeFileDescriptors(limit + 1))
244             << "FAIL: I expected maximizeFileDescriptors to return "
245                      << "the same max limit two times in a row";
246    }
247
248    limit = cb::io::maximizeFileDescriptors(
249        std::numeric_limits<uint64_t>::max());
250    if (limit != std::numeric_limits<uint64_t>::max()) {
251        // windows don't have a max limit, and that could be for other platforms
252        // as well..
253        EXPECT_EQ(limit, cb::io::maximizeFileDescriptors(limit + 1))
254                    << "FAIL: I expected maximizeFileDescriptors to return "
255                    << "the same max limit two times in a row";
256    }
257}
258