Files
firebase-ios-sdk/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm
T
Greg Soltis 9060078609 Implements transactions for leveldb (#881)
* Start work on leveldb transactions

* Style

* Working API. Not plumbed in yet

* Move files into correct place

* Wrangling file locations and associations

* Tests pass

* Add some comments

* style

* Fix copyright

* Rewrite iterator internals to handle deletion-while-iterating. Also add tests for same

* Switch to strings instead of slices

* Style

* More style fixes

* Response to feedback before updating docs

* Style

* Add comment

* Initialize version_

* Satisfy the linter

* Fix style

* snake_case

* More snakes

* LevelDBTransaction -> LevelDbTransaction
2018-03-15 16:52:13 -07:00

275 lines
8.4 KiB
Plaintext

/*
* Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Firestore/core/src/firebase/firestore/local/leveldb_transaction.h"
#import <XCTest/XCTest.h>
#include <absl/strings/string_view.h>
#include <leveldb/db.h>
#import "Firestore/Example/Tests/Local/FSTPersistenceTestHelpers.h"
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
NS_ASSUME_NONNULL_BEGIN
using leveldb::DB;
using leveldb::Options;
using leveldb::ReadOptions;
using leveldb::WriteOptions;
using leveldb::Status;
using firebase::firestore::local::LevelDbTransaction;
@interface FSTLevelDBTransactionTests : XCTestCase
@end
@implementation FSTLevelDBTransactionTests {
std::shared_ptr<DB> _db;
}
- (void)setUp {
Options options;
options.error_if_exists = true;
options.create_if_missing = true;
NSString *dir = [FSTPersistenceTestHelpers levelDBDir];
DB *db;
Status status = DB::Open(options, [dir UTF8String], &db);
XCTAssert(status.ok(), @"Failed to create db: %s", status.ToString().c_str());
_db.reset(db);
}
- (void)tearDown {
_db.reset();
}
- (void)testCreateTransaction {
LevelDbTransaction transaction(_db.get());
std::string key = "key1";
transaction.Put(key, "value");
std::unique_ptr<LevelDbTransaction::Iterator> iter(transaction.NewIterator());
iter->Seek(key);
XCTAssertEqual(key, iter->key());
iter->Next();
XCTAssertFalse(iter->Valid());
}
- (void)testCanReadCommittedAndMutations {
const std::string committed_key1 = "c_key1";
const std::string committed_value1 = "c_value1";
const WriteOptions &writeOptions = LevelDbTransaction::DefaultWriteOptions();
// add two things committed, mutate one, add another mutation
// verify you can get the original committed, the mutation, and the addition
Status status = _db->Put(writeOptions, committed_key1, committed_value1);
XCTAssertTrue(status.ok());
const std::string committed_key2 = "c_key2";
const std::string committed_value2 = "c_value2";
status = _db->Put(writeOptions, committed_key2, committed_value2);
XCTAssertTrue(status.ok());
LevelDbTransaction transaction(_db.get());
const std::string mutation_key1 = "m_key1";
const std::string mutation_value1 = "m_value1";
transaction.Put(mutation_key1, mutation_value1);
const std::string mutation_key2 = committed_key2;
const std::string mutation_value2 = "m_value2";
transaction.Put(mutation_key2, mutation_value2);
std::string value;
status = transaction.Get(committed_key1, &value);
XCTAssertTrue(status.ok());
XCTAssertEqual(value, committed_value1);
status = transaction.Get(mutation_key1, &value);
XCTAssertTrue(status.ok());
XCTAssertEqual(value, mutation_value1);
status = transaction.Get(committed_key2, &value);
XCTAssertTrue(status.ok());
XCTAssertEqual(value, mutation_value2);
}
- (void)testDeleteCommitted {
// add something committed, delete it, verify you can't read it
for (int i = 0; i < 3; ++i) {
Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
"value_" + std::to_string(i));
XCTAssertTrue(status.ok());
}
LevelDbTransaction transaction(_db.get());
transaction.Put("key_1", "new_value");
std::string value;
Status status = transaction.Get("key_1", &value);
XCTAssertTrue(status.ok());
XCTAssertEqual(value, "new_value");
transaction.Delete("key_1");
status = transaction.Get("key_1", &value);
XCTAssertTrue(status.IsNotFound());
LevelDbTransaction::Iterator iter(&transaction);
iter.Seek("");
XCTAssertEqual(iter.key(), "key_0");
iter.Next();
XCTAssertEqual(iter.key(), "key_2");
iter.Next();
XCTAssertFalse(iter.Valid());
}
- (void)testMutateDeleted {
// delete something, then mutate it, then read it.
// Also include an actual deletion
for (int i = 0; i < 4; ++i) {
Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
"value_" + std::to_string(i));
XCTAssertTrue(status.ok());
}
std::string value;
LevelDbTransaction transaction(_db.get());
transaction.Delete("key_1");
Status status = transaction.Get("key_1", &value);
XCTAssertTrue(status.IsNotFound());
transaction.Put("key_1", "new_value");
status = transaction.Get("key_1", &value);
XCTAssertTrue(status.ok());
XCTAssertEqual(value, "new_value");
transaction.Delete("key_3");
LevelDbTransaction::Iterator iter(&transaction);
iter.Seek("");
XCTAssertEqual(iter.key(), "key_0");
iter.Next();
XCTAssertEqual(iter.key(), "key_1");
XCTAssertEqual(iter.value(), "new_value");
iter.Next();
XCTAssertEqual(iter.key(), "key_2");
iter.Next();
XCTAssertFalse(iter.Valid());
// Commit, then check underlying db.
transaction.Commit();
const ReadOptions &readOptions = LevelDbTransaction::DefaultReadOptions();
status = _db->Get(readOptions, "key_0", &value);
XCTAssertTrue(status.ok());
XCTAssertEqual("value_0", value);
status = _db->Get(readOptions, "key_1", &value);
XCTAssertTrue(status.ok());
XCTAssertEqual("new_value", value);
status = _db->Get(readOptions, "key_2", &value);
XCTAssertTrue(status.ok());
XCTAssertEqual("value_2", value);
status = _db->Get(readOptions, "key_3", &value);
XCTAssertTrue(status.IsNotFound());
}
- (void)testProtobufSupport {
LevelDbTransaction transaction(_db.get());
FSTPBTarget *target = [FSTPBTarget message];
target.targetId = 1;
target.lastListenSequenceNumber = 2;
std::string key("theKey");
transaction.Put(key, target);
std::string value;
Status status = transaction.Get("theKey", &value);
NSData *result =
[[NSData alloc] initWithBytesNoCopy:(void *)value.data() length:value.size() freeWhenDone:NO];
NSError *error;
FSTPBTarget *parsed = [FSTPBTarget parseFromData:result error:&error];
XCTAssertNil(error);
XCTAssertTrue([target isEqual:parsed]);
}
- (void)testCanIterateAndDelete {
LevelDbTransaction transaction(_db.get());
for (int i = 0; i < 4; ++i) {
transaction.Put("key_" + std::to_string(i), "value_" + std::to_string(i));
}
std::unique_ptr<LevelDbTransaction::Iterator> it(transaction.NewIterator());
it->Seek("key_0");
for (int i = 0; i < 4; ++i) {
XCTAssertTrue(it->Valid());
const absl::string_view &key = it->key();
std::string expected = "key_" + std::to_string(i);
XCTAssertEqual(expected, key);
transaction.Delete(key);
it->Next();
}
}
- (void)testCanIterateFromDeletionToCommitted {
// Write keys key_0 and key_1
for (int i = 0; i < 2; ++i) {
Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
"value_" + std::to_string(i));
XCTAssertTrue(status.ok());
}
// Create a transaction, iterate, deleting key_0. Verify we still iterate key_1.
LevelDbTransaction transaction(_db.get());
std::unique_ptr<LevelDbTransaction::Iterator> it(transaction.NewIterator());
it->Seek("key_0");
XCTAssertTrue(it->Valid());
XCTAssertEqual("key_0", it->key());
transaction.Delete("key_0");
it->Next();
XCTAssertTrue(it->Valid());
XCTAssertEqual("key_1", it->key());
it->Next();
XCTAssertFalse(it->Valid());
}
- (void)testDeletingAheadOfAnIterator {
// Write keys
for (int i = 0; i < 4; ++i) {
Status status = _db->Put(LevelDbTransaction::DefaultWriteOptions(), "key_" + std::to_string(i),
"value_" + std::to_string(i));
XCTAssertTrue(status.ok());
}
// Create a transaction, iterate to key_1, delete key_2. Verify we still iterate key_3.
LevelDbTransaction transaction(_db.get());
std::unique_ptr<LevelDbTransaction::Iterator> it(transaction.NewIterator());
it->Seek("key_0");
XCTAssertTrue(it->Valid());
XCTAssertEqual("key_0", it->key());
it->Next();
XCTAssertTrue(it->Valid());
XCTAssertEqual("key_1", it->key());
transaction.Delete("key_2");
it->Next();
XCTAssertTrue(it->Valid());
XCTAssertEqual("key_3", it->key());
XCTAssertTrue(it->Valid());
it->Next();
XCTAssertFalse(it->Valid());
}
@end
NS_ASSUME_NONNULL_END