-
Notifications
You must be signed in to change notification settings - Fork 148
/
Copy pathSimultaneousCacheAccessTests.swift
71 lines (60 loc) · 2.26 KB
/
SimultaneousCacheAccessTests.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@_spi(Internals) import Dependencies
import XCTest
final class SimultaneousCacheAccessTests: XCTestCase {
// Test dependency holds onto a mock Product. Resetting the cache causes the mock Product to be
// released triggering the simultaneous access from Product's deinit.
func testDeinitCacheAccess() {
XCTExpectFailure {
@Dependency(FactoryDependency.self) var factory
_ = factory
// Reset the cache to trigger Product deinit
DependencyValues._current.cachedValues.resetCache()
} issueMatcher: { issue in
// Accessing a value during cache reset accesses both DependencyContextKey and the accessed
// dependency. Just match on the end of the error message to cover both keys.
issue.compactDescription.hasSuffix(
"Accessing a dependency during a cache reset will always return a new and uncached instance of the dependency."
)
}
}
// The live dependency does not hold onto a Product, so there's no simultaneous access on reset.
func testLiveDeinit() {
withDependencies {
$0.context = .live
} operation: {
@Dependency(FactoryDependency.self) var factory
_ = factory
// Reset the cache to validate that a Product deinit is not triggered
DependencyValues._current.cachedValues.resetCache()
}
}
}
struct NestedDependency: TestDependencyKey {
static let testValue = NestedDependency()
}
// Product accesses a dependency in its deinit method.
// This is fine as long as its deinit isn't called during cache reset
final class Product: Sendable {
deinit {
@Dependency(NestedDependency.self) var nested
_ = nested
}
}
// Factory dependency that vends Product instances
protocol Factory: Sendable {
func makeProduct() -> Product
}
enum FactoryDependency: DependencyKey {
static let liveValue: Factory = LiveFactory()
static var testValue: Factory { MockFactory() }
}
// Live factory instantiates a new product for each call
struct LiveFactory: Factory {
func makeProduct() -> Product { Product() }
}
// Mock factory holds onto a mock Product.
// This results in the mock Product being released during cache reset.
final class MockFactory: Factory {
let mockProduct = LockIsolated(Product())
func makeProduct() -> Product { mockProduct.value }
}