1 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. See the AUTHORS file for names of contributors.
4 //
5 // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
6 
7 #include <memory>
8 #include <string>
9 #include <vector>
10 #include <algorithm>
11 
12 #include "env/mock_env.h"
13 #include "rocksdb/env.h"
14 #include "test_util/testharness.h"
15 
16 namespace ROCKSDB_NAMESPACE {
17 
18 // Normalizes trivial differences across Envs such that these test cases can
19 // run on all Envs.
20 class NormalizingEnvWrapper : public EnvWrapper {
21  public:
22   explicit NormalizingEnvWrapper(Env* base) : EnvWrapper(base) {}
23 
24   // Removes . and .. from directory listing
25   Status GetChildren(const std::string& dir,
26                      std::vector<std::string>* result) override {
27     Status status = EnvWrapper::GetChildren(dir, result);
28     if (status.ok()) {
29       result->erase(std::remove_if(result->begin(), result->end(),
30                                    [](const std::string& s) {
31                                      return s == "." || s == "..";
32                                    }),
33                     result->end());
34     }
35     return status;
36   }
37 
38   // Removes . and .. from directory listing
39   Status GetChildrenFileAttributes(
40       const std::string& dir, std::vector<FileAttributes>* result) override {
41     Status status = EnvWrapper::GetChildrenFileAttributes(dir, result);
42     if (status.ok()) {
43       result->erase(std::remove_if(result->begin(), result->end(),
44                                    [](const FileAttributes& fa) {
45                                      return fa.name == "." || fa.name == "..";
46                                    }),
47                     result->end());
48     }
49     return status;
50   }
51 };
52 
53 class EnvBasicTestWithParam : public testing::Test,
54                               public ::testing::WithParamInterface<Env*> {
55  public:
56   Env* env_;
57   const EnvOptions soptions_;
58   std::string test_dir_;
59 
60   EnvBasicTestWithParam() : env_(GetParam()) {
61     test_dir_ = test::PerThreadDBPath(env_, "env_basic_test");
62   }
63 
64   void SetUp() override { env_->CreateDirIfMissing(test_dir_); }
65 
66   void TearDown() override {
67     std::vector<std::string> files;
68     env_->GetChildren(test_dir_, &files);
69     for (const auto& file : files) {
70       // don't know whether it's file or directory, try both. The tests must
71       // only create files or empty directories, so one must succeed, else the
72       // directory's corrupted.
73       Status s = env_->DeleteFile(test_dir_ + "/" + file);
74       if (!s.ok()) {
75         ASSERT_OK(env_->DeleteDir(test_dir_ + "/" + file));
76       }
77     }
78   }
79 };
80 
81 class EnvMoreTestWithParam : public EnvBasicTestWithParam {};
82 
83 static std::unique_ptr<Env> def_env(new NormalizingEnvWrapper(Env::Default()));
84 INSTANTIATE_TEST_CASE_P(EnvDefault, EnvBasicTestWithParam,
85                         ::testing::Values(def_env.get()));
86 INSTANTIATE_TEST_CASE_P(EnvDefault, EnvMoreTestWithParam,
87                         ::testing::Values(def_env.get()));
88 
89 static std::unique_ptr<Env> mock_env(new MockEnv(Env::Default()));
90 INSTANTIATE_TEST_CASE_P(MockEnv, EnvBasicTestWithParam,
91                         ::testing::Values(mock_env.get()));
92 #ifndef ROCKSDB_LITE
93 static std::unique_ptr<Env> mem_env(NewMemEnv(Env::Default()));
94 INSTANTIATE_TEST_CASE_P(MemEnv, EnvBasicTestWithParam,
95                         ::testing::Values(mem_env.get()));
96 
97 namespace {
98 
99 // Returns a vector of 0 or 1 Env*, depending whether an Env is registered for
100 // TEST_ENV_URI.
101 //
102 // The purpose of returning an empty vector (instead of nullptr) is that gtest
103 // ValuesIn() will skip running tests when given an empty collection.
104 std::vector<Env*> GetCustomEnvs() {
105   static Env* custom_env;
106   static bool init = false;
107   if (!init) {
108     init = true;
109     const char* uri = getenv("TEST_ENV_URI");
110     if (uri != nullptr) {
111       Env::LoadEnv(uri, &custom_env);
112     }
113   }
114 
115   std::vector<Env*> res;
116   if (custom_env != nullptr) {
117     res.emplace_back(custom_env);
118   }
119   return res;
120 }
121 
122 }  // anonymous namespace
123 
124 INSTANTIATE_TEST_CASE_P(CustomEnv, EnvBasicTestWithParam,
125                         ::testing::ValuesIn(GetCustomEnvs()));
126 
127 INSTANTIATE_TEST_CASE_P(CustomEnv, EnvMoreTestWithParam,
128                         ::testing::ValuesIn(GetCustomEnvs()));
129 
130 #endif  // ROCKSDB_LITE
131 
132 TEST_P(EnvBasicTestWithParam, Basics) {
133   uint64_t file_size;
134   std::unique_ptr<WritableFile> writable_file;
135   std::vector<std::string> children;
136 
137   // Check that the directory is empty.
138   ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/non_existent"));
139   ASSERT_TRUE(!env_->GetFileSize(test_dir_ + "/non_existent", &file_size).ok());
140   ASSERT_OK(env_->GetChildren(test_dir_, &children));
141   ASSERT_EQ(0U, children.size());
142 
143   // Create a file.
144   ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_));
145   ASSERT_OK(writable_file->Close());
146   writable_file.reset();
147 
148   // Check that the file exists.
149   ASSERT_OK(env_->FileExists(test_dir_ + "/f"));
150   ASSERT_OK(env_->GetFileSize(test_dir_ + "/f", &file_size));
151   ASSERT_EQ(0U, file_size);
152   ASSERT_OK(env_->GetChildren(test_dir_, &children));
153   ASSERT_EQ(1U, children.size());
154   ASSERT_EQ("f", children[0]);
155   ASSERT_OK(env_->DeleteFile(test_dir_ + "/f"));
156 
157   // Write to the file.
158   ASSERT_OK(
159       env_->NewWritableFile(test_dir_ + "/f1", &writable_file, soptions_));
160   ASSERT_OK(writable_file->Append("abc"));
161   ASSERT_OK(writable_file->Close());
162   writable_file.reset();
163   ASSERT_OK(
164       env_->NewWritableFile(test_dir_ + "/f2", &writable_file, soptions_));
165   ASSERT_OK(writable_file->Close());
166   writable_file.reset();
167 
168   // Check for expected size.
169   ASSERT_OK(env_->GetFileSize(test_dir_ + "/f1", &file_size));
170   ASSERT_EQ(3U, file_size);
171 
172   // Check that renaming works.
173   ASSERT_TRUE(
174       !env_->RenameFile(test_dir_ + "/non_existent", test_dir_ + "/g").ok());
175   ASSERT_OK(env_->RenameFile(test_dir_ + "/f1", test_dir_ + "/g"));
176   ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/f1"));
177   ASSERT_OK(env_->FileExists(test_dir_ + "/g"));
178   ASSERT_OK(env_->GetFileSize(test_dir_ + "/g", &file_size));
179   ASSERT_EQ(3U, file_size);
180 
181   // Check that renaming overwriting works
182   ASSERT_OK(env_->RenameFile(test_dir_ + "/f2", test_dir_ + "/g"));
183   ASSERT_OK(env_->GetFileSize(test_dir_ + "/g", &file_size));
184   ASSERT_EQ(0U, file_size);
185 
186   // Check that opening non-existent file fails.
187   std::unique_ptr<SequentialFile> seq_file;
188   std::unique_ptr<RandomAccessFile> rand_file;
189   ASSERT_TRUE(!env_->NewSequentialFile(test_dir_ + "/non_existent", &seq_file,
190                                        soptions_)
191                    .ok());
192   ASSERT_TRUE(!seq_file);
193   ASSERT_TRUE(!env_->NewRandomAccessFile(test_dir_ + "/non_existent",
194                                          &rand_file, soptions_)
195                    .ok());
196   ASSERT_TRUE(!rand_file);
197 
198   // Check that deleting works.
199   ASSERT_TRUE(!env_->DeleteFile(test_dir_ + "/non_existent").ok());
200   ASSERT_OK(env_->DeleteFile(test_dir_ + "/g"));
201   ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/g"));
202   ASSERT_OK(env_->GetChildren(test_dir_, &children));
203   ASSERT_EQ(0U, children.size());
204   ASSERT_TRUE(
205       env_->GetChildren(test_dir_ + "/non_existent", &children).IsNotFound());
206 }
207 
208 TEST_P(EnvBasicTestWithParam, ReadWrite) {
209   std::unique_ptr<WritableFile> writable_file;
210   std::unique_ptr<SequentialFile> seq_file;
211   std::unique_ptr<RandomAccessFile> rand_file;
212   Slice result;
213   char scratch[100];
214 
215   ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_));
216   ASSERT_OK(writable_file->Append("hello "));
217   ASSERT_OK(writable_file->Append("world"));
218   ASSERT_OK(writable_file->Close());
219   writable_file.reset();
220 
221   // Read sequentially.
222   ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_));
223   ASSERT_OK(seq_file->Read(5, &result, scratch));  // Read "hello".
224   ASSERT_EQ(0, result.compare("hello"));
225   ASSERT_OK(seq_file->Skip(1));
226   ASSERT_OK(seq_file->Read(1000, &result, scratch));  // Read "world".
227   ASSERT_EQ(0, result.compare("world"));
228   ASSERT_OK(seq_file->Read(1000, &result, scratch));  // Try reading past EOF.
229   ASSERT_EQ(0U, result.size());
230   ASSERT_OK(seq_file->Skip(100));  // Try to skip past end of file.
231   ASSERT_OK(seq_file->Read(1000, &result, scratch));
232   ASSERT_EQ(0U, result.size());
233 
234   // Random reads.
235   ASSERT_OK(env_->NewRandomAccessFile(test_dir_ + "/f", &rand_file, soptions_));
236   ASSERT_OK(rand_file->Read(6, 5, &result, scratch));  // Read "world".
237   ASSERT_EQ(0, result.compare("world"));
238   ASSERT_OK(rand_file->Read(0, 5, &result, scratch));  // Read "hello".
239   ASSERT_EQ(0, result.compare("hello"));
240   ASSERT_OK(rand_file->Read(10, 100, &result, scratch));  // Read "d".
241   ASSERT_EQ(0, result.compare("d"));
242 
243   // Too high offset.
244   ASSERT_TRUE(rand_file->Read(1000, 5, &result, scratch).ok());
245 }
246 
247 TEST_P(EnvBasicTestWithParam, Misc) {
248   std::unique_ptr<WritableFile> writable_file;
249   ASSERT_OK(env_->NewWritableFile(test_dir_ + "/b", &writable_file, soptions_));
250 
251   // These are no-ops, but we test they return success.
252   ASSERT_OK(writable_file->Sync());
253   ASSERT_OK(writable_file->Flush());
254   ASSERT_OK(writable_file->Close());
255   writable_file.reset();
256 }
257 
258 TEST_P(EnvBasicTestWithParam, LargeWrite) {
259   const size_t kWriteSize = 300 * 1024;
260   char* scratch = new char[kWriteSize * 2];
261 
262   std::string write_data;
263   for (size_t i = 0; i < kWriteSize; ++i) {
264     write_data.append(1, static_cast<char>(i));
265   }
266 
267   std::unique_ptr<WritableFile> writable_file;
268   ASSERT_OK(env_->NewWritableFile(test_dir_ + "/f", &writable_file, soptions_));
269   ASSERT_OK(writable_file->Append("foo"));
270   ASSERT_OK(writable_file->Append(write_data));
271   ASSERT_OK(writable_file->Close());
272   writable_file.reset();
273 
274   std::unique_ptr<SequentialFile> seq_file;
275   Slice result;
276   ASSERT_OK(env_->NewSequentialFile(test_dir_ + "/f", &seq_file, soptions_));
277   ASSERT_OK(seq_file->Read(3, &result, scratch));  // Read "foo".
278   ASSERT_EQ(0, result.compare("foo"));
279 
280   size_t read = 0;
281   std::string read_data;
282   while (read < kWriteSize) {
283     ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch));
284     read_data.append(result.data(), result.size());
285     read += result.size();
286   }
287   ASSERT_TRUE(write_data == read_data);
288   delete [] scratch;
289 }
290 
291 TEST_P(EnvMoreTestWithParam, GetModTime) {
292   ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/dir1"));
293   uint64_t mtime1 = 0x0;
294   ASSERT_OK(env_->GetFileModificationTime(test_dir_ + "/dir1", &mtime1));
295 }
296 
297 TEST_P(EnvMoreTestWithParam, MakeDir) {
298   ASSERT_OK(env_->CreateDir(test_dir_ + "/j"));
299   ASSERT_OK(env_->FileExists(test_dir_ + "/j"));
300   std::vector<std::string> children;
301   env_->GetChildren(test_dir_, &children);
302   ASSERT_EQ(1U, children.size());
303   // fail because file already exists
304   ASSERT_TRUE(!env_->CreateDir(test_dir_ + "/j").ok());
305   ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/j"));
306   ASSERT_OK(env_->DeleteDir(test_dir_ + "/j"));
307   ASSERT_EQ(Status::NotFound(), env_->FileExists(test_dir_ + "/j"));
308 }
309 
310 TEST_P(EnvMoreTestWithParam, GetChildren) {
311   // empty folder returns empty vector
312   std::vector<std::string> children;
313   std::vector<Env::FileAttributes> childAttr;
314   ASSERT_OK(env_->CreateDirIfMissing(test_dir_));
315   ASSERT_OK(env_->GetChildren(test_dir_, &children));
316   ASSERT_OK(env_->FileExists(test_dir_));
317   ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr));
318   ASSERT_EQ(0U, children.size());
319   ASSERT_EQ(0U, childAttr.size());
320 
321   // folder with contents returns relative path to test dir
322   ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/niu"));
323   ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/you"));
324   ASSERT_OK(env_->CreateDirIfMissing(test_dir_ + "/guo"));
325   ASSERT_OK(env_->GetChildren(test_dir_, &children));
326   ASSERT_OK(env_->GetChildrenFileAttributes(test_dir_, &childAttr));
327   ASSERT_EQ(3U, children.size());
328   ASSERT_EQ(3U, childAttr.size());
329   for (auto each : children) {
330     env_->DeleteDir(test_dir_ + "/" + each);
331   }  // necessary for default POSIX env
332 
333   // non-exist directory returns IOError
334   ASSERT_OK(env_->DeleteDir(test_dir_));
335   ASSERT_TRUE(!env_->FileExists(test_dir_).ok());
336   ASSERT_TRUE(!env_->GetChildren(test_dir_, &children).ok());
337   ASSERT_TRUE(!env_->GetChildrenFileAttributes(test_dir_, &childAttr).ok());
338 
339   // if dir is a file, returns IOError
340   ASSERT_OK(env_->CreateDir(test_dir_));
341   std::unique_ptr<WritableFile> writable_file;
342   ASSERT_OK(
343       env_->NewWritableFile(test_dir_ + "/file", &writable_file, soptions_));
344   ASSERT_OK(writable_file->Close());
345   writable_file.reset();
346   ASSERT_TRUE(!env_->GetChildren(test_dir_ + "/file", &children).ok());
347   ASSERT_EQ(0U, children.size());
348 }
349 
350 }  // namespace ROCKSDB_NAMESPACE
351 int main(int argc, char** argv) {
352   ::testing::InitGoogleTest(&argc, argv);
353   return RUN_ALL_TESTS();
354 }
355