add iOS
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
import XCTest
|
||||
import GRDB
|
||||
|
||||
class DatabaseConfigurationTests: GRDBTestCase {
|
||||
// MARK: - prepareDatabase
|
||||
|
||||
func testPrepareDatabase() throws {
|
||||
// prepareDatabase is called when connection opens
|
||||
var connectionCount = 0
|
||||
var configuration = Configuration()
|
||||
configuration.prepareDatabase { db in
|
||||
connectionCount += 1
|
||||
}
|
||||
|
||||
_ = try DatabaseQueue(configuration: configuration)
|
||||
XCTAssertEqual(connectionCount, 1)
|
||||
|
||||
_ = try makeDatabaseQueue(configuration: configuration)
|
||||
XCTAssertEqual(connectionCount, 2)
|
||||
|
||||
let pool = try makeDatabasePool(configuration: configuration)
|
||||
XCTAssertEqual(connectionCount, 3)
|
||||
|
||||
try pool.read { _ in }
|
||||
XCTAssertEqual(connectionCount, 4)
|
||||
|
||||
try pool.makeSnapshot().read { _ in }
|
||||
XCTAssertEqual(connectionCount, 5)
|
||||
|
||||
#if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER && (compiler(>=5.7.1) || !(os(macOS) || targetEnvironment(macCatalyst))))
|
||||
try pool.makeSnapshotPool().read { _ in }
|
||||
XCTAssertEqual(connectionCount, 6)
|
||||
#endif
|
||||
}
|
||||
|
||||
func testPrepareDatabaseError() throws {
|
||||
struct TestError: Error { }
|
||||
var error: TestError?
|
||||
|
||||
var configuration = Configuration()
|
||||
configuration.prepareDatabase { db in
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: what about in-memory DatabaseQueue???
|
||||
|
||||
do {
|
||||
error = TestError()
|
||||
_ = try makeDatabaseQueue(configuration: configuration)
|
||||
XCTFail("Expected TestError")
|
||||
} catch is TestError { }
|
||||
|
||||
do {
|
||||
error = TestError()
|
||||
_ = try makeDatabasePool(configuration: configuration)
|
||||
XCTFail("Expected TestError")
|
||||
} catch is TestError { }
|
||||
|
||||
do {
|
||||
error = nil
|
||||
let pool = try makeDatabasePool(configuration: configuration)
|
||||
|
||||
do {
|
||||
error = TestError()
|
||||
try pool.read { _ in }
|
||||
XCTFail("Expected TestError")
|
||||
} catch is TestError { }
|
||||
|
||||
do {
|
||||
error = TestError()
|
||||
_ = try pool.makeSnapshot()
|
||||
XCTFail("Expected TestError")
|
||||
} catch is TestError { }
|
||||
|
||||
#if SQLITE_ENABLE_SNAPSHOT || (!GRDBCUSTOMSQLITE && !GRDBCIPHER && (compiler(>=5.7.1) || !(os(macOS) || targetEnvironment(macCatalyst))))
|
||||
do {
|
||||
error = TestError()
|
||||
_ = try pool.makeSnapshotPool()
|
||||
XCTFail("Expected TestError")
|
||||
} catch is TestError { }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - acceptsDoubleQuotedStringLiterals
|
||||
|
||||
func testAcceptsDoubleQuotedStringLiteralsDefault() throws {
|
||||
let configuration = Configuration()
|
||||
XCTAssertFalse(configuration.acceptsDoubleQuotedStringLiterals)
|
||||
}
|
||||
|
||||
func testAcceptsDoubleQuotedStringLiteralsTrue() throws {
|
||||
var configuration = Configuration()
|
||||
configuration.acceptsDoubleQuotedStringLiterals = true
|
||||
let dbQueue = try makeDatabaseQueue(configuration: configuration)
|
||||
try dbQueue.inDatabase { db in
|
||||
try db.execute(sql: """
|
||||
CREATE TABLE player(name TEXT);
|
||||
INSERT INTO player DEFAULT VALUES;
|
||||
""")
|
||||
}
|
||||
|
||||
// Test SQLITE_DBCONFIG_DQS_DML
|
||||
let foo = try dbQueue.inDatabase { db in
|
||||
try String.fetchOne(db, sql: "SELECT \"foo\" FROM player")
|
||||
}
|
||||
XCTAssertEqual(foo, "foo")
|
||||
|
||||
// Test SQLITE_DBCONFIG_DQS_DDL
|
||||
try dbQueue.inDatabase { db in
|
||||
try db.execute(sql: "CREATE INDEX i ON player(\"foo\")")
|
||||
}
|
||||
}
|
||||
|
||||
func testAcceptsDoubleQuotedStringLiteralsFalse() throws {
|
||||
var configuration = Configuration()
|
||||
configuration.acceptsDoubleQuotedStringLiterals = false
|
||||
let dbQueue = try makeDatabaseQueue(configuration: configuration)
|
||||
try dbQueue.inDatabase { db in
|
||||
try db.execute(sql: """
|
||||
CREATE TABLE player(name TEXT);
|
||||
INSERT INTO player DEFAULT VALUES;
|
||||
""")
|
||||
}
|
||||
|
||||
// Test SQLITE_DBCONFIG_DQS_DML
|
||||
do {
|
||||
let foo = try dbQueue.inDatabase { db in
|
||||
try String.fetchOne(db, sql: "SELECT \"foo\" FROM player")
|
||||
}
|
||||
if sqlite3_libversion_number() >= 3029000 {
|
||||
XCTFail("Expected error")
|
||||
} else {
|
||||
XCTAssertEqual(foo, "foo")
|
||||
}
|
||||
} catch let error as DatabaseError {
|
||||
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
|
||||
XCTAssertEqual(error.sql, "SELECT \"foo\" FROM player")
|
||||
}
|
||||
|
||||
// Test SQLITE_DBCONFIG_DQS_DDL
|
||||
do {
|
||||
try dbQueue.inDatabase { db in
|
||||
try db.execute(sql: "CREATE INDEX i ON player(\"foo\")")
|
||||
}
|
||||
if sqlite3_libversion_number() >= 3029000 {
|
||||
XCTFail("Expected error")
|
||||
}
|
||||
} catch let error as DatabaseError {
|
||||
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
|
||||
XCTAssertEqual(error.sql, "CREATE INDEX i ON player(\"foo\")")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - busyMode
|
||||
|
||||
func testBusyModeImmediate() throws {
|
||||
let dbQueue1 = try makeDatabaseQueue(filename: "test.sqlite")
|
||||
#if GRDBCIPHER_USE_ENCRYPTION
|
||||
// Work around SQLCipher bug when two connections are open to the
|
||||
// same empty database: make sure the database is not empty before
|
||||
// running this test
|
||||
try dbQueue1.inDatabase { db in
|
||||
try db.execute(sql: "CREATE TABLE SQLCipherWorkAround (foo INTEGER)")
|
||||
}
|
||||
#endif
|
||||
|
||||
var configuration2 = dbQueue1.configuration
|
||||
configuration2.busyMode = .immediateError
|
||||
let dbQueue2 = try makeDatabaseQueue(filename: "test.sqlite", configuration: configuration2)
|
||||
|
||||
let s1 = DispatchSemaphore(value: 0)
|
||||
let s2 = DispatchSemaphore(value: 0)
|
||||
let queue = DispatchQueue.global(qos: .default)
|
||||
let group = DispatchGroup()
|
||||
|
||||
queue.async(group: group) {
|
||||
do {
|
||||
try dbQueue1.inTransaction(.exclusive) { db in
|
||||
s2.signal()
|
||||
queue.asyncAfter(deadline: .now() + 1) {
|
||||
s1.signal()
|
||||
}
|
||||
_ = s1.wait(timeout: .distantFuture)
|
||||
return .commit
|
||||
}
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
queue.async(group: group) {
|
||||
do {
|
||||
_ = s2.wait(timeout: .distantFuture)
|
||||
try dbQueue2.inTransaction(.exclusive) { db in return .commit }
|
||||
XCTFail("Expected error")
|
||||
} catch DatabaseError.SQLITE_BUSY {
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
_ = group.wait(timeout: .distantFuture)
|
||||
}
|
||||
|
||||
func testBusyModeTimeoutTooShort() throws {
|
||||
let dbQueue1 = try makeDatabaseQueue(filename: "test.sqlite")
|
||||
#if GRDBCIPHER_USE_ENCRYPTION
|
||||
// Work around SQLCipher bug when two connections are open to the
|
||||
// same empty database: make sure the database is not empty before
|
||||
// running this test
|
||||
try dbQueue1.inDatabase { db in
|
||||
try db.execute(sql: "CREATE TABLE SQLCipherWorkAround (foo INTEGER)")
|
||||
}
|
||||
#endif
|
||||
|
||||
var configuration2 = dbQueue1.configuration
|
||||
configuration2.busyMode = .timeout(0.1)
|
||||
let dbQueue2 = try makeDatabaseQueue(filename: "test.sqlite", configuration: configuration2)
|
||||
|
||||
let s1 = DispatchSemaphore(value: 0)
|
||||
let s2 = DispatchSemaphore(value: 0)
|
||||
let queue = DispatchQueue.global(qos: .default)
|
||||
let group = DispatchGroup()
|
||||
|
||||
queue.async(group: group) {
|
||||
do {
|
||||
try dbQueue1.inTransaction(.exclusive) { db in
|
||||
// Let dbQueue2 attempt at opening an exclusive transaction
|
||||
s2.signal()
|
||||
// Wait for dbQueue2 to fail 😈
|
||||
_ = s1.wait(timeout: .distantFuture)
|
||||
return .commit
|
||||
}
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
queue.async(group: group) {
|
||||
do {
|
||||
// Wait for dbQueue1 to start an exclusive transaction
|
||||
_ = s2.wait(timeout: .distantFuture)
|
||||
try dbQueue2.inTransaction(.exclusive) { db in return .commit }
|
||||
XCTFail("Expected error")
|
||||
} catch DatabaseError.SQLITE_BUSY {
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
// Let dbQueue1 end its transaction
|
||||
s1.signal()
|
||||
}
|
||||
|
||||
_ = group.wait(timeout: .distantFuture)
|
||||
}
|
||||
|
||||
func testBusyModeTimeoutTooLong() throws {
|
||||
let dbQueue1 = try makeDatabaseQueue(filename: "test.sqlite")
|
||||
#if GRDBCIPHER_USE_ENCRYPTION
|
||||
// Work around SQLCipher bug when two connections are open to the
|
||||
// same empty database: make sure the database is not empty before
|
||||
// running this test
|
||||
try dbQueue1.inDatabase { db in
|
||||
try db.execute(sql: "CREATE TABLE SQLCipherWorkAround (foo INTEGER)")
|
||||
}
|
||||
#endif
|
||||
|
||||
var configuration2 = dbQueue1.configuration
|
||||
configuration2.busyMode = .timeout(1)
|
||||
let dbQueue2 = try makeDatabaseQueue(filename: "test.sqlite", configuration: configuration2)
|
||||
|
||||
let s1 = DispatchSemaphore(value: 0)
|
||||
let s2 = DispatchSemaphore(value: 0)
|
||||
let queue = DispatchQueue.global(qos: .default)
|
||||
let group = DispatchGroup()
|
||||
|
||||
queue.async(group: group) {
|
||||
do {
|
||||
try dbQueue1.inTransaction(.exclusive) { db in
|
||||
// Let dbQueue2 attempt at opening an exclusive transaction
|
||||
s2.signal()
|
||||
// Wait for a delay and end dbQueue1 transaction
|
||||
queue.asyncAfter(deadline: .now() + 0.1) {
|
||||
s1.signal()
|
||||
}
|
||||
_ = s1.wait(timeout: .distantFuture)
|
||||
return .commit
|
||||
}
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
queue.async(group: group) {
|
||||
do {
|
||||
// Wait for dbQueue1 to start an exclusive transaction
|
||||
_ = s2.wait(timeout: .distantFuture)
|
||||
try dbQueue2.inTransaction(.exclusive) { db in return .commit }
|
||||
} catch {
|
||||
XCTFail("\(error)")
|
||||
}
|
||||
}
|
||||
|
||||
_ = group.wait(timeout: .distantFuture)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user