adopt sendable (#109)

motivation: adjust to swift 5.6

changes:
* define sendable shims for protocols and structs that may be used in async context
* refactor Gauge to include rather than inherit Recorder
* adjust tests
* add a test to make sure no warning are emitted
This commit is contained in:
tomer doron 2022-07-01 16:38:50 -07:00 committed by GitHub
parent fd0ee6956b
commit d885a4f5e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 214 additions and 59 deletions

View File

@ -12,7 +12,9 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// MARK: User API // MARK: - User API
// MARK: - Counter
extension Counter { extension Counter {
/// Create a new `Counter`. /// Create a new `Counter`.
@ -39,9 +41,9 @@ extension Counter {
/// This is the user-facing Counter API. /// This is the user-facing Counter API.
/// ///
/// Its behavior depends on the `CounterHandler` implementation. /// Its behavior depends on the `CounterHandler` implementation.
public class Counter { public final class Counter {
@usableFromInline @usableFromInline
var handler: CounterHandler let handler: CounterHandler
public let label: String public let label: String
public let dimensions: [(String, String)] public let dimensions: [(String, String)]
@ -90,6 +92,8 @@ extension Counter: CustomStringConvertible {
} }
} }
// MARK: - FloatingPointCounter
extension FloatingPointCounter { extension FloatingPointCounter {
/// Create a new `FloatingPointCounter`. /// Create a new `FloatingPointCounter`.
/// ///
@ -116,9 +120,9 @@ extension FloatingPointCounter {
/// This is the user-facing FloatingPointCounter API. /// This is the user-facing FloatingPointCounter API.
/// ///
/// Its behavior depends on the `FloatingCounterHandler` implementation. /// Its behavior depends on the `FloatingCounterHandler` implementation.
public class FloatingPointCounter { public final class FloatingPointCounter {
@usableFromInline @usableFromInline
var handler: FloatingPointCounterHandler let handler: FloatingPointCounterHandler
public let label: String public let label: String
public let dimensions: [(String, String)] public let dimensions: [(String, String)]
@ -167,13 +171,15 @@ extension FloatingPointCounter: CustomStringConvertible {
} }
} }
public extension Recorder { // MARK: - Recorder
extension Recorder {
/// Create a new `Recorder`. /// Create a new `Recorder`.
/// ///
/// - parameters: /// - parameters:
/// - label: The label for the `Recorder`. /// - label: The label for the `Recorder`.
/// - dimensions: The dimensions for the `Recorder`. /// - dimensions: The dimensions for the `Recorder`.
convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) { public convenience init(label: String, dimensions: [(String, String)] = [], aggregate: Bool = true) {
let handler = MetricsSystem.factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate) let handler = MetricsSystem.factory.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler) self.init(label: label, dimensions: dimensions, aggregate: aggregate, handler: handler)
} }
@ -181,7 +187,7 @@ public extension Recorder {
/// Signal the underlying metrics library that this recorder will never be updated again. /// Signal the underlying metrics library that this recorder will never be updated again.
/// In response the library MAY decide to eagerly release any resources held by this `Recorder`. /// In response the library MAY decide to eagerly release any resources held by this `Recorder`.
@inlinable @inlinable
func destroy() { public func destroy() {
MetricsSystem.factory.destroyRecorder(self.handler) MetricsSystem.factory.destroyRecorder(self.handler)
} }
} }
@ -193,7 +199,7 @@ public extension Recorder {
/// Its behavior depends on the `RecorderHandler` implementation. /// Its behavior depends on the `RecorderHandler` implementation.
public class Recorder { public class Recorder {
@usableFromInline @usableFromInline
var handler: RecorderHandler let handler: RecorderHandler
public let label: String public let label: String
public let dimensions: [(String, String)] public let dimensions: [(String, String)]
public let aggregate: Bool public let aggregate: Bool
@ -247,10 +253,12 @@ extension Recorder: CustomStringConvertible {
} }
} }
// MARK: - Gauge
/// A gauge is a metric that represents a single numerical value that can arbitrarily go up and down. /// A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.
/// Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of active threads. /// Gauges are typically used for measured values like temperatures or current memory usage, but also "counts" that can go up and down, like the number of active threads.
/// Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation. /// Gauges are modeled as `Recorder` with a sample size of 1 and that does not perform any aggregation.
public class Gauge: Recorder { public final class Gauge: Recorder {
/// Create a new `Gauge`. /// Create a new `Gauge`.
/// ///
/// - parameters: /// - parameters:
@ -261,6 +269,8 @@ public class Gauge: Recorder {
} }
} }
// MARK: - Timer
public struct TimeUnit: Equatable { public struct TimeUnit: Equatable {
private enum Code: Equatable { private enum Code: Equatable {
case nanoseconds case nanoseconds
@ -328,9 +338,9 @@ public extension Timer {
/// This is the user-facing Timer API. /// This is the user-facing Timer API.
/// ///
/// Its behavior depends on the `TimerHandler` implementation. /// Its behavior depends on the `TimerHandler` implementation.
public class Timer { public final class Timer {
@usableFromInline @usableFromInline
var handler: TimerHandler let handler: TimerHandler
public let label: String public let label: String
public let dimensions: [(String, String)] public let dimensions: [(String, String)]
@ -451,6 +461,8 @@ extension Timer: CustomStringConvertible {
} }
} }
// MARK: - MetricsSystem
/// The `MetricsSystem` is a global facility where the default metrics backend implementation (`MetricsFactory`) can be /// The `MetricsSystem` is a global facility where the default metrics backend implementation (`MetricsFactory`) can be
/// configured. `MetricsSystem` is set up just once in a given program to set up the desired metrics backend /// configured. `MetricsSystem` is set up just once in a given program to set up the desired metrics backend
/// implementation. /// implementation.
@ -459,7 +471,7 @@ public enum MetricsSystem {
/// `bootstrap` is an one-time configuration function which globally selects the desired metrics backend /// `bootstrap` is an one-time configuration function which globally selects the desired metrics backend
/// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will /// implementation. `bootstrap` can be called at maximum once in any given program, calling it more than once will
/// lead to undefined behaviour, most likely a crash. /// lead to undefined behavior, most likely a crash.
/// ///
/// - parameters: /// - parameters:
/// - factory: A factory that given an identifier produces instances of metrics handlers such as `CounterHandler`, `RecorderHandler` and `TimerHandler`. /// - factory: A factory that given an identifier produces instances of metrics handlers such as `CounterHandler`, `RecorderHandler` and `TimerHandler`.
@ -514,7 +526,9 @@ public enum MetricsSystem {
} }
} }
// MARK: Library SPI, intended to be implemented by backend libraries // MARK: - Library SPI, intended to be implemented by backend libraries
// MARK: - MetricsFactory
/// The `MetricsFactory` is the bridge between the `MetricsSystem` and the metrics backend implementation. /// The `MetricsFactory` is the bridge between the `MetricsSystem` and the metrics backend implementation.
/// `MetricsFactory`'s role is to initialize concrete implementations of the various metric types: /// `MetricsFactory`'s role is to initialize concrete implementations of the various metric types:
@ -601,7 +615,7 @@ public protocol MetricsFactory {
} }
/// Wraps a CounterHandler, adding support for incrementing by floating point values by storing an accumulated floating point value and recording increments to the underlying CounterHandler after crossing integer boundaries. /// Wraps a CounterHandler, adding support for incrementing by floating point values by storing an accumulated floating point value and recording increments to the underlying CounterHandler after crossing integer boundaries.
internal class AccumulatingRoundingFloatingPointCounter: FloatingPointCounterHandler { internal final class AccumulatingRoundingFloatingPointCounter: FloatingPointCounterHandler {
private let lock = Lock() private let lock = Lock()
private let counterHandler: CounterHandler private let counterHandler: CounterHandler
internal var fraction: Double = 0 internal var fraction: Double = 0
@ -689,6 +703,8 @@ extension MetricsFactory {
} }
} }
// MARK: - Backend Handlers
/// A `CounterHandler` represents a backend implementation of a `Counter`. /// A `CounterHandler` represents a backend implementation of a `Counter`.
/// ///
/// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend. /// This type is an implementation detail and should not be used directly, unless implementing your own metrics backend.
@ -700,7 +716,7 @@ extension MetricsFactory {
/// as expected regardless of the selected `CounterHandler` implementation. /// as expected regardless of the selected `CounterHandler` implementation.
/// ///
/// - The `CounterHandler` must be a `class`. /// - The `CounterHandler` must be a `class`.
public protocol CounterHandler: AnyObject { public protocol CounterHandler: AnyObject, _SwiftMetricsSendableProtocol {
/// Increment the counter. /// Increment the counter.
/// ///
/// - parameters: /// - parameters:
@ -722,7 +738,7 @@ public protocol CounterHandler: AnyObject {
/// as expected regardless of the selected `FloatingPointCounterHandler` implementation. /// as expected regardless of the selected `FloatingPointCounterHandler` implementation.
/// ///
/// - The `FloatingPointCounterHandler` must be a `class`. /// - The `FloatingPointCounterHandler` must be a `class`.
public protocol FloatingPointCounterHandler: AnyObject { public protocol FloatingPointCounterHandler: AnyObject, _SwiftMetricsSendableProtocol {
/// Increment the counter. /// Increment the counter.
/// ///
/// - parameters: /// - parameters:
@ -744,7 +760,7 @@ public protocol FloatingPointCounterHandler: AnyObject {
/// as expected regardless of the selected `RecorderHandler` implementation. /// as expected regardless of the selected `RecorderHandler` implementation.
/// ///
/// - The `RecorderHandler` must be a `class`. /// - The `RecorderHandler` must be a `class`.
public protocol RecorderHandler: AnyObject { public protocol RecorderHandler: AnyObject, _SwiftMetricsSendableProtocol {
/// Record a value. /// Record a value.
/// ///
/// - parameters: /// - parameters:
@ -768,7 +784,7 @@ public protocol RecorderHandler: AnyObject {
/// as expected regardless of the selected `TimerHandler` implementation. /// as expected regardless of the selected `TimerHandler` implementation.
/// ///
/// - The `TimerHandler` must be a `class`. /// - The `TimerHandler` must be a `class`.
public protocol TimerHandler: AnyObject { public protocol TimerHandler: AnyObject, _SwiftMetricsSendableProtocol {
/// Record a duration in nanoseconds. /// Record a duration in nanoseconds.
/// ///
/// - parameters: /// - parameters:
@ -788,7 +804,7 @@ extension TimerHandler {
} }
} }
// MARK: Predefined Metrics Handlers // MARK: - Predefined Metrics Handlers
/// A pseudo-metrics handler that can be used to send messages to multiple other metrics handlers. /// A pseudo-metrics handler that can be used to send messages to multiple other metrics handlers.
public final class MultiplexMetricsHandler: MetricsFactory { public final class MultiplexMetricsHandler: MetricsFactory {
@ -837,7 +853,7 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
} }
private class MuxCounter: CounterHandler { private final class MuxCounter: CounterHandler {
let counters: [CounterHandler] let counters: [CounterHandler]
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) { public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
self.counters = factories.map { $0.makeCounter(label: label, dimensions: dimensions) } self.counters = factories.map { $0.makeCounter(label: label, dimensions: dimensions) }
@ -852,7 +868,7 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
} }
private class MuxFloatingPointCounter: FloatingPointCounterHandler { private final class MuxFloatingPointCounter: FloatingPointCounterHandler {
let counters: [FloatingPointCounterHandler] let counters: [FloatingPointCounterHandler]
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) { public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
self.counters = factories.map { $0.makeFloatingPointCounter(label: label, dimensions: dimensions) } self.counters = factories.map { $0.makeFloatingPointCounter(label: label, dimensions: dimensions) }
@ -867,7 +883,7 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
} }
private class MuxRecorder: RecorderHandler { private final class MuxRecorder: RecorderHandler {
let recorders: [RecorderHandler] let recorders: [RecorderHandler]
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)], aggregate: Bool) { public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)], aggregate: Bool) {
self.recorders = factories.map { $0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate) } self.recorders = factories.map { $0.makeRecorder(label: label, dimensions: dimensions, aggregate: aggregate) }
@ -882,7 +898,7 @@ public final class MultiplexMetricsHandler: MetricsFactory {
} }
} }
private class MuxTimer: TimerHandler { private final class MuxTimer: TimerHandler {
let timers: [TimerHandler] let timers: [TimerHandler]
public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) { public init(factories: [MetricsFactory], label: String, dimensions: [(String, String)]) {
self.timers = factories.map { $0.makeTimer(label: label, dimensions: dimensions) } self.timers = factories.map { $0.makeTimer(label: label, dimensions: dimensions) }
@ -928,3 +944,22 @@ public final class NOOPMetricsHandler: MetricsFactory, CounterHandler, FloatingP
public func record(_: Double) {} public func record(_: Double) {}
public func recordNanoseconds(_: Int64) {} public func recordNanoseconds(_: Int64) {}
} }
// MARK: - Sendable support helpers
#if compiler(>=5.6)
extension MetricsSystem: Sendable {}
extension Counter: Sendable {}
extension FloatingPointCounter: Sendable {}
// must be @unchecked since Gauge inherits Recorder :(
extension Recorder: @unchecked Sendable {}
extension Timer: Sendable {}
// ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks
extension AccumulatingRoundingFloatingPointCounter: @unchecked Sendable {}
#endif
#if compiler(>=5.6)
@preconcurrency public protocol _SwiftMetricsSendableProtocol: Sendable {}
#else
public protocol _SwiftMetricsSendableProtocol {}
#endif

View File

@ -16,7 +16,7 @@
@_exported import class CoreMetrics.Timer @_exported import class CoreMetrics.Timer
import Foundation import Foundation
public extension Timer { extension Timer {
/// Convenience for measuring duration of a closure. /// Convenience for measuring duration of a closure.
/// ///
/// - parameters: /// - parameters:
@ -24,7 +24,7 @@ public extension Timer {
/// - dimensions: The dimensions for the Timer. /// - dimensions: The dimensions for the Timer.
/// - body: Closure to run & record. /// - body: Closure to run & record.
@inlinable @inlinable
static func measure<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T { public static func measure<T>(label: String, dimensions: [(String, String)] = [], body: @escaping () throws -> T) rethrows -> T {
let timer = Timer(label: label, dimensions: dimensions) let timer = Timer(label: label, dimensions: dimensions)
let start = DispatchTime.now().uptimeNanoseconds let start = DispatchTime.now().uptimeNanoseconds
defer { defer {
@ -39,18 +39,18 @@ public extension Timer {
/// - parameters: /// - parameters:
/// - since: Start of the interval as `DispatchTime`. /// - since: Start of the interval as `DispatchTime`.
/// - end: End of the interval, defaulting to `.now()`. /// - end: End of the interval, defaulting to `.now()`.
func recordInterval(since: DispatchTime, end: DispatchTime = .now()) { public func recordInterval(since: DispatchTime, end: DispatchTime = .now()) {
self.recordNanoseconds(end.uptimeNanoseconds - since.uptimeNanoseconds) self.recordNanoseconds(end.uptimeNanoseconds - since.uptimeNanoseconds)
} }
} }
public extension Timer { extension Timer {
/// Convenience for recording a duration based on TimeInterval. /// Convenience for recording a duration based on TimeInterval.
/// ///
/// - parameters: /// - parameters:
/// - duration: The duration to record. /// - duration: The duration to record.
@inlinable @inlinable
func record(_ duration: TimeInterval) { public func record(_ duration: TimeInterval) {
self.recordSeconds(duration) self.recordSeconds(duration)
} }
@ -59,7 +59,7 @@ public extension Timer {
/// - parameters: /// - parameters:
/// - duration: The duration to record. /// - duration: The duration to record.
@inlinable @inlinable
func record(_ duration: DispatchTimeInterval) { public func record(_ duration: DispatchTimeInterval) {
switch duration { switch duration {
case .nanoseconds(let value): case .nanoseconds(let value):
self.recordNanoseconds(value) self.recordNanoseconds(value)

View File

@ -139,14 +139,10 @@ extension TestMetrics.FullKey: Hashable {
} }
} }
// ==== ---------------------------------------------------------------------------------------------------------------- // MARK: - Assertions
// MARK: Assertions
extension TestMetrics { extension TestMetrics {
// ==== ------------------------------------------------------------------------------------------------------------ // MARK: - Counter
// MARK: Counter
public func expectCounter(_ metric: Counter) throws -> TestCounter { public func expectCounter(_ metric: Counter) throws -> TestCounter {
guard let counter = metric.handler as? TestCounter else { guard let counter = metric.handler as? TestCounter else {
@ -168,9 +164,7 @@ extension TestMetrics {
return testCounter return testCounter
} }
// ==== ------------------------------------------------------------------------------------------------------------ // MARK: - Gauge
// MARK: Gauge
public func expectGauge(_ metric: Gauge) throws -> TestRecorder { public func expectGauge(_ metric: Gauge) throws -> TestRecorder {
return try self.expectRecorder(metric) return try self.expectRecorder(metric)
@ -180,9 +174,7 @@ extension TestMetrics {
return try self.expectRecorder(label, dimensions) return try self.expectRecorder(label, dimensions)
} }
// ==== ------------------------------------------------------------------------------------------------------------ // MARK: - Recorder
// MARK: Recorder
public func expectRecorder(_ metric: Recorder) throws -> TestRecorder { public func expectRecorder(_ metric: Recorder) throws -> TestRecorder {
guard let recorder = metric.handler as? TestRecorder else { guard let recorder = metric.handler as? TestRecorder else {
@ -204,9 +196,7 @@ extension TestMetrics {
return testRecorder return testRecorder
} }
// ==== ------------------------------------------------------------------------------------------------------------ // MARK: - Timer
// MARK: Timer
public func expectTimer(_ metric: CoreMetrics.Timer) throws -> TestTimer { public func expectTimer(_ metric: CoreMetrics.Timer) throws -> TestTimer {
guard let timer = metric.handler as? TestTimer else { guard let timer = metric.handler as? TestTimer else {
@ -229,9 +219,7 @@ extension TestMetrics {
} }
} }
// ==== ---------------------------------------------------------------------------------------------------------------- // MARK: - Metric type implementations
// MARK: Metric type implementations
public protocol TestMetric { public protocol TestMetric {
associatedtype Value associatedtype Value
@ -418,11 +406,27 @@ extension NSLock {
} }
} }
// ==== ---------------------------------------------------------------------------------------------------------------- // MARK: - Errors
// MARK: Errors #if compiler(>=5.6)
public enum TestMetricsError: Error {
case missingMetric(label: String, dimensions: [(String, String)])
case illegalMetricType(metric: Sendable, expected: String)
}
#else
public enum TestMetricsError: Error { public enum TestMetricsError: Error {
case missingMetric(label: String, dimensions: [(String, String)]) case missingMetric(label: String, dimensions: [(String, String)])
case illegalMetricType(metric: Any, expected: String) case illegalMetricType(metric: Any, expected: String)
} }
#endif
// MARK: - Sendable support
#if compiler(>=5.6)
// ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks
extension TestMetrics: @unchecked Sendable {}
extension TestCounter: @unchecked Sendable {}
extension TestRecorder: @unchecked Sendable {}
extension TestTimer: @unchecked Sendable {}
#endif

View File

@ -384,7 +384,7 @@ class MetricsTests: XCTestCase {
} }
func testCustomFactory() { func testCustomFactory() {
class CustomHandler: CounterHandler { final class CustomHandler: CounterHandler {
func increment<DataType>(by: DataType) where DataType: BinaryInteger {} func increment<DataType>(by: DataType) where DataType: BinaryInteger {}
func reset() {} func reset() {}
} }

View File

@ -40,8 +40,8 @@ internal final class TestMetrics: MetricsFactory {
} }
private func make<Item>(label: String, dimensions: [(String, String)], registry: inout [String: Item], maker: (String, [(String, String)]) -> Item) -> Item { private func make<Item>(label: String, dimensions: [(String, String)], registry: inout [String: Item], maker: (String, [(String, String)]) -> Item) -> Item {
return self.lock.withLock {
let item = maker(label, dimensions) let item = maker(label, dimensions)
return self.lock.withLock {
registry[label] = item registry[label] = item
return item return item
} }
@ -49,21 +49,27 @@ internal final class TestMetrics: MetricsFactory {
func destroyCounter(_ handler: CounterHandler) { func destroyCounter(_ handler: CounterHandler) {
if let testCounter = handler as? TestCounter { if let testCounter = handler as? TestCounter {
self.lock.withLock { () -> Void in
self.counters.removeValue(forKey: testCounter.label) self.counters.removeValue(forKey: testCounter.label)
} }
} }
}
func destroyRecorder(_ handler: RecorderHandler) { func destroyRecorder(_ handler: RecorderHandler) {
if let testRecorder = handler as? TestRecorder { if let testRecorder = handler as? TestRecorder {
self.lock.withLock { () -> Void in
self.recorders.removeValue(forKey: testRecorder.label) self.recorders.removeValue(forKey: testRecorder.label)
} }
} }
}
func destroyTimer(_ handler: TimerHandler) { func destroyTimer(_ handler: TimerHandler) {
if let testTimer = handler as? TestTimer { if let testTimer = handler as? TestTimer {
self.lock.withLock { () -> Void in
self.timers.removeValue(forKey: testTimer.label) self.timers.removeValue(forKey: testTimer.label)
} }
} }
}
} }
internal class TestCounter: CounterHandler, Equatable { internal class TestCounter: CounterHandler, Equatable {
@ -176,8 +182,8 @@ internal class TestTimer: TimerHandler, Equatable {
} }
} }
private extension NSLock { extension NSLock {
func withLock<T>(_ body: () -> T) -> T { fileprivate func withLock<T>(_ body: () -> T) -> T {
self.lock() self.lock()
defer { defer {
self.unlock() self.unlock()
@ -185,3 +191,12 @@ private extension NSLock {
return body() return body()
} }
} }
// MARK: - Sendable support
#if compiler(>=5.6)
// ideally we would not be using @unchecked here, but concurrency-safety checks do not recognize locks
extension TestCounter: @unchecked Sendable {}
extension TestRecorder: @unchecked Sendable {}
extension TestTimer: @unchecked Sendable {}
#endif

View File

@ -0,0 +1,101 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift Metrics API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Metrics API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
@testable import CoreMetrics
import Dispatch
import XCTest
class SendableTest: XCTestCase {
#if compiler(>=5.6)
func testSendableMetrics() async {
// bootstrap with our test metrics
let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics)
do {
let name = "counter-\(NSUUID().uuidString)"
let value = Int.random(in: 0 ... 1000)
let counter = Counter(label: name)
let task = Task.detached { () -> [Int64] in
counter.increment(by: value)
let handler = counter.handler as! TestCounter
return handler.values.map { $0.1 }
}
let values = await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], Int64(value), "expected value to match")
}
do {
let name = "floating-point-counter-\(NSUUID().uuidString)"
let value = Double.random(in: 0 ... 0.9999)
let counter = FloatingPointCounter(label: name)
let task = Task.detached { () -> Double in
counter.increment(by: value)
let handler = counter.handler as! AccumulatingRoundingFloatingPointCounter
return handler.fraction
}
let fraction = await task.value
XCTAssertEqual(fraction, value)
}
do {
let name = "recorder-\(NSUUID().uuidString)"
let value = Double.random(in: -1000 ... 1000)
let recorder = Recorder(label: name)
let task = Task.detached { () -> [Double] in
recorder.record(value)
let handler = recorder.handler as! TestRecorder
return handler.values.map { $0.1 }
}
let values = await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], value, "expected value to match")
}
do {
let name = "gauge-\(NSUUID().uuidString)"
let value = Double.random(in: -1000 ... 1000)
let gauge = Gauge(label: name)
let task = Task.detached { () -> [Double] in
gauge.record(value)
let handler = gauge.handler as! TestRecorder
return handler.values.map { $0.1 }
}
let values = await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], value, "expected value to match")
}
do {
let name = "timer-\(NSUUID().uuidString)"
let value = Int64.random(in: 0 ... 1000)
let timer = Timer(label: name)
let task = Task.detached { () -> [Int64] in
timer.recordNanoseconds(value)
let handler = timer.handler as! TestTimer
return handler.values.map { $0.1 }
}
let values = await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match")
XCTAssertEqual(values[0], value, "expected value to match")
}
}
#endif
}

View File

@ -40,4 +40,4 @@ services:
shell: shell:
<<: *common <<: *common
entrypoint: /bin/bash entrypoint: /bin/bash -l

View File

@ -32,7 +32,7 @@ here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
function replace_acceptable_years() { function replace_acceptable_years() {
# this needs to replace all acceptable forms with 'YEARS' # this needs to replace all acceptable forms with 'YEARS'
sed -e 's/2017-2019/YEARS/' -e 's/2018-2019/YEARS/' -e 's/2019/YEARS/' -e 's/2021/YEARS/' sed -e 's/20[12][789012]-20[12][89012]/YEARS/' -e 's/20[12][89012]/YEARS/'
} }
printf "=> Checking linux tests... " printf "=> Checking linux tests... "