diff --git a/.github/workflows/min_version_test.yml b/.github/workflows/min_version_test.yml index 233a6cd1cc..f96746121b 100644 --- a/.github/workflows/min_version_test.yml +++ b/.github/workflows/min_version_test.yml @@ -59,12 +59,20 @@ jobs: with: ruby-version: '3.1.2' # https://github.com/flutter/flutter/issues/109385#issuecomment-1212614125 - - name: Update Pods - run: sudo gem update + - name: Uninstall existing CocoaPods and install globally + run: | + gem uninstall cocoapods -a + sudo gem install cocoapods + echo "$(which pod)" + - name: Build iOS run: | cd min_version_test flutter pub get + cd ios + pod repo update + pod install + cd .. flutter build ios --no-codesign build-web: diff --git a/CHANGELOG.md b/CHANGELOG.md index e45a587d11..0dc95ae913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,17 +42,57 @@ - [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#810) - [diff](https://github.com/getsentry/sentry-java/compare/7.20.1...8.1.0) ## Unreleased +## 8.14.0 +This release fixes an issue where Cold starts can be incorrectly reported as Warm starts on Android. + +### Behavioral changes + +- ⚠️ Auto IP assignment for `SentryUser` is now guarded by `sendDefaultPii` ([#2726](https://github.com/getsentry/sentry-dart/pull/2726)) + - If you rely on Sentry automatically processing the IP address of the user, set `options.sendDefaultPii = true` or manually set the IP address of the `SentryUser` to `{{auto}}` +- Adding the device name to Contexts is now guarded by `sendDefaultPii` ([#2741](https://github.com/getsentry/sentry-dart/pull/2741)) + - Set `options.sendDefaultPii = true` if you want to have the device name reported +- Remove macOS display refresh rate support ([#2628](https://github.com/getsentry/sentry-dart/pull/2628)) + - Can't reliably detect on multi-monitor systems and on older macOS versions. + - Not very meaningful, as other applications may be running in parallel and affecting it. ### Enhancements - Add Flutter runtime information ([#2742](https://github.com/getsentry/sentry-dart/pull/2742)) - This works if the version of Flutter you're using includes [this code](https://github.com/flutter/flutter/pull/163761). +- Use `loadDebugImagesForAddresses` API for Android ([#2706](https://github.com/getsentry/sentry-dart/pull/2706)) + - This reduces the envelope size and data transferred across method channels + - If debug images received by `loadDebugImagesForAddresses` are empty, the SDK loads all debug images as fallback +- Disable `ScreenshotIntegration`, `WidgetsBindingIntegration` and `SentryWidget` in multi-view apps #2366 ([#2366](https://github.com/getsentry/sentry-dart/pull/2366)) ### Fixes - Pass missing `captureFailedRequests` param to `FailedRequestInterceptor` ([#2744](https://github.com/getsentry/sentry-dart/pull/2744)) +- Bind root screen transaction to scope ([#2756](https://github.com/getsentry/sentry-dart/pull/2756)) +- Reference to `SentryWidgetsFlutterBinding` in warning message in `FramesTrackingIntegration` ([#2704](https://github.com/getsentry/sentry-dart/pull/2704)) +### Deprecations + +- Deprecate Drift `SentryQueryExecutor` ([#2715](https://github.com/getsentry/sentry-dart/pull/2715)) + - This will be replace by `SentryQueryInterceptor` in the next major v9 +```dart +// Example usage in Sentry Flutter v9 +final executor = NativeDatabase.memory().interceptWith( + SentryQueryInterceptor(databaseName: 'your_db_name'), +); + +final db = AppDatabase(executor); +``` +- Deprecate `autoAppStart` and `setAppStartEnd` ([#2681](https://github.com/getsentry/sentry-dart/pull/2681)) + +### Dependencies + +- Bump Native SDK from v0.7.19 to v0.7.20 ([#2652](https://github.com/getsentry/sentry-dart/pull/2652)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0720) + - [diff](https://github.com/getsentry/sentry-native/compare/0.7.19...0.7.20) +- Bump Cocoa SDK from v8.44.0 to v8.46.0 ([#2772](https://github.com/getsentry/sentry-dart/pull/2772)) + - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8460) + - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.44.0...8.46.0) ## 8.14.0-beta.1 @@ -109,6 +149,16 @@ final db = AppDatabase(executor); - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#8450) - [diff](https://github.com/getsentry/sentry-cocoa/compare/8.44.0...8.45.0) +## 8.13.3 + +This release fixes an issue where Cold starts can be incorrectly reported as Warm starts on Android. + +### Dependencies + +- Bump Android SDK from v7.22.0 to v7.22.1 ([#2785](https://github.com/getsentry/sentry-dart/pull/2785)) + - [changelog](https://github.com/getsentry/sentry-java/blob/7.x.x/CHANGELOG.md#7221) + - [diff](https://github.com/getsentry/sentry-java/compare/7.22.0...7.22.1) + ## 8.13.2 > [!WARNING] diff --git a/flutter/ios/sentry_flutter.podspec b/flutter/ios/sentry_flutter.podspec index 58a7f4365d..c9b28c297f 100644 --- a/flutter/ios/sentry_flutter.podspec +++ b/flutter/ios/sentry_flutter.podspec @@ -16,7 +16,7 @@ Sentry SDK for Flutter with support to native through sentry-cocoa. :tag => s.version.to_s } s.source_files = 'sentry_flutter/Sources/**/*' s.public_header_files = 'sentry_flutter/Sources/**/*.h' - s.dependency 'Sentry/HybridSDK', '8.45.0' + s.dependency 'Sentry/HybridSDK', '8.46.0' s.ios.dependency 'Flutter' s.osx.dependency 'FlutterMacOS' s.ios.deployment_target = '12.0' diff --git a/flutter/ios/sentry_flutter/Package.swift b/flutter/ios/sentry_flutter/Package.swift index ed05801fc7..8e5cf7421b 100644 --- a/flutter/ios/sentry_flutter/Package.swift +++ b/flutter/ios/sentry_flutter/Package.swift @@ -13,7 +13,7 @@ let package = Package( .library(name: "sentry-flutter", targets: ["sentry_flutter", "sentry_flutter_objc"]) ], dependencies: [ - .package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.45.0") + .package(url: "https://github.com/getsentry/sentry-cocoa", from: "8.46.0") ], targets: [ .target( diff --git a/flutter/lib/src/integrations/native_app_start_handler.dart b/flutter/lib/src/integrations/native_app_start_handler.dart index 4ce177a9ce..25de0c933f 100644 --- a/flutter/lib/src/integrations/native_app_start_handler.dart +++ b/flutter/lib/src/integrations/native_app_start_handler.dart @@ -37,22 +37,27 @@ class NativeAppStartHandler { // Create Transaction & Span const screenName = 'root /'; - final transaction = _hub.startTransaction( + final rootScreenTransaction = _hub.startTransaction( screenName, SentrySpanOperations.uiLoad, startTimestamp: appStartInfo.start, ); + // Bind to scope if null + await _hub.configureScope((scope) { + scope.span ??= rootScreenTransaction; + }); + await options.timeToDisplayTracker.track( - transaction, + rootScreenTransaction, startTimestamp: appStartInfo.start, endTimestamp: appStartInfo.end, origin: SentryTraceOrigins.autoUiTimeToDisplay, ); SentryTracer sentryTracer; - if (transaction is SentryTracer) { - sentryTracer = transaction; + if (rootScreenTransaction is SentryTracer) { + sentryTracer = rootScreenTransaction; } else { return; } @@ -62,8 +67,15 @@ class NativeAppStartHandler { sentryTracer.measurements[measurement.name] = appStartInfo.toMeasurement(); await _attachAppStartSpans(appStartInfo, sentryTracer); + // Remove from scope + await _hub.configureScope((scope) { + if (scope.span == rootScreenTransaction) { + scope.span = null; + } + }); + // Finish Transaction - await transaction.finish(endTimestamp: appStartInfo.end); + await rootScreenTransaction.finish(endTimestamp: appStartInfo.end); } _AppStartInfo? _infoNativeAppStart( diff --git a/flutter/lib/src/native/cocoa/binding.dart b/flutter/lib/src/native/cocoa/binding.dart index dc984879de..e05cec9a13 100644 --- a/flutter/lib/src/native/cocoa/binding.dart +++ b/flutter/lib/src/native/cocoa/binding.dart @@ -30123,9 +30123,8 @@ class SentryCocoa { late final _sel_setEmail_1 = _registerName1("setEmail:"); late final _sel_message1 = _registerName1("message"); late final _sel_setMessage_1 = _registerName1("setMessage:"); - late final _class_SentryId11 = _getClass1("Sentry.SentryId"); - late final _sel_empty1 = _registerName1("empty"); - ffi.Pointer _objc_msgSend_1062( + late final _sel_source1 = _registerName1("source"); + int _objc_msgSend_1062( ffi.Pointer obj, ffi.Pointer sel, ) { @@ -30137,9 +30136,48 @@ class SentryCocoa { late final __objc_msgSend_1062Ptr = _lookup< ffi.NativeFunction< - ffi.Pointer Function( + ffi.Int32 Function( ffi.Pointer, ffi.Pointer)>>('objc_msgSend'); late final __objc_msgSend_1062 = __objc_msgSend_1062Ptr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + + late final _sel_setSource_1 = _registerName1("setSource:"); + void _objc_msgSend_1063( + ffi.Pointer obj, + ffi.Pointer sel, + int value, + ) { + return __objc_msgSend_1063( + obj, + sel, + value, + ); + } + + late final __objc_msgSend_1063Ptr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Int32)>>('objc_msgSend'); + late final __objc_msgSend_1063 = __objc_msgSend_1063Ptr.asFunction< + void Function(ffi.Pointer, ffi.Pointer, int)>(); + + late final _class_SentryId11 = _getClass1("Sentry.SentryId"); + late final _sel_empty1 = _registerName1("empty"); + ffi.Pointer _objc_msgSend_1064( + ffi.Pointer obj, + ffi.Pointer sel, + ) { + return __objc_msgSend_1064( + obj, + sel, + ); + } + + late final __objc_msgSend_1064Ptr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, ffi.Pointer)>>('objc_msgSend'); + late final __objc_msgSend_1064 = __objc_msgSend_1064Ptr.asFunction< ffi.Pointer Function( ffi.Pointer, ffi.Pointer)>(); @@ -30148,99 +30186,165 @@ class SentryCocoa { late final _sel_UUID1 = _registerName1("UUID"); late final _sel_initWithUUIDString_1 = _registerName1("initWithUUIDString:"); late final _sel_initWithUUIDBytes_1 = _registerName1("initWithUUIDBytes:"); - instancetype _objc_msgSend_1063( + instancetype _objc_msgSend_1065( ffi.Pointer obj, ffi.Pointer sel, ffi.Pointer bytes, ) { - return __objc_msgSend_1063( + return __objc_msgSend_1065( obj, sel, bytes, ); } - late final __objc_msgSend_1063Ptr = _lookup< + late final __objc_msgSend_1065Ptr = _lookup< ffi.NativeFunction< instancetype Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>>('objc_msgSend'); - late final __objc_msgSend_1063 = __objc_msgSend_1063Ptr.asFunction< + late final __objc_msgSend_1065 = __objc_msgSend_1065Ptr.asFunction< instancetype Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); late final _sel_getUUIDBytes_1 = _registerName1("getUUIDBytes:"); - void _objc_msgSend_1064( + void _objc_msgSend_1066( ffi.Pointer obj, ffi.Pointer sel, ffi.Pointer uuid, ) { - return __objc_msgSend_1064( + return __objc_msgSend_1066( obj, sel, uuid, ); } - late final __objc_msgSend_1064Ptr = _lookup< + late final __objc_msgSend_1066Ptr = _lookup< ffi.NativeFunction< ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>>('objc_msgSend'); - late final __objc_msgSend_1064 = __objc_msgSend_1064Ptr.asFunction< + late final __objc_msgSend_1066 = __objc_msgSend_1066Ptr.asFunction< void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); - int _objc_msgSend_1065( + int _objc_msgSend_1067( ffi.Pointer obj, ffi.Pointer sel, ffi.Pointer otherUUID, ) { - return __objc_msgSend_1065( + return __objc_msgSend_1067( obj, sel, otherUUID, ); } - late final __objc_msgSend_1065Ptr = _lookup< + late final __objc_msgSend_1067Ptr = _lookup< ffi.NativeFunction< ffi.Int32 Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>>('objc_msgSend'); - late final __objc_msgSend_1065 = __objc_msgSend_1065Ptr.asFunction< + late final __objc_msgSend_1067 = __objc_msgSend_1067Ptr.asFunction< int Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); late final _sel_UUIDString1 = _registerName1("UUIDString"); late final _sel_initWithUuid_1 = _registerName1("initWithUuid:"); - instancetype _objc_msgSend_1066( + instancetype _objc_msgSend_1068( ffi.Pointer obj, ffi.Pointer sel, ffi.Pointer uuid, ) { - return __objc_msgSend_1066( + return __objc_msgSend_1068( obj, sel, uuid, ); } - late final __objc_msgSend_1066Ptr = _lookup< + late final __objc_msgSend_1068Ptr = _lookup< ffi.NativeFunction< instancetype Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>>('objc_msgSend'); - late final __objc_msgSend_1066 = __objc_msgSend_1066Ptr.asFunction< + late final __objc_msgSend_1068 = __objc_msgSend_1068Ptr.asFunction< instancetype Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); late final _sel_isEqual_1 = _registerName1("isEqual:"); late final _sel_eventId1 = _registerName1("eventId"); - late final _sel_screenshot1 = _registerName1("screenshot"); - late final _sel_setScreenshot_1 = _registerName1("setScreenshot:"); late final _sel_associatedEventId1 = _registerName1("associatedEventId"); late final _sel_setAssociatedEventId_1 = _registerName1("setAssociatedEventId:"); + void _objc_msgSend_1069( + ffi.Pointer obj, + ffi.Pointer sel, + ffi.Pointer value, + ) { + return __objc_msgSend_1069( + obj, + sel, + value, + ); + } + + late final __objc_msgSend_1069Ptr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer)>>('objc_msgSend'); + late final __objc_msgSend_1069 = __objc_msgSend_1069Ptr.asFunction< + void Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer)>(); + + late final _sel_initWithMessage_name_email_source_associatedEventId_attachments_1 = + _registerName1( + "initWithMessage:name:email:source:associatedEventId:attachments:"); + instancetype _objc_msgSend_1070( + ffi.Pointer obj, + ffi.Pointer sel, + ffi.Pointer message, + ffi.Pointer name, + ffi.Pointer email, + int source, + ffi.Pointer associatedEventId, + ffi.Pointer attachments, + ) { + return __objc_msgSend_1070( + obj, + sel, + message, + name, + email, + source, + associatedEventId, + attachments, + ); + } + + late final __objc_msgSend_1070Ptr = _lookup< + ffi.NativeFunction< + instancetype Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Int32, + ffi.Pointer, + ffi.Pointer)>>('objc_msgSend'); + late final __objc_msgSend_1070 = __objc_msgSend_1070Ptr.asFunction< + instancetype Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + int, + ffi.Pointer, + ffi.Pointer)>(); + late final _sel_serialize1 = _registerName1("serialize"); late final _sel_dataDictionary1 = _registerName1("dataDictionary"); - late final _sel_attachments1 = _registerName1("attachments"); + late final _sel_attachmentsForEnvelope1 = + _registerName1("attachmentsForEnvelope"); } class _ObjCWrapper implements ffi.Finalizable { @@ -71263,41 +71367,58 @@ class SentryFeedback extends NSObject { _id, _lib._sel_setMessage_1, value?._id ?? ffi.nullptr); } - SentryId1? get eventId { - final _ret = _lib._objc_msgSend_1062(_id, _lib._sel_eventId1); - return _ret.address == 0 - ? null - : SentryId1._(_ret, _lib, retain: true, release: true); + int get source { + return _lib._objc_msgSend_1062(_id, _lib._sel_source1); } - /// PNG data for the screenshot image - NSData? get screenshot { - final _ret = _lib._objc_msgSend_39(_id, _lib._sel_screenshot1); - return _ret.address == 0 - ? null - : NSData._(_ret, _lib, retain: true, release: true); + set source(int value) { + return _lib._objc_msgSend_1063(_id, _lib._sel_setSource_1, value); } - /// PNG data for the screenshot image - set screenshot(NSData? value) { - return _lib._objc_msgSend_939( - _id, _lib._sel_setScreenshot_1, value?._id ?? ffi.nullptr); + SentryId1? get eventId { + final _ret = _lib._objc_msgSend_1064(_id, _lib._sel_eventId1); + return _ret.address == 0 + ? null + : SentryId1._(_ret, _lib, retain: true, release: true); } /// The event id that this feedback is associated with, like a crash report. - NSString? get associatedEventId { - final _ret = _lib._objc_msgSend_20(_id, _lib._sel_associatedEventId1); + SentryId1? get associatedEventId { + final _ret = _lib._objc_msgSend_1064(_id, _lib._sel_associatedEventId1); return _ret.address == 0 ? null - : NSString._(_ret, _lib, retain: true, release: true); + : SentryId1._(_ret, _lib, retain: true, release: true); } /// The event id that this feedback is associated with, like a crash report. - set associatedEventId(NSString? value) { - return _lib._objc_msgSend_509( + set associatedEventId(SentryId1? value) { + return _lib._objc_msgSend_1069( _id, _lib._sel_setAssociatedEventId_1, value?._id ?? ffi.nullptr); } + /// \param associatedEventId The ID for an event you’d like associated with the feedback. + /// + /// \param attachments Data objects for any attachments. Currently the web UI only supports showing one attached image, like for a screenshot. + SentryFeedback + initWithMessage_name_email_source_associatedEventId_attachments_( + NSString? message, + NSString? name, + NSString? email, + int source, + SentryId1? associatedEventId, + NSArray? attachments) { + final _ret = _lib._objc_msgSend_1070( + _id, + _lib._sel_initWithMessage_name_email_source_associatedEventId_attachments_1, + message?._id ?? ffi.nullptr, + name?._id ?? ffi.nullptr, + email?._id ?? ffi.nullptr, + source, + associatedEventId?._id ?? ffi.nullptr, + attachments?._id ?? ffi.nullptr); + return SentryFeedback._(_ret, _lib, retain: true, release: true); + } + @override SentryFeedback init() { final _ret = _lib._objc_msgSend_2(_id, _lib._sel_init1); @@ -71324,8 +71445,8 @@ class SentryFeedback extends NSObject { /// note: /// Currently there is only a single attachment possible, for the screenshot, of which there can be only one. - NSArray attachments() { - final _ret = _lib._objc_msgSend_79(_id, _lib._sel_attachments1); + NSArray attachmentsForEnvelope() { + final _ret = _lib._objc_msgSend_79(_id, _lib._sel_attachmentsForEnvelope1); return NSArray._(_ret, _lib, retain: true, release: true); } @@ -71410,6 +71531,11 @@ class SentryFeedback extends NSObject { } } +abstract class SentryFeedbackSource { + static const int SentryFeedbackSourceWidget = 0; + static const int SentryFeedbackSourceCustom = 1; +} + class SentryId1 extends NSObject { SentryId1._(ffi.Pointer id, SentryCocoa lib, {bool retain = false, bool release = false}) @@ -71435,7 +71561,7 @@ class SentryId1 extends NSObject { static SentryId1? getEmpty(SentryCocoa _lib) { final _ret = - _lib._objc_msgSend_1062(_lib._class_SentryId11, _lib._sel_empty1); + _lib._objc_msgSend_1064(_lib._class_SentryId11, _lib._sel_empty1); return _ret.address == 0 ? null : SentryId1._(_ret, _lib, retain: true, release: true); @@ -71459,7 +71585,7 @@ class SentryId1 extends NSObject { /// Creates a SentryId with the given UUID. SentryId1 initWithUuid_(NSUUID? uuid) { - final _ret = _lib._objc_msgSend_1066( + final _ret = _lib._objc_msgSend_1068( _id, _lib._sel_initWithUuid_1, uuid?._id ?? ffi.nullptr); return SentryId1._(_ret, _lib, retain: true, release: true); } @@ -71614,16 +71740,16 @@ class NSUUID extends NSObject { NSUUID initWithUUIDBytes_(ffi.Pointer bytes) { final _ret = - _lib._objc_msgSend_1063(_id, _lib._sel_initWithUUIDBytes_1, bytes); + _lib._objc_msgSend_1065(_id, _lib._sel_initWithUUIDBytes_1, bytes); return NSUUID._(_ret, _lib, retain: true, release: true); } void getUUIDBytes_(ffi.Pointer uuid) { - _lib._objc_msgSend_1064(_id, _lib._sel_getUUIDBytes_1, uuid); + _lib._objc_msgSend_1066(_id, _lib._sel_getUUIDBytes_1, uuid); } int compare_(NSUUID? otherUUID) { - return _lib._objc_msgSend_1065( + return _lib._objc_msgSend_1067( _id, _lib._sel_compare_1, otherUUID?._id ?? ffi.nullptr); } diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index 03cc81db39..eb14fc5071 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -297,7 +297,7 @@ class SentryNavigatorObserver extends RouteObserver> { _transaction = null; try { _hub.configureScope((scope) { - if (scope.span == transaction) { + if (transaction != null && scope.span == transaction) { scope.span = null; } }); diff --git a/flutter/test/integrations/native_app_start_handler_test.dart b/flutter/test/integrations/native_app_start_handler_test.dart index de55e42e74..4ee6c37985 100644 --- a/flutter/test/integrations/native_app_start_handler_test.dart +++ b/flutter/test/integrations/native_app_start_handler_test.dart @@ -23,7 +23,12 @@ void main() { startTimestamp: anyNamed('startTimestamp'), )).thenReturn(fixture.tracer); - when(fixture.hub.configureScope(captureAny)).thenAnswer((_) {}); + when(fixture.hub.configureScope(captureAny)).thenAnswer((invocation) { + final callback = invocation.positionalArguments[0] as ScopeCallback; + callback(fixture.scope); + return null; + }); + when(fixture.hub.captureTransaction( any, traceContext: anyNamed('traceContext'), @@ -125,6 +130,25 @@ void main() { traceContext: captureAnyNamed('traceContext'), )); }); + + test('added transaction is bound to scope', () async { + await fixture.call( + appStartEnd: DateTime.fromMillisecondsSinceEpoch(10), + ); + expect(fixture.scope.setSpans.length, 2); + expect(fixture.scope.setSpans[0], fixture.tracer); + expect(fixture.scope.setSpans[1], isNull); + }); + + test('added transaction is not bound to scope if already set', () async { + final alreadySet = MockSentryTracer(); + fixture.scope.span = alreadySet; + await fixture.call( + appStartEnd: DateTime.fromMillisecondsSinceEpoch(10), + ); + expect(fixture.scope.setSpans.length, 1); + expect(fixture.scope.setSpans[0], alreadySet); + }); }); group('App start spans', () { @@ -306,6 +330,7 @@ class Fixture { final options = SentryFlutterOptions(dsn: fakeDsn); final nativeBinding = MockSentryNativeBinding(); final hub = MockHub(); + final scope = MockScope(); late final tracer = SentryTracer( SentryTransactionContext( @@ -343,3 +368,14 @@ class Fixture { return args[0] as SentryTransaction; } } + +class MockScope extends Mock implements Scope { + final setSpans = []; + + @override + ISentrySpan? get span => setSpans.lastOrNull; + @override + set span(ISentrySpan? value) { + setSpans.add(value); + } +}