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(
name: "MetricsTests",
dependencies: ["Metrics"]
dependencies: ["Metrics", "MetricsTestKit"]
),
]
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
}

View File

@ -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")

View File

@ -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)"

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
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")