-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Concurrent dependency access (#950)
* Synchronize Dependency Access * Added unit tests for dependency registration and resolution * Formatting * Remove working files * Add dependency to Package.swift * Update DependencyInjection.swift Remove whitespace * Removed second sync. Added test for multiple queues
- Loading branch information
Showing
4 changed files
with
231 additions
and
26 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// swift-tools-version:5.3 | ||
|
||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "PrimerSDK", | ||
defaultLocalization: "en", | ||
platforms: [ | ||
.iOS("13.1") | ||
], | ||
products: [ | ||
.library( | ||
name: "PrimerSDK", | ||
targets: ["PrimerSDK"] | ||
) | ||
], | ||
targets: [ | ||
.target( | ||
name: "PrimerSDK", | ||
path: "Sources/PrimerSDK", | ||
resources: [ | ||
.process("Resources"), | ||
.copy("Classes/Third Party/PromiseKit/LICENSE") | ||
] | ||
), | ||
.testTarget( | ||
name: "Tests", | ||
dependencies: [ | ||
.byName(name: "PrimerSDK") | ||
], | ||
path: "Tests/", | ||
sources: [ | ||
"Primer/", | ||
"Utilities/" | ||
] | ||
) | ||
], | ||
swiftLanguageVersions: [.v5] | ||
) |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// | ||
// DependencyContainerTests.swift | ||
// | ||
// | ||
// Created by Niall Quinn on 23/07/24. | ||
// | ||
|
||
import XCTest | ||
@testable import PrimerSDK | ||
|
||
class DependencyContainerTests: XCTestCase { | ||
|
||
override func setUp() { | ||
super.setUp() | ||
} | ||
|
||
func test_concurrentRegistrationAndResolution() { | ||
let expectation = self.expectation(description: "Concurrent operations completed") | ||
let operationsCount = 1000 | ||
let queue = DispatchQueue(label: "testQueue", attributes: .concurrent) | ||
|
||
let appState = MockAppState() | ||
|
||
for i in 0..<operationsCount { | ||
queue.async { | ||
if i % 2 == 0 { | ||
DependencyContainer.register(appState) | ||
} else { | ||
_ = DependencyContainer.resolve() as AppStateProtocol | ||
} | ||
} | ||
} | ||
|
||
queue.async(flags: .barrier) { | ||
expectation.fulfill() | ||
} | ||
|
||
waitForExpectations(timeout: 20, handler: nil) | ||
} | ||
|
||
func test_concurrentRegistrationOfSameType() { | ||
let expectation = self.expectation(description: "Concurrent registrations completed") | ||
let operationsCount = 1000 | ||
let queue = DispatchQueue(label: "testQueue", attributes: .concurrent) | ||
|
||
let appState = MockAppState() | ||
|
||
for i in 0..<operationsCount { | ||
queue.async { | ||
DependencyContainer.register(appState) | ||
} | ||
} | ||
|
||
queue.async(flags: .barrier) { | ||
expectation.fulfill() | ||
} | ||
|
||
waitForExpectations(timeout: 5, handler: nil) | ||
|
||
let resolvedState: AppStateProtocol = DependencyContainer.resolve() | ||
|
||
XCTAssertNotNil(resolvedState) | ||
} | ||
|
||
func test_concurrentResolutionOfNonExistentDependency() { | ||
let expectation = self.expectation(description: "Concurrent resolutions completed") | ||
let operationsCount = 1000 | ||
let queue = DispatchQueue(label: "testQueue", attributes: .concurrent) | ||
|
||
for _ in 0..<operationsCount { | ||
queue.async { | ||
XCTAssertNil(DependencyContainer.resolve() as Int?) | ||
} | ||
} | ||
|
||
queue.async(flags: .barrier) { | ||
expectation.fulfill() | ||
} | ||
|
||
waitForExpectations(timeout: 5, handler: nil) | ||
} | ||
|
||
func test_concurrentRegistrationAndResolutionOfMultipleTypes() { | ||
let expectation = self.expectation(description: "Concurrent operations completed") | ||
let operationsCount = 1000 | ||
let queue = DispatchQueue(label: "testQueue", attributes: .concurrent) | ||
|
||
let appState = MockAppState() | ||
let settings = MockPrimerSettings() | ||
|
||
for i in 0..<operationsCount { | ||
queue.async { | ||
switch i % 2 { | ||
case 0: | ||
DependencyContainer.register(appState) | ||
case 1: | ||
DependencyContainer.register(settings) | ||
default: | ||
break | ||
} | ||
} | ||
} | ||
|
||
for _ in 0..<operationsCount { | ||
queue.async { | ||
_ = DependencyContainer.resolve() as AppStateProtocol | ||
_ = DependencyContainer.resolve() as PrimerSettingsProtocol | ||
} | ||
} | ||
|
||
queue.async(flags: .barrier) { | ||
expectation.fulfill() | ||
} | ||
|
||
waitForExpectations(timeout: 5, handler: nil) | ||
|
||
// Verify that the last value of each type was registered | ||
XCTAssertNotNil(DependencyContainer.resolve() as AppStateProtocol) | ||
XCTAssertNotNil(DependencyContainer.resolve() as PrimerSettingsProtocol) | ||
} | ||
|
||
func test_concurrentQueues() { | ||
let expectation = XCTestExpectation(description: "Concurrent access") | ||
let iterationCount = 1000 | ||
let concurrentQueues = 4 | ||
|
||
for _ in 0..<concurrentQueues { | ||
DispatchQueue.global().async { | ||
for i in 0..<iterationCount { | ||
// Register a unique dependency | ||
let dependency = MockPrimerSettings() | ||
DependencyContainer.register(dependency) | ||
|
||
// Immediately resolve the dependency | ||
let resolved: MockPrimerSettings = DependencyContainer.resolve() | ||
|
||
// Verify that the resolved dependency matches the registered one | ||
XCTAssertEqual(dependency.paymentHandling, resolved.paymentHandling) | ||
} | ||
|
||
expectation.fulfill() | ||
} | ||
} | ||
|
||
wait(for: [expectation], timeout: 10.0) | ||
} | ||
} |