Skip to content

Commit 85178bd

Browse files
KaQuMiQKacper
authored and
Kacper
committed
update signal test support
1 parent 9749f2a commit 85178bd

File tree

5 files changed

+169
-29
lines changed

5 files changed

+169
-29
lines changed

Sources/Futura/Synchronization/Mutex.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,23 @@ public enum Mutex {
8080
@inline(__always)
8181
public static func lock(_ pointer: Pointer, timeout: UInt8) throws -> Void {
8282
#if os(Linux)
83-
var timeout = __time_t(timeout) * mSecSec
83+
var currentTimeout = __time_t(timeout) * mSecSec
8484
#else
85-
var timeout = __darwin_time_t(timeout) * mSecSec
85+
var currentTimeout = __darwin_time_t(timeout) * mSecSec
8686
#endif
8787

8888
var rem = timespec(tv_sec: 0, tv_nsec: 0)
8989
var req = timespec(tv_sec: 0, tv_nsec: 0)
9090
while pthread_mutex_trylock(pointer) != 0 {
91-
req.tv_nsec = timeout < nSecMsec ? timeout : nSecMsec
91+
if currentTimeout <= 0 {
92+
throw Timeout()
93+
} else { /* continue waiting */ }
94+
let requested = currentTimeout < nSecMsec ? currentTimeout : nSecMsec
95+
req.tv_nsec = requested
9296
while nanosleep(&req, &rem) == EINTR {
9397
req.tv_nsec = rem.tv_nsec
9498
}
95-
timeout -= (req.tv_nsec - rem.tv_nsec)
96-
if timeout <= 0 {
97-
throw Timeout()
98-
} else { continue }
99+
currentTimeout -= (requested - rem.tv_nsec)
99100
}
100101
}
101102

Sources/FuturaTest/Future+Expectation.swift

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ import XCTest
1818
public extension Future {
1919

2020
/// Asserts Future value if appeared. Fails if there was no value or timed out.
21-
/// Assertion is performed by TestExpectation.
21+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
2222
/// Note that waiting for expectation will block current thread.
23-
/// Waiting will be performed automatically if returned value is ignored.
23+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
2424
///
2525
/// - Parameter validation: Value validation function, assertion will fail if returns false
2626
/// - Parameter timeout: Wait timeout in seconds, default is 3
27-
/// - Returns: Test expectation of this assertion, it will wait immediately if not used
28-
@discardableResult
27+
/// - Parameter message: Custom failure message
28+
/// - Returns: Test expectation of this assertion
2929
func expectValue(_ validation: @escaping (Value) -> Bool = { _ in true },
3030
timeout: UInt8 = 3,
3131
message: @escaping @autoclosure () -> String = String(),
@@ -40,9 +40,7 @@ public extension Future {
4040
guard let expectation = expectation else { return }
4141
guard !expectation.timedOut else { return }
4242
let result = validation(value)
43-
guard !result else {
44-
return
45-
}
43+
guard !result else { return }
4644
let message = message()
4745
XCTFail(message.isEmpty ? "Invalid value" : message, file: file, line: line)
4846
}
@@ -58,14 +56,14 @@ public extension Future {
5856
}
5957

6058
/// Asserts Future error if appeared. Fails if there was no error or timed out.
61-
/// Assertion is performed by TestExpectation.
59+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
6260
/// Note that waiting for expectation will block current thread.
63-
/// Waiting will be performed automatically if returned value is ignored.
61+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
6462
///
6563
/// - Parameter validation: Error validation function, assertion will fail if returns false
6664
/// - Parameter timeout: Wait timeout in seconds, default is 3
67-
/// - Returns: Test expectation of this assertion, it will wait immediately if not used
68-
@discardableResult
65+
/// - Parameter message: Custom failure message
66+
/// - Returns: Test expectation of this assertion
6967
func expectError(_ validation: @escaping (Error) -> Bool = { _ in true },
7068
timeout: UInt8 = 3,
7169
message: @escaping @autoclosure () -> String = String(),
@@ -75,14 +73,12 @@ public extension Future {
7573
let expectation: TestExpectation = .init(timeout: timeout, file: file, line: line)
7674
let errorAppeared: AtomicFlag.Pointer = AtomicFlag.make()
7775

78-
self.error { [weak expectation] (error) in
76+
self.error { [weak expectation] (error) in
7977
defer { AtomicFlag.readAndSet(errorAppeared) }
8078
guard let expectation = expectation else { return }
8179
guard !expectation.timedOut else { return }
8280
let result = validation(error)
83-
guard !result else {
84-
return
85-
}
81+
guard !result else { return }
8682
let message = message()
8783
XCTFail(message.isEmpty ? "Invalid error" : message, file: file, line: line)
8884

@@ -99,13 +95,13 @@ public extension Future {
9995
}
10096

10197
/// Asserts if Future was cancelled. Fails if not cancelled or timed out.
102-
/// Assertion is performed by TestExpectation.
98+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
10399
/// Note that waiting for expectation will block current thread.
104-
/// Waiting will be performed automatically if returned value is ignored.
100+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
105101
///
106102
/// - Parameter timeout: Wait timeout in seconds, default is 3
107-
/// - Returns: Test expectation of this assertion, it will wait immediately if not used
108-
@discardableResult
103+
/// - Parameter message: Custom failure message
104+
/// - Returns: Test expectation of this assertion
109105
func expectCancelled(timeout: UInt8 = 3,
110106
message: @escaping @autoclosure () -> String = String(),
111107
file: StaticString = #file,
@@ -116,7 +112,8 @@ public extension Future {
116112
self.resulted { [weak expectation] in
117113
guard let expectation = expectation else { return }
118114
guard !expectation.timedOut else { return }
119-
XCTFail("Future completed with result", file: file, line: line)
115+
let message = message()
116+
XCTFail(message.isEmpty ? "Future completed with result" : message, file: file, line: line)
120117
}
121118
self.always { [weak expectation] in
122119
guard let expectation = expectation else { return }

Sources/FuturaTest/Signal+Expectation.swift

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,146 @@ import Futura
1616
import XCTest
1717

1818
public extension Signal {
19-
// TODO: will be in future :)
19+
20+
/// Asserts Signal values if appeared. Fails if there was too few values or timed out.
21+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
22+
/// Note that waiting for expectation will block current thread.
23+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
24+
///
25+
/// - Parameter count: Number of values it will wait for, default is 1
26+
/// - Parameter validation: Value validation function, assertion will fail if returns false
27+
/// - Parameter timeout: Wait timeout in seconds, default is 3
28+
/// - Parameter message: Custom failure message
29+
/// - Returns: Test expectation of this assertion
30+
func expectValues(count: UInt = 1,
31+
_ validation: @escaping ([Value]) -> Bool = { _ in true },
32+
timeout: UInt8 = 3,
33+
message: @escaping @autoclosure () -> String = String(),
34+
file: StaticString = #file,
35+
line: UInt = #line) -> TestExpectation
36+
{
37+
let expectation: TestExpectation = .init(timeout: timeout, file: file, line: line)
38+
39+
var valuesList: Array<Value> = .init()
40+
let valuesMutex: Mutex.Pointer = Mutex.make(recursive: true)
41+
42+
self.values { [weak expectation] (value) in
43+
guard let expectation = expectation else { return }
44+
Mutex.lock(valuesMutex)
45+
defer { Mutex.unlock(valuesMutex) }
46+
47+
if valuesList.count < count {
48+
valuesList.append(value)
49+
} else { /* nothing */ }
50+
guard valuesList.count == count else { return }
51+
guard !expectation.timedOut else { return }
52+
defer { expectation.fulfill() }
53+
let result = validation(valuesList)
54+
guard !result else { return }
55+
let message = message()
56+
XCTFail(message.isEmpty ? "Invalid values" : message, file: file, line: line)
57+
}
58+
self.finished {
59+
Mutex.destroy(valuesMutex)
60+
}
61+
62+
return expectation
63+
}
64+
65+
/// Asserts Signal errors if appeared. Fails if there was too few errors or timed out.
66+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
67+
/// Note that waiting for expectation will block current thread.
68+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
69+
///
70+
/// - Parameter count: Number of errors it will wait for, default is 1
71+
/// - Parameter validation: Error validation function, assertion will fail if returns false
72+
/// - Parameter timeout: Wait timeout in seconds, default is 3
73+
/// - Parameter message: Custom failure message
74+
/// - Returns: Test expectation of this assertion
75+
func expectErrors(count: UInt = 1,
76+
_ validation: @escaping ([Error]) -> Bool = { _ in true },
77+
timeout: UInt8 = 3,
78+
message: @escaping @autoclosure () -> String = String(),
79+
file: StaticString = #file,
80+
line: UInt = #line) -> TestExpectation
81+
{
82+
let expectation: TestExpectation = .init(timeout: timeout, file: file, line: line)
83+
84+
var errorsList: Array<Error> = .init()
85+
let errorsMutex: Mutex.Pointer = Mutex.make(recursive: true)
86+
87+
self.errors { [weak expectation] (error) in
88+
guard let expectation = expectation else { return }
89+
Mutex.lock(errorsMutex)
90+
defer { Mutex.unlock(errorsMutex) }
91+
if errorsList.count < count {
92+
errorsList.append(error)
93+
} else { /* nothing */ }
94+
guard errorsList.count == count else { return }
95+
guard !expectation.timedOut else { return }
96+
defer { expectation.fulfill() }
97+
let result = validation(errorsList)
98+
guard !result else { return }
99+
let message = message()
100+
XCTFail(message.isEmpty ? "Invalid errors" : message, file: file, line: line)
101+
}
102+
self.finished {
103+
Mutex.destroy(errorsMutex)
104+
}
105+
106+
return expectation
107+
}
108+
109+
/// Asserts Signal is terminated. Fails if it was finished without error or timed out.
110+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
111+
/// Note that waiting for expectation will block current thread.
112+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
113+
///
114+
/// - Parameter validation: Error validation function, assertion will fail if returns false
115+
/// - Parameter timeout: Wait timeout in seconds, default is 3
116+
/// - Parameter message: Custom failure message
117+
/// - Returns: Test expectation of this assertion
118+
func expectTerminated(_ validation: @escaping (Error) -> Bool = { _ in true },
119+
timeout: UInt8 = 3,
120+
message: @escaping @autoclosure () -> String = String(),
121+
file: StaticString = #file,
122+
line: UInt = #line) -> TestExpectation
123+
{
124+
let expectation: TestExpectation = .init(timeout: timeout, file: file, line: line)
125+
126+
self.terminated { [weak expectation] reason in
127+
guard let expectation = expectation else { return }
128+
defer { expectation.fulfill() }
129+
let result = validation(reason)
130+
guard !result else { return }
131+
let message = message()
132+
XCTFail(message.isEmpty ? "Invalid error" : message, file: file, line: line)
133+
}
134+
135+
136+
return expectation
137+
}
138+
139+
/// Asserts Signal is ended. Fails if it was finished with error or timed out.
140+
/// Timeout assertion is performed by TestExpectation when executing `waitForExpectation()`.
141+
/// Note that waiting for expectation will block current thread.
142+
/// Waiting have to be done manually by calling `waitForExpectation()` on returned Expectation.
143+
///
144+
/// - Parameter timeout: Wait timeout in seconds, default is 3
145+
/// - Parameter message: Custom failure message
146+
/// - Returns: Test expectation of this assertion
147+
func expectEnded(timeout: UInt8 = 3,
148+
message: @escaping @autoclosure () -> String = String(),
149+
file: StaticString = #file,
150+
line: UInt = #line) -> TestExpectation
151+
{
152+
let expectation: TestExpectation = .init(timeout: timeout, file: file, line: line)
153+
154+
self.ended { [weak expectation] in
155+
guard let expectation = expectation else { return }
156+
expectation.fulfill()
157+
}
158+
159+
return expectation
160+
}
20161
}

Sources/FuturaTest/TestExpectation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public final class TestExpectation {
4242
}
4343

4444
deinit {
45-
waitForExpectation()
45+
XCTAssertTrue(AtomicFlag.readAndSet(executed), "Expectation was ignored, use `waitForExpectation()` to verify this expectation", file: file, line: line)
4646
AtomicFlag.destroy(executed)
4747
AtomicFlag.destroy(timeoutFlag)
4848
Mutex.destroy(waitingMutex)

Tests/FuturaPerformanceTests/FuturaPerformanceTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
limitations under the License. */
1414

1515
import Futura
16+
import FuturaTest
1617
import XCTest
1718

1819
class RecursiveLockPerformanceTests: XCTestCase {

0 commit comments

Comments
 (0)