//===----------------------------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// import MetricsTestKit import XCTest @testable import CoreMetrics class MetricsTests: XCTestCase { func testCounters() throws { // bootstrap with our test metrics let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let group = DispatchGroup() let name = "counter-\(UUID().uuidString)" let counter = Counter(label: name) let testCounter = try metrics.expectCounter(counter) let total = Int.random(in: 500...1000) for _ in 0..(by: DataType) where DataType: BinaryInteger {} func reset() {} } let counter1 = Counter(label: "foo") XCTAssertFalse(counter1._handler is CustomHandler, "expected non-custom log handler") let counter2 = Counter(label: "foo", dimensions: [], handler: CustomHandler()) XCTAssertTrue(counter2._handler is CustomHandler, "expected custom log handler") } func testCustomFactory() { // @unchecked Sendable is okay here - locking is done manually. final class CustomFactory: MetricsFactory, @unchecked Sendable { init(handler: CustomHandler) { self.handler = handler } final class CustomHandler: CounterHandler { func increment(by: DataType) where DataType: BinaryInteger {} func reset() {} } private let handler: CustomHandler private let lock: NSLock = NSLock() private var locked_didCallDestroyCounter: Bool = false var didCallDestroyCounter: Bool { self.lock.lock() defer { lock.unlock() } return self.locked_didCallDestroyCounter } func makeCounter(label: String, dimensions: [(String, String)]) -> any CoreMetrics.CounterHandler { handler } func makeRecorder( label: String, dimensions: [(String, String)], aggregate: Bool ) -> any CoreMetrics.RecorderHandler { fatalError("Unsupported") } func makeTimer(label: String, dimensions: [(String, String)]) -> any CoreMetrics.TimerHandler { fatalError("Unsupported") } func destroyCounter(_ handler: any CoreMetrics.CounterHandler) { XCTAssertTrue( handler === self.handler, "The handler to be destroyed doesn't match the expected handler." ) self.lock.lock() defer { lock.unlock() } self.locked_didCallDestroyCounter = true } func destroyRecorder(_ handler: any CoreMetrics.RecorderHandler) { fatalError("Unsupported") } func destroyTimer(_ handler: any CoreMetrics.TimerHandler) { fatalError("Unsupported") } } let handler = CustomFactory.CustomHandler() let factory = CustomFactory(handler: handler) XCTAssertFalse(factory.didCallDestroyCounter) do { let counter1 = Counter(label: "foo", factory: factory) XCTAssertTrue(counter1._handler is CustomFactory.CustomHandler, "expected a custom metrics handler") XCTAssertTrue(counter1._factory is CustomFactory, "expected a custom metrics factory") counter1.destroy() } XCTAssertTrue(factory.didCallDestroyCounter) } func testDestroyingGauge() throws { let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let name = "gauge-\(UUID().uuidString)" let value = Double.random(in: -1000...1000) let gauge = Gauge(label: name) gauge.record(value) let recorder = try metrics.expectRecorder(gauge) XCTAssertEqual(recorder.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorder.values.first, value, "expected value to match") XCTAssertEqual(metrics.recorders.count, 1, "recorder should have been stored") let identity = ObjectIdentifier(recorder) gauge.destroy() XCTAssertEqual(metrics.recorders.count, 0, "recorder should have been released") let gaugeAgain = Gauge(label: name) gaugeAgain.record(-value) let recorderAgain = try metrics.expectRecorder(gaugeAgain) XCTAssertEqual(recorderAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(recorderAgain.values.first, -value, "expected value to match") let identityAgain = ObjectIdentifier(recorderAgain) XCTAssertNotEqual( identity, identityAgain, "since the cached metric was released, the created a new should have a different identity" ) } func testDestroyingMeter() throws { let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let name = "meter-\(UUID().uuidString)" let value = Double.random(in: -1000...1000) let meter = Meter(label: name) meter.set(value) let testMeter = try metrics.expectMeter(meter) XCTAssertEqual(testMeter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeter.values.first, value, "expected value to match") XCTAssertEqual(metrics.meters.count, 1, "recorder should have been stored") let identity = ObjectIdentifier(testMeter) meter.destroy() XCTAssertEqual(metrics.recorders.count, 0, "recorder should have been released") let meterAgain = Meter(label: name) meterAgain.set(-value) let testMeterAgain = try metrics.expectMeter(meterAgain) XCTAssertEqual(testMeterAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(testMeterAgain.values.first, -value, "expected value to match") let identityAgain = ObjectIdentifier(testMeterAgain) XCTAssertNotEqual( identity, identityAgain, "since the cached metric was released, the created a new should have a different identity" ) } func testDestroyingCounter() throws { let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let name = "counter-\(UUID().uuidString)" let value = Int.random(in: 0...1000) let counter = Counter(label: name) counter.increment(by: value) let testCounter = try metrics.expectCounter(counter) XCTAssertEqual(testCounter.values.count, 1, "expected number of entries to match") XCTAssertEqual(testCounter.values.first, Int64(value), "expected value to match") XCTAssertEqual(metrics.counters.count, 1, "counter should have been stored") let identity = ObjectIdentifier(counter) counter.destroy() XCTAssertEqual(metrics.counters.count, 0, "counter should have been released") let counterAgain = Counter(label: name) counterAgain.increment(by: value) let testCounterAgain = try metrics.expectCounter(counterAgain) XCTAssertEqual(testCounterAgain.values.count, 1, "expected number of entries to match") XCTAssertEqual(testCounterAgain.values.first, Int64(value), "expected value to match") let identityAgain = ObjectIdentifier(counterAgain) XCTAssertNotEqual( identity, identityAgain, "since the cached metric was released, the created a new should have a different identity" ) } func testDestroyingTimer() throws { let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let name = "timer-\(UUID().uuidString)" let value = Int64.random(in: 0...1000) let timer = Timer(label: name) timer.recordNanoseconds(value) let testTimer = try metrics.expectTimer(timer) XCTAssertEqual(testTimer.values.count, 1, "expected number of entries 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) timer.destroy() XCTAssertEqual(metrics.timers.count, 0, "timer should have been released") let timerAgain = Timer(label: name) timerAgain.recordNanoseconds(value) let testTimerAgain = try metrics.expectTimer(timerAgain) XCTAssertEqual(testTimerAgain.values.count, 1, "expected number of entries 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" ) } func testDescriptions() throws { let metrics = TestMetrics() MetricsSystem.bootstrapInternal(metrics) let counter = Counter(label: "hello.counter") XCTAssertEqual("\(counter)", "Counter(hello.counter, dimensions: [])") let gauge = Gauge(label: "hello.gauge") XCTAssertEqual("\(gauge)", "Gauge(hello.gauge, dimensions: [], aggregate: false)") let meter = Meter(label: "hello.meter") XCTAssertEqual("\(meter)", "Meter(hello.meter, dimensions: [])") let timer = Timer(label: "hello.timer") XCTAssertEqual("\(timer)", "Timer(hello.timer, dimensions: [])") let recorder = Recorder(label: "hello.recorder") XCTAssertEqual("\(recorder)", "Recorder(hello.recorder, dimensions: [], aggregate: true)") } }