Skip to content

Commit 48ee78c

Browse files
authored
Add Waitable and Waiter. Update Tests errors (#1)
* Add Waitable and Waiter. Update Tests errors * Remove escaping from operation parameter
1 parent 69b849e commit 48ee78c

File tree

6 files changed

+156
-9
lines changed

6 files changed

+156
-9
lines changed

Sources/Test/Test.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ public func Test(
3737
functionName: String = #function,
3838
fileName: String = #file
3939
) throws {
40-
let result = t.suite(named: name) {
41-
try operation(tester)
40+
var result: Error?
41+
42+
t.suite(named: name) {
43+
do {
44+
try operation(tester)
45+
} catch {
46+
result = error
47+
}
4248
}
4349

44-
guard result == true else {
50+
if let result = result {
4551
let testName = name.map { "\($0) Test" } ?? "Test"
4652
throw TestError(
47-
description: "\(testName) failed.",
53+
description: "\(testName) failed. \(result.localizedDescription)",
4854
lineNumber: lineNumber,
4955
functionName: functionName,
5056
fileName: fileName
@@ -89,14 +95,20 @@ public func Test(
8995
functionName: String = #function,
9096
fileName: String = #file
9197
) async throws {
92-
let result = await t.suite(named: name) {
93-
try await operation(tester)
98+
var result: Error?
99+
100+
await t.suite(named: name) {
101+
do {
102+
try await operation(tester)
103+
} catch {
104+
result = error
105+
}
94106
}
95107

96-
guard result == true else {
108+
if let result = result {
97109
let testName = name.map { "\($0) Test" } ?? "Test"
98110
throw TestError(
99-
description: "\(testName) failed.",
111+
description: "\(testName) failed. \(result.localizedDescription)",
100112
lineNumber: lineNumber,
101113
functionName: functionName,
102114
fileName: fileName
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Foundation
2+
3+
extension Waitable {
4+
@discardableResult
5+
public func wait<Value>(
6+
for keyPath: KeyPath<Self, Value>,
7+
duration: TimeInterval = 3,
8+
interval: TimeInterval = 0.1,
9+
expecting: @escaping (Value) -> Bool
10+
) async throws -> Value {
11+
try await wait(
12+
for: keyPath,
13+
interation: 0,
14+
duration: abs(duration),
15+
interval: abs(interval),
16+
expecting: expecting
17+
)
18+
}
19+
20+
@discardableResult
21+
public func wait<Value: Equatable>(
22+
for keyPath: KeyPath<Self, Value>,
23+
duration: TimeInterval = 3,
24+
interval: TimeInterval = 0.1,
25+
expecting: Value
26+
) async throws -> Value {
27+
try await wait(
28+
for: keyPath,
29+
interation: 0,
30+
duration: abs(duration),
31+
interval: abs(interval),
32+
expecting: { value in
33+
expecting == value
34+
}
35+
)
36+
}
37+
38+
private func wait(duration: TimeInterval) async throws {
39+
try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * abs(duration)))
40+
}
41+
42+
private func wait<Value>(
43+
for keyPath: KeyPath<Self, Value>,
44+
interation: UInt,
45+
duration: TimeInterval,
46+
interval: TimeInterval,
47+
expecting: @escaping (Value) -> Bool
48+
) async throws -> Value {
49+
guard Double(interation) * interval < duration else {
50+
throw WaitableError.timeout(duration)
51+
}
52+
53+
let value = self[keyPath: keyPath]
54+
55+
if expecting(value) {
56+
return value
57+
}
58+
59+
try await wait(duration: interval)
60+
61+
return try await wait(
62+
for: keyPath,
63+
interation: interation + 1,
64+
duration: duration,
65+
interval: interval,
66+
expecting: expecting
67+
)
68+
}
69+
}

Sources/Test/Wait/Waitable.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Foundation
2+
3+
public protocol Waitable {
4+
@discardableResult
5+
func wait<Value>(
6+
for keyPath: KeyPath<Self, Value>,
7+
duration: TimeInterval,
8+
interval: TimeInterval,
9+
expecting: @escaping (Value) -> Bool
10+
) async throws -> Value
11+
12+
@discardableResult
13+
func wait<Value: Equatable>(
14+
for keyPath: KeyPath<Self, Value>,
15+
duration: TimeInterval,
16+
interval: TimeInterval,
17+
expecting: Value
18+
) async throws -> Value
19+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Foundation
2+
3+
public enum WaitableError: LocalizedError {
4+
case timeout(TimeInterval)
5+
6+
public var errorDescription: String? {
7+
switch self {
8+
case let .timeout(duration): return "Waitable Timeout: Exceeded duration of \(duration) seconds."
9+
}
10+
}
11+
}

Sources/Test/Wait/Waiter.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
public struct Waiter<Value: AnyObject>: Waitable {
4+
public var value: Value
5+
6+
public init(_ value: Value) {
7+
self.value = value
8+
}
9+
}

Tests/TestTests/TestTests.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ final class TestTests: XCTestCase {
2525
]
2626
),
2727
operation: { tester in
28-
try await Expect(description) {
28+
try await Expect("Examples") {
2929
tester.logInfo("Info!")
3030

3131
try tester.assert(0, isEqualTo: 0)
@@ -37,4 +37,31 @@ final class TestTests: XCTestCase {
3737
}
3838
)
3939
}
40+
41+
func testWaiter() async throws {
42+
class Value {
43+
var count = 0
44+
}
45+
46+
let value = Value()
47+
let waiter = Waiter(value)
48+
49+
try await waiter.wait(
50+
for: \.value.count,
51+
expecting: 0
52+
)
53+
54+
XCTAssertEqual(value.count, 0)
55+
56+
Task {
57+
try await Task.sleep(nanoseconds: 500_000_000)
58+
value.count += 1
59+
}
60+
61+
XCTAssertEqual(value.count, 0)
62+
63+
try await waiter.wait(for: \.value.count, duration: 2, interval: 0.5, expecting: 1)
64+
65+
XCTAssertEqual(value.count, 1)
66+
}
4067
}

0 commit comments

Comments
 (0)