@@ -4,11 +4,34 @@ import Nimble
44import SentryTestUtils
55import XCTest
66
7- final class SentryMetricsAPITests : XCTestCase , SentryMetricsAPIDelegate {
7+ final class SentryMetricsAPITests : XCTestCase {
88
9- func testInitWithDisabled_AllOperationsAreNoOps( ) throws {
9+ override func tearDown( ) {
10+ super. tearDown ( )
11+ clearTestState ( )
12+ }
13+
14+ private func getSut( enabled: Bool = true ) throws -> ( SentryMetricsAPI , TestMetricsClient , TestCurrentDateProvider , TestHub , Options ) {
15+
1016 let metricsClient = try TestMetricsClient ( )
11- let sut = SentryMetricsAPI ( enabled: false , client: metricsClient, currentDate: SentryCurrentDateProvider ( ) , dispatchQueue: SentryDispatchQueueWrapper ( ) , random: SentryRandom ( ) , beforeEmitMetric: { _, _ in true } )
17+ let currentDate = TestCurrentDateProvider ( )
18+ let sut = SentryMetricsAPI ( enabled: enabled, client: metricsClient, currentDate: currentDate, dispatchQueue: SentryDispatchQueueWrapper ( ) , random: SentryRandom ( ) , beforeEmitMetric: nil )
19+
20+ SentryDependencyContainer . sharedInstance ( ) . dateProvider = currentDate
21+
22+ let options = Options ( )
23+ options. enableMetrics = true
24+
25+ let testClient = try XCTUnwrap ( TestClient ( options: options) )
26+ let testHub = TestHub ( client: testClient, andScope: Scope ( ) )
27+
28+ sut. setDelegate ( testHub as? SentryMetricsAPIDelegate )
29+
30+ return ( sut, metricsClient, currentDate, testHub, options)
31+ }
32+
33+ func testInitWithDisabled_AllOperationsAreNoOps( ) throws {
34+ let ( sut, metricsClient, _, _, _) = try getSut ( enabled: false )
1235
1336 sut. increment ( key: " some " , value: 1.0 , unit: . none, tags: [ " yeah " : " sentry " ] )
1437 sut. gauge ( key: " some " , value: 1.0 , unit: . none, tags: [ " yeah " : " sentry " ] )
@@ -21,9 +44,9 @@ final class SentryMetricsAPITests: XCTestCase, SentryMetricsAPIDelegate {
2144 }
2245
2346 func testIncrement_EmitsIncrementMetric( ) throws {
24- let metricsClient = try TestMetricsClient ( )
25- let sut = SentryMetricsAPI ( enabled : true , client : metricsClient , currentDate : SentryCurrentDateProvider ( ) , dispatchQueue : SentryDispatchQueueWrapper ( ) , random : SentryRandom ( ) , beforeEmitMetric : { _ , _ in return true } )
26- sut. setDelegate ( self )
47+ let ( sut , metricsClient, _ , _ , _ ) = try getSut ( )
48+ let delegate = TestSentryMetricsAPIDelegate ( )
49+ sut. setDelegate ( delegate )
2750
2851 sut. increment ( key: " key " , value: 1.0 , unit: MeasurementUnitFraction . percent, tags: [ " yeah " : " sentry " ] )
2952
@@ -43,9 +66,9 @@ final class SentryMetricsAPITests: XCTestCase, SentryMetricsAPIDelegate {
4366 }
4467
4568 func testGauge_EmitsGaugeMetric( ) throws {
46- let metricsClient = try TestMetricsClient ( )
47- let sut = SentryMetricsAPI ( enabled : true , client : metricsClient , currentDate : SentryCurrentDateProvider ( ) , dispatchQueue : SentryDispatchQueueWrapper ( ) , random : SentryRandom ( ) , beforeEmitMetric : { _ , _ in return true } )
48- sut. setDelegate ( self )
69+ let ( sut , metricsClient, _ , _ , _ ) = try getSut ( )
70+ let delegate = TestSentryMetricsAPIDelegate ( )
71+ sut. setDelegate ( delegate )
4972
5073 sut. gauge ( key: " key " , value: 1.0 , unit: MeasurementUnitFraction . percent, tags: [ " yeah " : " sentry " ] )
5174
@@ -65,9 +88,9 @@ final class SentryMetricsAPITests: XCTestCase, SentryMetricsAPIDelegate {
6588 }
6689
6790 func testDistribution_EmitsDistributionMetric( ) throws {
68- let metricsClient = try TestMetricsClient ( )
69- let sut = SentryMetricsAPI ( enabled : true , client : metricsClient , currentDate : SentryCurrentDateProvider ( ) , dispatchQueue : SentryDispatchQueueWrapper ( ) , random : SentryRandom ( ) , beforeEmitMetric : { _ , _ in return true } )
70- sut. setDelegate ( self )
91+ let ( sut , metricsClient, _ , _ , _ ) = try getSut ( )
92+ let delegate = TestSentryMetricsAPIDelegate ( )
93+ sut. setDelegate ( delegate )
7194
7295 sut. distribution ( key: " key " , value: 1.0 , unit: MeasurementUnitFraction . percent, tags: [ " yeah " : " sentry " ] )
7396 sut. distribution ( key: " key " , value: 12.0 , unit: MeasurementUnitFraction . percent, tags: [ " yeah " : " sentry " ] )
@@ -88,9 +111,9 @@ final class SentryMetricsAPITests: XCTestCase, SentryMetricsAPIDelegate {
88111 }
89112
90113 func testSet_EmitsSetMetric( ) throws {
91- let metricsClient = try TestMetricsClient ( )
92- let sut = SentryMetricsAPI ( enabled : true , client : metricsClient , currentDate : SentryCurrentDateProvider ( ) , dispatchQueue : SentryDispatchQueueWrapper ( ) , random : SentryRandom ( ) , beforeEmitMetric : { _ , _ in return true } )
93- sut. setDelegate ( self )
114+ let ( sut , metricsClient, _ , _ , _ ) = try getSut ( )
115+ let delegate = TestSentryMetricsAPIDelegate ( )
116+ sut. setDelegate ( delegate )
94117
95118 sut. set ( key: " key " , value: " value1 " , unit: MeasurementUnitFraction . percent, tags: [ " yeah " : " sentry " ] )
96119 sut. set ( key: " key " , value: " value1 " , unit: MeasurementUnitFraction . percent, tags: [ " yeah " : " sentry " ] )
@@ -111,11 +134,114 @@ final class SentryMetricsAPITests: XCTestCase, SentryMetricsAPIDelegate {
111134 expect ( metric. tags) == [ " yeah " : " sentry " , " some " : " tag " ]
112135 }
113136
137+ func testTiming_WhenNoCurrentSpan_NoSpanCreatedAndNoMetricEmitted( ) throws {
138+ let ( sut, metricsClient, _, _, _) = try getSut ( )
139+ let delegate = TestSentryMetricsAPIDelegate ( )
140+ sut. setDelegate ( delegate)
141+
142+ let errorMessage = " It's broken "
143+ do {
144+ try sut. timing ( key: " key " ) {
145+ throw MetricsAPIError . runtimeError ( errorMessage)
146+ }
147+ } catch MetricsAPIError . runtimeError( let actualErrorMessage) {
148+ expect ( actualErrorMessage) == errorMessage
149+ }
150+
151+ expect ( metricsClient. captureInvocations. count) == 0
152+ }
153+
154+ func testTiming_WithCurrentSpan_CapturesGaugeMetric( ) throws {
155+ let ( sut, metricsClient, currentDate, testHub, options) = try getSut ( )
156+
157+ let transaction = testHub. startTransaction ( name: " hello " , operation: " operation " , bindToScope: true )
158+
159+ let errorMessage = " It's broken "
160+ do {
161+ try sut. timing ( key: " key " , tags: [ " some " : " tag " ] ) {
162+ currentDate. setDate ( date: currentDate. date ( ) . addingTimeInterval ( 1.0 ) )
163+ throw MetricsAPIError . runtimeError ( errorMessage)
164+ }
165+ } catch MetricsAPIError . runtimeError( let actualErrorMessage) {
166+ expect ( actualErrorMessage) == errorMessage
167+ }
168+
169+ sut. flush ( )
170+ transaction. finish ( )
171+
172+ expect ( metricsClient. captureInvocations. count) == 1
173+ let buckets = try XCTUnwrap ( metricsClient. captureInvocations. first)
174+
175+ let bucket = try XCTUnwrap ( buckets. first? . value)
176+ expect ( bucket. count) == 1
177+ let metric = try XCTUnwrap ( bucket. first as? DistributionMetric )
178+
179+ expect ( metric. key) == " key "
180+ expect ( metric. serialize ( ) ) == [ " 1.0 " ]
181+ expect ( metric. unit. unit) == MeasurementUnitDuration . second. unit
182+ expect ( metric. tags) == [ " some " : " tag " , " release " : options. releaseName, " environment " : options. environment]
183+ }
184+
185+ func testTiming_WithCurrentSpan_CreatesSpanWithMetricsSummary( ) throws {
186+ let ( sut, _, currentDate, testHub, options) = try getSut ( )
187+
188+ let transaction = testHub. startTransaction ( name: " hello " , operation: " operation " , bindToScope: true )
189+
190+ sut. timing ( key: " key " , tags: [ " some " : " tag " ] ) {
191+ currentDate. setDate ( date: currentDate. date ( ) . addingTimeInterval ( 1.0 ) )
192+ }
193+
194+ transaction. finish ( )
195+
196+ expect ( testHub. capturedTransactionsWithScope. count) == 1
197+ let serializedTransaction = try XCTUnwrap ( testHub. capturedTransactionsWithScope. first? . transaction)
198+ expect ( serializedTransaction. count) != 0
199+
200+ let spans = try XCTUnwrap ( serializedTransaction [ " spans " ] as? [ [ String : Any ] ] )
201+ expect ( spans. count) == 1
202+ let span = try XCTUnwrap ( spans. first)
203+
204+ expect ( span [ " op " ] as? String ) == " metric.timing "
205+ expect ( span [ " description " ] as? String ) == " key "
206+ expect ( span [ " origin " ] as? String ) == " manual "
207+ expect ( span [ " start_timestamp " ] as? Int ) == 978_307_200
208+ expect ( span [ " timestamp " ] as? Int ) == 978_307_201
209+ expect ( span [ " parent_span_id " ] as? String ) == transaction. spanId. sentrySpanIdString
210+ expect ( span [ " tags " ] as? [ String : String ] ) == [ " some " : " tag " , " release " : options. releaseName, " environment " : options. environment]
211+
212+ let metricsSummary = try XCTUnwrap ( span [ " _metrics_summary " ] as? [ String : [ [ String : Any ] ] ] )
213+ expect ( metricsSummary. count) == 1
214+
215+ let bucket = try XCTUnwrap ( metricsSummary [ " d:key@second " ] )
216+ expect ( bucket. count) == 1
217+ let metric = try XCTUnwrap ( bucket. first)
218+ expect ( metric [ " min " ] as? Double ) == 1.0
219+ expect ( metric [ " max " ] as? Double ) == 1.0
220+ expect ( metric [ " count " ] as? Int ) == 1
221+ expect ( metric [ " sum " ] as? Double ) == 1.0
222+ }
223+
224+ enum MetricsAPIError : Error {
225+ case runtimeError( String )
226+ }
227+ }
228+
229+ class TestSentryMetricsAPIDelegate : SentryMetricsAPIDelegate {
230+ var currentSpan : SentrySpan ?
231+
114232 func getDefaultTagsForMetrics( ) -> [ String : String ] {
115233 return [ " some " : " tag " , " yeah " : " not-taken " ]
116234 }
117235
118236 func getLocalMetricsAggregator( ) -> Sentry . LocalMetricsAggregator ? {
119- return nil
237+ return currentSpan? . getLocalMetricsAggregator ( )
238+ }
239+
240+ func getCurrentSpan( ) -> Span ? {
241+ return currentSpan
242+ }
243+
244+ func getLocalMetricsAggregator( span: Span ) -> Sentry . LocalMetricsAggregator ? {
245+ return ( span as? SentrySpan ) ? . getLocalMetricsAggregator ( )
120246 }
121247}
0 commit comments