diff options
Diffstat (limited to 'Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm')
-rw-r--r-- | Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm new file mode 100644 index 0000000..704f8c6 --- /dev/null +++ b/Firestore/Example/Tests/Local/FSTLevelDBTransactionTests.mm @@ -0,0 +1,274 @@ +/* + * 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 |