Repository navigation
Expand file tree
/
Copy pathJavaScriptEventLoopYieldIntervalTests.swift
More file actions
106 lines (96 loc) · 3.93 KB
/
Copy pathJavaScriptEventLoopYieldIntervalTests.swift
File metadata and controls
106 lines (96 loc) · 3.93 KB
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import JavaScriptEventLoop
import JavaScriptKit
import XCTest
final class JavaScriptEventLoopYieldIntervalTests: XCTestCase {
final class Box<Value>: @unchecked Sendable {
var value: Value
init(_ value: Value) { self.value = value }
}
override func tearDown() {
JavaScriptEventLoop.shared.yieldInterval = nil
super.tearDown()
}
static func busyWait(milliseconds: Int) {
let end = ContinuousClock.now + .milliseconds(milliseconds)
while ContinuousClock.now < end {}
}
/// Returns, for each busy task, whether a JavaScript timer scheduled before them had fired.
func timerRanAfterEachTask(count: Int, milliseconds: Int) async -> [Bool] {
let timerRan = Box(false)
_ = JSObject.global.setTimeout!(
JSOneshotClosure { _ in
timerRan.value = true
return .undefined
},
0
)
let observed = Box([Bool]())
let tasks = (0..<count).map { _ in
Task {
Self.busyWait(milliseconds: milliseconds)
observed.value.append(timerRan.value)
}
}
for task in tasks { await task.value }
return observed.value
}
func testYieldIntervalIsNilByDefault() {
XCTAssertNil(JavaScriptEventLoop.shared.yieldInterval)
}
func testJobsRunInOneDrainWithoutYieldInterval() async {
let observed = await timerRanAfterEachTask(count: 20, milliseconds: 2)
XCTAssertEqual(observed, Array(repeating: false, count: 20))
}
func testJavaScriptTasksRunBetweenJobsWithYieldInterval() async {
JavaScriptEventLoop.shared.yieldInterval = .milliseconds(5)
let observed = await timerRanAfterEachTask(count: 20, milliseconds: 2)
XCTAssertEqual(observed.first, false)
XCTAssertEqual(observed.last, true)
}
func testYieldIntervalAppliesToALaterDrain() async throws {
JavaScriptEventLoop.shared.yieldInterval = .milliseconds(5)
// Start a new drain.
try await Task.sleep(nanoseconds: 1_000_000)
let observed = await timerRanAfterEachTask(count: 20, milliseconds: 2)
XCTAssertEqual(observed.first, false)
XCTAssertEqual(observed.last, true)
}
func testJobsKeepTheirOrderAcrossYields() async {
JavaScriptEventLoop.shared.yieldInterval = .milliseconds(1)
let order = Box([Int]())
let tasks = (0..<20).map { index in
Task {
Self.busyWait(milliseconds: 1)
order.value.append(index)
}
}
for task in tasks { await task.value }
XCTAssertEqual(order.value, Array(0..<20))
}
func testPriorityOrderAcrossYields() async {
JavaScriptEventLoop.shared.yieldInterval = .milliseconds(1)
let order = Box([String]())
let expectedOrder = ["h0", "h1", "h2", "h3", "h4", "m0", "m1", "m2", "m3", "l0", "l1", "l2", "l3", "l4"]
// Tasks are not awaited, so no await raises the priority of a queued task.
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
@Sendable func record(_ name: String) {
Self.busyWait(milliseconds: 1)
order.value.append(name)
if order.value.count == expectedOrder.count { continuation.resume() }
}
let priorities: [(String, TaskPriority)] = [("l", .low), ("m", .medium), ("h", .high)]
for index in 0..<12 {
let (prefix, priority) = priorities[index % 3]
let name = "\(prefix)\(index / 3)"
Task(priority: priority) {
record(name)
if name == "h0" {
Task(priority: .low) { record("l4") }
Task(priority: .high) { record("h4") }
}
}
}
}
XCTAssertEqual(order.value, expectedOrder)
}
}