Skip to content

Commit 24db036

Browse files
authored
Merge pull request #19765 from wordpress-mobile/fix/skip-perform-on-confined-concurrency
Fix crash when `perform` is called on a context with deprecated concurrency type
2 parents b0f22e0 + 0caa881 commit 24db036

File tree

3 files changed

+129
-6
lines changed

3 files changed

+129
-6
lines changed

WordPress/Classes/Utility/ContainerContextFactory.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class ContainerContextFactory: NSObject, ManagedObjectContextFactory {
2727
completionBlock?()
2828
}
2929
}
30+
31+
/// Ensure that the `context`'s concurrency type is not `confinementConcurrencyType`, since it will crash if `perform` or `performAndWait` is called.
32+
guard context.concurrencyType == .mainQueueConcurrencyType || context.concurrencyType == .privateQueueConcurrencyType else {
33+
block()
34+
return
35+
}
36+
3037
if wait {
3138
context.performAndWait(block)
3239
} else {

WordPress/WordPress.xcodeproj/project.pbxproj

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5209,6 +5209,7 @@
52095209
FE25C235271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */; };
52105210
FE25C236271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */; };
52115211
FE2E3729281C839C00A1E82A /* BloggingPromptsServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2E3728281C839C00A1E82A /* BloggingPromptsServiceTests.swift */; };
5212+
FE320CC5294705990046899B /* ContainerContextFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE320CC4294705990046899B /* ContainerContextFactoryTests.swift */; };
52125213
FE32E7F12844971000744D80 /* ReminderScheduleCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32E7F02844971000744D80 /* ReminderScheduleCoordinatorTests.swift */; };
52135214
FE32EFFF275914390040BE67 /* MenuSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */; };
52145215
FE32F000275914390040BE67 /* MenuSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */; };
@@ -8810,6 +8811,7 @@
88108811
FE23EB4826E7C91F005A1698 /* richCommentStyle.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; name = richCommentStyle.css; path = Resources/HTML/richCommentStyle.css; sourceTree = "<group>"; };
88118812
FE25C234271F23000084E1DB /* ReaderCommentsNotificationSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderCommentsNotificationSheetViewController.swift; sourceTree = "<group>"; };
88128813
FE2E3728281C839C00A1E82A /* BloggingPromptsServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloggingPromptsServiceTests.swift; sourceTree = "<group>"; };
8814+
FE320CC4294705990046899B /* ContainerContextFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerContextFactoryTests.swift; sourceTree = "<group>"; };
88138815
FE32E7F02844971000744D80 /* ReminderScheduleCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReminderScheduleCoordinatorTests.swift; sourceTree = "<group>"; };
88148816
FE32E7F32846A68800744D80 /* WordPress 142.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 142.xcdatamodel"; sourceTree = "<group>"; };
88158817
FE32EFFE275914390040BE67 /* MenuSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSheetViewController.swift; sourceTree = "<group>"; };
@@ -9877,7 +9879,7 @@
98779879
path = Classes;
98789880
sourceTree = "<group>";
98799881
};
9880-
29B97314FDCFA39411CA2CEA = {
9882+
29B97314FDCFA39411CA2CEA /* CustomTemplate */ = {
98819883
isa = PBXGroup;
98829884
children = (
98839885
3F20FDF3276BF21000DA3CAD /* Packages */,
@@ -14564,6 +14566,7 @@
1456414566
93E9050319E6F242005513C9 /* ContextManagerTests.swift */,
1456514567
B5ECA6CC1DBAAD510062D7E0 /* CoreDataHelperTests.swift */,
1456614568
931D26FF19EDAE8600114F17 /* CoreDataMigrationTests.m */,
14569+
FE320CC4294705990046899B /* ContainerContextFactoryTests.swift */,
1456714570
);
1456814571
name = "Core Data";
1456914572
sourceTree = "<group>";
@@ -17869,14 +17872,14 @@
1786917872
bg,
1787017873
sk,
1787117874
);
17872-
mainGroup = 29B97314FDCFA39411CA2CEA;
17875+
mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */;
1787317876
packageReferences = (
1787417877
3FF1442E266F3C2400138163 /* XCRemoteSwiftPackageReference "ScreenObject" */,
1787517878
3FC2C33B26C4CF0A00C6D98F /* XCRemoteSwiftPackageReference "XCUITestHelpers" */,
1787617879
17A8858B2757B97F0071FCA3 /* XCRemoteSwiftPackageReference "AutomatticAbout-swift" */,
1787717880
3F2B62DA284F4E0B0008CD59 /* XCRemoteSwiftPackageReference "Charts" */,
1787817881
3F3B23C02858A1B300CACE60 /* XCRemoteSwiftPackageReference "test-collector-swift" */,
17879-
3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios.git" */,
17882+
3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios" */,
1788017883
3F338B6F289BD3040014ADC5 /* XCRemoteSwiftPackageReference "Nimble" */,
1788117884
);
1788217885
productRefGroup = 19C28FACFE9D520D11CA2CBB /* Products */;
@@ -22318,6 +22321,7 @@
2231822321
C738CB1128626606001BE107 /* QRLoginVerifyCoordinatorTests.swift in Sources */,
2231922322
FF0B2567237A023C004E255F /* GutenbergVideoUploadProcessorTests.swift in Sources */,
2232022323
FF1B11E7238FE27A0038B93E /* GutenbergGalleryUploadProcessorTests.swift in Sources */,
22324+
FE320CC5294705990046899B /* ContainerContextFactoryTests.swift in Sources */,
2232122325
F4D9AF51288AE23500803D40 /* SuggestionTableViewTests.swift in Sources */,
2232222326
8BC12F72231FEBA1004DDA72 /* PostCoordinatorTests.swift in Sources */,
2232322327
C3C2F84628AC8BC700937E45 /* JetpackBannerScrollVisibilityTests.swift in Sources */,
@@ -29112,7 +29116,7 @@
2911229116
minimumVersion = 0.3.0;
2911329117
};
2911429118
};
29115-
3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios.git" */ = {
29119+
3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios" */ = {
2911629120
isa = XCRemoteSwiftPackageReference;
2911729121
repositoryURL = "https://github.com/airbnb/lottie-ios.git";
2911829122
requirement = {
@@ -29185,12 +29189,12 @@
2918529189
};
2918629190
3F411B6E28987E3F002513AE /* Lottie */ = {
2918729191
isa = XCSwiftPackageProductDependency;
29188-
package = 3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios.git" */;
29192+
package = 3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios" */;
2918929193
productName = Lottie;
2919029194
};
2919129195
3F44DD57289C379C006334CD /* Lottie */ = {
2919229196
isa = XCSwiftPackageProductDependency;
29193-
package = 3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios.git" */;
29197+
package = 3F411B6D28987E3F002513AE /* XCRemoteSwiftPackageReference "lottie-ios" */;
2919429198
productName = Lottie;
2919529199
};
2919629200
3FC2C33C26C4CF0A00C6D98F /* XCUITestHelpers */ = {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import XCTest
2+
3+
@testable import WordPress
4+
5+
final class ContainerContextFactoryTests: XCTestCase {
6+
7+
private var persistentContainer: NSPersistentContainer!
8+
private var factory: ContainerContextFactory!
9+
10+
override func setUp() {
11+
super.setUp()
12+
13+
persistentContainer = makeInMemoryContainer()
14+
factory = ContainerContextFactory(persistentContainer: persistentContainer)
15+
}
16+
17+
override func tearDown() {
18+
persistentContainer = nil
19+
factory = nil
20+
21+
super.tearDown()
22+
}
23+
24+
// MARK: - `save` Tests
25+
26+
func test_save_givenMainQueueConcurrencyType_shouldCallPerform() {
27+
let context = makeMockContext(concurrencyType: .mainQueueConcurrencyType)
28+
29+
factory.save(context, andWait: false, withCompletionBlock: nil)
30+
31+
XCTAssertTrue(context.performCalled)
32+
XCTAssertFalse(context.performAndWaitCalled)
33+
}
34+
35+
func test_save_givenMainQueueConcurrencyType_andWaitIsSetToTrue_shouldCallPerformAndWait() {
36+
let context = makeMockContext(concurrencyType: .mainQueueConcurrencyType)
37+
38+
factory.save(context, andWait: true, withCompletionBlock: nil)
39+
40+
XCTAssertFalse(context.performCalled)
41+
XCTAssertTrue(context.performAndWaitCalled)
42+
}
43+
44+
func test_save_givenPrivateQueueConcurrencyType_shouldCallPerform() {
45+
let context = makeMockContext(concurrencyType: .privateQueueConcurrencyType)
46+
47+
factory.save(context, andWait: false, withCompletionBlock: nil)
48+
49+
XCTAssertTrue(context.performCalled)
50+
XCTAssertFalse(context.performAndWaitCalled)
51+
}
52+
53+
func test_save_givenPrivateQueueConcurrencyType_andWaitIsSetToTrue_shouldCallPerformAndWait() {
54+
let context = makeMockContext(concurrencyType: .privateQueueConcurrencyType)
55+
56+
factory.save(context, andWait: true, withCompletionBlock: nil)
57+
58+
XCTAssertFalse(context.performCalled)
59+
XCTAssertTrue(context.performAndWaitCalled)
60+
}
61+
62+
func test_save_givenDeprecatedConcurrencyType_shouldNotCallPerform() {
63+
/// creates a context with `.confinementConcurrencyType`. The enum is created from its raw value
64+
/// to prevent Xcode from complaining since the enum value is deprecated.
65+
let context = makeMockContext(concurrencyType: NSManagedObjectContextConcurrencyType(rawValue: 0)!)
66+
67+
factory.save(context, andWait: false, withCompletionBlock: nil)
68+
69+
XCTAssertFalse(context.performCalled)
70+
XCTAssertFalse(context.performAndWaitCalled)
71+
}
72+
}
73+
74+
// MARK: - Private Helpers
75+
76+
private extension ContainerContextFactoryTests {
77+
78+
class MockManagedObjectContext: NSManagedObjectContext {
79+
80+
var performCalled = false
81+
var performAndWaitCalled = false
82+
83+
override func perform(_ block: @escaping () -> Void) {
84+
performCalled = true
85+
}
86+
87+
override func performAndWait(_ block: () -> Void) {
88+
performAndWaitCalled = true
89+
}
90+
91+
override func save() throws {
92+
// do nothing. let's make sure nothing gets saved.
93+
}
94+
}
95+
96+
/// Creates an "in-memory" NSPersistentContainer.
97+
/// This follows the approach used in WWDC'18: https://developer.apple.com/videos/play/wwdc2018/224/
98+
///
99+
/// - Returns: An instance of NSPersistentContainer that stores data in memory.
100+
func makeInMemoryContainer() -> NSPersistentContainer {
101+
let container = NSPersistentContainer(name: "WordPress")
102+
let description = NSPersistentStoreDescription(url: .init(fileURLWithPath: "/dev/null"))
103+
container.persistentStoreDescriptions = [description]
104+
105+
return container
106+
}
107+
108+
func makeMockContext(concurrencyType: NSManagedObjectContextConcurrencyType) -> MockManagedObjectContext {
109+
return MockManagedObjectContext(concurrencyType: concurrencyType)
110+
}
111+
112+
}

0 commit comments

Comments
 (0)