blob: 87a562633001424cfa3e7b5acfaeb598255fc582 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import Foundation
import XCTest
@testable import SyncbaseCore
class BasicDatabaseTests: XCTestCase {
// MARK: Basic test helpers
func withTestDb(runBlock: Database throws -> Void) {
withTestDbAsync { (db, cleanup) in
defer { cleanup() }
try runBlock(db)
}
}
func withTestDbAsync(runBlock: (db: Database, cleanup: Void -> Void) throws -> Void) {
do {
// Randomize the name to prevent conflicts between tests
let dbName = "test\(NSUUID().UUIDString)".stringByReplacingOccurrencesOfString("-", withString: "")
let db = try Syncbase.instance.database(dbName)
let cleanup = {
do {
print("Destroying db \(db)")
try db.destroy()
XCTAssertFalse(try db.exists(), "Database shouldn't exist after being destroyed")
} catch let e {
log.warning("Unable to delete db: \(e)")
}
}
do {
print("Got db \(db)")
XCTAssertFalse(try db.exists(), "Database shouldn't exist before being created")
print("Creating db \(db)")
try db.create(nil)
XCTAssertTrue(try db.exists(), "Database should exist after being created")
// Always delete the db at the end to prevent conflicts between tests
try runBlock(db: db, cleanup: cleanup)
} catch let e {
XCTFail("Got unexpected exception: \(e)")
cleanup()
}
} catch (let e) {
XCTFail("Got unexpected exception: \(e)")
}
}
func withTestCollection(runBlock: (Database, Collection) throws -> Void) {
withTestDb { db in
let collection = try db.collection("collection1")
XCTAssertFalse(try collection.exists())
try collection.create(nil)
XCTAssertTrue(try collection.exists())
try runBlock(db, collection)
try collection.destroy()
XCTAssertFalse(try collection.exists())
}
}
func testDbCreateExistsDestroy() {
withTestDb { db in }
}
func testListDatabases() {
withTestDb { db in
let databases = try Syncbase.instance.listDatabases()
XCTAssertFalse(databases.isEmpty)
if !databases.isEmpty {
XCTAssertTrue(databases.first! == db.databaseId)
}
}
}
func testCollectionCreateExistsDestroy() {
withTestCollection { db, collection in }
}
func testListCollections() {
withTestCollection { db, collection in
let collections = try db.listCollections()
XCTAssertEqual(collections.count, 1)
XCTAssertTrue(collections.first! == collection.collectionId)
}
}
// MARK: Test getting/putting/deleting data
class func testGetPutRow<T: SyncbaseConvertible where T: Equatable>(collection: Collection, key: String, targetValue: T, equals: ((T, T) -> Bool)? = nil) throws {
var value: T? = try collection.get(key)
XCTAssertNil(value, "Row shouldn't exist yet; yet it has value \(value)")
try collection.put(key, value: targetValue)
value = try collection.get(key)
if let eq = equals {
XCTAssertTrue(eq(value!, targetValue), "Value should be defined and \(targetValue)")
} else {
XCTAssertEqual(value!, targetValue, "Value should be defined and \(targetValue)")
}
try collection.delete(key)
value = try collection.get(key)
XCTAssertNil(value, "Deleted row shouldn't exist")
}
func testRawBytesGetPut() {
withTestCollection { db, collection in
let key = "testrow"
try collection.delete(key)
try BasicDatabaseTests.testGetPutRow(collection, key: key, targetValue: NSData())
try BasicDatabaseTests.testGetPutRow(collection, key: key, targetValue: NSJSONSerialization.hackSerializeAnyObject(false))
try BasicDatabaseTests.testGetPutRow(collection, key: key, targetValue: NSJSONSerialization.hackSerializeAnyObject(M_PI))
try BasicDatabaseTests.testGetPutRow(collection, key: key, targetValue: NSJSONSerialization.hackSerializeAnyObject("你好,世界 👠💪🏿"))
try BasicDatabaseTests.testGetPutRow(collection, key: key, targetValue: "\0\0\0".dataUsingEncoding(NSUTF8StringEncoding)!)
if let p = NSBundle.mainBundle().executablePath,
data = NSFileManager.defaultManager().contentsAtPath(p) {
try BasicDatabaseTests.testGetPutRow(collection, key: key, targetValue: data)
}
}
}
func testDeleteRange() {
withTestCollection { db, collection in
// Generate some test data (1 to 4096 in hex)
var data = [String: NSData]()
for i in 1...4096 {
let key = NSString(format: "%x", i) as String
let value = key.dataUsingEncoding(NSUTF8StringEncoding)!
data[key] = value
try collection.put(key, value: value)
}
// Delete single row
try collection.deleteRange(RowRangeSingleRow(row: "9"))
let value: NSData? = try collection.get("9")
XCTAssertNil(value)
// Delete a*
try collection.deleteRange(RowRangePrefix(prefix: "a"))
var stream = try collection.scan(RowRangePrefix(prefix: "a"))
XCTAssertNil(stream.next())
// Delete b-bc
try collection.deleteRange(RowRangeStandard(start: "b", limit: "bc"))
// Get all the keys including bc and after
var keys = Array(data.keys.filter { $0.hasPrefix("b") }).sort()
let bcIdx = keys.indexOf("bc")!
keys = Array(keys.dropFirst(bcIdx))
// Verify that's what's in the db
stream = try collection.scan(RowRangePrefix(prefix: "b"))
for (key, _) in stream {
let targetKey = keys[0]
XCTAssertEqual(key, targetKey)
keys.removeFirst()
}
XCTAssertTrue(keys.isEmpty)
XCTAssertNil(stream.next())
}
}
func testScan() {
withTestCollection { db, collection in
// Generate some test data (1 to 200 in hex)
var data = [String: NSData]()
for i in 1...200 {
let key = NSString(format: "%x", i) as String
let value = key.dataUsingEncoding(NSUTF8StringEncoding)!
data[key] = value
try collection.put(key, value: value)
}
// All rows
var stream = try collection.scan(RowRangeAll())
var keys = Array(data.keys).sort() // lexographic sort
for (key, getValue) in stream {
let value = try getValue() as! NSData
let valueStr = NSString(data: value, encoding: NSUTF8StringEncoding)!
XCTAssertEqual(key, valueStr)
let targetKey = keys[0]
XCTAssertEqual(key, targetKey)
keys.removeFirst()
}
XCTAssertTrue(keys.isEmpty)
XCTAssertNil(stream.next())
// Single Row
stream = try collection.scan(RowRangeSingleRow(row: "a"))
guard let (key, getValue) = stream.next() else {
XCTFail()
return
}
XCTAssertEqual(key, "a")
let value = try getValue() as! NSData
let valueStr = NSString(data: value, encoding: NSUTF8StringEncoding)!
XCTAssertEqual(key, valueStr)
XCTAssertNil(stream.next())
// Doing it again should be ok
XCTAssertNil(stream.next())
// Prefix
stream = try collection.scan(RowRangePrefix(prefix: "8"))
keys = Array(data.keys.filter { $0.hasPrefix("8") }).sort() // lexographic sort
for (key, _) in stream {
let targetKey = keys[0]
XCTAssertEqual(key, targetKey)
keys.removeFirst()
}
XCTAssertTrue(keys.isEmpty)
XCTAssertNil(stream.next())
}
}
// MARK: Test batches
func testBatchCommit() {
withTestDb { db in
let batchDb = try db.beginBatch(nil)
let collection = try batchDb.collection("collection2")
try collection.create(nil)
try collection.put("a", value: NSData())
try collection.put("1", value: NSData())
try collection.put("2", value: NSData())
try batchDb.commit()
do {
let _: NSData? = try collection.get("a")
XCTFail("Should have thrown an UnknownBatch exception")
} catch SyncbaseError.UnknownBatch {
// Expect this to fail since the batch is already commited -- the collection reference
// is now invalid.
} catch {
XCTFail("Should have thrown an UnknownBatch exception")
}
let valueA: NSData? = try db.collection("collection2").get("a")
let value1: NSData? = try db.collection("collection2").get("1")
let value2: NSData? = try db.collection("collection2").get("2")
XCTAssertNotNil(valueA)
XCTAssertNotNil(value1)
XCTAssertNotNil(value2)
}
}
func testBatchAbort() {
withTestDb { db in
let batchDb = try db.beginBatch(nil)
let collection = try batchDb.collection("collection2")
try collection.create(nil)
try collection.put("b", value: NSData())
try collection.put("c", value: NSData())
try batchDb.abort()
do {
let _: NSData? = try collection.get("a")
XCTFail("Should have thrown an UnknownBatch exception")
} catch SyncbaseError.UnknownBatch {
// Expect this to fail since the batch is already commited -- the collection reference
// is now invalid.
} catch {
XCTFail("Should have thrown an UnknownBatch exception")
}
let valueB: NSData? = try db.collection("collection2").get("b")
let valueC: NSData? = try db.collection("collection2").get("c")
XCTAssertNil(valueB)
XCTAssertNil(valueC)
}
}
func testRunInBatchAutoCommit() {
let completed = expectationWithDescription("Completed runInBatch for auto commit")
withTestDbAsync { (db, cleanup) in
Batch.runInBatch(
db: db,
opts: nil,
op: { batchDb in
let collection = try batchDb.collection("collection3")
try collection.create(nil)
try collection.put("a", value: NSData())
},
completionHandler: { err in
XCTAssertNil(err)
do {
let collection = try db.collection("collection3")
let value: NSData? = try collection.get("a")
XCTAssertNotNil(value)
} catch let e {
XCTFail("Unexpected error: \(e)")
}
cleanup()
completed.fulfill()
})
}
waitForExpectationsWithTimeout(2) { XCTAssertNil($0) }
}
func testRunInBatchAbort() {
let completed = expectationWithDescription("Completed runInBatch for abort")
withTestDbAsync { (db, cleanup) in
Batch.runInBatch(
db: db,
opts: nil,
op: { batchDb in
let collection = try batchDb.collection("collection4")
try collection.create(nil)
try collection.put("a", value: NSData())
try batchDb.abort()
},
completionHandler: { err in
XCTAssertNil(err)
do {
let collection = try db.collection("collection4")
let value: NSData? = try collection.get("a")
XCTAssertNil(value)
} catch let e {
XCTFail("Unexpected error: \(e)")
}
cleanup()
completed.fulfill()
})
}
waitForExpectationsWithTimeout(2) { XCTAssertNil($0) }
}
}