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 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
|
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
private import _TestingInternals
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
extension Test {
/// A clock used to track time when events occur during testing.
///
/// This clock tracks time using both the [suspending clock](https://developer.apple.com/documentation/swift/suspendingclock)
/// and the wall clock. Only the suspending clock is used for comparing and
/// calculating; the wall clock is used for presentation when needed.
public struct Clock: Sendable {
/// An instant on the testing clock.
public struct Instant: Sendable {
/// The suspending-clock time corresponding to this instant.
fileprivate(set) var suspending: TimeValue = {
#if SWT_TARGET_OS_APPLE
// The testing library's availability on Apple platforms is earlier than
// that of the Swift Clock API, so we don't use `SuspendingClock`
// directly on them and instead derive a value from platform-specific
// API. SuspendingClock corresponds to CLOCK_UPTIME_RAW on Darwin.
// SEE: https://github.com/swiftlang/swift/blob/main/stdlib/public/Concurrency/Clock.cpp
var uptime = timespec()
_ = clock_gettime(CLOCK_UPTIME_RAW, &uptime)
return TimeValue(uptime)
#else
/// The corresponding suspending-clock time.
TimeValue(SuspendingClock.Instant.now)
#endif
}()
#if !SWT_NO_UTC_CLOCK
/// The wall-clock time corresponding to this instant.
fileprivate(set) var wall: TimeValue = {
var wall = timespec()
timespec_get(&wall, TIME_UTC)
return TimeValue(wall)
}()
#endif
/// The current time according to the testing clock.
public static var now: Self {
Self()
}
}
public init() {}
}
}
// MARK: -
@_spi(Experimental) @_spi(ForToolsIntegrationOnly)
@available(_clockAPI, *)
extension SuspendingClock.Instant {
/// Initialize this instant to the equivalent of the same instant on the
/// testing library's clock.
///
/// - Parameters:
/// - testClockInstant: The equivalent instant on ``Test/Clock``.
public init(_ testClockInstant: Test.Clock.Instant) {
self.init(testClockInstant.suspending)
}
}
extension Test.Clock.Instant {
#if !SWT_NO_UTC_CLOCK
/// The duration since 1970 represented by this instance as a tuple of seconds
/// and attoseconds.
///
/// The value of this property is the equivalent of `self` on the wall clock.
/// It is suitable for display to the user, but not for fine timing
/// calculations.
public var timeComponentsSince1970: (seconds: Int64, attoseconds: Int64) {
wall.components
}
/// The duration since 1970 represented by this instance.
///
/// The value of this property is the equivalent of `self` on the wall clock.
/// It is suitable for display to the user, but not for fine timing
/// calculations.
@available(_clockAPI, *)
public var durationSince1970: Duration {
Duration(wall)
}
#endif
/// Get the number of nanoseconds from this instance to another.
///
/// - Parameters:
/// - other: The later instant.
///
/// - Returns: The number of nanoseconds between `self` and `other`. If
/// `other` is ordered before this instance, the result is negative.
func nanoseconds(until other: Self) -> Int64 {
if other < self {
return -other.nanoseconds(until: self)
}
let otherNanoseconds = (other.suspending.seconds * 1_000_000_000) + (other.suspending.attoseconds / 1_000_000_000)
let selfNanoseconds = (suspending.seconds * 1_000_000_000) + (suspending.attoseconds / 1_000_000_000)
return otherNanoseconds - selfNanoseconds
}
}
// MARK: - Sleeping
extension Test.Clock {
/// Suspend the current task for the given duration.
///
/// - Parameters:
/// - duration: How long to suspend for.
///
/// - Throws: `CancellationError` if the current task was cancelled while it
/// was sleeping.
///
/// This function is not part of the public interface of the testing library.
/// It is primarily used by the testing library's own tests. External clients
/// can use ``sleep(for:tolerance:)`` or ``sleep(until:tolerance:)`` instead.
@available(_clockAPI, *)
static func sleep(for duration: Duration) async throws {
#if SWT_NO_UNSTRUCTURED_TASKS
let timeValue = TimeValue(duration)
var ts = timespec(timeValue)
var tsRemaining = ts
while 0 != nanosleep(&ts, &tsRemaining) {
try Task.checkCancellation()
ts = tsRemaining
}
#else
return try await SuspendingClock().sleep(for: duration)
#endif
}
}
// MARK: - Clock
@available(_clockAPI, *)
extension Test.Clock: _Concurrency.Clock {
public typealias Duration = SuspendingClock.Duration
public var now: Instant {
.now
}
public var minimumResolution: Duration {
#if SWT_TARGET_OS_APPLE
var res = timespec()
_ = clock_getres(CLOCK_UPTIME_RAW, &res)
return Duration(TimeValue(res))
#else
SuspendingClock().minimumResolution
#endif
}
public func sleep(until deadline: Instant, tolerance: Duration?) async throws {
let duration = Instant.now.duration(to: deadline)
#if SWT_NO_UNSTRUCTURED_TASKS
try await Self.sleep(for: duration)
#else
try await SuspendingClock().sleep(for: duration, tolerance: tolerance)
#endif
}
}
// MARK: - Equatable, Hashable, Comparable
extension Test.Clock.Instant: Equatable, Hashable, Comparable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.suspending == rhs.suspending
}
public func hash(into hasher: inout Hasher) {
hasher.combine(suspending)
}
public static func <(lhs: Self, rhs: Self) -> Bool {
lhs.suspending < rhs.suspending
}
}
// MARK: - InstantProtocol
@available(_clockAPI, *)
extension Test.Clock.Instant: InstantProtocol {
public typealias Duration = Swift.Duration
public func advanced(by duration: Duration) -> Self {
var result = self
result.suspending = TimeValue(Duration(result.suspending) + duration)
#if !SWT_NO_UTC_CLOCK
result.wall = TimeValue(Duration(result.wall) + duration)
#endif
return result
}
public func duration(to other: Test.Clock.Instant) -> Duration {
Duration(other.suspending) - Duration(suspending)
}
}
// MARK: - Duration descriptions
extension Test.Clock.Instant {
/// Get a description of the duration between this instance and another.
///
/// - Parameters:
/// - other: The later instant.
///
/// - Returns: A string describing the duration between `self` and `other`,
/// up to millisecond accuracy.
func descriptionOfDuration(to other: Test.Clock.Instant) -> String {
#if SWT_TARGET_OS_APPLE
let (seconds, nanosecondsRemaining) = nanoseconds(until: other).quotientAndRemainder(dividingBy: 1_000_000_000)
return String(describing: TimeValue((seconds, nanosecondsRemaining * 1_000_000_000)))
#else
return String(describing: TimeValue(Duration(other.suspending) - Duration(suspending)))
#endif
}
}
// MARK: - Codable
extension Test.Clock.Instant: Codable {}
|