Skip to content

Commit e47bf89

Browse files
committed
Make FailureMessage sendable. (#1131)
1 parent ed88c55 commit e47bf89

File tree

4 files changed

+162
-66
lines changed

4 files changed

+162
-66
lines changed

Nimble.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@
137137
892FDF1329D3EA7700523A80 /* AsyncExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892FDF1229D3EA7700523A80 /* AsyncExpression.swift */; };
138138
896962412A5FABD000A7929D /* AsyncAllPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896962402A5FABD000A7929D /* AsyncAllPass.swift */; };
139139
8969624A2A5FAD5F00A7929D /* AsyncAllPassTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896962452A5FAD4500A7929D /* AsyncAllPassTest.swift */; };
140+
897F84F42BA922B500BF354B /* NSLocking+Nimble.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897F84F32BA922B500BF354B /* NSLocking+Nimble.swift */; };
140141
898F28B025D9F4C30052B8D0 /* AlwaysFailMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */; };
141142
899441EF2902EE4B00C1FAF9 /* AsyncAwaitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */; };
142143
899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */; };
@@ -324,6 +325,7 @@
324325
8952ADDC2B4F159400D9305F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
325326
896962402A5FABD000A7929D /* AsyncAllPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAllPass.swift; sourceTree = "<group>"; };
326327
896962452A5FAD4500A7929D /* AsyncAllPassTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAllPassTest.swift; sourceTree = "<group>"; };
328+
897F84F32BA922B500BF354B /* NSLocking+Nimble.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSLocking+Nimble.swift"; sourceTree = "<group>"; };
327329
898F28AF25D9F4C30052B8D0 /* AlwaysFailMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlwaysFailMatcher.swift; sourceTree = "<group>"; };
328330
899441EE2902EE4B00C1FAF9 /* AsyncAwaitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitTest.swift; sourceTree = "<group>"; };
329331
899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DSL+AsyncAwait.swift"; sourceTree = "<group>"; };
@@ -612,6 +614,7 @@
612614
isa = PBXGroup;
613615
children = (
614616
1FD8CD261968AB07008ED995 /* PollAwait.swift */,
617+
897F84F32BA922B500BF354B /* NSLocking+Nimble.swift */,
615618
89F5E08B290B8D22001F9377 /* AsyncAwait.swift */,
616619
891A04702AB0164500B46613 /* AsyncTimerSequence.swift */,
617620
1FD8CD271968AB07008ED995 /* SourceLocation.swift */,
@@ -879,6 +882,7 @@
879882
1FD8CD571968AB07008ED995 /* Contain.swift in Sources */,
880883
7A0A26231E7F52360092A34E /* ToSucceed.swift in Sources */,
881884
89F5E0862908E655001F9377 /* Polling+AsyncAwait.swift in Sources */,
885+
897F84F42BA922B500BF354B /* NSLocking+Nimble.swift in Sources */,
882886
899441F82902EF2500C1FAF9 /* DSL+AsyncAwait.swift in Sources */,
883887
1FD8CD491968AB07008ED995 /* BeGreaterThanOrEqualTo.swift in Sources */,
884888
1FE661571E6574E30035F243 /* ExpectationMessage.swift in Sources */,

Sources/Nimble/ExpectationMessage.swift

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -174,33 +174,6 @@ public indirect enum ExpectationMessage: Sendable {
174174
}
175175
}
176176

177-
extension FailureMessage {
178-
internal func toExpectationMessage() -> ExpectationMessage {
179-
let defaultMessage = FailureMessage()
180-
if expected != defaultMessage.expected || _stringValueOverride != nil {
181-
return .fail(stringValue)
182-
}
183-
184-
var message: ExpectationMessage = .fail(userDescription ?? "")
185-
if actualValue != "" && actualValue != nil {
186-
message = .expectedCustomValueTo(postfixMessage, actual: actualValue ?? "")
187-
} else if postfixMessage != defaultMessage.postfixMessage {
188-
if actualValue == nil {
189-
message = .expectedTo(postfixMessage)
190-
} else {
191-
message = .expectedActualValueTo(postfixMessage)
192-
}
193-
}
194-
if postfixActual != defaultMessage.postfixActual {
195-
message = .appends(message, postfixActual)
196-
}
197-
if let extended = extendedMessage {
198-
message = .details(message, extended)
199-
}
200-
return message
201-
}
202-
}
203-
204177
#if canImport(Darwin)
205178
import class Foundation.NSObject
206179

Sources/Nimble/FailureMessage.swift

Lines changed: 147 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,81 @@ import Foundation
33
/// Encapsulates the failure message that matchers can report to the end user.
44
///
55
/// This is shared state between Nimble and matchers that mutate this value.
6-
public class FailureMessage: NSObject {
7-
public var expected: String = "expected"
8-
public var actualValue: String? = "" // empty string -> use default; nil -> exclude
9-
public var to: String = "to"
10-
public var postfixMessage: String = "match"
11-
public var postfixActual: String = ""
6+
public final class FailureMessage: NSObject, @unchecked Sendable {
7+
private let lock = NSRecursiveLock()
8+
9+
private var _expected: String = "expected"
10+
private var _actualValue: String? = "" // empty string -> use default; nil -> exclude
11+
private var _to: String = "to"
12+
private var _postfixMessage: String = "match"
13+
private var _postfixActual: String = ""
1214
/// An optional message that will be appended as a new line and provides additional details
1315
/// about the failure. This message will only be visible in the issue navigator / in logs but
1416
/// not directly in the source editor since only a single line is presented there.
15-
public var extendedMessage: String?
16-
public var userDescription: String?
17+
private var _extendedMessage: String?
18+
private var _userDescription: String?
1719

18-
public var stringValue: String {
20+
public var expected: String {
21+
get {
22+
return lock.sync { return _expected }
23+
}
24+
set {
25+
lock.sync { _expected = newValue }
26+
}
27+
}
28+
public var actualValue: String? {
29+
get {
30+
return lock.sync { return _actualValue }
31+
}
32+
set {
33+
lock.sync { _actualValue = newValue }
34+
}
35+
} // empty string -> use default; nil -> exclude
36+
public var to: String {
37+
get {
38+
return lock.sync { return _to }
39+
}
40+
set {
41+
lock.sync { _to = newValue }
42+
}
43+
}
44+
public var postfixMessage: String {
45+
get {
46+
return lock.sync { return _postfixMessage }
47+
}
48+
set {
49+
lock.sync { _postfixMessage = newValue }
50+
}
51+
}
52+
public var postfixActual: String {
53+
get {
54+
return lock.sync { return _postfixActual }
55+
}
56+
set {
57+
lock.sync { _postfixActual = newValue }
58+
}
59+
}
60+
/// An optional message that will be appended as a new line and provides additional details
61+
/// about the failure. This message will only be visible in the issue navigator / in logs but
62+
/// not directly in the source editor since only a single line is presented there.
63+
public var extendedMessage: String? {
64+
get {
65+
return lock.sync { return _extendedMessage }
66+
}
67+
set {
68+
lock.sync { _extendedMessage = newValue }
69+
}
70+
}
71+
public var userDescription: String? {
72+
get {
73+
return lock.sync { return _userDescription }
74+
}
75+
set {
76+
lock.sync { _userDescription = newValue }
77+
}
78+
}
79+
80+
private var _stringValue: String {
1981
get {
2082
if let value = _stringValueOverride {
2183
return value
@@ -27,66 +89,112 @@ public class FailureMessage: NSObject {
2789
_stringValueOverride = newValue
2890
}
2991
}
92+
public var stringValue: String {
93+
get {
94+
return lock.sync { return _stringValue }
95+
}
96+
set {
97+
lock.sync { _stringValue = newValue }
98+
}
99+
}
30100

31-
internal var _stringValueOverride: String?
32-
internal var hasOverriddenStringValue: Bool {
101+
private var _stringValueOverride: String?
102+
private var _hasOverriddenStringValue: Bool {
33103
return _stringValueOverride != nil
34104
}
35105

106+
internal var hasOverriddenStringValue: Bool {
107+
return lock.sync { return _hasOverriddenStringValue }
108+
}
109+
36110
public override init() {
111+
super.init()
37112
}
38113

39114
public init(stringValue: String) {
40115
_stringValueOverride = stringValue
41116
}
42117

43-
internal func stripNewlines(_ str: String) -> String {
118+
private func stripNewlines(_ str: String) -> String {
44119
let whitespaces = CharacterSet.whitespacesAndNewlines
45120
return str
46121
.components(separatedBy: "\n")
47122
.map { line in line.trimmingCharacters(in: whitespaces) }
48123
.joined(separator: "")
49124
}
50125

51-
internal func computeStringValue() -> String {
52-
var value = "\(expected) \(to) \(postfixMessage)"
53-
if let actualValue = actualValue {
54-
value = "\(expected) \(to) \(postfixMessage), got \(actualValue)\(postfixActual)"
55-
}
56-
value = stripNewlines(value)
126+
private func computeStringValue() -> String {
127+
return lock.sync {
128+
var value = "\(_expected) \(_to) \(_postfixMessage)"
129+
if let actualValue = _actualValue {
130+
value = "\(_expected) \(_to) \(_postfixMessage), got \(actualValue)\(_postfixActual)"
131+
}
132+
value = stripNewlines(value)
57133

58-
if let extendedMessage = extendedMessage {
59-
value += "\n\(extendedMessage)"
60-
}
134+
if let extendedMessage = _extendedMessage {
135+
value += "\n\(extendedMessage)"
136+
}
61137

62-
if let userDescription = userDescription {
63-
return "\(userDescription)\n\(value)"
64-
}
138+
if let userDescription = _userDescription {
139+
return "\(userDescription)\n\(value)"
140+
}
65141

66-
return value
142+
return value
143+
}
67144
}
68145

69146
internal func appendMessage(_ msg: String) {
70-
if hasOverriddenStringValue {
71-
stringValue += "\(msg)"
72-
} else if actualValue != nil {
73-
postfixActual += msg
74-
} else {
75-
postfixMessage += msg
147+
lock.sync {
148+
if _hasOverriddenStringValue {
149+
_stringValue += "\(msg)"
150+
} else if _actualValue != nil {
151+
_postfixActual += msg
152+
} else {
153+
_postfixMessage += msg
154+
}
76155
}
77156
}
78157

79158
internal func appendDetails(_ msg: String) {
80-
if hasOverriddenStringValue {
81-
if let desc = userDescription {
82-
stringValue = "\(desc)\n\(stringValue)"
159+
lock.sync {
160+
if _hasOverriddenStringValue {
161+
if let desc = _userDescription {
162+
_stringValue = "\(desc)\n\(_stringValue)"
163+
}
164+
_stringValue += "\n\(msg)"
165+
} else {
166+
if let desc = _userDescription {
167+
_userDescription = desc
168+
}
169+
_extendedMessage = msg
170+
}
171+
}
172+
}
173+
174+
internal func toExpectationMessage() -> ExpectationMessage {
175+
lock.sync {
176+
let defaultMessage = FailureMessage()
177+
if _expected != defaultMessage._expected || _hasOverriddenStringValue {
178+
return .fail(_stringValue)
179+
}
180+
181+
var message: ExpectationMessage = .fail(_userDescription ?? "")
182+
if _actualValue != "" && _actualValue != nil {
183+
message = .expectedCustomValueTo(_postfixMessage, actual: _actualValue ?? "")
184+
} else if _postfixMessage != defaultMessage._postfixMessage {
185+
if _actualValue == nil {
186+
message = .expectedTo(_postfixMessage)
187+
} else {
188+
message = .expectedActualValueTo(_postfixMessage)
189+
}
190+
}
191+
if _postfixActual != defaultMessage._postfixActual {
192+
message = .appends(message, _postfixActual)
83193
}
84-
stringValue += "\n\(msg)"
85-
} else {
86-
if let desc = userDescription {
87-
userDescription = desc
194+
if let extended = _extendedMessage {
195+
message = .details(message, extended)
88196
}
89-
extendedMessage = msg
197+
return message
90198
}
91199
}
92200
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Foundation
2+
3+
extension NSLocking {
4+
internal func sync<T>(_ closure: () throws -> T) rethrows -> T {
5+
lock()
6+
defer {
7+
unlock()
8+
}
9+
return try closure()
10+
}
11+
}

0 commit comments

Comments
 (0)