From 9d5ff3d48f71da89fb435245f1f0d656c65e16df Mon Sep 17 00:00:00 2001 From: hamzahrmalik Date: Fri, 26 May 2023 12:25:08 +0100 Subject: [PATCH] Use the MetricsTestKit in the MetricsTests rather than using a copy of the TestMetrics utilities (#128) Co-authored-by: Hamzah Malik --- Package.swift | 2 +- Package@swift-4.2.swift | 2 +- Package@swift-5.0.swift | 2 +- Package@swift-5.1.swift | 2 +- Package@swift-5.2.swift | 2 +- Package@swift-5.3.swift | 2 +- Package@swift-5.4.swift | 2 +- Package@swift-5.5.swift | 2 +- Sources/MetricsTestKit/TestMetrics.swift | 172 ++++++++------ Tests/MetricsTests/CoreMetricsTests.swift | 109 ++++----- Tests/MetricsTests/MetricsTests.swift | 21 +- Tests/MetricsTests/TestMetrics.swift | 268 ---------------------- Tests/MetricsTests/TestSendable.swift | 3 +- 13 files changed, 174 insertions(+), 415 deletions(-) delete mode 100644 Tests/MetricsTests/TestMetrics.swift diff --git a/Package.swift b/Package.swift index 75ba64a..a0e7e5b 100644 --- a/Package.swift +++ b/Package.swift @@ -40,7 +40,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-4.2.swift b/Package@swift-4.2.swift index 60f37eb..11f748a 100644 --- a/Package@swift-4.2.swift +++ b/Package@swift-4.2.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-5.0.swift b/Package@swift-5.0.swift index 1c00edd..ac536a6 100644 --- a/Package@swift-5.0.swift +++ b/Package@swift-5.0.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-5.1.swift b/Package@swift-5.1.swift index 7582cf7..e148e61 100644 --- a/Package@swift-5.1.swift +++ b/Package@swift-5.1.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-5.2.swift b/Package@swift-5.2.swift index 1c050b1..7488bc1 100644 --- a/Package@swift-5.2.swift +++ b/Package@swift-5.2.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-5.3.swift b/Package@swift-5.3.swift index ef21a20..23340f5 100644 --- a/Package@swift-5.3.swift +++ b/Package@swift-5.3.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift index a34834c..40e861e 100644 --- a/Package@swift-5.4.swift +++ b/Package@swift-5.4.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift index 7525979..5722b9c 100644 --- a/Package@swift-5.5.swift +++ b/Package@swift-5.5.swift @@ -37,7 +37,7 @@ let package = Package( ), .testTarget( name: "MetricsTests", - dependencies: ["Metrics"] + dependencies: ["Metrics", "MetricsTestKit"] ), ] ) diff --git a/Sources/MetricsTestKit/TestMetrics.swift b/Sources/MetricsTestKit/TestMetrics.swift index aee59e5..349f99a 100644 --- a/Sources/MetricsTestKit/TestMetrics.swift +++ b/Sources/MetricsTestKit/TestMetrics.swift @@ -48,10 +48,10 @@ public final class TestMetrics: MetricsFactory { let dimensions: [(String, String)] } - private var counters = [FullKey: CounterHandler]() - private var meters = [FullKey: MeterHandler]() - private var recorders = [FullKey: RecorderHandler]() - private var timers = [FullKey: TimerHandler]() + private var _counters = [FullKey: TestCounter]() + private var _meters = [FullKey: TestMeter]() + private var _recorders = [FullKey: TestRecorder]() + private var _timers = [FullKey: TestTimer]() public init() { // 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. public func reset() { self.lock.withLock { - self.counters = [:] - self.meters = [:] - self.recorders = [:] - self.timers = [:] + self._counters = [:] + self._recorders = [:] + self._meters = [:] + self._timers = [:] } } public func makeCounter(label: String, dimensions: [(String, String)]) -> CounterHandler { 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 } 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 } } public func makeMeter(label: String, dimensions: [(String, String)]) -> MeterHandler { 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 } 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 } } public func makeRecorder(label: String, dimensions: [(String, String)], aggregate: Bool) -> RecorderHandler { 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 } 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 } } public func makeTimer(label: String, dimensions: [(String, String)]) -> TimerHandler { 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 } 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 } } public func destroyCounter(_ handler: CounterHandler) { if let testCounter = handler as? TestCounter { - self.lock.withLock { () in - self.counters.removeValue(forKey: testCounter.key) + self.lock.withLock { + self._counters.removeValue(forKey: testCounter.key) } } } @@ -123,23 +123,23 @@ public final class TestMetrics: MetricsFactory { public func destroyMeter(_ handler: MeterHandler) { if let testMeter = handler as? TestMeter { self.lock.withLock { () in - self.meters.removeValue(forKey: testMeter.key) + self._meters.removeValue(forKey: testMeter.key) } } } public func destroyRecorder(_ handler: RecorderHandler) { if let testRecorder = handler as? TestRecorder { - self.lock.withLock { () in - self.recorders.removeValue(forKey: testRecorder.key) + self.lock.withLock { + self._recorders.removeValue(forKey: testRecorder.key) } } } public func destroyTimer(_ handler: TimerHandler) { if let testTimer = handler as? TestTimer { - self.lock.withLock { () in - self.timers.removeValue(forKey: testTimer.key) + self.lock.withLock { + self._timers.removeValue(forKey: testTimer.key) } } } @@ -174,17 +174,22 @@ extension TestMetrics { public func expectCounter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestCounter { 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) } - guard let testCounter = maybeCounter as? TestCounter else { - throw TestMetricsError.illegalMetricType(metric: maybeCounter, expected: "\(TestCounter.self)") - } 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 public func expectGauge(_ metric: Gauge) throws -> TestRecorder { @@ -206,17 +211,22 @@ extension TestMetrics { public func expectMeter(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestMeter { 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) } - guard let testMeter = maybeMeter as? TestMeter else { - throw TestMetricsError.illegalMetricType(metric: maybeMeter, expected: "\(TestMeter.self)") - } 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 public func expectRecorder(_ metric: Recorder) throws -> TestRecorder { @@ -228,17 +238,22 @@ extension TestMetrics { public func expectRecorder(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestRecorder { 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) } - guard let testRecorder = maybeRecorder as? TestRecorder else { - throw TestMetricsError.illegalMetricType(metric: maybeRecorder, expected: "\(TestRecorder.self)") - } 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 public func expectTimer(_ metric: CoreMetrics.Timer) throws -> TestTimer { @@ -250,16 +265,21 @@ extension TestMetrics { public func expectTimer(_ label: String, _ dimensions: [(String, String)] = []) throws -> TestTimer { 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) } - guard let testTimer = maybeTimer as? TestTimer else { - throw TestMetricsError.illegalMetricType(metric: maybeTimer, expected: "\(TestTimer.self)") - } 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 @@ -283,7 +303,7 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable { } let lock = NSLock() - private var values = [(Date, Int64)]() + private var _values = [(Date, Int64)]() init(label: String, dimensions: [(String, String)]) { self.id = UUID().uuidString @@ -293,31 +313,31 @@ public final class TestCounter: TestMetric, CounterHandler, Equatable { public func increment(by amount: Int64) { self.lock.withLock { - self.values.append((Date(), amount)) + self._values.append((Date(), amount)) } } public func reset() { return self.lock.withLock { - self.values = [] + self._values = [] } } public var lastValue: Int64? { - return self.lock.withLock { - return values.last?.1 - } + return self.values.last?.1 } public var totalValue: Int64 { - return self.lock.withLock { - return values.map { $0.1 }.reduce(0, +) - } + return self.values.map { $0.1 }.reduce(0, +) } public var last: (Date, Int64)? { + return self.values.last + } + + var values: [(Date, Int64)] { return self.lock.withLock { - values.last + self._values } } @@ -336,7 +356,7 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable { } let lock = NSLock() - private var values = [(Date, Double)]() + private var _values = [(Date, Double)]() init(label: String, dimensions: [(String, String)]) { self.id = NSUUID().uuidString @@ -350,36 +370,38 @@ public final class TestMeter: TestMetric, MeterHandler, Equatable { public func set(_ value: Double) { self.lock.withLock { - // this may loose precision but good enough as an example - values.append((Date(), Double(value))) + // this may lose precision but good enough as an example + _values.append((Date(), Double(value))) } } public func increment(by amount: Double) { self.lock.withLock { - let lastValue = self.values.last?.1 ?? 0 + let lastValue = self._values.last?.1 ?? 0 let newValue = lastValue - amount - values.append((Date(), Double(newValue))) + _values.append((Date(), Double(newValue))) } } public func decrement(by amount: Double) { self.lock.withLock { - let lastValue = self.values.last?.1 ?? 0 + let lastValue = self._values.last?.1 ?? 0 let newValue = lastValue - amount - values.append((Date(), Double(newValue))) + _values.append((Date(), Double(newValue))) } } public var lastValue: Double? { - return self.lock.withLock { - values.last?.1 - } + return self.last?.1 } public var last: (Date, Double)? { + return self.values.last + } + + var values: [(Date, Double)] { return self.lock.withLock { - values.last + self._values } } @@ -399,7 +421,7 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable { } let lock = NSLock() - private var values = [(Date, Double)]() + private var _values = [(Date, Double)]() init(label: String, dimensions: [(String, String)], aggregate: Bool) { self.id = NSUUID().uuidString @@ -414,20 +436,22 @@ public final class TestRecorder: TestMetric, RecorderHandler, Equatable { public func record(_ value: Double) { self.lock.withLock { - // this may loose precision but good enough as an example - values.append((Date(), Double(value))) + // this may lose precision but good enough as an example + _values.append((Date(), Double(value))) } } public var lastValue: Double? { - return self.lock.withLock { - values.last?.1 - } + return self.last?.1 } public var last: (Date, Double)? { + return self.values.last + } + + var values: [(Date, Double)] { return self.lock.withLock { - values.last + self._values } } @@ -480,19 +504,19 @@ public final class TestTimer: TestMetric, TimerHandler, Equatable { public var lastValue: Int64? { return self.lock.withLock { - return _values.last?.1 + return self._values.last?.1 } } public var values: [Int64] { return self.lock.withLock { - return _values.map { $0.1 } + return self._values.map { $0.1 } } } public var last: (Date, Int64)? { return self.lock.withLock { - return _values.last + return self._values.last } } diff --git a/Tests/MetricsTests/CoreMetricsTests.swift b/Tests/MetricsTests/CoreMetricsTests.swift index 4e2dc45..f51c2cf 100644 --- a/Tests/MetricsTests/CoreMetricsTests.swift +++ b/Tests/MetricsTests/CoreMetricsTests.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// @testable import CoreMetrics +@testable import MetricsTestKit import XCTest class MetricsTests: XCTestCase { @@ -46,7 +47,7 @@ class MetricsTests: XCTestCase { let name = "counter-\(UUID().uuidString)" let value = Int.random(in: Int.min ... Int.max) 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[0].1, Int64(value), "expected value to match") counter.reset() @@ -59,7 +60,7 @@ class MetricsTests: XCTestCase { MetricsSystem.bootstrapInternal(metrics) let label = "\(#function)-fp-counter-\(UUID())" 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.signalingNaN) XCTAssertEqual(counter.values.count, 0, "expected nan values to be ignored") @@ -71,7 +72,7 @@ class MetricsTests: XCTestCase { MetricsSystem.bootstrapInternal(metrics) let label = "\(#function)-fp-counter-\(UUID())" 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) XCTAssertEqual(counter.values.count, 0, "expected infinite values to be ignored") @@ -83,7 +84,7 @@ class MetricsTests: XCTestCase { MetricsSystem.bootstrapInternal(metrics) let label = "\(#function)-fp-counter-\(UUID())" let fpCounter = FloatingPointCounter(label: label) - let counter = metrics.counters[label] as! TestCounter + let counter = try metrics.expectCounter(label) fpCounter.increment(by: -100) XCTAssertEqual(counter.values.count, 0, "expected negative values to be ignored") } @@ -94,19 +95,19 @@ class MetricsTests: XCTestCase { MetricsSystem.bootstrapInternal(metrics) let label = "\(#function)-fp-counter-\(UUID())" 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) XCTAssertEqual(counter.values.count, 0, "expected zero values to be ignored") } - func testDefaultFloatingPointCounter_ceilsExtremeValues() { + func testDefaultFloatingPointCounter_ceilsExtremeValues() throws { // bootstrap with our test metrics let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let label = "\(#function)-fp-counter-\(UUID())" let fpCounter = FloatingPointCounter(label: label) - let counter = metrics.counters[label] as! TestCounter + let counter = try metrics.expectCounter(label) // Just larger than Int64 fpCounter.increment(by: Double(sign: .plus, exponent: 63, significand: 1)) // 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") } - func testDefaultFloatingPointCounter_accumulatesFloatingPointDecimalValues() { + func testDefaultFloatingPointCounter_accumulatesFloatingPointDecimalValues() throws { // bootstrap with our test metrics let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let label = "\(#function)-fp-counter-\(UUID())" let fpCounter = FloatingPointCounter(label: label) 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) fpCounter.increment(by: 0.75) @@ -205,9 +206,9 @@ class MetricsTests: XCTestCase { let name = "recorder-\(UUID().uuidString)" let value = Double.random(in: Double(Int.min) ... Double(Int.max)) 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[0].1, value, "expected value to match") + XCTAssertEqual(recorder.lastValue, value, "expected value to match") } func testTimers() throws { @@ -238,9 +239,9 @@ class MetricsTests: XCTestCase { let name = "timer-\(UUID().uuidString)" let value = Int64.random(in: Int64.min ... Int64.max) 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[0].1, value, "expected value to match") + XCTAssertEqual(timer.values[0], value, "expected value to match") } func testTimerVariants() throws { @@ -254,22 +255,22 @@ class MetricsTests: XCTestCase { let nano = Int64.random(in: 0 ... 5) timer.recordNanoseconds(nano) 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 let micro = Int64.random(in: 0 ... 5) timer.recordMicroseconds(micro) 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 let milli = Int64.random(in: 0 ... 5) timer.recordMilliseconds(milli) 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 let sec = Int64.random(in: 0 ... 5) timer.recordSeconds(sec) 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 { @@ -282,31 +283,31 @@ class MetricsTests: XCTestCase { // nano (integer) timer.recordNanoseconds(Int64.max) 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) timer.recordMicroseconds(Int64.max) 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) timer.recordMicroseconds(Double(Int64.max) + 1) 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) timer.recordMilliseconds(Int64.max) 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) timer.recordMilliseconds(Double(Int64.max) + 1) 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) timer.recordSeconds(Int64.max) 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) timer.recordSeconds(Double(Int64.max) * 1) 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 { @@ -319,19 +320,19 @@ class MetricsTests: XCTestCase { // nano timer.recordNanoseconds(UInt64.max) 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 timer.recordMicroseconds(UInt64.max) 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 timer.recordMilliseconds(UInt64.max) 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 timer.recordSeconds(UInt64.max) 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 { @@ -345,7 +346,7 @@ class MetricsTests: XCTestCase { gauge.record(value) let recorder = gauge._handler as! TestRecorder 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 { @@ -356,9 +357,9 @@ class MetricsTests: XCTestCase { let name = "gauge-\(UUID().uuidString)" let value = Double.random(in: -1000 ... 1000) 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[0].1, value, "expected value to match") + XCTAssertEqual(recorder.lastValue, value, "expected value to match") } func testMeter() throws { @@ -383,7 +384,7 @@ class MetricsTests: XCTestCase { let name = "meter-\(NSUUID().uuidString)" let value = Double.random(in: -1000 ... 1000) 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[0].1, value, "expected value to match") } @@ -398,15 +399,15 @@ class MetricsTests: XCTestCase { let muxCounter = Counter(label: name) muxCounter.increment(by: value) factories.forEach { factory in - let counter = factory.counters.first?.1 as! TestCounter - XCTAssertEqual(counter.label, name, "expected label to match") - XCTAssertEqual(counter.values.count, 1, "expected number of entries to match") - XCTAssertEqual(counter.values[0].1, Int64(value), "expected value to match") + let counter = factory.counters.first + XCTAssertEqual(counter?.label, name, "expected label to match") + XCTAssertEqual(counter?.values.count, 1, "expected number of entries to match") + XCTAssertEqual(counter?.lastValue, Int64(value), "expected value to match") } muxCounter.reset() factories.forEach { factory in - let counter = factory.counters.first?.1 as! TestCounter - XCTAssertEqual(counter.values.count, 0, "expected number of entries to match") + let counter = factory.counters.first + XCTAssertEqual(counter?.values.count, 0, "expected number of entries to match") } } @@ -420,10 +421,10 @@ class MetricsTests: XCTestCase { let muxMeter = Meter(label: name) muxMeter.set(value) factories.forEach { factory in - let meter = factory.meters.first?.1 as! TestMeter - XCTAssertEqual(meter.label, name, "expected label to match") - XCTAssertEqual(meter.values.count, 1, "expected number of entries to match") - XCTAssertEqual(meter.values[0].1, value, "expected value to match") + let meter = factory.meters.first + XCTAssertEqual(meter?.label, name, "expected label to match") + XCTAssertEqual(meter?.values.count, 1, "expected number of entries to match") + XCTAssertEqual(meter?.values[0].1, value, "expected value to match") } } @@ -437,10 +438,10 @@ class MetricsTests: XCTestCase { let muxRecorder = Recorder(label: name) muxRecorder.record(value) factories.forEach { factory in - let recorder = factory.recorders.first?.1 as! TestRecorder - XCTAssertEqual(recorder.label, name, "expected label to match") - XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") - XCTAssertEqual(recorder.values[0].1, value, "expected value to match") + let recorder = factory.recorders.first + XCTAssertEqual(recorder?.label, name, "expected label to match") + XCTAssertEqual(recorder?.values.count, 1, "expected number of entries 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) muxTimer.recordSeconds(seconds) factories.forEach { factory in - let timer = factory.timers.first?.1 as! TestTimer - XCTAssertEqual(timer.label, name, "expected label 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.displayUnit, .minutes, "expected value to match") - XCTAssertEqual(timer.retrieveValueInPreferredUnit(atIndex: 0), Double(seconds) / 60.0, "seconds should be returned as minutes") + let timer = factory.timers.first + XCTAssertEqual(timer?.label, name, "expected label to match") + XCTAssertEqual(timer?.values.count, 1, "expected number of entries 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?.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 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") let identity = ObjectIdentifier(timer) @@ -588,7 +589,7 @@ class MetricsTests: XCTestCase { timerAgain.recordNanoseconds(value) let testTimerAgain = timerAgain._handler as! TestTimer 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) XCTAssertNotEqual(identity, identityAgain, "since the cached metric was released, the created a new should have a different identity") diff --git a/Tests/MetricsTests/MetricsTests.swift b/Tests/MetricsTests/MetricsTests.swift index 27b4da7..2981734 100644 --- a/Tests/MetricsTests/MetricsTests.swift +++ b/Tests/MetricsTests/MetricsTests.swift @@ -14,6 +14,7 @@ @testable import CoreMetrics @testable import Metrics +@testable import MetricsTestKit import XCTest class MetricsExtensionsTests: XCTestCase { @@ -27,9 +28,9 @@ class MetricsExtensionsTests: XCTestCase { Timer.measure(label: name) { 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") - 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 { @@ -42,7 +43,7 @@ class MetricsExtensionsTests: XCTestCase { let timeInterval = TimeInterval(Double.random(in: 1 ... 500)) timer.record(timeInterval) 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 { @@ -56,26 +57,26 @@ class MetricsExtensionsTests: XCTestCase { let nano = DispatchTimeInterval.nanoseconds(Int.random(in: 1 ... 500)) timer.record(nano) 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 let micro = DispatchTimeInterval.microseconds(Int.random(in: 1 ... 500)) timer.record(micro) 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 let milli = DispatchTimeInterval.milliseconds(Int.random(in: 1 ... 500)) timer.record(milli) 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 let sec = DispatchTimeInterval.seconds(Int.random(in: 1 ... 500)) timer.record(sec) 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 timer.record(DispatchTimeInterval.never) 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() { @@ -91,7 +92,7 @@ class MetricsExtensionsTests: XCTestCase { let testTimer = timer._handler as! TestTimer 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") } @@ -107,7 +108,7 @@ class MetricsExtensionsTests: XCTestCase { let testTimer = timer._handler as! TestTimer 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") let secondsName = "timer-seconds-\(UUID().uuidString)" diff --git a/Tests/MetricsTests/TestMetrics.swift b/Tests/MetricsTests/TestMetrics.swift deleted file mode 100644 index a138742..0000000 --- a/Tests/MetricsTests/TestMetrics.swift +++ /dev/null @@ -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(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(_ 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 diff --git a/Tests/MetricsTests/TestSendable.swift b/Tests/MetricsTests/TestSendable.swift index 9e859d4..8741e7a 100644 --- a/Tests/MetricsTests/TestSendable.swift +++ b/Tests/MetricsTests/TestSendable.swift @@ -14,6 +14,7 @@ @testable import CoreMetrics import Dispatch +@testable import MetricsTestKit import XCTest class SendableTest: XCTestCase { @@ -90,7 +91,7 @@ class SendableTest: XCTestCase { let task = Task.detached { () -> [Int64] in timer.recordNanoseconds(value) let handler = timer._handler as! TestTimer - return handler.values.map { $0.1 } + return handler.values } let values = await task.value XCTAssertEqual(values.count, 1, "expected number of entries to match")