parent
76d54530d3
commit
9ab0132360
@ -0,0 +1,367 @@ |
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "rocksdb/env.h" |
||||
#include "rocksdb/status.h" |
||||
#include "port/port.h" |
||||
#include "util/mutexlock.h" |
||||
#include <map> |
||||
#include <string.h> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
namespace { |
||||
|
||||
class MemFile { |
||||
public: |
||||
enum Mode { |
||||
READ = 0, |
||||
WRITE = 1, |
||||
}; |
||||
|
||||
MemFile(Mode mode) : mode_(mode), refs_(0) {} |
||||
|
||||
void Ref() { |
||||
MutexLock lock(&mutex_); |
||||
++refs_; |
||||
} |
||||
|
||||
void Unref() { |
||||
bool do_delete = false; |
||||
{ |
||||
MutexLock lock(&mutex_); |
||||
--refs_; |
||||
assert(refs_ >= 0); |
||||
if (refs_ <= 0) { |
||||
do_delete = true; |
||||
} |
||||
} |
||||
|
||||
if (do_delete) { |
||||
delete this; |
||||
} |
||||
} |
||||
|
||||
void SetMode(Mode mode) { |
||||
mode_ = mode; |
||||
} |
||||
|
||||
uint64_t Size() const { return data_.size(); } |
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const { |
||||
assert(mode_ == READ); |
||||
if (offset > Size()) { |
||||
return Status::IOError("Offset greater than file size."); |
||||
} |
||||
const uint64_t available = Size() - offset; |
||||
if (n > available) { |
||||
n = available; |
||||
} |
||||
if (n == 0) { |
||||
*result = Slice(); |
||||
return Status::OK(); |
||||
} |
||||
if (scratch) { |
||||
memcpy(scratch, &(data_[offset]), n); |
||||
*result = Slice(scratch, n); |
||||
} else { |
||||
*result = Slice(&(data_[offset]), n); |
||||
} |
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status Append(const Slice& data) { |
||||
assert(mode_ == WRITE); |
||||
data_.append(data.data(), data.size()); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
Status Fsync() { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
private: |
||||
// Private since only Unref() should be used to delete it.
|
||||
~MemFile() { |
||||
assert(refs_ == 0); |
||||
} |
||||
|
||||
// No copying allowed.
|
||||
MemFile(const MemFile&); |
||||
void operator=(const MemFile&); |
||||
|
||||
Mode mode_; |
||||
port::Mutex mutex_; |
||||
int refs_; // Protected by mutex_;
|
||||
|
||||
std::string data_; |
||||
}; |
||||
|
||||
class SequentialFileImpl : public SequentialFile { |
||||
public: |
||||
explicit SequentialFileImpl(MemFile* file) : file_(file), pos_(0) { |
||||
file_->Ref(); |
||||
} |
||||
|
||||
~SequentialFileImpl() { |
||||
file_->Unref(); |
||||
} |
||||
|
||||
virtual Status Read(size_t n, Slice* result, char* scratch) { |
||||
Status s = file_->Read(pos_, n, result, scratch); |
||||
if (s.ok()) { |
||||
pos_ += result->size(); |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
virtual Status Skip(uint64_t n) { |
||||
if (pos_ > file_->Size()) { |
||||
return Status::IOError("pos_ > file_->Size()"); |
||||
} |
||||
const size_t available = file_->Size() - pos_; |
||||
if (n > available) { |
||||
n = available; |
||||
} |
||||
pos_ += n; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
private: |
||||
MemFile* file_; |
||||
size_t pos_; |
||||
}; |
||||
|
||||
class RandomAccessFileImpl : public RandomAccessFile { |
||||
public: |
||||
explicit RandomAccessFileImpl(MemFile* file) : file_(file) { |
||||
file_->Ref(); |
||||
} |
||||
|
||||
~RandomAccessFileImpl() { |
||||
file_->Unref(); |
||||
} |
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result, |
||||
char* scratch) const { |
||||
return file_->Read(offset, n, result, scratch); |
||||
} |
||||
|
||||
private: |
||||
MemFile* file_; |
||||
}; |
||||
|
||||
class WritableFileImpl : public WritableFile { |
||||
public: |
||||
WritableFileImpl(MemFile* file) : file_(file) { |
||||
file_->Ref(); |
||||
} |
||||
|
||||
~WritableFileImpl() { |
||||
file_->Unref(); |
||||
} |
||||
|
||||
virtual Status Append(const Slice& data) { |
||||
return file_->Append(data); |
||||
} |
||||
|
||||
virtual Status Close() { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status Flush() { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status Sync() { |
||||
return file_->Fsync(); |
||||
} |
||||
|
||||
private: |
||||
MemFile* file_; |
||||
}; |
||||
|
||||
class TestMemDirectory : public Directory { |
||||
public: |
||||
virtual Status Fsync() { return Status::OK(); } |
||||
}; |
||||
|
||||
class TestMemEnv : public EnvWrapper { |
||||
public: |
||||
explicit TestMemEnv(Env* base_env) : EnvWrapper(base_env) { } |
||||
|
||||
virtual ~TestMemEnv() { |
||||
for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i){ |
||||
i->second->Unref(); |
||||
} |
||||
} |
||||
|
||||
// Partial implementation of the Env interface.
|
||||
virtual Status NewSequentialFile(const std::string& fname, |
||||
unique_ptr<SequentialFile>* result, |
||||
const EnvOptions& soptions) { |
||||
MutexLock lock(&mutex_); |
||||
if (file_map_.find(fname) == file_map_.end()) { |
||||
*result = NULL; |
||||
return Status::IOError(fname, "File not found"); |
||||
} |
||||
auto* f = file_map_[fname]; |
||||
f->SetMode(MemFile::READ); |
||||
result->reset(new SequentialFileImpl(f)); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status NewRandomAccessFile(const std::string& fname, |
||||
unique_ptr<RandomAccessFile>* result, |
||||
const EnvOptions& soptions) { |
||||
MutexLock lock(&mutex_); |
||||
if (file_map_.find(fname) == file_map_.end()) { |
||||
*result = NULL; |
||||
return Status::IOError(fname, "File not found"); |
||||
} |
||||
auto* f = file_map_[fname]; |
||||
f->SetMode(MemFile::READ); |
||||
result->reset(new RandomAccessFileImpl(f)); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status NewWritableFile(const std::string& fname, |
||||
unique_ptr<WritableFile>* result, |
||||
const EnvOptions& soptions) { |
||||
MutexLock lock(&mutex_); |
||||
if (file_map_.find(fname) != file_map_.end()) { |
||||
DeleteFileInternal(fname); |
||||
} |
||||
MemFile* file = new MemFile(MemFile::WRITE); |
||||
file->Ref(); |
||||
file_map_[fname] = file; |
||||
|
||||
result->reset(new WritableFileImpl(file)); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status NewRandomRWFile(const std::string& fname, |
||||
unique_ptr<RandomRWFile>* result, |
||||
const EnvOptions& options) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status NewDirectory(const std::string& name, |
||||
unique_ptr<Directory>* result) { |
||||
result->reset(new TestMemDirectory()); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual bool FileExists(const std::string& fname) { |
||||
MutexLock lock(&mutex_); |
||||
return file_map_.find(fname) != file_map_.end(); |
||||
} |
||||
|
||||
virtual Status GetChildren(const std::string& dir, |
||||
std::vector<std::string>* result) { |
||||
MutexLock lock(&mutex_); |
||||
result->clear(); |
||||
|
||||
for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i){ |
||||
const std::string& filename = i->first; |
||||
|
||||
if (filename.size() >= dir.size() + 1 && filename[dir.size()] == '/' && |
||||
Slice(filename).starts_with(Slice(dir))) { |
||||
result->push_back(filename.substr(dir.size() + 1)); |
||||
} |
||||
} |
||||
|
||||
return Status::OK(); |
||||
} |
||||
|
||||
void DeleteFileInternal(const std::string& fname) { |
||||
if (file_map_.find(fname) == file_map_.end()) { |
||||
return; |
||||
} |
||||
|
||||
file_map_[fname]->Unref(); |
||||
file_map_.erase(fname); |
||||
} |
||||
|
||||
virtual Status DeleteFile(const std::string& fname) { |
||||
MutexLock lock(&mutex_); |
||||
if (file_map_.find(fname) == file_map_.end()) { |
||||
return Status::IOError(fname, "File not found"); |
||||
} |
||||
|
||||
DeleteFileInternal(fname); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status CreateDir(const std::string& dirname) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status CreateDirIfMissing(const std::string& dirname) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status DeleteDir(const std::string& dirname) { |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) { |
||||
MutexLock lock(&mutex_); |
||||
if (file_map_.find(fname) == file_map_.end()) { |
||||
return Status::IOError(fname, "File not found"); |
||||
} |
||||
|
||||
*file_size = file_map_[fname]->Size(); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status GetFileModificationTime(const std::string& fname, |
||||
uint64_t* time) { |
||||
return Status::NotSupported("getFileMTime", "Not supported in MemEnv"); |
||||
} |
||||
|
||||
virtual Status RenameFile(const std::string& src, |
||||
const std::string& target) { |
||||
MutexLock lock(&mutex_); |
||||
if (file_map_.find(src) == file_map_.end()) { |
||||
return Status::IOError(src, "File not found"); |
||||
} |
||||
|
||||
DeleteFileInternal(target); |
||||
file_map_[target] = file_map_[src]; |
||||
file_map_.erase(src); |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status LockFile(const std::string& fname, FileLock** lock) { |
||||
*lock = new FileLock; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status UnlockFile(FileLock* lock) { |
||||
delete lock; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
virtual Status GetTestDirectory(std::string* path) { |
||||
*path = "/test"; |
||||
return Status::OK(); |
||||
} |
||||
|
||||
private: |
||||
// Map from filenames to MemFile objects, representing a simple file system.
|
||||
typedef std::map<std::string, MemFile*> FileSystem; |
||||
port::Mutex mutex_; |
||||
FileSystem file_map_; // Protected by mutex_.
|
||||
}; |
||||
|
||||
} // namespace
|
||||
|
||||
Env* NewTestMemEnv(Env* base_env) { |
||||
return new TestMemEnv(base_env); |
||||
} |
||||
|
||||
} // namespace rocksdb
|
@ -0,0 +1,231 @@ |
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_impl.h" |
||||
#include "rocksdb/db.h" |
||||
#include "rocksdb/env.h" |
||||
#include "util/testharness.h" |
||||
#include <memory> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
namespace rocksdb { |
||||
|
||||
class MemEnvTest { |
||||
public: |
||||
Env* env_; |
||||
const EnvOptions soptions_; |
||||
|
||||
MemEnvTest() |
||||
: env_(NewMemEnv(Env::Default())) { |
||||
} |
||||
~MemEnvTest() { |
||||
delete env_; |
||||
} |
||||
}; |
||||
|
||||
TEST(MemEnvTest, Basics) { |
||||
uint64_t file_size; |
||||
unique_ptr<WritableFile> writable_file; |
||||
std::vector<std::string> children; |
||||
|
||||
ASSERT_OK(env_->CreateDir("/dir")); |
||||
|
||||
// Check that the directory is empty.
|
||||
ASSERT_TRUE(!env_->FileExists("/dir/non_existent")); |
||||
ASSERT_TRUE(!env_->GetFileSize("/dir/non_existent", &file_size).ok()); |
||||
ASSERT_OK(env_->GetChildren("/dir", &children)); |
||||
ASSERT_EQ(0U, children.size()); |
||||
|
||||
// Create a file.
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); |
||||
writable_file.reset(); |
||||
|
||||
// Check that the file exists.
|
||||
ASSERT_TRUE(env_->FileExists("/dir/f")); |
||||
ASSERT_OK(env_->GetFileSize("/dir/f", &file_size)); |
||||
ASSERT_EQ(0U, file_size); |
||||
ASSERT_OK(env_->GetChildren("/dir", &children)); |
||||
ASSERT_EQ(1U, children.size()); |
||||
ASSERT_EQ("f", children[0]); |
||||
|
||||
// Write to the file.
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); |
||||
ASSERT_OK(writable_file->Append("abc")); |
||||
writable_file.reset(); |
||||
|
||||
// Check for expected size.
|
||||
ASSERT_OK(env_->GetFileSize("/dir/f", &file_size)); |
||||
ASSERT_EQ(3U, file_size); |
||||
|
||||
// Check that renaming works.
|
||||
ASSERT_TRUE(!env_->RenameFile("/dir/non_existent", "/dir/g").ok()); |
||||
ASSERT_OK(env_->RenameFile("/dir/f", "/dir/g")); |
||||
ASSERT_TRUE(!env_->FileExists("/dir/f")); |
||||
ASSERT_TRUE(env_->FileExists("/dir/g")); |
||||
ASSERT_OK(env_->GetFileSize("/dir/g", &file_size)); |
||||
ASSERT_EQ(3U, file_size); |
||||
|
||||
// Check that opening non-existent file fails.
|
||||
unique_ptr<SequentialFile> seq_file; |
||||
unique_ptr<RandomAccessFile> rand_file; |
||||
ASSERT_TRUE(!env_->NewSequentialFile("/dir/non_existent", &seq_file, |
||||
soptions_).ok()); |
||||
ASSERT_TRUE(!seq_file); |
||||
ASSERT_TRUE(!env_->NewRandomAccessFile("/dir/non_existent", &rand_file, |
||||
soptions_).ok()); |
||||
ASSERT_TRUE(!rand_file); |
||||
|
||||
// Check that deleting works.
|
||||
ASSERT_TRUE(!env_->DeleteFile("/dir/non_existent").ok()); |
||||
ASSERT_OK(env_->DeleteFile("/dir/g")); |
||||
ASSERT_TRUE(!env_->FileExists("/dir/g")); |
||||
ASSERT_OK(env_->GetChildren("/dir", &children)); |
||||
ASSERT_EQ(0U, children.size()); |
||||
ASSERT_OK(env_->DeleteDir("/dir")); |
||||
} |
||||
|
||||
TEST(MemEnvTest, ReadWrite) { |
||||
unique_ptr<WritableFile> writable_file; |
||||
unique_ptr<SequentialFile> seq_file; |
||||
unique_ptr<RandomAccessFile> rand_file; |
||||
Slice result; |
||||
char scratch[100]; |
||||
|
||||
ASSERT_OK(env_->CreateDir("/dir")); |
||||
|
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); |
||||
ASSERT_OK(writable_file->Append("hello ")); |
||||
ASSERT_OK(writable_file->Append("world")); |
||||
writable_file.reset(); |
||||
|
||||
// Read sequentially.
|
||||
ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_)); |
||||
ASSERT_OK(seq_file->Read(5, &result, scratch)); // Read "hello".
|
||||
ASSERT_EQ(0, result.compare("hello")); |
||||
ASSERT_OK(seq_file->Skip(1)); |
||||
ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Read "world".
|
||||
ASSERT_EQ(0, result.compare("world")); |
||||
ASSERT_OK(seq_file->Read(1000, &result, scratch)); // Try reading past EOF.
|
||||
ASSERT_EQ(0U, result.size()); |
||||
ASSERT_OK(seq_file->Skip(100)); // Try to skip past end of file.
|
||||
ASSERT_OK(seq_file->Read(1000, &result, scratch)); |
||||
ASSERT_EQ(0U, result.size()); |
||||
|
||||
// Random reads.
|
||||
ASSERT_OK(env_->NewRandomAccessFile("/dir/f", &rand_file, soptions_)); |
||||
ASSERT_OK(rand_file->Read(6, 5, &result, scratch)); // Read "world".
|
||||
ASSERT_EQ(0, result.compare("world")); |
||||
ASSERT_OK(rand_file->Read(0, 5, &result, scratch)); // Read "hello".
|
||||
ASSERT_EQ(0, result.compare("hello")); |
||||
ASSERT_OK(rand_file->Read(10, 100, &result, scratch)); // Read "d".
|
||||
ASSERT_EQ(0, result.compare("d")); |
||||
|
||||
// Too high offset.
|
||||
ASSERT_TRUE(!rand_file->Read(1000, 5, &result, scratch).ok()); |
||||
} |
||||
|
||||
TEST(MemEnvTest, Locks) { |
||||
FileLock* lock; |
||||
|
||||
// These are no-ops, but we test they return success.
|
||||
ASSERT_OK(env_->LockFile("some file", &lock)); |
||||
ASSERT_OK(env_->UnlockFile(lock)); |
||||
} |
||||
|
||||
TEST(MemEnvTest, Misc) { |
||||
std::string test_dir; |
||||
ASSERT_OK(env_->GetTestDirectory(&test_dir)); |
||||
ASSERT_TRUE(!test_dir.empty()); |
||||
|
||||
unique_ptr<WritableFile> writable_file; |
||||
ASSERT_OK(env_->NewWritableFile("/a/b", &writable_file, soptions_)); |
||||
|
||||
// These are no-ops, but we test they return success.
|
||||
ASSERT_OK(writable_file->Sync()); |
||||
ASSERT_OK(writable_file->Flush()); |
||||
ASSERT_OK(writable_file->Close()); |
||||
writable_file.reset(); |
||||
} |
||||
|
||||
TEST(MemEnvTest, LargeWrite) { |
||||
const size_t kWriteSize = 300 * 1024; |
||||
char* scratch = new char[kWriteSize * 2]; |
||||
|
||||
std::string write_data; |
||||
for (size_t i = 0; i < kWriteSize; ++i) { |
||||
write_data.append(1, static_cast<char>(i)); |
||||
} |
||||
|
||||
unique_ptr<WritableFile> writable_file; |
||||
ASSERT_OK(env_->NewWritableFile("/dir/f", &writable_file, soptions_)); |
||||
ASSERT_OK(writable_file->Append("foo")); |
||||
ASSERT_OK(writable_file->Append(write_data)); |
||||
writable_file.reset(); |
||||
|
||||
unique_ptr<SequentialFile> seq_file; |
||||
Slice result; |
||||
ASSERT_OK(env_->NewSequentialFile("/dir/f", &seq_file, soptions_)); |
||||
ASSERT_OK(seq_file->Read(3, &result, scratch)); // Read "foo".
|
||||
ASSERT_EQ(0, result.compare("foo")); |
||||
|
||||
size_t read = 0; |
||||
std::string read_data; |
||||
while (read < kWriteSize) { |
||||
ASSERT_OK(seq_file->Read(kWriteSize - read, &result, scratch)); |
||||
read_data.append(result.data(), result.size()); |
||||
read += result.size(); |
||||
} |
||||
ASSERT_TRUE(write_data == read_data); |
||||
delete [] scratch; |
||||
} |
||||
|
||||
TEST(MemEnvTest, DBTest) { |
||||
Options options; |
||||
options.create_if_missing = true; |
||||
options.env = env_; |
||||
DB* db; |
||||
|
||||
const Slice keys[] = {Slice("aaa"), Slice("bbb"), Slice("ccc")}; |
||||
const Slice vals[] = {Slice("foo"), Slice("bar"), Slice("baz")}; |
||||
|
||||
ASSERT_OK(DB::Open(options, "/dir/db", &db)); |
||||
for (size_t i = 0; i < 3; ++i) { |
||||
ASSERT_OK(db->Put(WriteOptions(), keys[i], vals[i])); |
||||
} |
||||
|
||||
for (size_t i = 0; i < 3; ++i) { |
||||
std::string res; |
||||
ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); |
||||
ASSERT_TRUE(res == vals[i]); |
||||
} |
||||
|
||||
Iterator* iterator = db->NewIterator(ReadOptions()); |
||||
iterator->SeekToFirst(); |
||||
for (size_t i = 0; i < 3; ++i) { |
||||
ASSERT_TRUE(iterator->Valid()); |
||||
ASSERT_TRUE(keys[i] == iterator->key()); |
||||
ASSERT_TRUE(vals[i] == iterator->value()); |
||||
iterator->Next(); |
||||
} |
||||
ASSERT_TRUE(!iterator->Valid()); |
||||
delete iterator; |
||||
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db); |
||||
ASSERT_OK(dbi->TEST_FlushMemTable()); |
||||
|
||||
for (size_t i = 0; i < 3; ++i) { |
||||
std::string res; |
||||
ASSERT_OK(db->Get(ReadOptions(), keys[i], &res)); |
||||
ASSERT_TRUE(res == vals[i]); |
||||
} |
||||
|
||||
delete db; |
||||
} |
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
return rocksdb::test::RunAllTests(); |
||||
} |
Loading…
Reference in new issue