Initial prototype for Swift 6 mode

This commit is contained in:
John Estropia
2026-07-07 11:18:17 +09:00
parent 38ec439b18
commit 49f65601cd
71 changed files with 998 additions and 684 deletions
+24 -12
View File
@@ -24,6 +24,7 @@
//
import XCTest
import os
@testable
import CoreStore
@@ -47,7 +48,11 @@ class BaseTestCase: XCTestCase {
// MARK: Internal
@nonobjc
func prepareStack(configurations: [ModelConfiguration] = [nil], _ closure: (_ dataStack: DataStack) throws -> Void) {
@MainActor
func prepareStack(
configurations: [ModelConfiguration] = [nil],
_ closure: (_ dataStack: DataStack) throws -> Void
) {
let stack = DataStack(
xcodeModelName: "Model",
@@ -79,6 +84,7 @@ class BaseTestCase: XCTestCase {
}
@nonobjc
@MainActor
func expectLogger<T>(_ expectations: [TestLogger.Expectation], closure: () throws -> T) rethrows -> T {
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations(expectations))
@@ -97,6 +103,7 @@ class BaseTestCase: XCTestCase {
}
@nonobjc
@MainActor
func expectError<T>(code: CoreStoreErrorCode, closure: () throws -> T) {
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations([.logError]))
@@ -135,12 +142,14 @@ class BaseTestCase: XCTestCase {
}
@nonobjc
@MainActor
func checkExpectationsImmediately() {
self.waitForExpectations(timeout: 0, handler: { _ in })
}
@nonobjc
@MainActor
func waitAndCheckExpectations() {
self.waitForExpectations(timeout: 10, handler: {_ in })
@@ -174,7 +183,7 @@ class BaseTestCase: XCTestCase {
// MARK: - TestLogger
class TestLogger: CoreStoreLogger {
final class TestLogger: CoreStoreLogger {
enum Expectation {
@@ -187,13 +196,13 @@ class TestLogger: CoreStoreLogger {
init(_ expectations: [Expectation: XCTestExpectation]) {
self.expectations = expectations
self.expectations = .init(initialState: expectations)
}
// MARK: CoreStoreLogger
var enableObjectConcurrencyDebugging: Bool = true
let enableObjectConcurrencyDebugging: Bool = true
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
@@ -227,18 +236,21 @@ class TestLogger: CoreStoreLogger {
// MARK: Private
private var expectations: [Expectation: XCTestExpectation]
private let expectations: OSAllocatedUnfairLock<[Expectation: XCTestExpectation]>
private func fulfill(_ expectation: Expectation) {
if let instance = self.expectations[expectation] {
self.expectations.withLock {
instance.fulfill()
self.expectations[expectation] = nil
}
else {
XCTFail("Unexpected Logger Action: \(expectation)")
if let instance = $0[expectation] {
instance.fulfill()
$0[expectation] = nil
}
else {
XCTFail("Unexpected Logger Action: \(expectation)")
}
}
}
}
+2
View File
@@ -35,6 +35,7 @@ import CoreStore
class ConvenienceTests: BaseTestCase {
@objc
@MainActor
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
self.prepareStack { (stack) in
@@ -62,6 +63,7 @@ class ConvenienceTests: BaseTestCase {
}
@objc
@MainActor
dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() {
self.prepareStack { (stack) in
+35 -1
View File
@@ -162,6 +162,18 @@ class Person: CoreStoreObject {
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
private var spouseInverse: Person?
@Field.Coded("ownerInfosJson", coder: FieldCoders.Json.self)
var ownerInfosJson: [OwnerInfo] = []
@Field.Coded("ownerInfosPlist", coder: FieldCoders.Plist.self)
var ownerInfosPlist: [OwnerInfo] = []
struct OwnerInfo: Codable, Equatable {
var id: UUID
var displayName: String?
var otherIDs: Set<UUID>
}
private static func getDisplayName(_ object: ObjectProxy<Person>, _ field: ObjectProxy<Person>.FieldProxy<String?>) -> String? {
if let value = field.primitiveValue {
@@ -203,7 +215,7 @@ class DynamicModelTests: BaseTestDataTestCase {
versionLock: [
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
"Dog": [0xad6de93adc5565d, 0x7897e51253eba5a3, 0xd12b9ce0b13600f3, 0x5a4827cd794cd15e],
"Person": [0xf3e6ba6016bbedc6, 0x50dedf64f0eba490, 0xa32088a0ee83468d, 0xb72d1d0b37bd0992]
"Person": [0xbaec6549a025b59f, 0x3415c71e9f46fcf3, 0xb86b33433cb335eb, 0xfe441fde120dab67]
]
)
)
@@ -223,6 +235,14 @@ class DynamicModelTests: BaseTestDataTestCase {
let willSetPriorObserverDone = self.expectation(description: "willSet-observe-prior-done")
let willSetNotPriorObserverDone = self.expectation(description: "willSet-observe-notPrior-done")
let didSetObserverDone = self.expectation(description: "didSet-observe-done")
let ownerInfos: [Person.OwnerInfo] = (1...3).map {
.init(
id: .init(),
displayName: "ownerInfo_\($0)",
otherIDs: Set((1...3).map({ _ in .init() }))
)
}
stack.perform(
asynchronous: { (transaction) in
@@ -373,11 +393,19 @@ class DynamicModelTests: BaseTestDataTestCase {
person.job = .engineer
XCTAssertEqual(person.job, .engineer)
person.ownerInfosJson = ownerInfos
XCTAssertEqual(person.ownerInfosJson, ownerInfos)
person.ownerInfosPlist = ownerInfos
XCTAssertEqual(person.ownerInfosPlist, ownerInfos)
let personSnapshot2 = person.asSnapshot(in: transaction)!
XCTAssertEqual(person.name, personSnapshot2.$name)
XCTAssertEqual(person.title, personSnapshot2.$title)
XCTAssertEqual(person.displayName, personSnapshot2.$displayName)
XCTAssertEqual(person.job, personSnapshot2.$job)
XCTAssertEqual(person.ownerInfosJson, personSnapshot2.$ownerInfosJson)
XCTAssertEqual(person.ownerInfosPlist, personSnapshot2.$ownerInfosPlist)
var personSnapshot3 = personSnapshot2
personSnapshot3.$name = "James"
@@ -390,6 +418,8 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertEqual(personSnapshot3.$name, "James")
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
XCTAssertEqual(personSnapshot3.$job, .engineer)
XCTAssertEqual(personSnapshot3.$ownerInfosJson, ownerInfos)
XCTAssertEqual(personSnapshot3.$ownerInfosPlist, ownerInfos)
@@ -409,6 +439,8 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertEqual(personPublisher.$name, "John")
XCTAssertEqual(personPublisher.$displayName, "Sir John")
XCTAssertEqual(personPublisher.$job, .engineer)
XCTAssertEqual(personPublisher.$ownerInfosJson, ownerInfos)
XCTAssertEqual(personPublisher.$ownerInfosPlist, ownerInfos)
updateDone.fulfill()
},
@@ -444,6 +476,8 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertEqual(person!.customField.string, "customString")
XCTAssertEqual(person!.job, .engineer)
XCTAssertEqual(person!.pets.first, dog)
XCTAssertEqual(person!.ownerInfosJson, ownerInfos)
XCTAssertEqual(person!.ownerInfosPlist, ownerInfos)
let p3 = Where<Dog>({ $0.$age == 10 })
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
+1
View File
@@ -63,6 +63,7 @@ final class GroupByTests: BaseTestCase {
}
@objc
@MainActor
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
self.prepareStack { (dataStack) in
+9 -5
View File
@@ -36,6 +36,7 @@ import CoreStore
class ListPublisherTests: BaseTestDataTestCase {
@objc
@MainActor
dynamic func test_ThatListPublishers_CanReceiveInsertNotifications() {
self.prepareStack { (stack) in
@@ -62,7 +63,7 @@ class ListPublisherTests: BaseTestDataTestCase {
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
asynchronous: { [dateFormatter] (transaction) -> Bool in
let object = transaction.create(Into<TestEntity1>())
object.testBoolean = NSNumber(value: true)
@@ -70,7 +71,7 @@ class ListPublisherTests: BaseTestDataTestCase {
object.testDecimal = NSDecimalNumber(string: "1")
object.testString = "nil:TestEntity1:1"
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
object.testDate = dateFormatter.date(from: "2000-01-01T00:00:00Z")!
return transaction.hasChanges
},
@@ -92,6 +93,7 @@ class ListPublisherTests: BaseTestDataTestCase {
}
@objc
@MainActor
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
@@ -126,7 +128,7 @@ class ListPublisherTests: BaseTestDataTestCase {
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
asynchronous: { [dateFormatter] (transaction) -> Bool in
if let object = try transaction.fetchOne(
From<TestEntity1>(),
@@ -136,7 +138,7 @@ class ListPublisherTests: BaseTestDataTestCase {
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
object.testDate = dateFormatter.date(from: "2000-01-11T00:00:00Z")!
}
else {
@@ -150,7 +152,7 @@ class ListPublisherTests: BaseTestDataTestCase {
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
object.testDate = dateFormatter.date(from: "2000-01-22T00:00:00Z")!
}
else {
@@ -176,6 +178,7 @@ class ListPublisherTests: BaseTestDataTestCase {
}
@objc
@MainActor
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
self.prepareStack { (stack) in
@@ -242,6 +245,7 @@ class ListPublisherTests: BaseTestDataTestCase {
}
@objc
@MainActor
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
+54 -39
View File
@@ -24,6 +24,7 @@
//
import XCTest
import os
@testable
import CoreStore
@@ -34,6 +35,7 @@ import CoreStore
class ObjectObserverTests: BaseTestDataTestCase {
@objc
@MainActor
dynamic func test_ThatObjectObservers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
@@ -54,23 +56,27 @@ class ObjectObserverTests: BaseTestDataTestCase {
XCTAssertEqual(monitor.object, object)
XCTAssertFalse(monitor.isObjectDeleted)
var events = 0
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
_ = self.expectation(
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
["object": object] as NSDictionary
)
defer {
nonisolated(unsafe) let note = note
return events.withLock { events in
events += 1
XCTAssertEqual(events, 0)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
["object": object] as NSDictionary
)
defer {
events += 1
}
return events == 0
}
return events == 0
}
)
_ = self.expectation(
@@ -78,28 +84,32 @@ class ObjectObserverTests: BaseTestDataTestCase {
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 1)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
[
"object": object,
"changedPersistentKeys": Set(
[
#keyPath(TestEntity1.testNumber),
#keyPath(TestEntity1.testString)
]
)
] as NSDictionary
)
let object = note.userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testNumber, NSNumber(value: 10))
XCTAssertEqual(object?.testString, "nil:TestEntity1:10")
defer {
nonisolated(unsafe) let note = note
return events.withLock { events in
events += 1
XCTAssertEqual(events, 1)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
[
"object": object,
"changedPersistentKeys": Set(
[
#keyPath(TestEntity1.testNumber),
#keyPath(TestEntity1.testString)
]
)
] as NSDictionary
)
let object = note.userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testNumber, NSNumber(value: 10))
XCTAssertEqual(object?.testString, "nil:TestEntity1:10")
defer {
events += 1
}
return events == 1
}
return events == 1
}
)
let saveExpectation = self.expectation(description: "save")
@@ -131,6 +141,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
}
@objc
@MainActor
dynamic func test_ThatObjectObservers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
@@ -151,23 +162,27 @@ class ObjectObserverTests: BaseTestDataTestCase {
XCTAssertEqual(monitor.object, object)
XCTAssertFalse(monitor.isObjectDeleted)
var events = 0
let events: OSAllocatedUnfairLock<Int> = .init(initialState: 0)
_ = self.expectation(
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
["object": object] as NSDictionary
)
defer {
nonisolated(unsafe) let note = note
return events.withLock { events in
events += 1
XCTAssertEqual(events, 0)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
["object": object] as NSDictionary
)
defer {
events += 1
}
return events == 0
}
return events == 0
}
)
let saveExpectation = self.expectation(description: "save")
@@ -202,7 +217,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
// MARK: TestObjectObserver
class TestObjectObserver: ObjectObserver {
final class TestObjectObserver: ObjectObserver {
typealias ObjectEntityType = TestEntity1
@@ -34,6 +34,7 @@ import CoreStore
class ObjectPublisherTests: BaseTestDataTestCase {
@objc
@MainActor
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
@@ -93,6 +94,7 @@ class ObjectPublisherTests: BaseTestDataTestCase {
}
@objc
@MainActor
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
+1
View File
@@ -35,6 +35,7 @@ import CoreStore
class SetupTests: BaseTestDataTestCase {
@objc
@MainActor
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
do {