Use the MetricsTestKit in the MetricsTests rather than using a copy of the TestMetrics utilities (#128)

Co-authored-by: Hamzah Malik <hamzah_malik@apple.com>
This commit is contained in:
hamzahrmalik 2023-05-26 12:25:08 +01:00 committed by GitHub
parent 32eef8ae84
commit 9d5ff3d48f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 174 additions and 415 deletions

View File

@ -40,7 +40,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -37,7 +37,7 @@ let package = Package(
), ),
.testTarget( .testTarget(
name: "MetricsTests", name: "MetricsTests",
dependencies: ["Metrics"] dependencies: ["Metrics", "MetricsTestKit"]
), ),
] ]
) )

View File

@ -48,10 +48,10 @@ public final class TestMetrics: MetricsFactory {
let dimensions: [(String, String)] let dimensions: [(String, String)]
} }
private var counters = [FullKey: CounterHandler]() private var _counters = [FullKey: TestCounter]()
private var meters = [FullKey: MeterHandler]() private var _meters = [FullKey: TestMeter]()
private var recorders = [FullKey: RecorderHandler]() private var _recorders = [FullKey: TestRecorder]()
private var timers = [FullKey: TimerHandler]() private var _timers = [FullKey: TestTimer]()
public init() { public init() {
// nothing to do // nothing to do
@ -61,61 +61,61 @@ public final class TestMetrics: MetricsFactory {
/// Invoke this method in between test runs to verify that Counters are created as needed. /// Invoke this method in between test runs to verify that Counters are created as needed.
public func reset() { public func reset() {
self.lock.withLock { self.lock.withLock {
self.counters = [:] self._counters = [:]
self.meters = [:] self._recorders = [:]
self.recorders = [:] self._meters = [:]
self.timers = [:] self._timers = [:]
} }
} }
public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler { public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
return self.lock.withLock { () -> CounterHandler in return self.lock.withLock { () -> CounterHandler in
if let existing = self.counters[.init(label: label, dimensions: dimensions)] { if let existing = self._counters[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
let item = TestCounter(label: label, dimensions: dimensions) let item = TestCounter(label: label, dimensions: dimensions)
self.counters[.init(label: label, dimensions: dimensions)] = item self._counters[.init(label: label, dimensions: dimensions)] = item
return item return item
} }
} }
public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler { public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
return self.lock.withLock { () -> MeterHandler in return self.lock.withLock { () -> MeterHandler in
if let existing = self.meters[.init(label: label, dimensions: dimensions)] { if let existing = self._meters[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
let item = TestMeter(label: label, dimensions: dimensions) let item = TestMeter(label: label, dimensions: dimensions)
self.meters[.init(label: label, dimensions: dimensions)] = item self._meters[.init(label: label, dimensions: dimensions)] = item
return item return item
} }
} }
public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler { public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
return self.lock.withLock { () -> RecorderHandler in return self.lock.withLock { () -> RecorderHandler in
if let existing = self.recorders[.init(label: label, dimensions: dimensions)] { if let existing = self._recorders[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
let item = TestRecorder(label: label, dimensions: dimensions, aggregate: aggregate) let item = TestRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
self.recorders[.init(label: label, dimensions: dimensions)] = item self._recorders[.init(label: label, dimensions: dimensions)] = item
return item return item
} }
} }
public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler { public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
return self.lock.withLock { () -> TimerHandler in return self.lock.withLock { () -> TimerHandler in
if let existing = self.timers[.init(label: label, dimensions: dimensions)] { if let existing = self._timers[.init(label: label, dimensions: dimensions)] {
return existing return existing
} }
let item = TestTimer(label: label, dimensions: dimensions) let item = TestTimer(label: label, dimensions: dimensions)
self.timers[.init(label: label, dimensions: dimensions)] = item self._timers[.init(label: label, dimensions: dimensions)] = item
return item return item
} }
} }
public func destroyCounter(_ handler: CounterHandler) { public func destroyCounter(_ handler: CounterHandler) {
if let testCounter = handler as? TestCounter { if let testCounter = handler as? TestCounter {
self.lock.withLock { () in self.lock.withLock {
self.counters.removeValue(forKey: testCounter.key) self._counters.removeValue(forKey: testCounter.key)
} }
} }
} }
@ -123,23 +123,23 @@ public final class TestMetrics: MetricsFactory {
public func destroyMeter(_ handler: MeterHandler) { public func destroyMeter(_ handler: MeterHandler) {
if let testMeter = handler as? TestMeter { if let testMeter = handler as? TestMeter {
self.lock.withLock { () in self.lock.withLock { () in
self.meters.removeValue(forKey: testMeter.key) self._meters.removeValue(forKey: testMeter.key)
} }
} }
} }
public func destroyRecorder(_ handler: RecorderHandler) { public func destroyRecorder(_ handler: RecorderHandler) {
if let testRecorder = handler as? TestRecorder { if let testRecorder = handler as? TestRecorder {
self.lock.withLock { () in self.lock.withLock {
self.recorders.removeValue(forKey: testRecorder.key) self._recorders.removeValue(forKey: testRecorder.key)
} }
} }
} }
public func destroyTimer(_ handler: TimerHandler) { public func destroyTimer(_ handler: TimerHandler) {
if let testTimer = handler as? TestTimer { if let testTimer = handler as? TestTimer {
self.lock.withLock { () in self.lock.withLock {
self.timers.removeValue(forKey: testTimer.key) self._timers.removeValue(forKey: testTimer.key)
} }
} }
} }
@ -174,17 +174,22 @@ extension TestMetrics {
public func expectCounter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestCounter { public func expectCounter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestCounter {
let maybeItem = self.lock.withLock { let maybeItem = self.lock.withLock {
self.counters[.init(label: label, dimensions: dimensions)] self._counters[.init(label: label, dimensions: dimensions)]
} }
guard let maybeCounter = maybeItem else { guard let testCounter = maybeItem else {
throw TestMetricsError.missingMetric(label: label, dimensions: dimensions) throw TestMetricsError.missingMetric(label: label, dimensions: dimensions)
} }
guard let testCounter = maybeCounter as? TestCounter else {
throw TestMetricsError.illegalMetricType(metric: maybeCounter, expected: "\(TestCounter.self)")
}
return testCounter return testCounter
} }
/// All the counters which have been created and not destroyed
var counters: [TestCounter] {
let counters = self.lock.withLock {
self._counters
}
return Array(counters.values)
}
// MARK: - Gauge // MARK: - Gauge
public func expectGauge(_ metric: Gauge) throws -> TestRecorder { public func expectGauge(_ metric: Gauge) throws -> TestRecorder {
@ -206,17 +211,22 @@ extension TestMetrics {
public func expectMeter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestMeter { public func expectMeter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestMeter {
let maybeItem = self.lock.withLock { let maybeItem = self.lock.withLock {
self.meters[.init(label: label, dimensions: dimensions)] self._meters[.init(label: label, dimensions: dimensions)]
} }
guard let maybeMeter = maybeItem else { guard let testMeter = maybeItem else {
throw TestMetricsError.missingMetric(label: label, dimensions: dimensions) throw TestMetricsError.missingMetric(label: label, dimensions: dimensions)
} }
guard let testMeter = maybeMeter as? TestMeter else {
throw TestMetricsError.illegalMetricType(metric: maybeMeter, expected: "\(TestMeter.self)")
}
return testMeter return testMeter
} }
/// All the meters which have been created and not destroyed
var meters: [TestMeter] {
let meters = self.lock.withLock {
self._meters
}
return Array(meters.values)
}
// MARK: - Recorder // MARK: - Recorder
public func expectRecorder(_ metric: Recorder) throws -> TestRecorder { public func expectRecorder(_ metric: Recorder) throws -> TestRecorder {
@ -228,17 +238,22 @@ extension TestMetrics {
public func expectRecorder(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder { public func expectRecorder(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder {
let maybeItem = self.lock.withLock { let maybeItem = self.lock.withLock {
self.recorders[.init(label: label, dimensions: dimensions)] self._recorders[.init(label: label, dimensions: dimensions)]
} }
guard let maybeRecorder = maybeItem else { guard let testRecorder = maybeItem else {
throw TestMetricsError.missingMetric(label: label, dimensions: dimensions) throw TestMetricsError.missingMetric(label: label, dimensions: dimensions)
} }
guard let testRecorder = maybeRecorder as? TestRecorder else {
throw TestMetricsError.illegalMetricType(metric: maybeRecorder, expected: "\(TestRecorder.self)")
}
return testRecorder return testRecorder
} }
/// All the recorders which have been created and not destroyed
var recorders: [TestRecorder] {
let recorders = self.lock.withLock {
self._recorders
}
return Array(recorders.values)
}
// MARK: - Timer // MARK: - Timer
public func expectTimer(_ metric: CoreMetrics.Timer) throws -> TestTimer { public func expectTimer(_ metric: CoreMetrics.Timer) throws -> TestTimer {
@ -250,16 +265,21 @@ extension TestMetrics {
public func expectTimer(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestTimer { public func expectTimer(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestTimer {
let maybeItem = self.lock.withLock { let maybeItem = self.lock.withLock {
self.timers[.init(label: label, dimensions: dimensions)] self._timers[.init(label: label, dimensions: dimensions)]
} }
guard let maybeTimer = maybeItem else { guard let testTimer = maybeItem else {
throw TestMetricsError.missingMetric(label: label, dimensions: dimensions) throw TestMetricsError.missingMetric(label: label, dimensions: dimensions)
} }
guard let testTimer = maybeTimer as? TestTimer else {
throw TestMetricsError.illegalMetricType(metric: maybeTimer, expected: "\(TestTimer.self)")
}
return testTimer return testTimer
} }
/// All the timers which have been created and not destroyed
var timers: [TestTimer] {
let timers = self.lock.withLock {
self._timers
}
return Array(timers.values)
}
} }
// MARK: - Metric type implementations // MARK: - Metric type implementations
@ -283,7 +303,7 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable {
} }
let lock = NSLock() let lock = NSLock()
private var values = [(Date, Int64)]() private var _values = [(Date, Int64)]()
init(label: String, dimensions: [(String, String)]) { init(label: String, dimensions: [(String, String)]) {
self.id = UUID().uuidString self.id = UUID().uuidString
@ -293,31 +313,31 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable {
public func increment(by amount: Int64) { public func increment(by amount: Int64) {
self.lock.withLock { self.lock.withLock {
self.values.append((Date(), amount)) self._values.append((Date(), amount))
} }
} }
public func reset() { public func reset() {
return self.lock.withLock { return self.lock.withLock {
self.values = [] self._values = []
} }
} }
public var lastValue: Int64? { public var lastValue: Int64? {
return self.lock.withLock { return self.values.last?.1
return values.last?.1
}
} }
public var totalValue: Int64 { public var totalValue: Int64 {
return self.lock.withLock { return self.values.map { $0.1 }.reduce(0, +)
return values.map { $0.1 }.reduce(0, +)
}
} }
public var last: (Date, Int64)? { public var last: (Date, Int64)? {
return self.values.last
}
var values: [(Date, Int64)] {
return self.lock.withLock { return self.lock.withLock {
values.last self._values
} }
} }
@ -336,7 +356,7 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable {
} }
let lock = NSLock() let lock = NSLock()
private var values = [(Date, Double)]() private var _values = [(Date, Double)]()
init(label: String, dimensions: [(String, String)]) { init(label: String, dimensions: [(String, String)]) {
self.id = NSUUID().uuidString self.id = NSUUID().uuidString
@ -350,36 +370,38 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable {
public func set(_ value: Double) { public func set(_ value: Double) {
self.lock.withLock { self.lock.withLock {
// this may loose precision but good enough as an example // this may lose precision but good enough as an example
values.append((Date(), Double(value))) _values.append((Date(), Double(value)))
} }
} }
public func increment(by amount: Double) { public func increment(by amount: Double) {
self.lock.withLock { self.lock.withLock {
let lastValue = self.values.last?.1 ?? 0 let lastValue = self._values.last?.1 ?? 0
let newValue = lastValue - amount let newValue = lastValue - amount
values.append((Date(), Double(newValue))) _values.append((Date(), Double(newValue)))
} }
} }
public func decrement(by amount: Double) { public func decrement(by amount: Double) {
self.lock.withLock { self.lock.withLock {
let lastValue = self.values.last?.1 ?? 0 let lastValue = self._values.last?.1 ?? 0
let newValue = lastValue - amount let newValue = lastValue - amount
values.append((Date(), Double(newValue))) _values.append((Date(), Double(newValue)))
} }
} }
public var lastValue: Double? { public var lastValue: Double? {
return self.lock.withLock { return self.last?.1
values.last?.1
}
} }
public var last: (Date, Double)? { public var last: (Date, Double)? {
return self.values.last
}
var values: [(Date, Double)] {
return self.lock.withLock { return self.lock.withLock {
values.last self._values
} }
} }
@ -399,7 +421,7 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable {
} }
let lock = NSLock() let lock = NSLock()
private var values = [(Date, Double)]() private var _values = [(Date, Double)]()
init(label: String, dimensions: [(String, String)], aggregate: Bool) { init(label: String, dimensions: [(String, String)], aggregate: Bool) {
self.id = NSUUID().uuidString self.id = NSUUID().uuidString
@ -414,20 +436,22 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable {
public func record(_ value: Double) { public func record(_ value: Double) {
self.lock.withLock { self.lock.withLock {
// this may loose precision but good enough as an example // this may lose precision but good enough as an example
values.append((Date(), Double(value))) _values.append((Date(), Double(value)))
} }
} }
public var lastValue: Double? { public var lastValue: Double? {
return self.lock.withLock { return self.last?.1
values.last?.1
}
} }
public var last: (Date, Double)? { public var last: (Date, Double)? {
return self.values.last
}
var values: [(Date, Double)] {
return self.lock.withLock { return self.lock.withLock {
values.last self._values
} }
} }
@ -480,19 +504,19 @@ public final class TestTimer: TestMetric, TimerHandler, Equatable {
public var lastValue: Int64? { public var lastValue: Int64? {
return self.lock.withLock { return self.lock.withLock {
return _values.last?.1 return self._values.last?.1
} }
} }
public var values: [Int64] { public var values: [Int64] {
return self.lock.withLock { return self.lock.withLock {
return _values.map { $0.1 } return self._values.map { $0.1 }
} }
} }
public var last: (Date, Int64)? { public var last: (Date, Int64)? {
return self.lock.withLock { return self.lock.withLock {
return _values.last return self._values.last
} }
} }

View File

@ -13,6 +13,7 @@
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@testable import CoreMetrics @testable import CoreMetrics
@testable import MetricsTestKit
import XCTest import XCTest
class MetricsTests: XCTestCase { class MetricsTests: XCTestCase {
@ -46,7 +47,7 @@ class MetricsTests: XCTestCase {
let name = "counter-\(UUID().uuidString)" let name = "counter-\(UUID().uuidString)"
let value = Int.random(in: Int.min ... Int.max) let value = Int.random(in: Int.min ... Int.max)
Counter(label: name).increment(by: value) Counter(label: name).increment(by: value)
let counter = metrics.counters[name] as! TestCounter let counter = try metrics.expectCounter(name)
XCTAssertEqual(counter.values.count, 1, "expected number of entries to match") XCTAssertEqual(counter.values.count, 1, "expected number of entries to match")
XCTAssertEqual(counter.values[0].1, Int64(value), "expected value to match") XCTAssertEqual(counter.values[0].1, Int64(value), "expected value to match")
counter.reset() counter.reset()
@ -59,7 +60,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let label = "\(#function)-fp-counter-\(UUID())" let label = "\(#function)-fp-counter-\(UUID())"
let fpCounter = FloatingPointCounter(label: label) let fpCounter = FloatingPointCounter(label: label)
let counter = metrics.counters[label] as! TestCounter let counter = try metrics.expectCounter(label)
fpCounter.increment(by: Double.nan) fpCounter.increment(by: Double.nan)
fpCounter.increment(by: Double.signalingNaN) fpCounter.increment(by: Double.signalingNaN)
XCTAssertEqual(counter.values.count, 0, "expected nan values to be ignored") XCTAssertEqual(counter.values.count, 0, "expected nan values to be ignored")
@ -71,7 +72,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let label = "\(#function)-fp-counter-\(UUID())" let label = "\(#function)-fp-counter-\(UUID())"
let fpCounter = FloatingPointCounter(label: label) let fpCounter = FloatingPointCounter(label: label)
let counter = metrics.counters[label] as! TestCounter let counter = try metrics.expectCounter(label)
fpCounter.increment(by: Double.infinity) fpCounter.increment(by: Double.infinity)
fpCounter.increment(by: -Double.infinity) fpCounter.increment(by: -Double.infinity)
XCTAssertEqual(counter.values.count, 0, "expected infinite values to be ignored") XCTAssertEqual(counter.values.count, 0, "expected infinite values to be ignored")
@ -83,7 +84,7 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let label = "\(#function)-fp-counter-\(UUID())" let label = "\(#function)-fp-counter-\(UUID())"
let fpCounter = FloatingPointCounter(label: label) let fpCounter = FloatingPointCounter(label: label)
let counter = metrics.counters[label] as! TestCounter let counter = try metrics.expectCounter(label)
fpCounter.increment(by: -100) fpCounter.increment(by: -100)
XCTAssertEqual(counter.values.count, 0, "expected negative values to be ignored") XCTAssertEqual(counter.values.count, 0, "expected negative values to be ignored")
} }
@ -94,19 +95,19 @@ class MetricsTests: XCTestCase {
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let label = "\(#function)-fp-counter-\(UUID())" let label = "\(#function)-fp-counter-\(UUID())"
let fpCounter = FloatingPointCounter(label: label) let fpCounter = FloatingPointCounter(label: label)
let counter = metrics.counters[label] as! TestCounter let counter = try metrics.expectCounter(label)
fpCounter.increment(by: 0) fpCounter.increment(by: 0)
fpCounter.increment(by: -0) fpCounter.increment(by: -0)
XCTAssertEqual(counter.values.count, 0, "expected zero values to be ignored") XCTAssertEqual(counter.values.count, 0, "expected zero values to be ignored")
} }
func testDefaultFloatingPointCounter_ceilsExtremeValues() { func testDefaultFloatingPointCounter_ceilsExtremeValues() throws {
// bootstrap with our test metrics // bootstrap with our test metrics
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let label = "\(#function)-fp-counter-\(UUID())" let label = "\(#function)-fp-counter-\(UUID())"
let fpCounter = FloatingPointCounter(label: label) let fpCounter = FloatingPointCounter(label: label)
let counter = metrics.counters[label] as! TestCounter let counter = try metrics.expectCounter(label)
// Just larger than Int64 // Just larger than Int64
fpCounter.increment(by: Double(sign: .plus, exponent: 63, significand: 1)) fpCounter.increment(by: Double(sign: .plus, exponent: 63, significand: 1))
// Much larger than Int64 // Much larger than Int64
@ -116,14 +117,14 @@ class MetricsTests: XCTestCase {
XCTAssertEqual(values, [Int64.max, Int64.max], "expected extremely large values to be replaced with Int64.max") XCTAssertEqual(values, [Int64.max, Int64.max], "expected extremely large values to be replaced with Int64.max")
} }
func testDefaultFloatingPointCounter_accumulatesFloatingPointDecimalValues() { func testDefaultFloatingPointCounter_accumulatesFloatingPointDecimalValues() throws {
// bootstrap with our test metrics // bootstrap with our test metrics
let metrics = TestMetrics() let metrics = TestMetrics()
MetricsSystem.bootstrapInternal(metrics) MetricsSystem.bootstrapInternal(metrics)
let label = "\(#function)-fp-counter-\(UUID())" let label = "\(#function)-fp-counter-\(UUID())"
let fpCounter = FloatingPointCounter(label: label) let fpCounter = FloatingPointCounter(label: label)
let rawFpCounter = fpCounter._handler as! AccumulatingRoundingFloatingPointCounter let rawFpCounter = fpCounter._handler as! AccumulatingRoundingFloatingPointCounter
let counter = metrics.counters[label] as! TestCounter let counter = try metrics.expectCounter(label)
// Increment by a small value (perfectly representable) // Increment by a small value (perfectly representable)
fpCounter.increment(by: 0.75) fpCounter.increment(by: 0.75)
@ -205,9 +206,9 @@ class MetricsTests: XCTestCase {
let name = "recorder-\(UUID().uuidString)" let name = "recorder-\(UUID().uuidString)"
let value = Double.random(in: Double(Int.min) ... Double(Int.max)) let value = Double.random(in: Double(Int.min) ... Double(Int.max))
Recorder(label: name).record(value) Recorder(label: name).record(value)
let recorder = metrics.recorders[name] as! TestRecorder let recorder = try metrics.expectRecorder(name)
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
XCTAssertEqual(recorder.values[0].1, value, "expected value to match") XCTAssertEqual(recorder.lastValue, value, "expected value to match")
} }
func testTimers() throws { func testTimers() throws {
@ -238,9 +239,9 @@ class MetricsTests: XCTestCase {
let name = "timer-\(UUID().uuidString)" let name = "timer-\(UUID().uuidString)"
let value = Int64.random(in: Int64.min ... Int64.max) let value = Int64.random(in: Int64.min ... Int64.max)
Timer(label: name).recordNanoseconds(value) Timer(label: name).recordNanoseconds(value)
let timer = metrics.timers[name] as! TestTimer let timer = try metrics.expectTimer(name)
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match") XCTAssertEqual(timer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(timer.values[0].1, value, "expected value to match") XCTAssertEqual(timer.values[0], value, "expected value to match")
} }
func testTimerVariants() throws { func testTimerVariants() throws {
@ -254,22 +255,22 @@ class MetricsTests: XCTestCase {
let nano = Int64.random(in: 0 ... 5) let nano = Int64.random(in: 0 ... 5)
timer.recordNanoseconds(nano) timer.recordNanoseconds(nano)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values[0].1, nano, "expected value to match") XCTAssertEqual(testTimer.values[0], nano, "expected value to match")
// micro // micro
let micro = Int64.random(in: 0 ... 5) let micro = Int64.random(in: 0 ... 5)
timer.recordMicroseconds(micro) timer.recordMicroseconds(micro)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(testTimer.values[1].1, micro * 1000, "expected value to match") XCTAssertEqual(testTimer.values[1], micro * 1000, "expected value to match")
// milli // milli
let milli = Int64.random(in: 0 ... 5) let milli = Int64.random(in: 0 ... 5)
timer.recordMilliseconds(milli) timer.recordMilliseconds(milli)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(testTimer.values[2].1, milli * 1_000_000, "expected value to match") XCTAssertEqual(testTimer.values[2], milli * 1_000_000, "expected value to match")
// seconds // seconds
let sec = Int64.random(in: 0 ... 5) let sec = Int64.random(in: 0 ... 5)
timer.recordSeconds(sec) timer.recordSeconds(sec)
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
XCTAssertEqual(testTimer.values[3].1, sec * 1_000_000_000, "expected value to match") XCTAssertEqual(testTimer.values[3], sec * 1_000_000_000, "expected value to match")
} }
func testTimerOverflow() throws { func testTimerOverflow() throws {
@ -282,31 +283,31 @@ class MetricsTests: XCTestCase {
// nano (integer) // nano (integer)
timer.recordNanoseconds(Int64.max) timer.recordNanoseconds(Int64.max)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values[0].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[0], Int64.max, "expected value to match")
// micro (integer) // micro (integer)
timer.recordMicroseconds(Int64.max) timer.recordMicroseconds(Int64.max)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(testTimer.values[1].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[1], Int64.max, "expected value to match")
// micro (double) // micro (double)
timer.recordMicroseconds(Double(Int64.max) + 1) timer.recordMicroseconds(Double(Int64.max) + 1)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(testTimer.values[1].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[1], Int64.max, "expected value to match")
// milli (integer) // milli (integer)
timer.recordMilliseconds(Int64.max) timer.recordMilliseconds(Int64.max)
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
XCTAssertEqual(testTimer.values[2].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[2], Int64.max, "expected value to match")
// milli (double) // milli (double)
timer.recordMilliseconds(Double(Int64.max) + 1) timer.recordMilliseconds(Double(Int64.max) + 1)
XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match")
XCTAssertEqual(testTimer.values[2].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[2], Int64.max, "expected value to match")
// seconds (integer) // seconds (integer)
timer.recordSeconds(Int64.max) timer.recordSeconds(Int64.max)
XCTAssertEqual(testTimer.values.count, 6, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 6, "expected number of entries to match")
XCTAssertEqual(testTimer.values[3].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[3], Int64.max, "expected value to match")
// seconds (double) // seconds (double)
timer.recordSeconds(Double(Int64.max) * 1) timer.recordSeconds(Double(Int64.max) * 1)
XCTAssertEqual(testTimer.values.count, 7, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 7, "expected number of entries to match")
XCTAssertEqual(testTimer.values[3].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[3], Int64.max, "expected value to match")
} }
func testTimerHandlesUnsignedOverflow() throws { func testTimerHandlesUnsignedOverflow() throws {
@ -319,19 +320,19 @@ class MetricsTests: XCTestCase {
// nano // nano
timer.recordNanoseconds(UInt64.max) timer.recordNanoseconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values[0].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[0], Int64.max, "expected value to match")
// micro // micro
timer.recordMicroseconds(UInt64.max) timer.recordMicroseconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(testTimer.values[1].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[1], Int64.max, "expected value to match")
// milli // milli
timer.recordMilliseconds(UInt64.max) timer.recordMilliseconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(testTimer.values[2].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[2], Int64.max, "expected value to match")
// seconds // seconds
timer.recordSeconds(UInt64.max) timer.recordSeconds(UInt64.max)
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
XCTAssertEqual(testTimer.values[3].1, Int64.max, "expected value to match") XCTAssertEqual(testTimer.values[3], Int64.max, "expected value to match")
} }
func testGauge() throws { func testGauge() throws {
@ -345,7 +346,7 @@ class MetricsTests: XCTestCase {
gauge.record(value) gauge.record(value)
let recorder = gauge._handler as! TestRecorder let recorder = gauge._handler as! TestRecorder
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
XCTAssertEqual(recorder.values[0].1, value, "expected value to match") XCTAssertEqual(recorder.lastValue, value, "expected value to match")
} }
func testGaugeBlock() throws { func testGaugeBlock() throws {
@ -356,9 +357,9 @@ class MetricsTests: XCTestCase {
let name = "gauge-\(UUID().uuidString)" let name = "gauge-\(UUID().uuidString)"
let value = Double.random(in: -1000 ... 1000) let value = Double.random(in: -1000 ... 1000)
Gauge(label: name).record(value) Gauge(label: name).record(value)
let recorder = metrics.recorders[name] as! TestRecorder let recorder = try metrics.expectRecorder(name)
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match")
XCTAssertEqual(recorder.values[0].1, value, "expected value to match") XCTAssertEqual(recorder.lastValue, value, "expected value to match")
} }
func testMeter() throws { func testMeter() throws {
@ -383,7 +384,7 @@ class MetricsTests: XCTestCase {
let name = "meter-\(NSUUID().uuidString)" let name = "meter-\(NSUUID().uuidString)"
let value = Double.random(in: -1000 ... 1000) let value = Double.random(in: -1000 ... 1000)
Meter(label: name).set(value) Meter(label: name).set(value)
let testMeter = metrics.meters[name] as! TestMeter let testMeter = try metrics.expectMeter(name)
XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testMeter.values[0].1, value, "expected value to match") XCTAssertEqual(testMeter.values[0].1, value, "expected value to match")
} }
@ -398,15 +399,15 @@ class MetricsTests: XCTestCase {
let muxCounter = Counter(label: name) let muxCounter = Counter(label: name)
muxCounter.increment(by: value) muxCounter.increment(by: value)
factories.forEach { factory in factories.forEach { factory in
let counter = factory.counters.first?.1 as! TestCounter let counter = factory.counters.first
XCTAssertEqual(counter.label, name, "expected label to match") XCTAssertEqual(counter?.label, name, "expected label to match")
XCTAssertEqual(counter.values.count, 1, "expected number of entries to match") XCTAssertEqual(counter?.values.count, 1, "expected number of entries to match")
XCTAssertEqual(counter.values[0].1, Int64(value), "expected value to match") XCTAssertEqual(counter?.lastValue, Int64(value), "expected value to match")
} }
muxCounter.reset() muxCounter.reset()
factories.forEach { factory in factories.forEach { factory in
let counter = factory.counters.first?.1 as! TestCounter let counter = factory.counters.first
XCTAssertEqual(counter.values.count, 0, "expected number of entries to match") XCTAssertEqual(counter?.values.count, 0, "expected number of entries to match")
} }
} }
@ -420,10 +421,10 @@ class MetricsTests: XCTestCase {
let muxMeter = Meter(label: name) let muxMeter = Meter(label: name)
muxMeter.set(value) muxMeter.set(value)
factories.forEach { factory in factories.forEach { factory in
let meter = factory.meters.first?.1 as! TestMeter let meter = factory.meters.first
XCTAssertEqual(meter.label, name, "expected label to match") XCTAssertEqual(meter?.label, name, "expected label to match")
XCTAssertEqual(meter.values.count, 1, "expected number of entries to match") XCTAssertEqual(meter?.values.count, 1, "expected number of entries to match")
XCTAssertEqual(meter.values[0].1, value, "expected value to match") XCTAssertEqual(meter?.values[0].1, value, "expected value to match")
} }
} }
@ -437,10 +438,10 @@ class MetricsTests: XCTestCase {
let muxRecorder = Recorder(label: name) let muxRecorder = Recorder(label: name)
muxRecorder.record(value) muxRecorder.record(value)
factories.forEach { factory in factories.forEach { factory in
let recorder = factory.recorders.first?.1 as! TestRecorder let recorder = factory.recorders.first
XCTAssertEqual(recorder.label, name, "expected label to match") XCTAssertEqual(recorder?.label, name, "expected label to match")
XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder?.values.count, 1, "expected number of entries to match")
XCTAssertEqual(recorder.values[0].1, value, "expected value to match") XCTAssertEqual(recorder?.values[0].1, value, "expected value to match")
} }
} }
@ -454,12 +455,12 @@ class MetricsTests: XCTestCase {
let muxTimer = Timer(label: name, preferredDisplayUnit: .minutes) let muxTimer = Timer(label: name, preferredDisplayUnit: .minutes)
muxTimer.recordSeconds(seconds) muxTimer.recordSeconds(seconds)
factories.forEach { factory in factories.forEach { factory in
let timer = factory.timers.first?.1 as! TestTimer let timer = factory.timers.first
XCTAssertEqual(timer.label, name, "expected label to match") XCTAssertEqual(timer?.label, name, "expected label to match")
XCTAssertEqual(timer.values.count, 1, "expected number of entries to match") XCTAssertEqual(timer?.values.count, 1, "expected number of entries to match")
XCTAssertEqual(timer.values[0].1, Int64(seconds * 1_000_000_000), "expected value to match") XCTAssertEqual(timer?.values[0], Int64(seconds * 1_000_000_000), "expected value to match")
XCTAssertEqual(timer.displayUnit, .minutes, "expected value to match") XCTAssertEqual(timer?.displayUnit, .minutes, "expected value to match")
XCTAssertEqual(timer.retrieveValueInPreferredUnit(atIndex: 0), Double(seconds) / 60.0, "seconds should be returned as minutes") XCTAssertEqual(timer?.retrieveValueInPreferredUnit(atIndex: 0), Double(seconds) / 60.0, "seconds should be returned as minutes")
} }
} }
@ -577,7 +578,7 @@ class MetricsTests: XCTestCase {
let testTimer = timer._handler as! TestTimer let testTimer = timer._handler as! TestTimer
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values.first!.1, value, "expected value to match") XCTAssertEqual(testTimer.values.first, value, "expected value to match")
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
let identity = ObjectIdentifier(timer) let identity = ObjectIdentifier(timer)
@ -588,7 +589,7 @@ class MetricsTests: XCTestCase {
timerAgain.recordNanoseconds(value) timerAgain.recordNanoseconds(value)
let testTimerAgain = timerAgain._handler as! TestTimer let testTimerAgain = timerAgain._handler as! TestTimer
XCTAssertEqual(testTimerAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimerAgain.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimerAgain.values.first!.1, value, "expected value to match") XCTAssertEqual(testTimerAgain.values.first, value, "expected value to match")
let identityAgain = ObjectIdentifier(timerAgain) let identityAgain = ObjectIdentifier(timerAgain)
XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity") XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity")

View File

@ -14,6 +14,7 @@
@testable import CoreMetrics @testable import CoreMetrics
@testable import Metrics @testable import Metrics
@testable import MetricsTestKit
import XCTest import XCTest
class MetricsExtensionsTests: XCTestCase { class MetricsExtensionsTests: XCTestCase {
@ -27,9 +28,9 @@ class MetricsExtensionsTests: XCTestCase {
Timer.measure(label: name) { Timer.measure(label: name) {
Thread.sleep(forTimeInterval: delay) Thread.sleep(forTimeInterval: delay)
} }
let timer = metrics.timers[name] as! TestTimer let timer = try metrics.expectTimer(name)
XCTAssertEqual(1, timer.values.count, "expected number of entries to match") XCTAssertEqual(1, timer.values.count, "expected number of entries to match")
XCTAssertGreaterThan(timer.values[0].1, Int64(delay * 1_000_000_000), "expected delay to match") XCTAssertGreaterThan(timer.values[0], Int64(delay * 1_000_000_000), "expected delay to match")
} }
func testTimerWithTimeInterval() throws { func testTimerWithTimeInterval() throws {
@ -42,7 +43,7 @@ class MetricsExtensionsTests: XCTestCase {
let timeInterval = TimeInterval(Double.random(in: 1 ... 500)) let timeInterval = TimeInterval(Double.random(in: 1 ... 500))
timer.record(timeInterval) timer.record(timeInterval)
XCTAssertEqual(1, testTimer.values.count, "expected number of entries to match") XCTAssertEqual(1, testTimer.values.count, "expected number of entries to match")
XCTAssertEqual(testTimer.values[0].1, Int64(timeInterval * 1_000_000_000), "expected value to match") XCTAssertEqual(testTimer.values[0], Int64(timeInterval * 1_000_000_000), "expected value to match")
} }
func testTimerWithDispatchTime() throws { func testTimerWithDispatchTime() throws {
@ -56,26 +57,26 @@ class MetricsExtensionsTests: XCTestCase {
let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500)) let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500))
timer.record(nano) timer.record(nano)
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[0].1), nano.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[0]), nano.nano(), "expected value to match")
// micro // micro
let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500)) let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500))
timer.record(micro) timer.record(micro)
XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 2, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[1].1), micro.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[1]), micro.nano(), "expected value to match")
// milli // milli
let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500)) let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500))
timer.record(milli) timer.record(milli)
XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 3, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[2].1), milli.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[2]), milli.nano(), "expected value to match")
// seconds // seconds
let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500)) let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500))
timer.record(sec) timer.record(sec)
XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 4, "expected number of entries to match")
XCTAssertEqual(Int(testTimer.values[3].1), sec.nano(), "expected value to match") XCTAssertEqual(Int(testTimer.values[3]), sec.nano(), "expected value to match")
// never // never
timer.record(DispatchTimeInterval.never) timer.record(DispatchTimeInterval.never)
XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 5, "expected number of entries to match")
XCTAssertEqual(testTimer.values[4].1, 0, "expected value to match") XCTAssertEqual(testTimer.values[4], 0, "expected value to match")
} }
func testTimerWithDispatchTimeInterval() { func testTimerWithDispatchTimeInterval() {
@ -91,7 +92,7 @@ class MetricsExtensionsTests: XCTestCase {
let testTimer = timer._handler as! TestTimer let testTimer = timer._handler as! TestTimer
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(UInt64(testTimer.values.first!.1), end.uptimeNanoseconds - start.uptimeNanoseconds, "expected value to match") XCTAssertEqual(UInt64(testTimer.values.first!), end.uptimeNanoseconds - start.uptimeNanoseconds, "expected value to match")
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
} }
@ -107,7 +108,7 @@ class MetricsExtensionsTests: XCTestCase {
let testTimer = timer._handler as! TestTimer let testTimer = timer._handler as! TestTimer
XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match") XCTAssertEqual(testTimer.values.count, 1, "expected number of entries to match")
XCTAssertEqual(testTimer.values.first!.1, value, "expected value to match") XCTAssertEqual(testTimer.values.first, value, "expected value to match")
XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored") XCTAssertEqual(metrics.timers.count, 1, "timer should have been stored")
let secondsName = "timer-seconds-\(UUID().uuidString)" let secondsName = "timer-seconds-\(UUID().uuidString)"

View File

@ -1,268 +0,0 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Metrics API open source project
//
// Copyright (c) 2018-2019 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
@testable import class CoreMetrics.Timer
import Foundation
/// Metrics factory which allows inspecting recorded metrics programmatically.
/// Only intended for tests of the Metrics API itself.
internal final class TestMetrics: MetricsFactory {
private let lock = NSLock()
var counters = [String: CounterHandler]()
var meters = [String: MeterHandler]()
var recorders = [String: RecorderHandler]()
var timers = [String: TimerHandler]()
func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler {
return self.make(label: label, dimensions: dimensions, registry: &self.counters, maker: TestCounter.init)
}
func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler {
return self.make(label: label, dimensions: dimensions, registry: &self.meters, maker: TestMeter.init)
}
func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler {
let maker = { (label: String, dimensions: [(String, String)]) -> RecorderHandler in
TestRecorder(label: label, dimensions: dimensions, aggregate: aggregate)
}
return self.make(label: label, dimensions: dimensions, registry: &self.recorders, maker: maker)
}
func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler {
return self.make(label: label, dimensions: dimensions, registry: &self.timers, maker: TestTimer.init)
}
private func make<Item>(label: String, dimensions: [(String, String)], registry: inout [String: Item], maker: (String, [(String, String)]) -> Item) -> Item {
let item = maker(label, dimensions)
return self.lock.withLock {
registry[label] = item
return item
}
}
func destroyCounter(_ handler: CounterHandler) {
if let testCounter = handler as? TestCounter {
self.lock.withLock { () in
self.counters.removeValue(forKey: testCounter.label)
}
}
}
func destroyMeter(_ handler: MeterHandler) {
if let testMeter = handler as? TestMeter {
self.lock.withLock { () in
self.meters.removeValue(forKey: testMeter.label)
}
}
}
func destroyRecorder(_ handler: RecorderHandler) {
if let testRecorder = handler as? TestRecorder {
self.lock.withLock { () in
self.recorders.removeValue(forKey: testRecorder.label)
}
}
}
func destroyTimer(_ handler: TimerHandler) {
if let testTimer = handler as? TestTimer {
self.lock.withLock { () in
self.timers.removeValue(forKey: testTimer.label)
}
}
}
}
internal final class TestCounter: CounterHandler, Equatable {
let id: String
let label: String
let dimensions: [(String, String)]
let lock = NSLock()
var values = [(Date, Int64)]()
init(label: String, dimensions: [(String, String)]) {
self.id = UUID().uuidString
self.label = label
self.dimensions = dimensions
}
func increment(by amount: Int64) {
self.lock.withLock {
self.values.append((Date(), amount))
}
print("adding \(amount) to \(self.label)")
}
func reset() {
self.lock.withLock {
self.values = []
}
print("reseting \(self.label)")
}
public static func == (lhs: TestCounter, rhs: TestCounter) -> Bool {
return lhs.id == rhs.id
}
}
internal final class TestMeter: MeterHandler, Equatable {
let id: String
let label: String
let dimensions: [(String, String)]
let lock = NSLock()
var values = [(Date, Double)]()
init(label: String, dimensions: [(String, String)]) {
self.id = NSUUID().uuidString
self.label = label
self.dimensions = dimensions
}
func set(_ value: Int64) {
self.set(Double(value))
}
func set(_ value: Double) {
self.lock.withLock {
// this may loose precision but good enough as an example
values.append((Date(), Double(value)))
}
print("setting \(value) in \(self.label)")
}
func increment(by amount: Double) {
let newValue: Double = self.lock.withLock {
let lastValue = self.values.last?.1 ?? 0
let newValue = lastValue + amount
values.append((Date(), Double(newValue)))
return newValue
}
print("recording \(newValue) in \(self.label)")
}
func decrement(by amount: Double) {
let newValue: Double = self.lock.withLock {
let lastValue = self.values.last?.1 ?? 0
let newValue = lastValue - amount
values.append((Date(), Double(newValue)))
return newValue
}
print("recording \(newValue) in \(self.label)")
}
public static func == (lhs: TestMeter, rhs: TestMeter) -> Bool {
return lhs.id == rhs.id
}
}
internal final class TestRecorder: RecorderHandler, Equatable {
let id: String
let label: String
let dimensions: [(String, String)]
let aggregate: Bool
let lock = NSLock()
var values = [(Date, Double)]()
init(label: String, dimensions: [(String, String)], aggregate: Bool) {
self.id = NSUUID().uuidString
self.label = label
self.dimensions = dimensions
self.aggregate = aggregate
}
func record(_ value: Int64) {
self.record(Double(value))
}
func record(_ value: Double) {
self.lock.withLock {
// this may loose precision but good enough as an example
values.append((Date(), Double(value)))
}
print("recording \(value) in \(self.label)")
}
public static func == (lhs: TestRecorder, rhs: TestRecorder) -> Bool {
return lhs.id == rhs.id
}
}
internal final class TestTimer: TimerHandler, Equatable {
let id: String
let label: String
var displayUnit: TimeUnit?
let dimensions: [(String, String)]
let lock = NSLock()
var values = [(Date, Int64)]()
init(label: String, dimensions: [(String, String)]) {
self.id = UUID().uuidString
self.label = label
self.displayUnit = nil
self.dimensions = dimensions
}
func preferDisplayUnit(_ unit: TimeUnit) {
self.lock.withLock {
self.displayUnit = unit
}
}
func retrieveValueInPreferredUnit(atIndex i: Int) -> Double {
return self.lock.withLock {
let value = values[i].1
guard let displayUnit = self.displayUnit else {
return Double(value)
}
return Double(value) / Double(displayUnit.scaleFromNanoseconds)
}
}
func recordNanoseconds(_ duration: Int64) {
self.lock.withLock {
values.append((Date(), duration))
}
print("recording \(duration) \(self.label)")
}
static func == (lhs: TestTimer, rhs: TestTimer) -> Bool {
return lhs.id == rhs.id
}
}
extension NSLock {
@discardableResult
fileprivate func withLock<T>(_ body: () -> T) -> T {
self.lock()
defer {
self.unlock()
}
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 TestMeter: @unchecked Sendable {}
extension TestRecorder: @unchecked Sendable {}
extension TestTimer: @unchecked Sendable {}
#endif

View File

@ -14,6 +14,7 @@
@testable import CoreMetrics @testable import CoreMetrics
import Dispatch import Dispatch
@testable import MetricsTestKit
import XCTest import XCTest
class SendableTest: XCTestCase { class SendableTest: XCTestCase {
@ -90,7 +91,7 @@ class SendableTest: XCTestCase {
let task = Task.detached { () -> [Int64] in let task = Task.detached { () -> [Int64] in
timer.recordNanoseconds(value) timer.recordNanoseconds(value)
let handler = timer._handler as! TestTimer let handler = timer._handler as! TestTimer
return handler.values.map { $0.1 } return handler.values
} }
let values = await task.value let values = await task.value
XCTAssertEqual(values.count, 1, "expected number of entries to match") XCTAssertEqual(values.count, 1, "expected number of entries to match")