From c9784770c2d501233c3643f98ae182e51f08f6c2 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Mon, 6 May 2024 17:06:43 +0200 Subject: [PATCH] refactor: move native SDK init and close to SentryNativeBinding (#2030) * refactor: move native SDK init and close to SentryNativeBinding * fixup * rename test file * test files cleanup --- .../integrations/native_sdk_integration.dart | 71 ++---- flutter/lib/src/native/factory_real.dart | 3 + flutter/lib/src/native/factory_web.dart | 2 +- .../src/native/java/sentry_native_java.dart | 10 + flutter/lib/src/native/sentry_native.dart | 5 + .../lib/src/native/sentry_native_binding.dart | 3 + .../lib/src/native/sentry_native_channel.dart | 43 ++++ flutter/lib/src/sentry_flutter.dart | 4 +- .../init_native_sdk_integration_test.dart | 210 ------------------ .../integrations/init_native_sdk_test.dart | 182 +++++++++++++++ .../native_sdk_integration_test.dart | 128 +++++------ flutter/test/mocks.dart | 30 +++ 12 files changed, 362 insertions(+), 329 deletions(-) create mode 100644 flutter/lib/src/native/java/sentry_native_java.dart delete mode 100644 flutter/test/integrations/init_native_sdk_integration_test.dart create mode 100644 flutter/test/integrations/init_native_sdk_test.dart diff --git a/flutter/lib/src/integrations/native_sdk_integration.dart b/flutter/lib/src/integrations/native_sdk_integration.dart index 715a821cf9..35181afc1e 100644 --- a/flutter/lib/src/integrations/native_sdk_integration.dart +++ b/flutter/lib/src/integrations/native_sdk_integration.dart @@ -1,61 +1,26 @@ import 'dart:async'; -import 'package:flutter/services.dart'; import 'package:sentry/sentry.dart'; +import '../native/sentry_native.dart'; import '../sentry_flutter_options.dart'; /// Enables Sentry's native SDKs (Android and iOS) with options. class NativeSdkIntegration implements Integration { - NativeSdkIntegration(this._channel); + NativeSdkIntegration(this._native); - final MethodChannel _channel; SentryFlutterOptions? _options; + final SentryNative _native; @override Future call(Hub hub, SentryFlutterOptions options) async { _options = options; + if (!options.autoInitializeNativeSdk) { return; } - try { - await _channel.invokeMethod('initNativeSdk', { - 'dsn': options.dsn, - 'debug': options.debug, - 'environment': options.environment, - 'release': options.release, - 'enableAutoSessionTracking': options.enableAutoSessionTracking, - 'enableNativeCrashHandling': options.enableNativeCrashHandling, - 'attachStacktrace': options.attachStacktrace, - 'attachThreads': options.attachThreads, - 'autoSessionTrackingIntervalMillis': - options.autoSessionTrackingInterval.inMilliseconds, - 'dist': options.dist, - 'integrations': options.sdk.integrations, - 'packages': - options.sdk.packages.map((e) => e.toJson()).toList(growable: false), - 'diagnosticLevel': options.diagnosticLevel.name, - 'maxBreadcrumbs': options.maxBreadcrumbs, - 'anrEnabled': options.anrEnabled, - 'anrTimeoutIntervalMillis': options.anrTimeoutInterval.inMilliseconds, - 'enableAutoNativeBreadcrumbs': options.enableAutoNativeBreadcrumbs, - 'maxCacheItems': options.maxCacheItems, - 'sendDefaultPii': options.sendDefaultPii, - 'enableWatchdogTerminationTracking': - options.enableWatchdogTerminationTracking, - 'enableNdkScopeSync': options.enableNdkScopeSync, - 'enableAutoPerformanceTracing': options.enableAutoPerformanceTracing, - 'sendClientReports': options.sendClientReports, - 'proguardUuid': options.proguardUuid, - 'maxAttachmentSize': options.maxAttachmentSize, - 'recordHttpBreadcrumbs': options.recordHttpBreadcrumbs, - 'captureFailedRequests': options.captureFailedRequests, - 'enableAppHangTracking': options.enableAppHangTracking, - 'connectionTimeoutMillis': options.connectionTimeout.inMilliseconds, - 'readTimeoutMillis': options.readTimeout.inMilliseconds, - 'appHangTimeoutIntervalMillis': - options.appHangTimeoutInterval.inMilliseconds, - }); + try { + await _native.init(options); options.sdk.addIntegration('nativeSdkIntegration'); } catch (exception, stackTrace) { options.logger( @@ -69,19 +34,17 @@ class NativeSdkIntegration implements Integration { @override Future close() async { - final options = _options; - if (options != null && !options.autoInitializeNativeSdk) { - return; - } - try { - await _channel.invokeMethod('closeNativeSdk'); - } catch (exception, stackTrace) { - _options?.logger( - SentryLevel.fatal, - 'nativeSdkIntegration failed to be closed', - exception: exception, - stackTrace: stackTrace, - ); + if (_options?.autoInitializeNativeSdk == true) { + try { + await _native.close(); + } catch (exception, stackTrace) { + _options?.logger( + SentryLevel.fatal, + 'nativeSdkIntegration failed to be closed', + exception: exception, + stackTrace: stackTrace, + ); + } } } } diff --git a/flutter/lib/src/native/factory_real.dart b/flutter/lib/src/native/factory_real.dart index 3c918b7be7..d5ee4f5cca 100644 --- a/flutter/lib/src/native/factory_real.dart +++ b/flutter/lib/src/native/factory_real.dart @@ -2,12 +2,15 @@ import 'package:flutter/services.dart'; import '../../sentry_flutter.dart'; import 'cocoa/sentry_native_cocoa.dart'; +import 'java/sentry_native_java.dart'; import 'sentry_native_binding.dart'; import 'sentry_native_channel.dart'; SentryNativeBinding createBinding(PlatformChecker pc, MethodChannel channel) { if (pc.platform.isIOS || pc.platform.isMacOS) { return SentryNativeCocoa(channel); + } else if (pc.platform.isAndroid) { + return SentryNativeJava(channel); } else { return SentryNativeChannel(channel); } diff --git a/flutter/lib/src/native/factory_web.dart b/flutter/lib/src/native/factory_web.dart index 8038ea9780..c2cd57ada7 100644 --- a/flutter/lib/src/native/factory_web.dart +++ b/flutter/lib/src/native/factory_web.dart @@ -4,6 +4,6 @@ import '../../sentry_flutter.dart'; import 'sentry_native_binding.dart'; // This isn't actually called, see SentryFlutter.init() -SentryNativeBinding createBinding(PlatformChecker pc, MethodChannel channel) { +SentryNativeBinding createBinding(PlatformChecker _, MethodChannel __) { throw UnsupportedError("Native binding is not supported on this platform."); } diff --git a/flutter/lib/src/native/java/sentry_native_java.dart b/flutter/lib/src/native/java/sentry_native_java.dart new file mode 100644 index 0000000000..20774e11fa --- /dev/null +++ b/flutter/lib/src/native/java/sentry_native_java.dart @@ -0,0 +1,10 @@ +import 'package:meta/meta.dart'; + +import '../sentry_native_channel.dart'; + +// Note: currently this doesn't do anything. Later, it shall be used with +// generated JNI bindings. See https://github.com/getsentry/sentry-dart/issues/1444 +@internal +class SentryNativeJava extends SentryNativeChannel { + SentryNativeJava(super.channel); +} diff --git a/flutter/lib/src/native/sentry_native.dart b/flutter/lib/src/native/sentry_native.dart index 4d26239a60..893c55d6bc 100644 --- a/flutter/lib/src/native/sentry_native.dart +++ b/flutter/lib/src/native/sentry_native.dart @@ -30,6 +30,11 @@ class SentryNative { /// Flag indicating if app start measurement was added to the first transaction. bool didAddAppStartMeasurement = false; + Future init(SentryFlutterOptions options) async => + _invoke("init", () => _binding.init(options)); + + Future close() async => _invoke("close", _binding.close); + /// Fetch [NativeAppStart] from native channels. Can only be called once. Future fetchNativeAppStart() async { _didFetchAppStart = true; diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 54d335d529..950e7f9994 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -9,6 +9,9 @@ import 'sentry_native.dart'; @internal abstract class SentryNativeBinding { // TODO Move other native calls here. + Future init(SentryFlutterOptions options); + + Future close(); Future fetchNativeAppStart(); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index 4bf9745cb3..61b361de30 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -17,6 +17,49 @@ class SentryNativeChannel implements SentryNativeBinding { // TODO Move other native calls here. + @override + Future init(SentryFlutterOptions options) async => + _channel.invokeMethod('initNativeSdk', { + 'dsn': options.dsn, + 'debug': options.debug, + 'environment': options.environment, + 'release': options.release, + 'enableAutoSessionTracking': options.enableAutoSessionTracking, + 'enableNativeCrashHandling': options.enableNativeCrashHandling, + 'attachStacktrace': options.attachStacktrace, + 'attachThreads': options.attachThreads, + 'autoSessionTrackingIntervalMillis': + options.autoSessionTrackingInterval.inMilliseconds, + 'dist': options.dist, + 'integrations': options.sdk.integrations, + 'packages': + options.sdk.packages.map((e) => e.toJson()).toList(growable: false), + 'diagnosticLevel': options.diagnosticLevel.name, + 'maxBreadcrumbs': options.maxBreadcrumbs, + 'anrEnabled': options.anrEnabled, + 'anrTimeoutIntervalMillis': options.anrTimeoutInterval.inMilliseconds, + 'enableAutoNativeBreadcrumbs': options.enableAutoNativeBreadcrumbs, + 'maxCacheItems': options.maxCacheItems, + 'sendDefaultPii': options.sendDefaultPii, + 'enableWatchdogTerminationTracking': + options.enableWatchdogTerminationTracking, + 'enableNdkScopeSync': options.enableNdkScopeSync, + 'enableAutoPerformanceTracing': options.enableAutoPerformanceTracing, + 'sendClientReports': options.sendClientReports, + 'proguardUuid': options.proguardUuid, + 'maxAttachmentSize': options.maxAttachmentSize, + 'recordHttpBreadcrumbs': options.recordHttpBreadcrumbs, + 'captureFailedRequests': options.captureFailedRequests, + 'enableAppHangTracking': options.enableAppHangTracking, + 'connectionTimeoutMillis': options.connectionTimeout.inMilliseconds, + 'readTimeoutMillis': options.readTimeout.inMilliseconds, + 'appHangTimeoutIntervalMillis': + options.appHangTimeoutInterval.inMilliseconds, + }); + + @override + Future close() async => _channel.invokeMethod('closeNativeSdk'); + @override Future fetchNativeAppStart() async { final json = diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index 60c9811bf0..9b0792c7e3 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -158,8 +158,8 @@ mixin SentryFlutter { // The ordering here matters, as we'd like to first start the native integration. // That allow us to send events to the network and then the Flutter integrations. // Flutter Web doesn't need that, only Android and iOS. - if (platformChecker.hasNativeIntegration) { - integrations.add(NativeSdkIntegration(channel)); + if (_native != null) { + integrations.add(NativeSdkIntegration(_native!)); } // Will enrich events with device context, native packages and integrations diff --git a/flutter/test/integrations/init_native_sdk_integration_test.dart b/flutter/test/integrations/init_native_sdk_integration_test.dart deleted file mode 100644 index b41c2d51cf..0000000000 --- a/flutter/test/integrations/init_native_sdk_integration_test.dart +++ /dev/null @@ -1,210 +0,0 @@ -@TestOn('vm') - -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/integrations/native_sdk_integration.dart'; -import 'package:sentry_flutter/src/version.dart'; - -import '../mocks.dart'; - -void main() { - group(NativeSdkIntegration, () { - late Fixture fixture; - setUp(() { - fixture = Fixture(); - TestWidgetsFlutterBinding.ensureInitialized(); - }); - - test('test default values', () async { - String? methodName; - dynamic arguments; - final channel = createChannelWithCallback((call) async { - methodName = call.method; - arguments = call.arguments; - }); - var sut = fixture.getSut(channel); - - await sut.call(HubAdapter(), createOptions()); - - channel.setMethodCallHandler(null); - - expect(methodName, 'initNativeSdk'); - expect(arguments, { - 'dsn': fakeDsn, - 'debug': false, - 'environment': null, - 'release': null, - 'enableAutoSessionTracking': true, - 'enableNativeCrashHandling': true, - 'attachStacktrace': true, - 'attachThreads': false, - 'autoSessionTrackingIntervalMillis': 30000, - 'dist': null, - 'integrations': [], - 'packages': [ - {'name': 'pub:sentry_flutter', 'version': sdkVersion} - ], - 'diagnosticLevel': 'debug', - 'maxBreadcrumbs': 100, - 'anrEnabled': false, - 'anrTimeoutIntervalMillis': 5000, - 'enableAutoNativeBreadcrumbs': true, - 'maxCacheItems': 30, - 'sendDefaultPii': false, - 'enableWatchdogTerminationTracking': true, - 'enableNdkScopeSync': true, - 'enableAutoPerformanceTracing': true, - 'sendClientReports': true, - 'proguardUuid': null, - 'maxAttachmentSize': 20 * 1024 * 1024, - 'recordHttpBreadcrumbs': true, - 'captureFailedRequests': true, - 'enableAppHangTracking': true, - 'connectionTimeoutMillis': 5000, - 'readTimeoutMillis': 5000, - 'appHangTimeoutIntervalMillis': 2000, - }); - }); - - test('test custom values', () async { - String? methodName; - dynamic arguments; - final channel = createChannelWithCallback((call) async { - methodName = call.method; - arguments = call.arguments; - }); - var sut = fixture.getSut(channel); - - final options = createOptions() - ..debug = false - ..environment = 'foo' - ..release = 'foo@bar+1' - ..enableAutoSessionTracking = false - ..enableNativeCrashHandling = false - ..attachStacktrace = false - ..attachThreads = true - ..autoSessionTrackingInterval = Duration(milliseconds: 240000) - ..dist = 'distfoo' - ..diagnosticLevel = SentryLevel.error - ..maxBreadcrumbs = 0 - ..anrEnabled = false - ..anrTimeoutInterval = Duration(seconds: 1) - ..enableAutoNativeBreadcrumbs = false - ..maxCacheItems = 0 - ..sendDefaultPii = true - ..enableWatchdogTerminationTracking = false - ..enableAutoPerformanceTracing = false - ..sendClientReports = false - ..enableNdkScopeSync = true - ..proguardUuid = fakeProguardUuid - ..maxAttachmentSize = 10 - ..recordHttpBreadcrumbs = false - ..captureFailedRequests = false - ..enableAppHangTracking = false - ..connectionTimeout = Duration(milliseconds: 9001) - ..readTimeout = Duration(milliseconds: 9002) - ..appHangTimeoutInterval = Duration(milliseconds: 9003); - - options.sdk.addIntegration('foo'); - options.sdk.addPackage('bar', '1'); - - await sut.call(HubAdapter(), options); - - channel.setMethodCallHandler(null); - - expect(methodName, 'initNativeSdk'); - expect(arguments, { - 'dsn': fakeDsn, - 'debug': false, - 'environment': 'foo', - 'release': 'foo@bar+1', - 'enableAutoSessionTracking': false, - 'enableNativeCrashHandling': false, - 'attachStacktrace': false, - 'attachThreads': true, - 'autoSessionTrackingIntervalMillis': 240000, - 'dist': 'distfoo', - 'integrations': ['foo'], - 'packages': [ - {'name': 'pub:sentry_flutter', 'version': sdkVersion}, - {'name': 'bar', 'version': '1'}, - ], - 'diagnosticLevel': 'error', - 'maxBreadcrumbs': 0, - 'anrEnabled': false, - 'anrTimeoutIntervalMillis': 1000, - 'enableAutoNativeBreadcrumbs': false, - 'maxCacheItems': 0, - 'sendDefaultPii': true, - 'enableWatchdogTerminationTracking': false, - 'enableNdkScopeSync': true, - 'enableAutoPerformanceTracing': false, - 'sendClientReports': false, - 'proguardUuid': fakeProguardUuid, - 'maxAttachmentSize': 10, - 'recordHttpBreadcrumbs': false, - 'captureFailedRequests': false, - 'enableAppHangTracking': false, - 'connectionTimeoutMillis': 9001, - 'readTimeoutMillis': 9002, - 'appHangTimeoutIntervalMillis': 9003, - }); - }); - - test('adds integration', () async { - final channel = createChannelWithCallback((call) async {}); - var sut = fixture.getSut(channel); - - final options = createOptions(); - await sut.call(HubAdapter(), options); - - expect(options.sdk.integrations, ['nativeSdkIntegration']); - - channel.setMethodCallHandler(null); - }); - - test('integration is not added in case of an exception', () async { - final channel = createChannelWithCallback((call) async { - throw Exception('foo'); - }); - var sut = fixture.getSut(channel); - - final options = createOptions(); - await sut.call(NoOpHub(), options); - - expect(options.sdk.integrations, []); - - channel.setMethodCallHandler(null); - }); - }); -} - -MethodChannel createChannelWithCallback( - Future? Function(MethodCall call)? handler, -) { - final channel = MethodChannel('initNativeSdk'); - // ignore: deprecated_member_use - channel.setMockMethodCallHandler(handler); - return channel; -} - -SentryFlutterOptions createOptions() { - final mockPlatformChecker = MockPlatformChecker(hasNativeIntegration: true); - final options = SentryFlutterOptions( - dsn: fakeDsn, - checker: mockPlatformChecker, - ); - options.sdk = SdkVersion( - name: sdkName, - version: sdkVersion, - ); - options.sdk.addPackage('pub:sentry_flutter', sdkVersion); - return options; -} - -class Fixture { - NativeSdkIntegration getSut(MethodChannel channel) { - return NativeSdkIntegration(channel); - } -} diff --git a/flutter/test/integrations/init_native_sdk_test.dart b/flutter/test/integrations/init_native_sdk_test.dart new file mode 100644 index 0000000000..fa3b76f153 --- /dev/null +++ b/flutter/test/integrations/init_native_sdk_test.dart @@ -0,0 +1,182 @@ +@TestOn('vm') + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/native/sentry_native_channel.dart'; +import 'package:sentry_flutter/src/version.dart'; + +import '../mocks.dart'; + +void main() { + late Fixture fixture; + setUp(() { + fixture = Fixture(); + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + test('test default values', () async { + String? methodName; + dynamic arguments; + final channel = createChannelWithCallback((call) async { + methodName = call.method; + arguments = call.arguments; + }); + var sut = fixture.getSut(channel); + + await sut.init(createOptions()); + + channel.setMethodCallHandler(null); + + expect(methodName, 'initNativeSdk'); + expect(arguments, { + 'dsn': fakeDsn, + 'debug': false, + 'environment': null, + 'release': null, + 'enableAutoSessionTracking': true, + 'enableNativeCrashHandling': true, + 'attachStacktrace': true, + 'attachThreads': false, + 'autoSessionTrackingIntervalMillis': 30000, + 'dist': null, + 'integrations': [], + 'packages': [ + {'name': 'pub:sentry_flutter', 'version': sdkVersion} + ], + 'diagnosticLevel': 'debug', + 'maxBreadcrumbs': 100, + 'anrEnabled': false, + 'anrTimeoutIntervalMillis': 5000, + 'enableAutoNativeBreadcrumbs': true, + 'maxCacheItems': 30, + 'sendDefaultPii': false, + 'enableWatchdogTerminationTracking': true, + 'enableNdkScopeSync': true, + 'enableAutoPerformanceTracing': true, + 'sendClientReports': true, + 'proguardUuid': null, + 'maxAttachmentSize': 20 * 1024 * 1024, + 'recordHttpBreadcrumbs': true, + 'captureFailedRequests': true, + 'enableAppHangTracking': true, + 'connectionTimeoutMillis': 5000, + 'readTimeoutMillis': 5000, + 'appHangTimeoutIntervalMillis': 2000, + }); + }); + + test('test custom values', () async { + String? methodName; + dynamic arguments; + final channel = createChannelWithCallback((call) async { + methodName = call.method; + arguments = call.arguments; + }); + var sut = fixture.getSut(channel); + + final options = createOptions() + ..debug = false + ..environment = 'foo' + ..release = 'foo@bar+1' + ..enableAutoSessionTracking = false + ..enableNativeCrashHandling = false + ..attachStacktrace = false + ..attachThreads = true + ..autoSessionTrackingInterval = Duration(milliseconds: 240000) + ..dist = 'distfoo' + ..diagnosticLevel = SentryLevel.error + ..maxBreadcrumbs = 0 + ..anrEnabled = false + ..anrTimeoutInterval = Duration(seconds: 1) + ..enableAutoNativeBreadcrumbs = false + ..maxCacheItems = 0 + ..sendDefaultPii = true + ..enableWatchdogTerminationTracking = false + ..enableAutoPerformanceTracing = false + ..sendClientReports = false + ..enableNdkScopeSync = true + ..proguardUuid = fakeProguardUuid + ..maxAttachmentSize = 10 + ..recordHttpBreadcrumbs = false + ..captureFailedRequests = false + ..enableAppHangTracking = false + ..connectionTimeout = Duration(milliseconds: 9001) + ..readTimeout = Duration(milliseconds: 9002) + ..appHangTimeoutInterval = Duration(milliseconds: 9003); + + options.sdk.addIntegration('foo'); + options.sdk.addPackage('bar', '1'); + + await sut.init(options); + + channel.setMethodCallHandler(null); + + expect(methodName, 'initNativeSdk'); + expect(arguments, { + 'dsn': fakeDsn, + 'debug': false, + 'environment': 'foo', + 'release': 'foo@bar+1', + 'enableAutoSessionTracking': false, + 'enableNativeCrashHandling': false, + 'attachStacktrace': false, + 'attachThreads': true, + 'autoSessionTrackingIntervalMillis': 240000, + 'dist': 'distfoo', + 'integrations': ['foo'], + 'packages': [ + {'name': 'pub:sentry_flutter', 'version': sdkVersion}, + {'name': 'bar', 'version': '1'}, + ], + 'diagnosticLevel': 'error', + 'maxBreadcrumbs': 0, + 'anrEnabled': false, + 'anrTimeoutIntervalMillis': 1000, + 'enableAutoNativeBreadcrumbs': false, + 'maxCacheItems': 0, + 'sendDefaultPii': true, + 'enableWatchdogTerminationTracking': false, + 'enableNdkScopeSync': true, + 'enableAutoPerformanceTracing': false, + 'sendClientReports': false, + 'proguardUuid': fakeProguardUuid, + 'maxAttachmentSize': 10, + 'recordHttpBreadcrumbs': false, + 'captureFailedRequests': false, + 'enableAppHangTracking': false, + 'connectionTimeoutMillis': 9001, + 'readTimeoutMillis': 9002, + 'appHangTimeoutIntervalMillis': 9003, + }); + }); +} + +MethodChannel createChannelWithCallback( + Future? Function(MethodCall call)? handler, +) { + final channel = MethodChannel('initNativeSdk'); + // ignore: deprecated_member_use + channel.setMockMethodCallHandler(handler); + return channel; +} + +SentryFlutterOptions createOptions() { + final mockPlatformChecker = MockPlatformChecker(hasNativeIntegration: true); + final options = SentryFlutterOptions( + dsn: fakeDsn, + checker: mockPlatformChecker, + ); + options.sdk = SdkVersion( + name: sdkName, + version: sdkVersion, + ); + options.sdk.addPackage('pub:sentry_flutter', sdkVersion); + return options; +} + +class Fixture { + SentryNativeChannel getSut(MethodChannel native) { + return SentryNativeChannel(native); + } +} diff --git a/flutter/test/integrations/native_sdk_integration_test.dart b/flutter/test/integrations/native_sdk_integration_test.dart index e48a22933a..b67eccbe0e 100644 --- a/flutter/test/integrations/native_sdk_integration_test.dart +++ b/flutter/test/integrations/native_sdk_integration_test.dart @@ -9,93 +9,90 @@ import '../mocks.dart'; import '../mocks.mocks.dart'; void main() { - const _channel = MethodChannel('sentry_flutter'); + group(NativeSdkIntegration, () { + const _channel = MethodChannel('sentry_flutter'); - TestWidgetsFlutterBinding.ensureInitialized(); + TestWidgetsFlutterBinding.ensureInitialized(); - late Fixture fixture; + late Fixture fixture; - setUp(() { - fixture = Fixture(); - }); + setUp(() { + fixture = Fixture(); + }); - tearDown(() { - // ignore: deprecated_member_use - _channel.setMockMethodCallHandler(null); - }); + tearDown(() { + // ignore: deprecated_member_use + _channel.setMockMethodCallHandler(null); + }); + + test('adds integration', () async { + // ignore: deprecated_member_use + _channel.setMockMethodCallHandler((MethodCall methodCall) async {}); + final mock = TestMockSentryNative(); + final integration = NativeSdkIntegration(mock); - test('nativeSdkIntegration adds integration', () async { - // ignore: deprecated_member_use - _channel.setMockMethodCallHandler((MethodCall methodCall) async {}); + await integration(fixture.hub, fixture.options); - final integration = NativeSdkIntegration(_channel); + expect( + fixture.options.sdk.integrations, contains('nativeSdkIntegration')); + expect(mock.numberOfInitCalls, 1); + }); - await integration(fixture.hub, fixture.options); + test('do not throw', () async { + final integration = NativeSdkIntegration(_ThrowingMockSentryNative()); - expect(fixture.options.sdk.integrations.contains('nativeSdkIntegration'), - true); - }); + await integration(fixture.hub, fixture.options); - test('nativeSdkIntegration do not throw', () async { - // ignore: deprecated_member_use - _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw Exception(); + expect(fixture.options.sdk.integrations.contains('nativeSdkIntegration'), + false); }); - final integration = NativeSdkIntegration(_channel); + test('closes native SDK', () async { + final mock = TestMockSentryNative(); + final integration = NativeSdkIntegration(mock); - await integration(fixture.hub, fixture.options); + await integration.call(fixture.hub, fixture.options); + await integration.close(); - expect(fixture.options.sdk.integrations.contains('nativeSdkIntegration'), - false); - }); - - test('nativeSdkIntegration closes native SDK', () async { - var closeCalled = false; - // ignore: deprecated_member_use - _channel.setMockMethodCallHandler((MethodCall methodCall) async { - expect(methodCall.method, 'closeNativeSdk'); - closeCalled = true; + expect(mock.numberOfCloseCalls, 1); }); - final integration = NativeSdkIntegration(_channel); + test('does not call native sdk when auto init disabled', () async { + final mock = TestMockSentryNative(); + final integration = NativeSdkIntegration(mock); + fixture.options.autoInitializeNativeSdk = false; - await integration.close(); + await integration.call(fixture.hub, fixture.options); - expect(closeCalled, true); - }); - - test('nativeSdkIntegration does not call native sdk when auto init disabled', - () async { - var methodChannelCalled = false; - // ignore: deprecated_member_use - _channel.setMockMethodCallHandler((MethodCall methodCall) async { - methodChannelCalled = true; + expect(mock.numberOfInitCalls, 0); }); - fixture.options.autoInitializeNativeSdk = false; - - final integration = NativeSdkIntegration(_channel); - await integration.call(fixture.hub, fixture.options); + test('does not close native when auto init disabled', () async { + final mock = TestMockSentryNative(); + final integration = NativeSdkIntegration(mock); + fixture.options.autoInitializeNativeSdk = false; - expect(methodChannelCalled, false); - }); + await integration(fixture.hub, fixture.options); + await integration.close(); - test('nativeSdkIntegration does not close native when auto init disabled', - () async { - var methodChannelCalled = false; - // ignore: deprecated_member_use - _channel.setMockMethodCallHandler((MethodCall methodCall) async { - methodChannelCalled = true; + expect(mock.numberOfCloseCalls, 0); }); - fixture.options.autoInitializeNativeSdk = false; - final integration = NativeSdkIntegration(_channel); + test('adds integration', () async { + final mock = TestMockSentryNative(); + final integration = NativeSdkIntegration(mock); - await integration(fixture.hub, fixture.options); - await integration.close(); + await integration.call(fixture.hub, fixture.options); + + expect(fixture.options.sdk.integrations, ['nativeSdkIntegration']); + }); - expect(methodChannelCalled, false); + test(' is not added in case of an exception', () async { + final integration = NativeSdkIntegration(_ThrowingMockSentryNative()); + + await integration.call(fixture.hub, fixture.options); + expect(fixture.options.sdk.integrations, []); + }); }); } @@ -103,3 +100,10 @@ class Fixture { final hub = MockHub(); final options = SentryFlutterOptions(dsn: fakeDsn); } + +class _ThrowingMockSentryNative extends TestMockSentryNative { + @override + Future init(SentryFlutterOptions options) async { + throw Exception(); + } +} diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 0520a09884..a373ee7511 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -199,6 +199,9 @@ class TestMockSentryNative implements SentryNative { var numberOfStartProfilerCalls = 0; var numberOfDiscardProfilerCalls = 0; var numberOfCollectProfileCalls = 0; + var numberOfInitCalls = 0; + SentryFlutterOptions? initOptions; + var numberOfCloseCalls = 0; @override Future addBreadcrumb(Breadcrumb breadcrumb) async { @@ -294,6 +297,19 @@ class TestMockSentryNative implements SentryNative { numberOfDiscardProfilerCalls++; return Future.value(null); } + + @override + Future init(SentryFlutterOptions options) { + numberOfInitCalls++; + initOptions = options; + return Future.value(null); + } + + @override + Future close() { + numberOfCloseCalls++; + return Future.value(null); + } } // TODO can this be replaced with https://pub.dev/packages/mockito#verifying-exact-number-of-invocations--at-least-x--never @@ -316,6 +332,8 @@ class MockNativeChannel implements SentryNativeBinding { int numberOfStartProfilerCalls = 0; int numberOfDiscardProfilerCalls = 0; int numberOfCollectProfileCalls = 0; + int numberOfInitCalls = 0; + int numberOfCloseCalls = 0; @override Future fetchNativeAppStart() async => nativeAppStart; @@ -395,6 +413,18 @@ class MockNativeChannel implements SentryNativeBinding { numberOfDiscardProfilerCalls++; return Future.value(null); } + + @override + Future init(SentryFlutterOptions options) { + numberOfInitCalls++; + return Future.value(null); + } + + @override + Future close() { + numberOfCloseCalls++; + return Future.value(null); + } } class MockRendererWrapper implements RendererWrapper {