Last active
November 5, 2022 17:04
-
-
Save southerton81/6ebf422ee63ff63e68fdea08a1decdc5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import XCTest | |
import CoreData | |
@testable import iosApp | |
class iosAppTests: XCTestCase { | |
/* | |
ChartState - is a CoreData entity with two fields: chartLen and seed: | |
<entity name="ChartState" representedClassName="ChartState" syncable="YES" codeGenerationType="class"> | |
<attribute name="chartLen" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/> | |
<attribute name="seed" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/> | |
</entity> | |
*/ | |
/* | |
CoreDataInventory - is a class defined in the app, it holds the NSPersistentContainer instance: | |
final class CoreDataInventory { | |
static let instance = CoreDataInventory() | |
let persistentContainer: NSPersistentContainer | |
private init() { | |
persistentContainer = NSPersistentContainer(name: "Data") | |
persistentContainer.loadPersistentStores { _, error in | |
if let error = error as NSError? { | |
fatalError("Unresolved error \(error), \(error.userInfo)") | |
} | |
} | |
} | |
} | |
*/ | |
func testCoreDataMergeConflict() { | |
let coreData = CoreDataInventory.instance.persistentContainer | |
let expectation = XCTestExpectation(description: "") | |
expectation.expectedFulfillmentCount = 2 | |
// 1. Launch new background task to create a new ChartState entity and save it | |
coreData.performBackgroundTask { (context) in | |
let newChartState = ChartState(context: context) | |
newChartState.chartLen = 100 | |
newChartState.seed = 1 | |
do { | |
try context.save() | |
} catch { | |
let nsError = error as NSError | |
fatalError("saveContext() error: \(nsError), \(nsError.userInfo)") | |
} | |
} | |
// 2. Launch two simultaneous background tasks | |
// 2.1 This task sleeps for 1 second, reads the written ChartLen from CoreData and modifies it, then saves it | |
coreData.performBackgroundTask { (context) in | |
sleep(1) | |
self.modifyChartStateSeed(context, 2) | |
sleep(1) // Wait 1 seconds before save for the next background task to read the same version of entity | |
do { | |
try context.save() | |
} catch { | |
let nsError = error as NSError | |
fatalError("saveContext() error: \(nsError), \(nsError.userInfo)") | |
} | |
expectation.fulfill() | |
} | |
// 2.2 This task also modifies the same entity, but waits another second before saving it | |
coreData.performBackgroundTask { (context) in | |
// Uncomment to set one of the Merge Policies, and bypass an error by prefering saved or in-memory data version | |
//context.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType) | |
sleep(1) | |
self.modifyChartStateSeed(context, 3) | |
sleep(2) // Wait 2 seconds before save, so that the previous performBackgroundTask() would have already saved its ChartState version | |
do { | |
// This save would produce a merge conflict, because read entity was already modified and saved by the other context, | |
// so the entity saved version is already greater than the one that was read in this thread | |
try context.save() | |
} catch { | |
let nsError = error as NSError | |
// Would land here with the "Could not merge changes ... oldVersion = 1 and newVersion = 2 and | |
// old object snapshot = {chartLen = 100; seed = 1;} and new cached row = {chartLen = 100; seed = 2;}"" | |
fatalError("saveContext() error: \(nsError), \(nsError.userInfo)") | |
} | |
expectation.fulfill() | |
} | |
wait(for: [expectation], timeout: 6) | |
} | |
private func modifyChartStateSeed(_ context: NSManagedObjectContext, _ seed: Int32) { | |
let fetchRequest: NSFetchRequest<ChartState> = ChartState.fetchRequest() | |
fetchRequest.predicate = NSPredicate(format: "chartLen = %i", 100) | |
let chartStateEntity = try? context.fetch(fetchRequest).first | |
chartStateEntity?.seed = seed | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment