1// Copyright (c) 2013 The Chromium 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.
4
5#include "base/files/file.h"
6#include "base/files/file_enumerator.h"
7#include "base/files/file_path.h"
8#include "base/files/file_util.h"
9#include "base/files/scoped_temp_dir.h"
10#include "base/test/test_suite.h"
11#include "third_party/leveldatabase/env_chromium_stdio.h"
12#if defined(OS_WIN)
13#include "third_party/leveldatabase/env_chromium_win.h"
14#endif
15#include "testing/gtest/include/gtest/gtest.h"
16#include "third_party/leveldatabase/env_idb.h"
17#include "third_party/leveldatabase/src/include/leveldb/db.h"
18
19#define FPL FILE_PATH_LITERAL
20
21using leveldb::DB;
22using leveldb::Env;
23using leveldb::IDBEnv;
24using leveldb::Options;
25using leveldb::ReadOptions;
26using leveldb::Slice;
27using leveldb::Status;
28using leveldb::WritableFile;
29using leveldb::WriteOptions;
30using leveldb_env::ChromiumEnvStdio;
31#if defined(OS_WIN)
32using leveldb_env::ChromiumEnvWin;
33#endif
34using leveldb_env::MethodID;
35
36TEST(ErrorEncoding, OnlyAMethod) {
37  const MethodID in_method = leveldb_env::kSequentialFileRead;
38  const Status s = MakeIOError("Somefile.txt", "message", in_method);
39  MethodID method;
40  int error = -75;
41  EXPECT_EQ(leveldb_env::METHOD_ONLY,
42            ParseMethodAndError(s.ToString().c_str(), &method, &error));
43  EXPECT_EQ(in_method, method);
44  EXPECT_EQ(-75, error);
45}
46
47TEST(ErrorEncoding, FileError) {
48  const MethodID in_method = leveldb_env::kWritableFileClose;
49  const base::File::Error fe = base::File::FILE_ERROR_INVALID_OPERATION;
50  const Status s = MakeIOError("Somefile.txt", "message", in_method, fe);
51  MethodID method;
52  int error;
53  EXPECT_EQ(leveldb_env::METHOD_AND_PFE,
54            ParseMethodAndError(s.ToString().c_str(), &method, &error));
55  EXPECT_EQ(in_method, method);
56  EXPECT_EQ(fe, error);
57}
58
59TEST(ErrorEncoding, Errno) {
60  const MethodID in_method = leveldb_env::kWritableFileFlush;
61  const int some_errno = ENOENT;
62  const Status s =
63      MakeIOError("Somefile.txt", "message", in_method, some_errno);
64  MethodID method;
65  int error;
66  EXPECT_EQ(leveldb_env::METHOD_AND_ERRNO,
67            ParseMethodAndError(s.ToString().c_str(), &method, &error));
68  EXPECT_EQ(in_method, method);
69  EXPECT_EQ(some_errno, error);
70}
71
72#if defined(OS_WIN)
73TEST(ErrorEncoding, ErrnoWin32) {
74  const MethodID in_method = leveldb_env::kWritableFileFlush;
75  const DWORD some_errno = ERROR_FILE_NOT_FOUND;
76  const Status s =
77      MakeIOErrorWin("Somefile.txt", "message", in_method, some_errno);
78  MethodID method;
79  int error;
80  EXPECT_EQ(leveldb_env::METHOD_AND_ERRNO,
81            ParseMethodAndError(s.ToString().c_str(), &method, &error));
82  EXPECT_EQ(in_method, method);
83  EXPECT_EQ(some_errno, error);
84}
85#endif
86
87TEST(ErrorEncoding, NoEncodedMessage) {
88  Status s = Status::IOError("Some message", "from leveldb itself");
89  MethodID method = leveldb_env::kRandomAccessFileRead;
90  int error = 4;
91  EXPECT_EQ(leveldb_env::NONE,
92            ParseMethodAndError(s.ToString().c_str(), &method, &error));
93  EXPECT_EQ(leveldb_env::kRandomAccessFileRead, method);
94  EXPECT_EQ(4, error);
95}
96
97template <typename T>
98class MyEnv : public T {
99 public:
100  MyEnv() : directory_syncs_(0) {}
101  int directory_syncs() { return directory_syncs_; }
102
103 protected:
104  virtual void DidSyncDir(const std::string& fname) {
105    ++directory_syncs_;
106    leveldb_env::ChromiumEnv::DidSyncDir(fname);
107  }
108
109 private:
110  int directory_syncs_;
111};
112
113template <typename T>
114class ChromiumEnvMultiPlatformTests : public ::testing::Test {
115 public:
116};
117
118#if defined(OS_WIN)
119typedef ::testing::Types<ChromiumEnvStdio, ChromiumEnvWin>
120    ChromiumEnvMultiPlatformTestsTypes;
121#else
122typedef ::testing::Types<ChromiumEnvStdio> ChromiumEnvMultiPlatformTestsTypes;
123#endif
124TYPED_TEST_CASE(ChromiumEnvMultiPlatformTests,
125                ChromiumEnvMultiPlatformTestsTypes);
126
127TYPED_TEST(ChromiumEnvMultiPlatformTests, DirectorySyncing) {
128  MyEnv<TypeParam> env;
129
130  base::ScopedTempDir dir;
131  ASSERT_TRUE(dir.CreateUniqueTempDir());
132  base::FilePath dir_path = dir.path();
133  std::string some_data = "some data";
134  Slice data = some_data;
135
136  std::string manifest_file_name = leveldb_env::FilePathToString(
137      dir_path.Append(FILE_PATH_LITERAL("MANIFEST-001")));
138  WritableFile* manifest_file_ptr;
139  Status s = env.NewWritableFile(manifest_file_name, &manifest_file_ptr);
140  EXPECT_TRUE(s.ok());
141  scoped_ptr<WritableFile> manifest_file(manifest_file_ptr);
142  manifest_file->Append(data);
143  EXPECT_EQ(0, env.directory_syncs());
144  manifest_file->Append(data);
145  EXPECT_EQ(0, env.directory_syncs());
146
147  std::string sst_file_name = leveldb_env::FilePathToString(
148      dir_path.Append(FILE_PATH_LITERAL("000003.sst")));
149  WritableFile* sst_file_ptr;
150  s = env.NewWritableFile(sst_file_name, &sst_file_ptr);
151  EXPECT_TRUE(s.ok());
152  scoped_ptr<WritableFile> sst_file(sst_file_ptr);
153  sst_file->Append(data);
154  EXPECT_EQ(0, env.directory_syncs());
155
156  manifest_file->Append(data);
157  EXPECT_EQ(1, env.directory_syncs());
158  manifest_file->Append(data);
159  EXPECT_EQ(1, env.directory_syncs());
160}
161
162int CountFilesWithExtension(const base::FilePath& dir,
163                            const base::FilePath::StringType& extension) {
164  int matching_files = 0;
165  base::FileEnumerator dir_reader(
166      dir, false, base::FileEnumerator::FILES);
167  for (base::FilePath fname = dir_reader.Next(); !fname.empty();
168       fname = dir_reader.Next()) {
169    if (fname.MatchesExtension(extension))
170      matching_files++;
171  }
172  return matching_files;
173}
174
175bool GetFirstLDBFile(const base::FilePath& dir, base::FilePath* ldb_file) {
176  base::FileEnumerator dir_reader(
177      dir, false, base::FileEnumerator::FILES);
178  for (base::FilePath fname = dir_reader.Next(); !fname.empty();
179       fname = dir_reader.Next()) {
180    if (fname.MatchesExtension(FPL(".ldb"))) {
181      *ldb_file = fname;
182      return true;
183    }
184  }
185  return false;
186}
187
188TEST(ChromiumEnv, BackupTables) {
189  Options options;
190  options.create_if_missing = true;
191  options.env = IDBEnv();
192
193  base::ScopedTempDir scoped_temp_dir;
194  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
195  base::FilePath dir = scoped_temp_dir.path();
196
197  DB* db;
198  Status status = DB::Open(options, dir.AsUTF8Unsafe(), &db);
199  EXPECT_TRUE(status.ok()) << status.ToString();
200  status = db->Put(WriteOptions(), "key", "value");
201  EXPECT_TRUE(status.ok()) << status.ToString();
202  Slice a = "a";
203  Slice z = "z";
204  db->CompactRange(&a, &z);
205  int ldb_files = CountFilesWithExtension(dir, FPL(".ldb"));
206  int bak_files = CountFilesWithExtension(dir, FPL(".bak"));
207  EXPECT_GT(ldb_files, 0);
208  EXPECT_EQ(ldb_files, bak_files);
209  base::FilePath ldb_file;
210  EXPECT_TRUE(GetFirstLDBFile(dir, &ldb_file));
211  delete db;
212  EXPECT_TRUE(base::DeleteFile(ldb_file, false));
213  EXPECT_EQ(ldb_files - 1, CountFilesWithExtension(dir, FPL(".ldb")));
214
215  // The ldb file deleted above should be restored in Open.
216  status = leveldb::DB::Open(options, dir.AsUTF8Unsafe(), &db);
217  EXPECT_TRUE(status.ok()) << status.ToString();
218  std::string value;
219  status = db->Get(ReadOptions(), "key", &value);
220  EXPECT_TRUE(status.ok()) << status.ToString();
221  EXPECT_EQ("value", value);
222  delete db;
223
224  // Ensure that deleting an ldb file also deletes its backup.
225  int orig_ldb_files = CountFilesWithExtension(dir, FPL(".ldb"));
226  EXPECT_GT(ldb_files, 0);
227  EXPECT_EQ(ldb_files, bak_files);
228  EXPECT_TRUE(GetFirstLDBFile(dir, &ldb_file));
229  options.env->DeleteFile(ldb_file.AsUTF8Unsafe());
230  ldb_files = CountFilesWithExtension(dir, FPL(".ldb"));
231  bak_files = CountFilesWithExtension(dir, FPL(".bak"));
232  EXPECT_EQ(orig_ldb_files - 1, ldb_files);
233  EXPECT_EQ(bak_files, ldb_files);
234}
235
236TEST(ChromiumEnv, GetChildrenEmptyDir) {
237  base::ScopedTempDir scoped_temp_dir;
238  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
239  base::FilePath dir = scoped_temp_dir.path();
240
241  Env* env = IDBEnv();
242  std::vector<std::string> result;
243  leveldb::Status status = env->GetChildren(dir.AsUTF8Unsafe(), &result);
244  EXPECT_TRUE(status.ok());
245  EXPECT_EQ(0U, result.size());
246}
247
248TEST(ChromiumEnv, GetChildrenPriorResults) {
249  base::ScopedTempDir scoped_temp_dir;
250  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
251  base::FilePath dir = scoped_temp_dir.path();
252
253  base::FilePath new_file_dir = dir.Append(FPL("tmp_file"));
254  FILE* f = fopen(new_file_dir.AsUTF8Unsafe().c_str(), "w");
255  if (f) {
256    fputs("Temp file contents", f);
257    fclose(f);
258  }
259
260  Env* env = IDBEnv();
261  std::vector<std::string> result;
262  leveldb::Status status = env->GetChildren(dir.AsUTF8Unsafe(), &result);
263  EXPECT_TRUE(status.ok());
264  EXPECT_EQ(1U, result.size());
265
266  // And a second time should also return one result
267  status = env->GetChildren(dir.AsUTF8Unsafe(), &result);
268  EXPECT_TRUE(status.ok());
269  EXPECT_EQ(1U, result.size());
270}
271
272int main(int argc, char** argv) { return base::TestSuite(argc, argv).Run(); }
273