diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 3b9fc45d9a..4e4ef1be21 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -20,7 +20,7 @@ jobs: # TODO: cedx/setup-dart@v2 doesn't work on Windows (doesn't add pub to the PATH?) # os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, macos-latest] - sdk: [beta, dev, stable] + sdk: [stable, beta, dev] exclude: # Bad state: Could not run tests with Observatory enabled. Try setting a different port with --port option. - os: ubuntu-latest @@ -83,8 +83,7 @@ jobs: TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} run: | PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) - # revert to 100 when we are nul-safety ready because of (http) - if (( $PERCENTAGE < 90 )) + if (( $PERCENTAGE < 100 )) then echo Score too low! exit 1 diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index cbf63f73aa..fabbe55b39 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -23,9 +23,6 @@ jobs: target: ios - os: windows-latest target: ios - # No web on stable yet - - channel: stable - target: web # macos-latest is taking hours due to limited resources - os: macos-latest target: android @@ -87,8 +84,6 @@ jobs: steps: - uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 - with: - channel: 'beta' - run: | cd flutter flutter pub get @@ -110,7 +105,8 @@ jobs: TOTAL_MAX: ${{ steps.analysis.outputs.total_max }} run: | PERCENTAGE=$(( $TOTAL * 100 / $TOTAL_MAX )) - if (( $PERCENTAGE < 90 )) + # 70 because pana does not understand null-safety yet + if (( $PERCENTAGE < 70 )) then echo Score too low! exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c390d46c..32458d6991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ * Fix: Do not append stack trace to the exception if there are no frames * Fix: Empty DSN disables the SDK and runs the App +* Refactoring: Migrate Sentry Dart to null safety +* Refactoring: Null safety on sentry_flutter (#337) # 4.0.6 diff --git a/dart/lib/src/http_client/sentry_http_client.dart b/dart/lib/src/http_client/sentry_http_client.dart index 7b3ccc46fa..dfbd2c7298 100644 --- a/dart/lib/src/http_client/sentry_http_client.dart +++ b/dart/lib/src/http_client/sentry_http_client.dart @@ -46,13 +46,12 @@ import '../../sentry.dart'; // For example with Darts Stopwatch: // https://api.dart.dev/stable/2.10.4/dart-core/Stopwatch-class.html class SentryHttpClient extends BaseClient { - SentryHttpClient({Client client, Hub hub}) { - _hub = hub ?? HubAdapter(); - _client = client ?? Client(); - } + SentryHttpClient({Client? client, Hub? hub}) + : _hub = hub ?? HubAdapter(), + _client = client ?? Client(); - Client _client; - Hub _hub; + final Client _client; + final Hub _hub; @override Future send(BaseRequest request) async { diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart index 9329ce3b33..4288a45dfe 100644 --- a/dart/lib/src/hub.dart +++ b/dart/lib/src/hub.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:collection'; -import 'noop_sentry_client.dart'; import 'protocol.dart'; import 'scope.dart'; import 'sentry_client.dart'; @@ -18,8 +17,9 @@ class Hub { final ListQueue<_StackItem> _stack = ListQueue(); - /// if stack is empty, it throws IterableElementError.noElement() - _StackItem _peek() => _stack.isNotEmpty ? _stack.first : null; + // peek can never return null since Stack can be created only with an item and + // pop does not drop the last item. + _StackItem _peek() => _stack.first; final SentryOptions _options; @@ -35,11 +35,7 @@ class Hub { } static void _validateOptions(SentryOptions options) { - if (options == null) { - throw ArgumentError('SentryOptions is required.'); - } - - if (options.dsn?.isNotEmpty != true) { + if (options.dsn == null) { throw ArgumentError('DSN is required.'); } } @@ -67,34 +63,23 @@ class Hub { SentryLevel.warning, "Instance is disabled and this 'captureEvent' call is a no-op.", ); - } else if (event == null) { - _options.logger( - SentryLevel.warning, - 'captureEvent called with null parameter.', - ); } else { final item = _peek(); - if (item != null) { - try { - sentryId = await item.client.captureEvent( - event, - stackTrace: stackTrace, - scope: item.scope, - hint: hint, - ); - } catch (err) { - _options.logger( - SentryLevel.error, - 'Error while capturing event with id: ${event.eventId}, error: $err', - ); - } finally { - _lastEventId = sentryId; - } - } else { + + try { + sentryId = await item.client.captureEvent( + event, + stackTrace: stackTrace, + scope: item.scope, + hint: hint, + ); + } catch (err) { _options.logger( - SentryLevel.fatal, - 'Stack peek was null when captureEvent', + SentryLevel.error, + 'Error while capturing event with id: ${event.eventId}, error: $err', ); + } finally { + _lastEventId = sentryId; } } return sentryId; @@ -120,27 +105,21 @@ class Hub { ); } else { final item = _peek(); - if (item != null) { - try { - sentryId = await item.client.captureException( - throwable, - stackTrace: stackTrace, - scope: item.scope, - hint: hint, - ); - } catch (err) { - _options.logger( - SentryLevel.error, - 'Error while capturing exception : $throwable', - ); - } finally { - _lastEventId = sentryId; - } - } else { + + try { + sentryId = await item.client.captureException( + throwable, + stackTrace: stackTrace, + scope: item.scope, + hint: hint, + ); + } catch (err) { _options.logger( - SentryLevel.fatal, - 'Stack peek was null when captureException', + SentryLevel.error, + 'Error while capturing exception : $throwable', ); + } finally { + _lastEventId = sentryId; } } @@ -149,10 +128,10 @@ class Hub { /// Captures the message. Future captureMessage( - String message, { - SentryLevel level, - String template, - List params, + String? message, { + SentryLevel? level, + String? template, + List? params, dynamic hint, }) async { var sentryId = SentryId.empty(); @@ -169,29 +148,23 @@ class Hub { ); } else { final item = _peek(); - if (item != null) { - try { - sentryId = await item.client.captureMessage( - message, - level: level, - template: template, - params: params, - scope: item.scope, - hint: hint, - ); - } catch (err) { - _options.logger( - SentryLevel.error, - 'Error while capturing message with id: $message, error: $err', - ); - } finally { - _lastEventId = sentryId; - } - } else { + + try { + sentryId = await item.client.captureMessage( + message, + level: level, + template: template, + params: params, + scope: item.scope, + hint: hint, + ); + } catch (err) { _options.logger( - SentryLevel.fatal, - 'Stack peek was null when captureMessage', + SentryLevel.error, + 'Error while capturing message with id: $message, error: $err', ); + } finally { + _lastEventId = sentryId; } } return sentryId; @@ -204,21 +177,9 @@ class Hub { SentryLevel.warning, "Instance is disabled and this 'addBreadcrumb' call is a no-op.", ); - } else if (crumb == null) { - _options.logger( - SentryLevel.warning, - 'addBreadcrumb called with null parameter.', - ); } else { final item = _peek(); - if (item != null) { - item.scope.addBreadcrumb(crumb, hint: hint); - } else { - _options.logger( - SentryLevel.fatal, - 'Stack peek was null when addBreadcrumb', - ); - } + item.scope.addBreadcrumb(crumb, hint: hint); } } @@ -229,20 +190,8 @@ class Hub { "Instance is disabled and this 'bindClient' call is a no-op."); } else { final item = _peek(); - if (item != null) { - if (client != null) { - _options.logger(SentryLevel.debug, 'New client bound to scope.'); - item.client = client; - } else { - _options.logger(SentryLevel.debug, 'NoOp client bound to scope.'); - item.client = NoOpSentryClient(); - } - } else { - _options.logger( - SentryLevel.fatal, - 'Stack peek was null when bindClient', - ); - } + _options.logger(SentryLevel.debug, 'New client bound to scope.'); + item.client = client; } } @@ -272,21 +221,16 @@ class Hub { } final item = _peek(); - if (item != null) { - try { - item.client.close(); - } catch (err) { - _options.logger( - SentryLevel.error, - 'Error while closing the Hub, error: $err', - ); - } - } else { + + try { + item.client.close(); + } catch (err) { _options.logger( - SentryLevel.fatal, - 'Stack peek was NULL when closing Hub', + SentryLevel.error, + 'Error while closing the Hub, error: $err', ); } + _isEnabled = false; } } @@ -300,19 +244,13 @@ class Hub { ); } else { final item = _peek(); - if (item != null) { - try { - callback(item.scope); - } catch (err) { - _options.logger( - SentryLevel.error, - "Error in the 'configureScope' callback, error: $err", - ); - } - } else { + + try { + callback(item.scope); + } catch (err) { _options.logger( - SentryLevel.fatal, - 'Stack peek was NULL when configureScope', + SentryLevel.error, + "Error in the 'configureScope' callback, error: $err", ); } } diff --git a/dart/lib/src/hub_adapter.dart b/dart/lib/src/hub_adapter.dart index ad33c76a49..facbe5244a 100644 --- a/dart/lib/src/hub_adapter.dart +++ b/dart/lib/src/hub_adapter.dart @@ -48,10 +48,10 @@ class HubAdapter implements Hub { @override Future captureMessage( - String message, { - SentryLevel level, - String template, - List params, + String? message, { + SentryLevel? level, + String? template, + List? params, dynamic hint, }) => Sentry.captureMessage( diff --git a/dart/lib/src/isolate_error_integration.dart b/dart/lib/src/isolate_error_integration.dart index 6adeeabe75..f27f7dcc32 100644 --- a/dart/lib/src/isolate_error_integration.dart +++ b/dart/lib/src/isolate_error_integration.dart @@ -10,7 +10,7 @@ import 'sentry_options.dart'; import 'throwable_mechanism.dart'; class IsolateErrorIntegration extends Integration { - RawReceivePort _receivePort; + late RawReceivePort _receivePort; @override FutureOr call(Hub hub, SentryOptions options) async { diff --git a/dart/lib/src/noop_client.dart b/dart/lib/src/noop_client.dart index 004ed4a892..2ad9c1a76d 100644 --- a/dart/lib/src/noop_client.dart +++ b/dart/lib/src/noop_client.dart @@ -22,34 +22,40 @@ class NoOpClient implements Client { void close() {} @override - Future delete(url, {Map headers}) => _response; + Future delete( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) => + _response; @override - Future get(url, {Map headers}) => _response; + Future get(url, {Map? headers}) => _response; @override - Future head(url, {Map headers}) => _response; + Future head(url, {Map? headers}) => _response; @override Future patch(url, - {Map headers, body, Encoding encoding}) => + {Map? headers, body, Encoding? encoding}) => _response; @override Future post(url, - {Map headers, body, Encoding encoding}) => + {Map? headers, body, Encoding? encoding}) => _response; @override Future put(url, - {Map headers, body, Encoding encoding}) => + {Map? headers, body, Encoding? encoding}) => _response; @override - Future read(url, {Map headers}) => _string; + Future read(url, {Map? headers}) => _string; @override - Future readBytes(url, {Map headers}) => _intList; + Future readBytes(url, {Map? headers}) => _intList; @override Future send(BaseRequest request) => _streamedResponse; diff --git a/dart/lib/src/noop_hub.dart b/dart/lib/src/noop_hub.dart index 793a65eaf1..3f4c5c31b1 100644 --- a/dart/lib/src/noop_hub.dart +++ b/dart/lib/src/noop_hub.dart @@ -34,10 +34,10 @@ class NoOpHub implements Hub { @override Future captureMessage( - String message, { - SentryLevel level, - String template, - List params, + String? message, { + SentryLevel? level, + String? template, + List? params, dynamic hint, }) => Future.value(SentryId.empty()); diff --git a/dart/lib/src/noop_sentry_client.dart b/dart/lib/src/noop_sentry_client.dart index 09997e55ea..be34d6a18f 100644 --- a/dart/lib/src/noop_sentry_client.dart +++ b/dart/lib/src/noop_sentry_client.dart @@ -17,7 +17,7 @@ class NoOpSentryClient implements SentryClient { Future captureEvent( SentryEvent event, { dynamic stackTrace, - Scope scope, + Scope? scope, dynamic hint, }) => Future.value(SentryId.empty()); @@ -26,18 +26,18 @@ class NoOpSentryClient implements SentryClient { Future captureException( dynamic exception, { dynamic stackTrace, - Scope scope, + Scope? scope, dynamic hint, }) => Future.value(SentryId.empty()); @override Future captureMessage( - String message, { - SentryLevel level, - String template, - List params, - Scope scope, + String? message, { + SentryLevel? level, + String? template, + List? params, + Scope? scope, dynamic hint, }) => Future.value(SentryId.empty()); diff --git a/dart/lib/src/protocol/app.dart b/dart/lib/src/protocol/app.dart index d7cec9c5ca..fd0b97446f 100644 --- a/dart/lib/src/protocol/app.dart +++ b/dart/lib/src/protocol/app.dart @@ -31,56 +31,56 @@ class App { ); /// Human readable application name, as it appears on the platform. - final String name; + final String? name; /// Human readable application version, as it appears on the platform. - final String version; + final String? version; /// Version-independent application identifier, often a dotted bundle ID. - final String identifier; + final String? identifier; /// Internal build identifier, as it appears on the platform. - final String build; + final String? build; /// String identifying the kind of build, e.g. `testflight`. - final String buildType; + final String? buildType; /// When the application was started by the user. - final DateTime startTime; + final DateTime? startTime; /// Application specific device identifier. - final String deviceAppHash; + final String? deviceAppHash; /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; if (name != null) { - json['app_name'] = name; + json['app_name'] = name!; } if (version != null) { - json['app_version'] = version; + json['app_version'] = version!; } if (identifier != null) { - json['app_identifier'] = identifier; + json['app_identifier'] = identifier!; } if (build != null) { - json['app_build'] = build; + json['app_build'] = build!; } if (buildType != null) { - json['build_type'] = buildType; + json['build_type'] = buildType!; } if (startTime != null) { - json['app_start_time'] = startTime.toIso8601String(); + json['app_start_time'] = startTime!.toIso8601String(); } if (deviceAppHash != null) { - json['device_app_hash'] = deviceAppHash; + json['device_app_hash'] = deviceAppHash!; } return json; @@ -97,13 +97,13 @@ class App { ); App copyWith({ - String name, - String version, - String identifier, - String build, - String buildType, - DateTime startTime, - String deviceAppHash, + String? name, + String? version, + String? identifier, + String? build, + String? buildType, + DateTime? startTime, + String? deviceAppHash, }) => App( name: name ?? this.name, diff --git a/dart/lib/src/protocol/breadcrumb.dart b/dart/lib/src/protocol/breadcrumb.dart index e3ae510208..cd3700d9b5 100644 --- a/dart/lib/src/protocol/breadcrumb.dart +++ b/dart/lib/src/protocol/breadcrumb.dart @@ -24,10 +24,10 @@ class Breadcrumb { /// Creates a breadcrumb that can be attached to an [Event]. Breadcrumb({ this.message, - DateTime timestamp, + DateTime? timestamp, this.category, this.data, - SentryLevel level, + SentryLevel? level, this.type, }) : timestamp = timestamp ?? getUtcDateTime(), level = level ?? SentryLevel.info; @@ -35,12 +35,12 @@ class Breadcrumb { /// Describes the breadcrumb. /// /// This field is optional and may be set to null. - final String message; + final String? message; /// A dot-separated string describing the source of the breadcrumb, e.g. "ui.click". /// /// This field is optional and may be set to null. - final String category; + final String? category; /// Data associated with the breadcrumb. /// @@ -51,12 +51,12 @@ class Breadcrumb { /// See also: /// /// * https://docs.sentry.io/development/sdk-dev/event-payloads/breadcrumbs/#breadcrumb-types - final Map data; + final Map? data; /// Severity of the breadcrumb. /// /// This field is optional and may be set to null. - final SentryLevel level; + final SentryLevel? level; /// Describes what type of breadcrumb this is. /// @@ -67,7 +67,7 @@ class Breadcrumb { /// See also: /// /// * https://docs.sentry.io/development/sdk-dev/event-payloads/breadcrumbs/#breadcrumb-types - final String type; + final String? type; /// The time the breadcrumb was recorded. /// @@ -82,18 +82,20 @@ class Breadcrumb { final json = { 'timestamp': formatDateAsIso8601WithMillisPrecision(timestamp), }; + if (message != null) { json['message'] = message; } if (category != null) { json['category'] = category; } - if (data != null && data.isNotEmpty) { + if (data?.isNotEmpty ?? false) { json['data'] = data; } if (level != null) { - json['level'] = level.name; + json['level'] = level!.name; } + if (type != null) { json['type'] = type; } @@ -101,12 +103,12 @@ class Breadcrumb { } Breadcrumb copyWith({ - String message, - String category, - Map data, - SentryLevel level, - String type, - DateTime timestamp, + String? message, + String? category, + Map? data, + SentryLevel? level, + String? type, + DateTime? timestamp, }) => Breadcrumb( message: message ?? this.message, diff --git a/dart/lib/src/protocol/browser.dart b/dart/lib/src/protocol/browser.dart index 254ee8b6c1..f6c6d9db2c 100644 --- a/dart/lib/src/protocol/browser.dart +++ b/dart/lib/src/protocol/browser.dart @@ -17,10 +17,10 @@ class Browser { ); /// Human readable application name, as it appears on the platform. - final String name; + final String? name; /// Human readable application version, as it appears on the platform. - final String version; + final String? version; /// Produces a [Map] that can be serialized to JSON. Map toJson() { @@ -40,8 +40,8 @@ class Browser { Browser clone() => Browser(name: name, version: version); Browser copyWith({ - String name, - String version, + String? name, + String? version, }) => Browser( name: name ?? this.name, diff --git a/dart/lib/src/protocol/contexts.dart b/dart/lib/src/protocol/contexts.dart index 552d02c5a2..7ebeb8eb8f 100644 --- a/dart/lib/src/protocol/contexts.dart +++ b/dart/lib/src/protocol/contexts.dart @@ -10,12 +10,12 @@ import '../protocol.dart'; /// See also: https://develop.sentry.dev/sdk/event-payloads/contexts/. class Contexts extends MapView { Contexts({ - Device device, - OperatingSystem operatingSystem, - List runtimes, - App app, - Browser browser, - Gpu gpu, + Device? device, + OperatingSystem? operatingSystem, + List? runtimes, + App? app, + Browser? browser, + Gpu? gpu, }) : super({ Device.type: device, OperatingSystem.type: operatingSystem, @@ -28,27 +28,22 @@ class Contexts extends MapView { factory Contexts.fromJson(Map data) { final contexts = Contexts( device: data[Device.type] != null - ? Device.fromJson(Map.from(data[Device.type])) + ? Device.fromJson(Map.from(data[Device.type])) : null, operatingSystem: data[OperatingSystem.type] != null - ? OperatingSystem.fromJson( - Map.from(data[OperatingSystem.type])) + ? OperatingSystem.fromJson(Map.from(data[OperatingSystem.type])) : null, app: data[App.type] != null - ? App.fromJson(Map.from(data[App.type])) + ? App.fromJson(Map.from(data[App.type])) : null, browser: data[Browser.type] != null - ? Browser.fromJson(Map.from(data[Browser.type])) + ? Browser.fromJson(Map.from(data[Browser.type])) : null, gpu: data[Gpu.type] != null - ? Gpu.fromJson(Map.from(data[Gpu.type])) + ? Gpu.fromJson(Map.from(data[Gpu.type])) : null, runtimes: data[SentryRuntime.type] != null - ? [ - SentryRuntime.fromJson( - Map.from(data[SentryRuntime.type]), - ), - ] + ? [SentryRuntime.fromJson(Map.from(data[SentryRuntime.type]))] : null, ); @@ -60,24 +55,24 @@ class Contexts extends MapView { } /// This describes the device that caused the event. - Device get device => this[Device.type]; + Device? get device => this[Device.type]; - set device(Device device) => this[Device.type] = device; + set device(Device? device) => this[Device.type] = device; /// Describes the operating system on which the event was created. /// /// In web contexts, this is the operating system of the browse /// (normally pulled from the User-Agent string). - OperatingSystem get operatingSystem => this[OperatingSystem.type]; + OperatingSystem? get operatingSystem => this[OperatingSystem.type]; - set operatingSystem(OperatingSystem operatingSystem) => + set operatingSystem(OperatingSystem? operatingSystem) => this[OperatingSystem.type] = operatingSystem; /// Describes an immutable list of runtimes in more detail /// (for instance if you have a Flutter application running /// on top of Android). List get runtimes => - List.unmodifiable(this[SentryRuntime.listType]); + List.unmodifiable(this[SentryRuntime.listType] ?? []); void addRuntime(SentryRuntime runtime) => this[SentryRuntime.listType].add(runtime); @@ -89,23 +84,23 @@ class Contexts extends MapView { /// /// As opposed to the runtime, this is the actual application that was /// running and carries metadata about the current session. - App get app => this[App.type]; + App? get app => this[App.type]; - set app(App app) => this[App.type] = app; + set app(App? app) => this[App.type] = app; /// Carries information about the browser or user agent for web-related /// errors. /// /// This can either be the browser this event ocurred in, or the user /// agent of a web request that triggered the event. - Browser get browser => this[Browser.type]; + Browser? get browser => this[Browser.type]; - set browser(Browser browser) => this[Browser.type] = browser; + set browser(Browser? browser) => this[Browser.type] = browser; /// GPU context describes the GPU of the device. - Gpu get gpu => this[Gpu.type]; + Gpu? get gpu => this[Gpu.type]; - set gpu(Gpu gpu) => this[Gpu.type] = gpu; + set gpu(Gpu? gpu) => this[Gpu.type] = gpu; /// Produces a [Map] that can be serialized to JSON. Map toJson() { @@ -115,68 +110,63 @@ class Contexts extends MapView { if (value == null) return; switch (key) { case Device.type: - Map deviceMap; - if (device != null && (deviceMap = device.toJson()).isNotEmpty) { + final deviceMap = device?.toJson(); + if (deviceMap?.isNotEmpty ?? false) { json[Device.type] = deviceMap; } break; case OperatingSystem.type: - Map osMap; - if (operatingSystem != null && - (osMap = operatingSystem.toJson()).isNotEmpty) { + final osMap = operatingSystem?.toJson(); + if (osMap?.isNotEmpty ?? false) { json[OperatingSystem.type] = osMap; } break; case App.type: - Map appMap; - if (app != null && (appMap = app.toJson()).isNotEmpty) { + final appMap = app?.toJson(); + if (appMap?.isNotEmpty ?? false) { json[App.type] = appMap; } break; case Browser.type: - Map browserMap; - if (browser != null && (browserMap = browser.toJson()).isNotEmpty) { + final browserMap = browser?.toJson(); + if (browserMap?.isNotEmpty ?? false) { json[Browser.type] = browserMap; } break; case Gpu.type: - Map gpuMap; - if (gpu != null && (gpuMap = gpu.toJson()).isNotEmpty) { + final gpuMap = gpu?.toJson(); + if (gpuMap?.isNotEmpty ?? false) { json[Gpu.type] = gpuMap; } break; case SentryRuntime.listType: - if (runtimes != null) { - if (runtimes.length == 1) { - final runtime = runtimes[0]; - Map runtimeMap; - if (runtime != null && - (runtimeMap = runtime.toJson()).isNotEmpty) { - final key = runtime.key ?? SentryRuntime.type; - - json[key] = runtimeMap; - } - } else if (runtimes.length > 1) { - for (final runtime in runtimes) { - Map runtimeMap; - if (runtime != null && - (runtimeMap = runtime.toJson()).isNotEmpty) { - var key = runtime.key ?? runtime.name.toLowerCase(); - - if (json.containsKey(key)) { - var k = 0; - while (json.containsKey(key)) { - key = '$key$k'; - k++; - } + if (runtimes.length == 1) { + final runtime = runtimes[0]; + final runtimeMap = runtime.toJson(); + if (runtimeMap.isNotEmpty) { + final key = runtime.key ?? SentryRuntime.type; + + json[key] = runtimeMap; + } + } else if (runtimes.length > 1) { + for (final runtime in runtimes) { + final runtimeMap = runtime.toJson(); + if (runtimeMap.isNotEmpty) { + var key = runtime.key ?? runtime.name!.toLowerCase(); + + if (json.containsKey(key)) { + var k = 0; + while (json.containsKey(key)) { + key = '$key$k'; + k++; } - json[key] = runtimeMap - ..addAll({'type': SentryRuntime.type}); } + json[key] = runtimeMap + ..addAll({'type': SentryRuntime.type}); } } } @@ -209,12 +199,12 @@ class Contexts extends MapView { } Contexts copyWith({ - Device device, - OperatingSystem operatingSystem, - List runtimes, - App app, - Browser browser, - Gpu gpu, + Device? device, + OperatingSystem? operatingSystem, + List? runtimes, + App? app, + Browser? browser, + Gpu? gpu, }) => Contexts( device: device ?? this.device, diff --git a/dart/lib/src/protocol/debug_image.dart b/dart/lib/src/protocol/debug_image.dart index 30912e7e4c..4fd3eb1972 100644 --- a/dart/lib/src/protocol/debug_image.dart +++ b/dart/lib/src/protocol/debug_image.dart @@ -11,35 +11,35 @@ import 'package:meta/meta.dart'; /// more details : https://develop.sentry.dev/sdk/event-payloads/debugmeta/ @immutable class DebugImage { - final String uuid; + final String? uuid; /// Required. Type of the debug image. Must be "macho". final String type; /// Required. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID. - final String debugId; + final String? debugId; /// Required. Memory address, at which the image is mounted in the virtual address space of the process. /// Should be a string in hex representation prefixed with "0x". - final String imageAddr; + final String? imageAddr; /// Required. The size of the image in virtual memory. If missing, Sentry will assume that the image spans up to the next image, which might lead to invalid stack traces. - final int imageSize; + final int? imageSize; /// OptionalName or absolute path to the dSYM file containing debug information for this image. This value might be required to retrieve debug files from certain symbol servers. - final String debugFile; + final String? debugFile; /// Optional. The absolute path to the dynamic library or executable. This helps to locate the file if it is missing on Sentry. - final String codeFile; + final String? codeFile; /// Optional Architecture of the module. If missing, this will be backfilled by Sentry. - final String arch; + final String? arch; /// Optional. Identifier of the dynamic library or executable. It is the value of the LC_UUID load command in the Mach header, formatted as UUID. Can be empty for Mach images, as it is equivalent to the debug identifier. - final String codeId; + final String? codeId; const DebugImage({ - this.type, + required this.type, this.imageAddr, this.debugId, this.debugFile, @@ -57,9 +57,7 @@ class DebugImage { json['uuid'] = uuid; } - if (type != null) { - json['type'] = type; - } + json['type'] = type; if (debugId != null) { json['debug_id'] = debugId; @@ -93,15 +91,15 @@ class DebugImage { } DebugImage copyWith({ - String uuid, - String type, - String debugId, - String debugFile, - String codeFile, - String imageAddr, - int imageSize, - String arch, - String codeId, + String? uuid, + String? type, + String? debugId, + String? debugFile, + String? codeFile, + String? imageAddr, + int? imageSize, + String? arch, + String? codeId, }) => DebugImage( uuid: uuid ?? this.uuid, diff --git a/dart/lib/src/protocol/debug_meta.dart b/dart/lib/src/protocol/debug_meta.dart index 03e0cd708a..1b6aebd993 100644 --- a/dart/lib/src/protocol/debug_meta.dart +++ b/dart/lib/src/protocol/debug_meta.dart @@ -6,29 +6,28 @@ import '../protocol.dart'; @immutable class DebugMeta { /// An object describing the system SDK. - final SdkInfo sdk; + final SdkInfo? sdk; - final List _images; + final List? _images; /// The immutable list of debug images contains all dynamic libraries loaded /// into the process and their memory addresses. /// Instruction addresses in the Stack Trace are mapped into the list of debug /// images in order to retrieve debug files for symbolication. - List get images => List.unmodifiable(_images); + List get images => List.unmodifiable(_images ?? const []); - DebugMeta({this.sdk, List images}) - : _images = images != null ? List.from(images) : null; + DebugMeta({this.sdk, List? images}) : _images = images; Map toJson() { final json = {}; - Map sdkInfo; - if (sdk != null && (sdkInfo = sdk.toJson()).isNotEmpty) { + final sdkInfo = sdk?.toJson(); + if (sdkInfo?.isNotEmpty ?? false) { json['sdk_info'] = sdkInfo; } - if (_images != null && _images.isNotEmpty) { - json['images'] = _images + if (_images?.isNotEmpty ?? false) { + json['images'] = _images! .map((e) => e.toJson()) .where((element) => element.isNotEmpty) .toList(growable: false); @@ -38,8 +37,8 @@ class DebugMeta { } DebugMeta copyWith({ - SdkInfo sdk, - List images, + SdkInfo? sdk, + List? images, }) => DebugMeta( sdk: sdk ?? this.sdk, diff --git a/dart/lib/src/protocol/device.dart b/dart/lib/src/protocol/device.dart index 22157c2532..584e3c840e 100644 --- a/dart/lib/src/protocol/device.dart +++ b/dart/lib/src/protocol/device.dart @@ -35,7 +35,8 @@ class Device { this.bootTime, this.timezone, }) : assert( - batteryLevel == null || (batteryLevel >= 0 && batteryLevel <= 100)); + batteryLevel == null || (batteryLevel >= 0 && batteryLevel <= 100), + ); factory Device.fromJson(Map data) => Device( name: data['name'], @@ -68,92 +69,92 @@ class Device { ); /// The name of the device. This is typically a hostname. - final String name; + final String? name; /// The family of the device. /// /// This is normally the common part of model names across generations. /// For instance `iPhone` would be a reasonable family, /// so would be `Samsung Galaxy`. - final String family; + final String? family; /// The model name. This for instance can be `Samsung Galaxy S3`. - final String model; + final String? model; /// An internal hardware revision to identify the device exactly. - final String modelId; + final String? modelId; /// The CPU architecture. - final String arch; + final String? arch; /// If the device has a battery, this can be an floating point value /// defining the battery level (in the range 0-100). - final double batteryLevel; + final double? batteryLevel; /// Defines the orientation of a device. - final Orientation orientation; + final Orientation? orientation; /// The manufacturer of the device. - final String manufacturer; + final String? manufacturer; /// The brand of the device. - final String brand; + final String? brand; /// The screen resolution. (e.g.: `800x600`, `3040x1444`). - final String screenResolution; + final String? screenResolution; /// A floating point denoting the screen density. - final double screenDensity; + final double? screenDensity; /// A decimal value reflecting the DPI (dots-per-inch) density. - final int screenDpi; + final int? screenDpi; /// Whether the device was online or not. - final bool online; + final bool? online; /// Whether the device was charging or not. - final bool charging; + final bool? charging; /// Whether the device was low on memory. - final bool lowMemory; + final bool? lowMemory; /// A flag indicating whether this device is a simulator or an actual device. - final bool simulator; + final bool? simulator; /// Total system memory available in bytes. - final int memorySize; + final int? memorySize; /// Free system memory in bytes. - final int freeMemory; + final int? freeMemory; /// Memory usable for the app in bytes. - final int usableMemory; + final int? usableMemory; /// Total device storage in bytes. - final int storageSize; + final int? storageSize; /// Free device storage in bytes. - final int freeStorage; + final int? freeStorage; /// Total size of an attached external storage in bytes /// (e.g.: android SDK card). - final int externalStorageSize; + final int? externalStorageSize; /// Free size of an attached external storage in bytes /// (e.g.: android SDK card). - final int externalFreeStorage; + final int? externalFreeStorage; /// When the system was booted - final DateTime bootTime; + final DateTime? bootTime; /// The timezone of the device, e.g.: `Europe/Vienna`. - final String timezone; + final String? timezone; /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; - String orientation; + String? orientation; switch (this.orientation) { case Orientation.portrait: @@ -162,6 +163,9 @@ class Device { case Orientation.landscape: orientation = 'landscape'; break; + case null: + orientation = null; + break; } if (name != null) { @@ -257,7 +261,7 @@ class Device { } if (bootTime != null) { - json['boot_time'] = bootTime.toIso8601String(); + json['boot_time'] = bootTime!.toIso8601String(); } if (timezone != null) { @@ -296,31 +300,31 @@ class Device { ); Device copyWith({ - String name, - String family, - String model, - String modelId, - String arch, - double batteryLevel, - Orientation orientation, - String manufacturer, - String brand, - String screenResolution, - double screenDensity, - int screenDpi, - bool online, - bool charging, - bool lowMemory, - bool simulator, - int memorySize, - int freeMemory, - int usableMemory, - int storageSize, - int freeStorage, - int externalStorageSize, - int externalFreeStorage, - DateTime bootTime, - String timezone, + String? name, + String? family, + String? model, + String? modelId, + String? arch, + double? batteryLevel, + Orientation? orientation, + String? manufacturer, + String? brand, + String? screenResolution, + double? screenDensity, + int? screenDpi, + bool? online, + bool? charging, + bool? lowMemory, + bool? simulator, + int? memorySize, + int? freeMemory, + int? usableMemory, + int? storageSize, + int? freeStorage, + int? externalStorageSize, + int? externalFreeStorage, + DateTime? bootTime, + String? timezone, }) => Device( name: name ?? this.name, diff --git a/dart/lib/src/protocol/dsn.dart b/dart/lib/src/protocol/dsn.dart index 999d1c542d..9f60755b40 100644 --- a/dart/lib/src/protocol/dsn.dart +++ b/dart/lib/src/protocol/dsn.dart @@ -4,8 +4,8 @@ import 'package:meta/meta.dart'; @immutable class Dsn { const Dsn({ - @required this.publicKey, - @required this.projectId, + required this.publicKey, + required this.projectId, this.uri, this.secretKey, }); @@ -14,7 +14,7 @@ class Dsn { final String publicKey; /// The Sentry.io secret key for the project. - final String secretKey; + final String? secretKey; /// The ID issued by Sentry.io to your project. /// @@ -22,26 +22,29 @@ class Dsn { final String projectId; /// The DSN URI. - final Uri uri; + final Uri? uri; - String get postUri { - final port = uri.hasPort && - ((uri.scheme == 'http' && uri.port != 80) || - (uri.scheme == 'https' && uri.port != 443)) - ? ':${uri.port}' + Uri get postUri { + final uriCopy = uri!; + final port = uriCopy.hasPort && + ((uriCopy.scheme == 'http' && uriCopy.port != 80) || + (uriCopy.scheme == 'https' && uriCopy.port != 443)) + ? ':${uriCopy.port}' : ''; - final pathLength = uri.pathSegments.length; + final pathLength = uriCopy.pathSegments.length; String apiPath; if (pathLength > 1) { // some paths would present before the projectID in the uri apiPath = - (uri.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); + (uriCopy.pathSegments.sublist(0, pathLength - 1) + ['api']).join('/'); } else { apiPath = 'api'; } - return '${uri.scheme}://${uri.host}$port/$apiPath/$projectId/store/'; + return Uri.parse( + '${uriCopy.scheme}://${uriCopy.host}$port/$apiPath/$projectId/store/', + ); } /// Parses a DSN String to a Dsn object diff --git a/dart/lib/src/protocol/gpu.dart b/dart/lib/src/protocol/gpu.dart index 9ebeaa60a6..26fa7f85fc 100644 --- a/dart/lib/src/protocol/gpu.dart +++ b/dart/lib/src/protocol/gpu.dart @@ -18,31 +18,31 @@ class Gpu { static const type = 'gpu'; /// The name of the graphics device. - final String name; + final String? name; /// The PCI identifier of the graphics device. - final int id; + final int? id; /// The PCI vendor identifier of the graphics device. - final int vendorId; + final int? vendorId; /// The vendor name as reported by the graphics device. - final String vendorName; + final String? vendorName; /// The total GPU memory available in Megabytes. - final int memorySize; + final int? memorySize; /// The device low-level API type. - final String apiType; + final String? apiType; /// Whether the GPU has multi-threaded rendering or not. - final bool multiThreadedRendering; + final bool? multiThreadedRendering; /// The Version of the graphics device. - final String version; + final String? version; /// The Non-Power-Of-Two-Support support. - final String npotSupport; + final String? npotSupport; const Gpu({ this.name, @@ -124,15 +124,15 @@ class Gpu { } Gpu copyWith({ - String name, - int id, - int vendorId, - String vendorName, - int memorySize, - String apiType, - bool multiThreadedRendering, - String version, - String npotSupport, + String? name, + int? id, + int? vendorId, + String? vendorName, + int? memorySize, + String? apiType, + bool? multiThreadedRendering, + String? version, + String? npotSupport, }) => Gpu( name: name ?? this.name, diff --git a/dart/lib/src/protocol/mechanism.dart b/dart/lib/src/protocol/mechanism.dart index c66f4a7fcb..69b6e0320b 100644 --- a/dart/lib/src/protocol/mechanism.dart +++ b/dart/lib/src/protocol/mechanism.dart @@ -15,15 +15,15 @@ class Mechanism { final String type; /// Optional human readable description of the error mechanism and a possible hint on how to solve this error - final String description; + final String? description; /// Optional fully qualified URL to an online help resource, possible interpolated with error parameters - final String helpLink; + final String? helpLink; /// Optional flag indicating whether the exception has been handled by the user (e.g. via try..catch) - final bool handled; + final bool? handled; - final Map _meta; + final Map? _meta; /// Optional information from the operating system or runtime on the exception mechanism /// The mechanism meta data usually carries error codes reported by @@ -32,37 +32,37 @@ class Mechanism { /// descriptions for well known error codes, as it will be filled out by /// Sentry. For proprietary or vendor-specific error codes, /// adding these values will give additional information to the user. - Map get meta => Map.unmodifiable(_meta); + Map get meta => Map.unmodifiable(_meta ?? const {}); - final Map _data; + final Map? _data; /// Arbitrary extra data that might help the user understand the error thrown by this mechanism - Map get data => Map.unmodifiable(_data); + Map get data => Map.unmodifiable(_data ?? const {}); /// An optional flag indicating that this error is synthetic. /// Synthetic errors are errors that carry little meaning by themselves. /// This may be because they are created at a central place (like a crash handler), and are all called the same: Error, Segfault etc. When the flag is set, Sentry will then try to use other information (top in-app frame function) rather than exception type and value in the UI for the primary event display. This flag should be set for all "segfaults" for instance as every single error group would look very similar otherwise. - final bool synthetic; + final bool? synthetic; Mechanism({ - @required this.type, + required this.type, this.description, this.helpLink, this.handled, this.synthetic, - Map meta, - Map data, + Map? meta, + Map? data, }) : _meta = meta != null ? Map.from(meta) : null, _data = data != null ? Map.from(data) : null; Mechanism copyWith({ - String type, - String description, - String helpLink, - bool handled, - Map meta, - Map data, - bool synthetic, + String? type, + String? description, + String? helpLink, + bool? handled, + Map? meta, + Map? data, + bool? synthetic, }) => Mechanism( type: type ?? this.type, @@ -77,9 +77,7 @@ class Mechanism { Map toJson() { final json = {}; - if (type != null) { - json['type'] = type; - } + json['type'] = type; if (description != null) { json['description'] = description; @@ -93,11 +91,11 @@ class Mechanism { json['handled'] = handled; } - if (_meta != null && _meta.isNotEmpty) { + if (_meta?.isNotEmpty ?? false) { json['meta'] = _meta; } - if (_data != null && _data.isNotEmpty) { + if (_data?.isNotEmpty ?? false) { json['data'] = _data; } diff --git a/dart/lib/src/protocol/message.dart b/dart/lib/src/protocol/message.dart index a7d295dd35..58f09b9e4a 100644 --- a/dart/lib/src/protocol/message.dart +++ b/dart/lib/src/protocol/message.dart @@ -15,25 +15,23 @@ class Message { /// The raw message string (uninterpolated). /// example : "My raw message with interpreted strings like %s", - final String template; + final String? template; /// A list of formatting parameters, preferably strings. Non-strings will be coerced to strings. - final List params; + final List? params; const Message(this.formatted, {this.template, this.params}); Map toJson() { final json = {}; - if (formatted != null) { - json['formatted'] = formatted; - } + json['formatted'] = formatted; if (template != null) { json['message'] = template; } - if (params != null && params.isNotEmpty) { + if (params?.isNotEmpty ?? false) { json['params'] = params; } @@ -41,9 +39,9 @@ class Message { } Message copyWith({ - String formatted, - String template, - List params, + String? formatted, + String? template, + List? params, }) => Message( formatted ?? this.formatted, diff --git a/dart/lib/src/protocol/operating_system.dart b/dart/lib/src/protocol/operating_system.dart index 9ccd7b8f4b..b8779ee925 100644 --- a/dart/lib/src/protocol/operating_system.dart +++ b/dart/lib/src/protocol/operating_system.dart @@ -28,27 +28,27 @@ class OperatingSystem { ); /// The name of the operating system. - final String name; + final String? name; /// The version of the operating system. - final String version; + final String? version; /// The internal build revision of the operating system. - final String build; + final String? build; /// An independent kernel version string. /// /// This is typically the entire output of the `uname` syscall. - final String kernelVersion; + final String? kernelVersion; /// A flag indicating whether the OS has been jailbroken or rooted. - final bool rooted; + final bool? rooted; /// An unprocessed description string obtained by the operating system. /// /// For some well-known runtimes, Sentry will attempt to parse name and /// version from this string, if they are not explicitly given. - final String rawDescription; + final String? rawDescription; /// Produces a [Map] that can be serialized to JSON. Map toJson() { @@ -91,12 +91,12 @@ class OperatingSystem { ); OperatingSystem copyWith({ - String name, - String version, - String build, - String kernelVersion, - bool rooted, - String rawDescription, + String? name, + String? version, + String? build, + String? kernelVersion, + bool? rooted, + String? rawDescription, }) => OperatingSystem( name: name ?? this.name, diff --git a/dart/lib/src/protocol/request.dart b/dart/lib/src/protocol/request.dart index 1399f72b72..4a0fed5bcf 100644 --- a/dart/lib/src/protocol/request.dart +++ b/dart/lib/src/protocol/request.dart @@ -8,19 +8,19 @@ class Request { ///The URL of the request if available. ///The query string can be declared either as part of the url, ///or separately in queryString. - final String url; + final String? url; ///The HTTP method of the request. - final String method; + final String? method; /// The query string component of the URL. /// /// If the query string is not declared and part of the url parameter, /// Sentry moves it to the query string. - final String queryString; + final String? queryString; /// The cookie values as string. - final String cookies; + final String? cookies; final dynamic _data; @@ -37,25 +37,23 @@ class Request { return _data; } - final Map _headers; + final Map? _headers; /// An immutable dictionary of submitted headers. /// If a header appears multiple times it, /// needs to be merged according to the HTTP standard for header merging. /// Header names are treated case-insensitively by Sentry. - Map get headers => - _headers != null ? Map.unmodifiable(_headers) : null; + Map get headers => Map.unmodifiable(_headers ?? const {}); - final Map _env; + final Map? _env; /// An immutable dictionary containing environment information passed from the server. /// This is where information such as CGI/WSGI/Rack keys go that are not HTTP headers. - Map get env => _env != null ? Map.unmodifiable(_env) : null; + Map get env => Map.unmodifiable(_env ?? const {}); - final Map _other; + final Map? _other; - Map get other => - _other != null ? Map.unmodifiable(_other) : null; + Map get other => Map.unmodifiable(_other ?? const {}); Request({ this.url, @@ -63,9 +61,9 @@ class Request { this.queryString, this.cookies, dynamic data, - Map headers, - Map env, - Map other, + Map? headers, + Map? env, + Map? other, }) : _data = data, _headers = headers != null ? Map.from(headers) : null, _env = env != null ? Map.from(env) : null, @@ -94,15 +92,15 @@ class Request { json['cookies'] = cookies; } - if (headers != null && headers.isNotEmpty) { + if (headers.isNotEmpty) { json['headers'] = headers; } - if (env != null && env.isNotEmpty) { + if (env.isNotEmpty) { json['env'] = env; } - if (other != null && other.isNotEmpty) { + if (other.isNotEmpty) { json['other'] = other; } @@ -110,14 +108,14 @@ class Request { } Request copyWith({ - String url, - String method, - String queryString, - String cookies, + String? url, + String? method, + String? queryString, + String? cookies, dynamic data, - Map headers, - Map env, - Map other, + Map? headers, + Map? env, + Map? other, }) => Request( url: url ?? this.url, diff --git a/dart/lib/src/protocol/sdk_info.dart b/dart/lib/src/protocol/sdk_info.dart index b7e98ca522..a58ab63e73 100644 --- a/dart/lib/src/protocol/sdk_info.dart +++ b/dart/lib/src/protocol/sdk_info.dart @@ -3,10 +3,10 @@ import 'package:meta/meta.dart'; /// An object describing the system SDK. @immutable class SdkInfo { - final String sdkName; - final int versionMajor; - final int versionMinor; - final int versionPatchlevel; + final String? sdkName; + final int? versionMajor; + final int? versionMinor; + final int? versionPatchlevel; const SdkInfo({ this.sdkName, @@ -37,10 +37,10 @@ class SdkInfo { } SdkInfo copyWith({ - String sdkName, - int versionMajor, - int versionMinor, - int versionPatchlevel, + String? sdkName, + int? versionMajor, + int? versionMinor, + int? versionPatchlevel, }) => SdkInfo( sdkName: sdkName ?? this.sdkName, diff --git a/dart/lib/src/protocol/sdk_version.dart b/dart/lib/src/protocol/sdk_version.dart index 06a3d44184..dc4a7c9ad3 100644 --- a/dart/lib/src/protocol/sdk_version.dart +++ b/dart/lib/src/protocol/sdk_version.dart @@ -36,14 +36,14 @@ import 'sentry_package.dart'; class SdkVersion { /// Creates an [SdkVersion] object which represents the SDK that created an [Event]. SdkVersion({ - @required this.name, - @required this.version, - List integrations, - List packages, - }) : assert(name != null || version != null), + required this.name, + required this.version, + List? integrations, + List? packages, + }) : // List.from prevents from having immutable lists - _integrations = integrations != null ? List.from(integrations) : [], - _packages = packages != null ? List.from(packages) : []; + _integrations = List.from(integrations ?? []), + _packages = List.from(packages ?? []); /// The name of the SDK. final String name; @@ -66,20 +66,17 @@ class SdkVersion { /// Produces a [Map] that can be serialized to JSON. Map toJson() { final json = {}; - if (name != null) { - json['name'] = name; - } - if (version != null) { - json['version'] = version; - } + json['name'] = name; + + json['version'] = version; - if (packages != null && packages.isNotEmpty) { + if (packages.isNotEmpty) { json['packages'] = packages.map((p) => p.toJson()).toList(growable: false); } - if (integrations != null && integrations.isNotEmpty) { + if (integrations.isNotEmpty) { json['integrations'] = integrations; } return json; @@ -96,11 +93,12 @@ class SdkVersion { _integrations.add(integration); } - SdkVersion copyWith( - {String name, - String version, - List integrations, - List packages}) => + SdkVersion copyWith({ + String? name, + String? version, + List? integrations, + List? packages, + }) => SdkVersion( name: name ?? this.name, version: version ?? this.version, diff --git a/dart/lib/src/protocol/sentry_event.dart b/dart/lib/src/protocol/sentry_event.dart index 6f7d7b9526..d3c85b5fb5 100644 --- a/dart/lib/src/protocol/sentry_event.dart +++ b/dart/lib/src/protocol/sentry_event.dart @@ -9,13 +9,13 @@ import '../utils.dart'; class SentryEvent { /// Creates an event. SentryEvent({ - SentryId eventId, - DateTime timestamp, - Map modules, - Map tags, - Map extra, - List fingerprint, - List breadcrumbs, + SentryId? eventId, + DateTime? timestamp, + Map? modules, + Map? tags, + Map? extra, + List? fingerprint, + List? breadcrumbs, this.sdk, this.platform, this.logger, @@ -31,7 +31,7 @@ class SentryEvent { this.level, this.culprit, this.user, - Contexts contexts, + Contexts? contexts, this.request, this.debugMeta, }) : eventId = eventId ?? SentryId.newId(), @@ -57,30 +57,30 @@ class SentryEvent { final DateTime timestamp; /// A string representing the platform the SDK is submitting from. This will be used by the Sentry interface to customize various components in the interface. - final String platform; + final String? platform; /// The logger that logged the event. - final String logger; + final String? logger; /// Identifies the server that logged this event. - final String serverName; + final String? serverName; /// The version of the application that logged the event. - final String release; + final String? release; /// The distribution of the application. - final String dist; + final String? dist; /// The environment that logged the event, e.g. "production", "staging". - final String environment; + final String? environment; /// A list of relevant modules and their versions. - final Map modules; + final Map? modules; /// Event message. /// /// Generally an event either contains a [message] or an [exception]. - final Message message; + final Message? message; final dynamic _throwable; @@ -100,42 +100,42 @@ class SentryEvent { /// an optional attached StackTrace /// used when event has no throwable or exception, see [SentryOptions.attachStacktrace] - final SentryStackTrace stackTrace; + final SentryStackTrace? stackTrace; /// an exception or error that occurred in a program /// TODO more doc - final SentryException exception; + final SentryException? exception; /// The name of the transaction which generated this event, /// for example, the route name: `"/users//"`. - final String transaction; + final String? transaction; /// How important this event is. - final SentryLevel level; + final SentryLevel? level; /// What caused this event to be logged. - final String culprit; + final String? culprit; /// Name/value pairs that events can be searched by. - final Map tags; + final Map? tags; /// Arbitrary name/value pairs attached to the event. /// /// Sentry.io docs do not talk about restrictions on the values, other than /// they must be JSON-serializable. - final Map extra; + final Map? extra; /// List of breadcrumbs for this event. /// /// See also: /// * https://docs.sentry.io/enriching-error-data/breadcrumbs/?platform=javascript - final List breadcrumbs; + final List? breadcrumbs; /// Information about the current user. /// /// The value in this field overrides the user context /// set in [Scope.user] for this logged event. - final User user; + final User? user; /// The context interfaces provide additional context data. /// Typically this is data related to the current user, @@ -156,45 +156,45 @@ class SentryEvent { /// var custom = ['foo', 'bar', 'baz']; /// // A fingerprint that supplements the default one with value 'foo': /// var supplemented = [SentryEvent.defaultFingerprint, 'foo']; - final List fingerprint; + final List? fingerprint; /// The SDK Interface describes the Sentry SDK and its configuration used to capture and transmit an event. - final SdkVersion sdk; + final SdkVersion? sdk; /// contains information on a HTTP request related to the event. /// In client, this can be an outgoing request, or the request that rendered the current web page. /// On server, this could be the incoming web request that is being handled - final Request request; + final Request? request; /// The debug meta interface carries debug information for processing errors and crash reports. - final DebugMeta debugMeta; + final DebugMeta? debugMeta; SentryEvent copyWith({ - SentryId eventId, - DateTime timestamp, - String platform, - String logger, - String serverName, - String release, - String dist, - String environment, - Map modules, - Message message, - String transaction, + SentryId? eventId, + DateTime? timestamp, + String? platform, + String? logger, + String? serverName, + String? release, + String? dist, + String? environment, + Map? modules, + Message? message, + String? transaction, dynamic throwable, - SentryException exception, + SentryException? exception, dynamic stackTrace, - SentryLevel level, - String culprit, - Map tags, - Map extra, - List fingerprint, - User user, - Contexts contexts, - List breadcrumbs, - SdkVersion sdk, - Request request, - DebugMeta debugMeta, + SentryLevel? level, + String? culprit, + Map? tags, + Map? extra, + List? fingerprint, + User? user, + Contexts? contexts, + List? breadcrumbs, + SdkVersion? sdk, + Request? request, + DebugMeta? debugMeta, }) => SentryEvent( eventId: eventId ?? this.eventId, @@ -230,13 +230,9 @@ class SentryEvent { Map toJson() { final json = {}; - if (eventId != null) { - json['event_id'] = eventId.toString(); - } + json['event_id'] = eventId.toString(); - if (timestamp != null) { - json['timestamp'] = formatDateAsIso8601WithMillisPrecision(timestamp); - } + json['timestamp'] = formatDateAsIso8601WithMillisPrecision(timestamp); if (platform != null) { json['platform'] = platform; @@ -262,12 +258,12 @@ class SentryEvent { json['environment'] = environment; } - if (modules != null && modules.isNotEmpty) { + if (modules != null && modules!.isNotEmpty) { json['modules'] = modules; } Map messageMap; - if (message != null && (messageMap = message.toJson()).isNotEmpty) { + if (message != null && (messageMap = message!.toJson()).isNotEmpty) { json['message'] = messageMap; } @@ -275,14 +271,13 @@ class SentryEvent { json['transaction'] = transaction; } - Map exceptionMap; - Map stackTraceMap; - if (exception != null && (exceptionMap = exception.toJson()).isNotEmpty) { + final exceptionMap = exception?.toJson(); + final stackTraceMap = stackTrace?.toJson(); + if (exceptionMap?.isNotEmpty ?? false) { json['exception'] = { 'values': [exceptionMap].toList(growable: false) }; - } else if (stackTrace != null && - (stackTraceMap = stackTrace.toJson()).isNotEmpty) { + } else if (stackTraceMap?.isNotEmpty ?? false) { json['threads'] = { 'values': [ { @@ -296,52 +291,52 @@ class SentryEvent { } if (level != null) { - json['level'] = level.name; + json['level'] = level!.name; } if (culprit != null) { json['culprit'] = culprit; } - if (tags != null && tags.isNotEmpty) { + if (tags?.isNotEmpty ?? false) { json['tags'] = tags; } - if (extra != null && extra.isNotEmpty) { + if (extra?.isNotEmpty ?? false) { json['extra'] = extra; } - Map contextsMap; - if (contexts != null && (contextsMap = contexts.toJson()).isNotEmpty) { + final contextsMap = contexts.toJson(); + if (contextsMap.isNotEmpty) { json['contexts'] = contextsMap; } - Map userMap; - if (user != null && (userMap = user.toJson()).isNotEmpty) { + final userMap = user?.toJson(); + if (userMap?.isNotEmpty ?? false) { json['user'] = userMap; } - if (fingerprint != null && fingerprint.isNotEmpty) { + if (fingerprint?.isNotEmpty ?? false) { json['fingerprint'] = fingerprint; } - if (breadcrumbs != null && breadcrumbs.isNotEmpty) { + if (breadcrumbs?.isNotEmpty ?? false) { json['breadcrumbs'] = - breadcrumbs.map((b) => b.toJson()).toList(growable: false); + breadcrumbs?.map((b) => b.toJson()).toList(growable: false); } - Map sdkMap; - if (sdk != null && (sdkMap = sdk.toJson()).isNotEmpty) { + final sdkMap = sdk?.toJson(); + if (sdkMap?.isNotEmpty ?? false) { json['sdk'] = sdkMap; } - Map requestMap; - if (request != null && (requestMap = request.toJson()).isNotEmpty) { + final requestMap = request?.toJson(); + if (requestMap?.isNotEmpty ?? false) { json['request'] = requestMap; } - Map debugMetaMap; - if (debugMeta != null && (debugMetaMap = debugMeta.toJson()).isNotEmpty) { + final debugMetaMap = debugMeta?.toJson(); + if (debugMetaMap?.isNotEmpty ?? false) { json['debug_meta'] = debugMetaMap; } diff --git a/dart/lib/src/protocol/sentry_exception.dart b/dart/lib/src/protocol/sentry_exception.dart index ec17800db0..c4d2558240 100644 --- a/dart/lib/src/protocol/sentry_exception.dart +++ b/dart/lib/src/protocol/sentry_exception.dart @@ -6,26 +6,26 @@ import '../protocol.dart'; @immutable class SentryException { /// Required. The type of exception - final String type; + final String? type; /// Required. The value of the exception - final String value; + final String? value; /// The optional module, or package which the exception type lives in. - final String module; + final String? module; /// An optional stack trace object - final SentryStackTrace stackTrace; + final SentryStackTrace? stackTrace; /// An optional object describing the [Mechanism] that created this exception - final Mechanism mechanism; + final Mechanism? mechanism; /// Represents a thread id. not available in Dart - final int threadId; + final int? threadId; const SentryException({ - @required this.type, - @required this.value, + required this.type, + required this.value, this.module, this.stackTrace, this.mechanism, @@ -48,11 +48,11 @@ class SentryException { } if (stackTrace != null) { - json['stacktrace'] = stackTrace.toJson(); + json['stacktrace'] = stackTrace!.toJson(); } if (mechanism != null) { - json['mechanism'] = mechanism.toJson(); + json['mechanism'] = mechanism!.toJson(); } if (threadId != null) { @@ -63,12 +63,12 @@ class SentryException { } SentryException copyWith({ - String type, - String value, - String module, - SentryStackTrace stackTrace, - Mechanism mechanism, - int threadId, + String? type, + String? value, + String? module, + SentryStackTrace? stackTrace, + Mechanism? mechanism, + int? threadId, }) => SentryException( type: type ?? this.type, diff --git a/dart/lib/src/protocol/sentry_id.dart b/dart/lib/src/protocol/sentry_id.dart index a3250b5d98..d9291d42d0 100644 --- a/dart/lib/src/protocol/sentry_id.dart +++ b/dart/lib/src/protocol/sentry_id.dart @@ -12,7 +12,7 @@ class SentryId { static final Uuid _uuidGenerator = Uuid(); - SentryId._internal({String id}) : _id = id ?? _uuidGenerator.v4(); + SentryId._internal({String? id}) : _id = id ?? _uuidGenerator.v4(); /// Generates a new SentryId factory SentryId.newId() => SentryId._internal(); diff --git a/dart/lib/src/protocol/sentry_package.dart b/dart/lib/src/protocol/sentry_package.dart index 23253243a5..57e5e4a9f4 100644 --- a/dart/lib/src/protocol/sentry_package.dart +++ b/dart/lib/src/protocol/sentry_package.dart @@ -4,8 +4,7 @@ import 'package:meta/meta.dart'; @immutable class SentryPackage { /// Creates an [SentryPackage] object that is part of the [Sdk]. - const SentryPackage(this.name, this.version) - : assert(name != null && version != null); + const SentryPackage(this.name, this.version); /// The name of the SDK. final String name; @@ -22,8 +21,8 @@ class SentryPackage { } SentryPackage copyWith({ - String name, - String version, + String? name, + String? version, }) => SentryPackage( name ?? this.name, diff --git a/dart/lib/src/protocol/sentry_runtime.dart b/dart/lib/src/protocol/sentry_runtime.dart index 8de93c8d2a..54bf50a266 100644 --- a/dart/lib/src/protocol/sentry_runtime.dart +++ b/dart/lib/src/protocol/sentry_runtime.dart @@ -23,19 +23,19 @@ class SentryRuntime { /// in the Sentry UI. Defaults to lower case version of [name]. /// /// Unused if only one [SentryRuntime] is provided in [Contexts]. - final String key; + final String? key; /// The name of the runtime. - final String name; + final String? name; /// The version identifier of the runtime. - final String version; + final String? version; /// An unprocessed description string obtained by the runtime. /// /// For some well-known runtimes, Sentry will attempt to parse name /// and version from this string, if they are not explicitly given. - final String rawDescription; + final String? rawDescription; /// Produces a [Map] that can be serialized to JSON. Map toJson() { @@ -64,10 +64,10 @@ class SentryRuntime { ); SentryRuntime copyWith({ - String key, - String name, - String version, - String rawDescription, + String? key, + String? name, + String? version, + String? rawDescription, }) => SentryRuntime( key: key ?? this.key, diff --git a/dart/lib/src/protocol/sentry_stack_frame.dart b/dart/lib/src/protocol/sentry_stack_frame.dart index 62dabecbd5..685cac4df2 100644 --- a/dart/lib/src/protocol/sentry_stack_frame.dart +++ b/dart/lib/src/protocol/sentry_stack_frame.dart @@ -23,10 +23,10 @@ class SentryStackFrame { this.symbolAddr, this.instructionAddr, this.rawFunction, - List framesOmitted, - List preContext, - List postContext, - Map vars, + List? framesOmitted, + List? preContext, + List? postContext, + Map? vars, }) : _framesOmitted = framesOmitted != null ? List.from(framesOmitted) : null, _preContext = preContext != null ? List.from(preContext) : null, @@ -34,24 +34,24 @@ class SentryStackFrame { _vars = vars != null ? Map.from(vars) : null; /// The absolute path to filename. - final String absPath; + final String? absPath; - final List _preContext; + final List? _preContext; /// An immutable list of source code lines before context_line (in order) – usually [lineno - 5:lineno]. - List get preContext => List.unmodifiable(_preContext); + List get preContext => List.unmodifiable(_preContext ?? const []); - final List _postContext; + final List? _postContext; /// An immutable list of source code lines after context_line (in order) – usually [lineno + 1:lineno + 5]. - List get postContext => List.unmodifiable(_postContext); + List get postContext => List.unmodifiable(_postContext ?? const []); - final Map _vars; + final Map? _vars; /// An immutable mapping of variables which were available within this frame (usually context-locals). - Map get vars => Map.unmodifiable(_vars); + Map get vars => Map.unmodifiable(_vars ?? const {}); - final List _framesOmitted; + final List? _framesOmitted; /// Which frames were omitted, if any. /// @@ -62,68 +62,68 @@ class SentryStackFrame { /// Example : If you only removed the 8th frame, the value would be (8, 9), /// meaning it started at the 8th frame, and went untilthe 9th (the number of frames omitted is end-start). /// The values should be based on a one-index. - List get framesOmitted => List.unmodifiable(_framesOmitted); + List get framesOmitted => List.unmodifiable(_framesOmitted ?? const []); /// The relative file path to the call. - final String fileName; + final String? fileName; /// The name of the function being called. - final String function; + final String? function; /// Platform-specific module path. - final String module; + final String? module; /// The column number of the call - final int lineNo; + final int? lineNo; /// The column number of the call - final int colNo; + final int? colNo; /// Source code in filename at line number. - final String contextLine; + final String? contextLine; /// Signifies whether this frame is related to the execution of the relevant code in this stacktrace. /// /// For example, the frames that might power the framework’s web server of your app are probably not relevant, however calls to the framework’s library once you start handling code likely are. - final bool inApp; + final bool? inApp; /// The "package" the frame was contained in. - final String package; + final String? package; - final bool native; + final bool? native; /// This can override the platform for a single frame. Otherwise, the platform of the event is assumed. This can be used for multi-platform stack traces - final String platform; + final String? platform; /// Optionally an address of the debug image to reference. - final String imageAddr; + final String? imageAddr; /// An optional address that points to a symbol. We use the instruction address for symbolication, but this can be used to calculate an instruction offset automatically. - final String symbolAddr; + final String? symbolAddr; /// The instruction address /// The official docs refer to it as 'The difference between instruction address and symbol address in bytes.' - final String instructionAddr; + final String? instructionAddr; /// The original function name, if the function name is shortened or demangled. Sentry shows the raw function when clicking on the shortened one in the UI. - final String rawFunction; + final String? rawFunction; Map toJson() { final json = {}; - if (_preContext != null && _preContext.isNotEmpty) { + if (_preContext?.isNotEmpty ?? false) { json['pre_context'] = _preContext; } - if (_postContext != null && _postContext.isNotEmpty) { + if (_postContext?.isNotEmpty ?? false) { json['post_context'] = _postContext; } - if (_vars != null && _vars.isNotEmpty) { + if (_vars?.isNotEmpty ?? false) { json['vars'] = _vars; } - if (_framesOmitted != null && _framesOmitted.isNotEmpty) { + if (_framesOmitted?.isNotEmpty ?? false) { json['frames_omitted'] = _framesOmitted; } @@ -195,25 +195,25 @@ class SentryStackFrame { } SentryStackFrame copyWith({ - String absPath, - String fileName, - String function, - String module, - int lineNo, - int colNo, - String contextLine, - bool inApp, - String package, - bool native, - String platform, - String imageAddr, - String symbolAddr, - String instructionAddr, - String rawFunction, - List framesOmitted, - List preContext, - List postContext, - Map vars, + String? absPath, + String? fileName, + String? function, + String? module, + int? lineNo, + int? colNo, + String? contextLine, + bool? inApp, + String? package, + bool? native, + String? platform, + String? imageAddr, + String? symbolAddr, + String? instructionAddr, + String? rawFunction, + List? framesOmitted, + List? preContext, + List? postContext, + Map? vars, }) => SentryStackFrame( absPath: absPath ?? this.absPath, diff --git a/dart/lib/src/protocol/sentry_stack_trace.dart b/dart/lib/src/protocol/sentry_stack_trace.dart index 56d44c6846..112ecd81c4 100644 --- a/dart/lib/src/protocol/sentry_stack_trace.dart +++ b/dart/lib/src/protocol/sentry_stack_trace.dart @@ -5,35 +5,35 @@ import 'sentry_stack_frame.dart'; /// Stacktrace holds information about the frames of the stack. @immutable class SentryStackTrace { - const SentryStackTrace({ - @required List frames, - Map registers, + SentryStackTrace({ + required List frames, + Map? registers, }) : _frames = frames, - _registers = registers; + _registers = Map.from(registers ?? {}); - final List _frames; + final List? _frames; /// Required. A non-empty immutable list of stack frames (see below). /// The list is ordered from caller to callee, or oldest to youngest. /// The last frame is the one creating the exception. - List get frames => List.unmodifiable(_frames); + List get frames => List.unmodifiable(_frames ?? const []); - final Map _registers; + final Map? _registers; /// Optional. A map of register names and their values. /// The values should contain the actual register values of the thread, /// thus mapping to the last frame in the list. - Map get registers => Map.unmodifiable(_registers); + Map get registers => Map.unmodifiable(_registers ?? const {}); Map toJson() { final json = {}; - if (_frames != null && _frames.isNotEmpty) { + if (_frames?.isNotEmpty ?? false) { json['frames'] = - _frames.map((frame) => frame.toJson()).toList(growable: false); + _frames?.map((frame) => frame.toJson()).toList(growable: false); } - if (_registers != null && _registers.isNotEmpty ?? false) { + if (_registers?.isNotEmpty ?? false) { json['registers'] = _registers; } @@ -41,8 +41,8 @@ class SentryStackTrace { } SentryStackTrace copyWith({ - List frames, - Map registers, + List? frames, + Map? registers, }) => SentryStackTrace( frames: frames ?? this.frames, diff --git a/dart/lib/src/protocol/user.dart b/dart/lib/src/protocol/user.dart index 67d4078fc2..e6fc3a6e2f 100644 --- a/dart/lib/src/protocol/user.dart +++ b/dart/lib/src/protocol/user.dart @@ -31,27 +31,27 @@ class User { this.username, this.email, this.ipAddress, - Map extras, + Map? extras, }) : assert(id != null || ipAddress != null), - extras = extras != null ? Map.from(extras) : null; + extras = extras == null ? null : Map.from(extras); /// A unique identifier of the user. - final String id; + final String? id; /// The username of the user. - final String username; + final String? username; /// The email address of the user. - final String email; + final String? email; /// The IP of the user. - final String ipAddress; + final String? ipAddress; /// Any other user context information that may be helpful. /// /// These keys are stored as extra information but not specifically processed /// by Sentry. - final Map extras; + final Map? extras; /// Produces a [Map] that can be serialized to JSON. Map toJson() { @@ -65,11 +65,11 @@ class User { } User copyWith({ - String id, - String username, - String email, - String ipAddress, - Map extras, + String? id, + String? username, + String? email, + String? ipAddress, + Map? extras, }) => User( id: id ?? this.id, diff --git a/dart/lib/src/scope.dart b/dart/lib/src/scope.dart index 5110f47fd9..b285fbf6a5 100644 --- a/dart/lib/src/scope.dart +++ b/dart/lib/src/scope.dart @@ -6,16 +6,16 @@ import 'sentry_options.dart'; /// Scope data to be sent with the event class Scope { /// How important this event is. - SentryLevel level; + SentryLevel? level; /// The name of the transaction which generated this event, /// for example, the route name: `"/users//"`. - String transaction; + String? transaction; /// Information about the current user. - User user; + User? user; - List _fingerprint; + List _fingerprint = []; /// Used to deduplicate events by grouping ones with the same fingerprint /// together. @@ -24,11 +24,10 @@ class Scope { /// /// // A completely custom fingerprint: /// var custom = ['foo', 'bar', 'baz']; - List get fingerprint => - _fingerprint != null ? List.unmodifiable(_fingerprint) : null; + List get fingerprint => List.unmodifiable(_fingerprint); set fingerprint(List fingerprint) { - _fingerprint = (fingerprint != null ? List.from(fingerprint) : fingerprint); + _fingerprint = List.from(fingerprint); } /// List of breadcrumbs for this scope. @@ -61,8 +60,6 @@ class Scope { /// add an entry to the Scope's contexts void setContexts(String key, dynamic value) { - if (key == null || value == null) return; - _contexts[key] = (value is num || value is bool || value is String) ? {'value': value} : value; @@ -83,33 +80,31 @@ class Scope { final SentryOptions _options; - Scope(this._options) { - if (_options == null) { - throw ArgumentError('SentryOptions is required'); - } - } + Scope(this._options); /// Adds a breadcrumb to the breadcrumbs queue void addBreadcrumb(Breadcrumb breadcrumb, {dynamic hint}) { - assert(breadcrumb != null, "Breadcrumb can't be null"); - // bail out if maxBreadcrumbs is zero if (_options.maxBreadcrumbs == 0) { return; } + Breadcrumb? processedBreadcrumb = breadcrumb; // run before breadcrumb callback if set if (_options.beforeBreadcrumb != null) { - breadcrumb = _options.beforeBreadcrumb(breadcrumb, hint: hint); + processedBreadcrumb = _options.beforeBreadcrumb!( + processedBreadcrumb, + hint: hint, + ); - if (breadcrumb == null) { + if (processedBreadcrumb == null) { _options.logger( SentryLevel.info, 'Breadcrumb was dropped by beforeBreadcrumb'); return; } } - // remove first item if list if full + // remove first item if list is full if (_breadcrumbs.length >= _options.maxBreadcrumbs && _breadcrumbs.isNotEmpty) { _breadcrumbs.removeFirst(); @@ -125,8 +120,6 @@ class Scope { /// Adds an event processor void addEventProcessor(EventProcessor eventProcessor) { - assert(eventProcessor != null, "EventProcessor can't be null"); - _eventProcessors.add(eventProcessor); } @@ -136,7 +129,7 @@ class Scope { level = null; transaction = null; user = null; - _fingerprint = null; + _fingerprint = []; _tags.clear(); _extra.clear(); _eventProcessors.clear(); @@ -144,9 +137,6 @@ class Scope { /// Sets a tag to the Scope void setTag(String key, String value) { - assert(key != null, "Key can't be null"); - assert(value != null, "Key can't be null"); - _tags[key] = value; } @@ -157,23 +147,22 @@ class Scope { /// Sets an extra to the Scope void setExtra(String key, dynamic value) { - assert(key != null, "Key can't be null"); - assert(value != null, "Value can't be null"); - _extra[key] = value; } /// Removes an extra from the Scope void removeExtra(String key) => _extra.remove(key); - Future applyToEvent(SentryEvent event, dynamic hint) async { + Future applyToEvent(SentryEvent event, dynamic hint) async { event = event.copyWith( transaction: event.transaction ?? transaction, user: event.user ?? user, - fingerprint: event.fingerprint ?? - (_fingerprint != null ? List.from(_fingerprint) : null), - breadcrumbs: event.breadcrumbs ?? - (_breadcrumbs != null ? List.from(_breadcrumbs) : null), + fingerprint: (event.fingerprint?.isNotEmpty ?? false) + ? event.fingerprint + : _fingerprint, + breadcrumbs: (event.breadcrumbs?.isNotEmpty ?? false) + ? event.breadcrumbs + : List.from(_breadcrumbs), tags: tags.isNotEmpty ? _mergeEventTags(event) : event.tags, extra: extra.isNotEmpty ? _mergeEventExtra(event) : event.extra, level: level ?? event.level, @@ -190,22 +179,23 @@ class Scope { } }); + SentryEvent? processedEvent = event; for (final processor in _eventProcessors) { try { - event = await processor(event, hint: hint); + processedEvent = await processor(processedEvent!, hint: hint)!; } catch (err) { _options.logger( SentryLevel.error, 'An exception occurred while processing event by a processor : $err', ); } - if (event == null) { + if (processedEvent == null) { _options.logger(SentryLevel.debug, 'Event was dropped by a processor'); break; } } - return event; + return processedEvent; } /// merge the scope contexts runtimes and the event contexts runtimes @@ -227,11 +217,11 @@ class Scope { Scope clone() { final clone = Scope(_options) ..user = user - ..fingerprint = fingerprint != null ? List.from(fingerprint) : null + ..fingerprint = List.from(fingerprint) ..transaction = transaction; for (final tag in _tags.keys) { - clone.setTag(tag, _tags[tag]); + clone.setTag(tag, _tags[tag]!); } for (final extraKey in _extra.keys) { diff --git a/dart/lib/src/sentry.dart b/dart/lib/src/sentry.dart index a4327b40ba..561d2b0306 100644 --- a/dart/lib/src/sentry.dart +++ b/dart/lib/src/sentry.dart @@ -36,27 +36,23 @@ class Sentry { /// such as SentryFlutter. static Future init( OptionsConfiguration optionsConfiguration, { - AppRunner appRunner, - SentryOptions options, + AppRunner? appRunner, + SentryOptions? options, }) async { - if (optionsConfiguration == null) { - throw ArgumentError('OptionsConfiguration is required.'); - } - final sentryOptions = options ?? SentryOptions(); await _initDefaultValues(sentryOptions, appRunner); await optionsConfiguration(sentryOptions); - if (sentryOptions == null) { - throw ArgumentError('SentryOptions is required.'); + if (sentryOptions.dsn == null) { + throw ArgumentError('DSN is required.'); } await _init(sentryOptions, appRunner); } static Future _initDefaultValues( - SentryOptions options, AppRunner appRunner) async { + SentryOptions options, AppRunner? appRunner) async { // We infer the enviroment based on the release/non-release and profile // constants. var environment = options.platformChecker.isReleaseMode() @@ -84,7 +80,7 @@ class Sentry { } /// Initializes the SDK - static Future _init(SentryOptions options, AppRunner appRunner) async { + static Future _init(SentryOptions options, AppRunner? appRunner) async { if (isEnabled) { options.logger( SentryLevel.warning, @@ -149,10 +145,10 @@ class Sentry { ); static Future captureMessage( - String message, { - SentryLevel level, - String template, - List params, + String? message, { + SentryLevel? level = SentryLevel.info, + String? template, + List? params, dynamic hint, }) async => currentHub.captureMessage( @@ -191,20 +187,14 @@ class Sentry { static void bindClient(SentryClient client) => currentHub.bindClient(client); static bool _setDefaultConfiguration(SentryOptions options) { - // if DSN is null, let's crash the App. - if (options.dsn == null) { - throw ArgumentError( - 'DSN is required. Use empty string to disable SDK.', - ); - } // if the DSN is empty, let's disable the SDK - if (options.dsn.isEmpty) { + if (options.dsn?.isEmpty ?? false) { close(); return false; } // try parsing the dsn - Dsn.parse(options.dsn); + Dsn.parse(options.dsn!); // if logger os NoOp, let's set a logger that prints on the console if (options.debug && options.logger == noOpLogger) { diff --git a/dart/lib/src/sentry_client.dart b/dart/lib/src/sentry_client.dart index 4b40766067..8da02d78b1 100644 --- a/dart/lib/src/sentry_client.dart +++ b/dart/lib/src/sentry_client.dart @@ -14,20 +14,16 @@ import 'version.dart'; class SentryClient { final SentryOptions _options; - final Random _random; + final Random? _random; static final _sentryId = Future.value(SentryId.empty()); - SentryExceptionFactory _exceptionFactory; + late SentryExceptionFactory _exceptionFactory; - SentryStackTraceFactory _stackTraceFactory; + late SentryStackTraceFactory _stackTraceFactory; /// Instantiates a client using [SentryOptions] factory SentryClient(SentryOptions options) { - if (options == null) { - throw ArgumentError('SentryOptions is required.'); - } - if (options.transport is NoOpTransport) { options.transport = HttpTransport(options); } @@ -40,15 +36,15 @@ class SentryClient { : _random = _options.sampleRate == null ? null : Random() { _stackTraceFactory = SentryStackTraceFactory(_options); _exceptionFactory = SentryExceptionFactory( - options: _options, - stacktraceFactory: _stackTraceFactory, + _options, + _stackTraceFactory, ); } /// Reports an [event] to Sentry.io. Future captureEvent( SentryEvent event, { - Scope scope, + Scope? scope, dynamic stackTrace, dynamic hint, }) async { @@ -60,37 +56,39 @@ class SentryClient { return _sentryId; } - event = _prepareEvent(event, stackTrace: stackTrace); + SentryEvent? preparedEvent = _prepareEvent(event, stackTrace: stackTrace); if (scope != null) { - event = await scope.applyToEvent(event, hint); + preparedEvent = await scope.applyToEvent(preparedEvent, hint); } else { _options.logger(SentryLevel.debug, 'No scope is defined'); } // dropped by scope event processors - if (event == null) { + if (preparedEvent == null) { return _sentryId; } - event = - await _processEvent(event, eventProcessors: _options.eventProcessors); + preparedEvent = await _processEvent( + preparedEvent, + eventProcessors: _options.eventProcessors, + ); // dropped by event processors - if (event == null) { + if (preparedEvent == null) { return _sentryId; } if (_options.beforeSend != null) { try { - event = _options.beforeSend(event, hint: hint); + preparedEvent = _options.beforeSend!(preparedEvent, hint: hint); } catch (err) { _options.logger( SentryLevel.error, 'The BeforeSend callback threw an exception, error: $err', ); } - if (event == null) { + if (preparedEvent == null) { _options.logger( SentryLevel.debug, 'Event was dropped by BeforeSend callback', @@ -99,7 +97,7 @@ class SentryClient { } } - return _options.transport.send(event); + return _options.transport.send(preparedEvent); } SentryEvent _prepareEvent(SentryEvent event, {dynamic stackTrace}) { @@ -125,7 +123,7 @@ class SentryClient { stackTrace ??= StackTrace.current; final frames = _stackTraceFactory.getStackFrames(stackTrace); - if (frames != null && frames.isNotEmpty) { + if (frames.isNotEmpty) { event = event.copyWith(stackTrace: SentryStackTrace(frames: frames)); } } @@ -137,7 +135,7 @@ class SentryClient { Future captureException( dynamic throwable, { dynamic stackTrace, - Scope scope, + Scope? scope, dynamic hint, }) { final event = SentryEvent( @@ -156,10 +154,10 @@ class SentryClient { /// Reports the [template] Future captureMessage( String formatted, { - SentryLevel level, - String template, - List params, - Scope scope, + SentryLevel? level, + String? template, + List? params, + Scope? scope, dynamic hint, }) { final event = SentryEvent( @@ -171,33 +169,34 @@ class SentryClient { return captureEvent(event, scope: scope, hint: hint); } - void close() => _options.httpClient?.close(); + void close() => _options.httpClient.close(); - Future _processEvent( + Future _processEvent( SentryEvent event, { dynamic hint, - List eventProcessors, + required List eventProcessors, }) async { + SentryEvent? processedEvent = event; for (final processor in eventProcessors) { try { - event = await processor(event, hint: hint); + processedEvent = await processor(processedEvent!, hint: hint)!; } catch (err) { _options.logger( SentryLevel.error, 'An exception occurred while processing event by a processor : $err', ); } - if (event == null) { + if (processedEvent == null) { _options.logger(SentryLevel.debug, 'Event was dropped by a processor'); break; } } - return event; + return processedEvent; } bool _sampleRate() { if (_options.sampleRate != null && _random != null) { - return (_options.sampleRate < _random.nextDouble()); + return (_options.sampleRate! < _random!.nextDouble()); } return false; } diff --git a/dart/lib/src/sentry_exception_factory.dart b/dart/lib/src/sentry_exception_factory.dart index bee16b63b5..01ea070d6b 100644 --- a/dart/lib/src/sentry_exception_factory.dart +++ b/dart/lib/src/sentry_exception_factory.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - import 'protocol.dart'; import 'sentry_options.dart'; import 'sentry_stack_trace_factory.dart'; @@ -11,21 +9,7 @@ class SentryExceptionFactory { final SentryStackTraceFactory _stacktraceFactory; - SentryExceptionFactory._(this._options, this._stacktraceFactory); - - factory SentryExceptionFactory({ - @required SentryOptions options, - @required SentryStackTraceFactory stacktraceFactory, - }) { - if (options == null) { - throw ArgumentError('SentryOptions is required.'); - } - - if (stacktraceFactory == null) { - throw ArgumentError('SentryStackTraceFactory is required.'); - } - return SentryExceptionFactory._(options, stacktraceFactory); - } + SentryExceptionFactory(this._options, this._stacktraceFactory); SentryException getSentryException( dynamic exception, { @@ -44,10 +28,11 @@ class SentryExceptionFactory { stackTrace ??= StackTrace.current; } - SentryStackTrace sentryStackTrace; + SentryStackTrace? sentryStackTrace; if (stackTrace != null) { final frames = _stacktraceFactory.getStackFrames(stackTrace); - if (frames != null && frames.isNotEmpty) { + + if (frames.isNotEmpty) { sentryStackTrace = SentryStackTrace( frames: frames, ); diff --git a/dart/lib/src/sentry_options.dart b/dart/lib/src/sentry_options.dart index a0531c8a85..13e50be5a1 100644 --- a/dart/lib/src/sentry_options.dart +++ b/dart/lib/src/sentry_options.dart @@ -20,35 +20,23 @@ class SentryOptions { /// Default Log level if not specified Default is DEBUG static final SentryLevel _defaultDiagnosticLevel = SentryLevel.debug; - /// The DSN tells the SDK where to send the events to. If this value is not provided, the SDK will - /// just not send any events. - String dsn; - - bool _compressPayload = true; + /// The DSN tells the SDK where to send the events to. If an empty string is + /// used, the SDK will not send any events. + String? dsn; /// If [compressPayload] is `true` the outgoing HTTP payloads are compressed /// using gzip. Otherwise, the payloads are sent in plain UTF8-encoded JSON - /// text. If not specified, the compression is enabled by default. - bool get compressPayload => _compressPayload; - - set compressPayload(bool compressPayload) => - _compressPayload = compressPayload ?? _compressPayload; - - Client _httpClient = NoOpClient(); + /// text. The compression is enabled by default. + bool compressPayload = true; /// If [httpClient] is provided, it is used instead of the default client to /// make HTTP calls to Sentry.io. This is useful in tests. - Client get httpClient => _httpClient; - - set httpClient(Client httpClient) => _httpClient = httpClient ?? _httpClient; - - ClockProvider _clock = getUtcDateTime; + /// If you don't need to send events, use [NoOpClient]. + Client httpClient = NoOpClient(); /// If [clock] is provided, it is used to get time instead of the system /// clock. This is useful in tests. Should be an implementation of [ClockProvider]. - ClockProvider get clock => _clock; - - set clock(ClockProvider clock) => _clock = clock ?? _clock; + ClockProvider clock = getUtcDateTime; int _maxBreadcrumbs = 100; @@ -56,9 +44,8 @@ class SentryOptions { int get maxBreadcrumbs => _maxBreadcrumbs; set maxBreadcrumbs(int maxBreadcrumbs) { - _maxBreadcrumbs = (maxBreadcrumbs != null && maxBreadcrumbs >= 0) - ? maxBreadcrumbs - : _maxBreadcrumbs; + assert(maxBreadcrumbs >= 0); + _maxBreadcrumbs = maxBreadcrumbs; } SentryLogger _logger = noOpLogger; @@ -67,7 +54,7 @@ class SentryOptions { SentryLogger get logger => _logger; set logger(SentryLogger logger) { - _logger = logger != null ? DiagnosticLogger(logger, this).log : _logger; + _logger = DiagnosticLogger(logger, this).log; } final List _eventProcessors = []; @@ -88,49 +75,37 @@ class SentryOptions { /// along with code that inserts those bindings and activates them. List get integrations => List.unmodifiable(_integrations); - bool _debug = false; - /// Turns debug mode on or off. If debug is enabled SDK will attempt to print out useful debugging /// information if something goes wrong. Default is disabled. - bool get debug => _debug; - - set debug(bool debug) { - _debug = debug ?? _debug; - } - - SentryLevel _diagnosticLevel = _defaultDiagnosticLevel; - - set diagnosticLevel(SentryLevel level) { - _diagnosticLevel = level ?? _diagnosticLevel; - } + bool debug = false; /// minimum LogLevel to be used if debug is enabled - SentryLevel get diagnosticLevel => _diagnosticLevel; + SentryLevel diagnosticLevel = _defaultDiagnosticLevel; /// Sentry client name used for the HTTP authHeader and userAgent eg /// sentry.{language}.{platform}/{version} eg sentry.java.android/2.0.0 would be a valid case - String sentryClientName; + String? sentryClientName; /// This function is called with an SDK specific event object and can return a modified event /// object or nothing to skip reporting the event - BeforeSendCallback beforeSend; + BeforeSendCallback? beforeSend; /// This function is called with an SDK specific breadcrumb object before the breadcrumb is added /// to the scope. When nothing is returned from the function, the breadcrumb is dropped - BeforeBreadcrumbCallback beforeBreadcrumb; + BeforeBreadcrumbCallback? beforeBreadcrumb; /// Sets the release. SDK will try to automatically configure a release out of the box - String release; + String? release; /// Sets the environment. This string is freeform and not set by default. A release can be /// associated with more than one environment to separate them in the UI Think staging vs prod or /// similar. - String environment; + String? environment; /// Configures the sample rate as a percentage of events to be sent in the range of 0.0 to 1.0. if /// 1.0 is set it means that 100% of events are sent. If set to 0.1 only 10% of events will be /// sent. Events are picked randomly. Default is null (disabled) - double sampleRate; + double? sampleRate; final List _inAppExcludes = []; @@ -147,29 +122,17 @@ class SentryOptions { /// example : ['sentry'] will include exception from 'package:sentry/sentry.dart' List get inAppIncludes => List.unmodifiable(_inAppIncludes); - Transport _transport = NoOpTransport(); - /// The transport is an internal construct of the client that abstracts away the event sending. - Transport get transport => _transport; - - set transport(Transport transport) => _transport = transport ?? _transport; + Transport transport = NoOpTransport(); /// Sets the distribution. Think about it together with release and environment - String dist; + String? dist; /// The server name used in the Sentry messages. - String serverName; - - SdkVersion _sdk = SdkVersion(name: sdkName, version: sdkVersion); + String? serverName; /// Sdk object that contains the Sentry Client Name and its version - SdkVersion get sdk => _sdk; - - set sdk(SdkVersion sdk) { - _sdk = sdk ?? _sdk; - } - - bool _attachStacktrace = true; + SdkVersion sdk = SdkVersion(name: sdkName, version: sdkVersion); /// When enabled, stack traces are automatically attached to all messages logged. /// Stack traces are always attached to exceptions; @@ -180,29 +143,14 @@ class SentryOptions { /// /// Grouping in Sentry is different for events with stack traces and without. /// As a result, you will get new groups as you enable or disable this flag for certain events. - bool get attachStacktrace => _attachStacktrace; - - set attachStacktrace(bool attachStacktrace) { - _attachStacktrace = attachStacktrace ?? _attachStacktrace; - } - - PlatformChecker _platformChecker = PlatformChecker(); + bool attachStacktrace = true; /// If [platformChecker] is provided, it is used get the envirnoment. /// This is useful in tests. Should be an implementation of [PlatformChecker]. - PlatformChecker get platformChecker => _platformChecker; - - set platformChecker(PlatformChecker platformChecker) => - _platformChecker = platformChecker ?? _platformChecker; - - bool _attachThreads = false; + PlatformChecker platformChecker = PlatformChecker(); /// When enabled, all the threads are automatically attached to all logged events (Android). - bool get attachThreads => _attachThreads; - - set attachThreads(bool attachThreads) { - _attachThreads = attachThreads ?? _attachThreads; - } + bool attachThreads = false; // TODO: Scope observers, enableScopeSync @@ -250,21 +198,21 @@ class SentryOptions { /// This function is called with an SDK specific event object and can return a modified event /// object or nothing to skip reporting the event -typedef BeforeSendCallback = SentryEvent Function(SentryEvent event, +typedef BeforeSendCallback = SentryEvent? Function(SentryEvent event, {dynamic hint}); /// This function is called with an SDK specific breadcrumb object before the breadcrumb is added /// to the scope. When nothing is returned from the function, the breadcrumb is dropped -typedef BeforeBreadcrumbCallback = Breadcrumb Function(Breadcrumb breadcrumb, +typedef BeforeBreadcrumbCallback = Breadcrumb? Function(Breadcrumb? breadcrumb, {dynamic hint}); /// Are callbacks that run for every event. They can either return a new event which in most cases /// means just adding data OR return null in case the event will be dropped and not sent. -typedef EventProcessor = FutureOr Function(SentryEvent event, +typedef EventProcessor = FutureOr Function(SentryEvent event, {dynamic hint}); /// Logger interface to log useful debugging information if debug is enabled -typedef SentryLogger = Function(SentryLevel level, String message); +typedef SentryLogger = void Function(SentryLevel level, String message); /// Used to provide timestamp for logging. typedef ClockProvider = DateTime Function(); diff --git a/dart/lib/src/sentry_stack_trace_factory.dart b/dart/lib/src/sentry_stack_trace_factory.dart index 391e561e29..fdbae7b574 100644 --- a/dart/lib/src/sentry_stack_trace_factory.dart +++ b/dart/lib/src/sentry_stack_trace_factory.dart @@ -7,23 +7,16 @@ import 'sentry_options.dart'; /// converts [StackTrace] to [SentryStackFrames] class SentryStackTraceFactory { - SentryOptions _options; + final SentryOptions _options; final _absRegex = RegExp('abs +([A-Fa-f0-9]+)'); static const _stackTraceViolateDartStandard = 'This VM has been configured to produce stack traces that violate the Dart standard.'; - SentryStackTraceFactory(SentryOptions options) { - if (options == null) { - throw ArgumentError('SentryOptions is required.'); - } - _options = options; - } + SentryStackTraceFactory(this._options); /// returns the [SentryStackFrame] list from a stackTrace ([StackTrace] or [String]) List getStackFrames(dynamic stackTrace) { - if (stackTrace == null) return null; - // TODO : fix : in release mode on Safari passing a stacktrace object fails, but works if it's passed as String final chain = (stackTrace is StackTrace) ? Chain.forTrace(stackTrace) @@ -71,11 +64,11 @@ class SentryStackTraceFactory { /// converts [Frame] to [SentryStackFrame] @visibleForTesting - SentryStackFrame encodeStackTraceFrame(Frame frame, + SentryStackFrame? encodeStackTraceFrame(Frame frame, {bool symbolicated = true}) { final member = frame.member; - SentryStackFrame sentryStackFrame; + SentryStackFrame? sentryStackFrame; if (symbolicated) { final fileName = frame.uri.pathSegments.isNotEmpty @@ -93,14 +86,14 @@ class SentryStackTraceFactory { package: frame.package, ); - if (frame.line != null && frame.line >= 0) { + if (frame.line != null && frame.line! >= 0) { sentryStackFrame = sentryStackFrame.copyWith(lineNo: frame.line); } - if (frame.column != null && frame.column >= 0) { + if (frame.column != null && frame.column! >= 0) { sentryStackFrame = sentryStackFrame.copyWith(colNo: frame.column); } - } else { + } else if (member != null) { // if --split-debug-info is enabled, thats what we see: // warning: This VM has been configured to produce stack traces that violate the Dart standard. // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** @@ -153,22 +146,19 @@ class SentryStackTraceFactory { bool isInApp(Frame frame) { final scheme = frame.uri.scheme; - if (scheme == null || scheme.isEmpty) { + if (scheme.isEmpty) { return true; } - if (_options.inAppIncludes != null) { - for (final include in _options.inAppIncludes) { - if (frame.package != null && frame.package == include) { - return true; - } + for (final include in _options.inAppIncludes) { + if (frame.package != null && frame.package == include) { + return true; } } - if (_options.inAppExcludes != null) { - for (final exclude in _options.inAppExcludes) { - if (frame.package != null && frame.package == exclude) { - return false; - } + + for (final exclude in _options.inAppExcludes) { + if (frame.package != null && frame.package == exclude) { + return false; } } diff --git a/dart/lib/src/transport/http_transport.dart b/dart/lib/src/transport/http_transport.dart index 42e80c44da..b21f0fb67c 100644 --- a/dart/lib/src/transport/http_transport.dart +++ b/dart/lib/src/transport/http_transport.dart @@ -16,15 +16,11 @@ class HttpTransport implements Transport { final Dsn _dsn; - _CredentialBuilder _credentialBuilder; + late _CredentialBuilder _credentialBuilder; final Map _headers; factory HttpTransport(SentryOptions options) { - if (options == null) { - throw ArgumentError('SentryOptions is required.'); - } - if (options.httpClient is NoOpClient) { options.httpClient = Client(); } @@ -33,7 +29,7 @@ class HttpTransport implements Transport { } HttpTransport._(this._options) - : _dsn = Dsn.parse(_options.dsn), + : _dsn = Dsn.parse(_options.dsn!), _headers = _buildHeaders(_options.sdk.identifier) { _credentialBuilder = _CredentialBuilder( _dsn, @@ -83,7 +79,7 @@ class HttpTransport implements Transport { List _bodyEncoder( Map data, Map headers, { - bool compressPayload, + required bool compressPayload, }) { // [SentryIOClient] implement gzip compression // gzip compression is not available on browser @@ -118,9 +114,9 @@ class _CredentialBuilder { } static String _buildAuthHeader({ - String publicKey, - String secretKey, - String sdkIdentifier, + required String publicKey, + String? secretKey, + required String sdkIdentifier, }) { var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, ' 'sentry_key=$publicKey'; @@ -132,7 +128,7 @@ class _CredentialBuilder { return header; } - Map configure(Map headers) { + Map configure(Map headers) { return headers ..addAll( { diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml index 259d37eacb..9beeeb91ba 100644 --- a/dart/pubspec.yaml +++ b/dart/pubspec.yaml @@ -7,18 +7,22 @@ homepage: https://docs.sentry.io/platforms/dart/ repository: https://github.com/getsentry/sentry-dart environment: - sdk: ">=2.8.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' + +analyzer: + enable-experiment: + - non-nullable dependencies: - http: ^0.12.0 - meta: ^1.0.0 - stack_trace: ^1.0.0 - uuid: ^2.0.0 + http: ^0.13.0 + meta: ^1.3.0 + stack_trace: ^1.10.0 + uuid: ^3.0.1 dev_dependencies: - mockito: ^4.1.3 - pedantic: ^1.9.2 - test: ^1.15.7 - yaml: ^2.2.1 # needed for version match (code and pubspec) - collection: ^1.14.13 - coverage: ^0.14.2 + mockito: ^5.0.0 + pedantic: ^1.11.0 + test: ^1.16.5 + yaml: ^3.1.0 # needed for version match (code and pubspec) + collection: ^1.15.0 + coverage: ^1.0.1 diff --git a/dart/test/contexts_test.dart b/dart/test/contexts_test.dart index c89e3f00c2..e03daeeccf 100644 --- a/dart/test/contexts_test.dart +++ b/dart/test/contexts_test.dart @@ -111,11 +111,12 @@ void main() { test('clone context', () { final clone = contexts.clone(); - expect(clone.app.toJson(), contexts.app.toJson()); - expect(clone.browser.toJson(), contexts.browser.toJson()); - expect(clone.device.toJson(), contexts.device.toJson()); - expect(clone.operatingSystem.toJson(), contexts.operatingSystem.toJson()); - expect(clone.gpu.toJson(), contexts.gpu.toJson()); + expect(clone.app!.toJson(), contexts.app!.toJson()); + expect(clone.browser!.toJson(), contexts.browser!.toJson()); + expect(clone.device!.toJson(), contexts.device!.toJson()); + expect( + clone.operatingSystem!.toJson(), contexts.operatingSystem!.toJson()); + expect(clone.gpu!.toJson(), contexts.gpu!.toJson()); contexts.runtimes.forEach((element) { expect( @@ -136,7 +137,7 @@ void main() { final contexts = Contexts.fromJson(jsonDecode(jsonContexts)); expect( MapEquality().equals( - contexts.operatingSystem.toJson(), + contexts.operatingSystem!.toJson(), { 'build': '19H2', 'rooted': false, @@ -149,7 +150,7 @@ void main() { true, ); expect( - MapEquality().equals(contexts.device.toJson(), { + MapEquality().equals(contexts.device!.toJson(), { 'simulator': true, 'model_id': 'simulator', 'arch': 'x86', @@ -167,7 +168,7 @@ void main() { expect( MapEquality().equals( - contexts.app.toJson(), + contexts.app!.toJson(), { 'app_name': 'sentry_flutter_example', 'app_version': '0.1.2', @@ -189,12 +190,12 @@ void main() { true, ); expect( - MapEquality().equals(contexts.browser.toJson(), {'version': '12.3.4'}), + MapEquality().equals(contexts.browser!.toJson(), {'version': '12.3.4'}), true, ); expect( MapEquality() - .equals(contexts.gpu.toJson(), {'name': 'Radeon', 'version': '1'}), + .equals(contexts.gpu!.toJson(), {'name': 'Radeon', 'version': '1'}), true, ); }); diff --git a/dart/test/default_integrations_test.dart b/dart/test/default_integrations_test.dart index 275f2a3f7e..55d07845c8 100644 --- a/dart/test/default_integrations_test.dart +++ b/dart/test/default_integrations_test.dart @@ -1,11 +1,11 @@ -import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; import 'mocks.dart'; +import 'mocks/mock_hub.dart'; void main() { - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); @@ -42,12 +42,8 @@ void main() { // that handles and captures it. await handleIsolateError(fixture.hub, fixture.options, error); - final event = verify( - await fixture.hub.captureEvent( - captureAny, - stackTrace: captureAnyNamed('stackTrace'), - ), - ).captured.first as SentryEvent; + expect(fixture.hub.captureEventCalls.length, 1); + final event = fixture.hub.captureEventCalls.first.event; expect(SentryLevel.fatal, event.level); @@ -102,10 +98,8 @@ void main() { await integration(fixture.hub, fixture.options); - final event = verify( - await fixture.hub - .captureEvent(captureAny, stackTrace: captureAnyNamed('stackTrace')), - ).captured.first as SentryEvent; + expect(fixture.hub.captureEventCalls.length, 1); + final event = fixture.hub.captureEventCalls.first.event; expect(SentryLevel.fatal, event.level); @@ -118,5 +112,5 @@ void main() { class Fixture { final hub = MockHub(); - final options = SentryOptions(); + final options = SentryOptions(dsn: fakeDsn); } diff --git a/dart/test/exception_factory_test.dart b/dart/test/exception_factory_test.dart index 0933f5b1d3..283e2579a9 100644 --- a/dart/test/exception_factory_test.dart +++ b/dart/test/exception_factory_test.dart @@ -3,11 +3,15 @@ import 'package:sentry/src/sentry_exception_factory.dart'; import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:test/test.dart'; +import 'mocks.dart'; + void main() { group('Exception factory', () { - final options = SentryOptions(); + final options = SentryOptions(dsn: fakeDsn); final exceptionFactory = SentryExceptionFactory( - options: options, stacktraceFactory: SentryStackTraceFactory(options)); + options, + SentryStackTraceFactory(options), + ); test('getSentryException with frames', () { SentryException sentryException; @@ -21,7 +25,22 @@ void main() { } expect(sentryException.type, 'StateError'); - expect(sentryException.stackTrace.frames, isNotEmpty); + expect(sentryException.stackTrace!.frames, isNotEmpty); + }); + + test('getSentryException without frames', () { + SentryException sentryException; + try { + throw StateError('a state error'); + } catch (err, _) { + sentryException = exceptionFactory.getSentryException( + err, + stackTrace: '', + ); + } + + expect(sentryException.type, 'StateError'); + expect(sentryException.stackTrace, isNull); }); test('getSentryException without frames', () { @@ -55,26 +74,9 @@ void main() { } expect(sentryException.type, 'StateError'); - expect(sentryException.stackTrace.frames.first.lineNo, 46); - expect(sentryException.stackTrace.frames.first.colNo, 9); - expect(sentryException.stackTrace.frames.first.fileName, 'test.dart'); + expect(sentryException.stackTrace!.frames.first.lineNo, 46); + expect(sentryException.stackTrace!.frames.first.colNo, 9); + expect(sentryException.stackTrace!.frames.first.fileName, 'test.dart'); }); }); - - test("options can't be null", () { - expect( - () => SentryExceptionFactory( - options: null, - stacktraceFactory: SentryStackTraceFactory(SentryOptions()), - ), - throwsArgumentError); - }); - - test("stacktraceFactory can't be null", () { - expect( - () => SentryExceptionFactory( - options: SentryOptions(), stacktraceFactory: null), - throwsArgumentError, - ); - }); } diff --git a/dart/test/fake_platform_checker.dart b/dart/test/fake_platform_checker.dart index a1eb3ddaa2..816ad3d607 100644 --- a/dart/test/fake_platform_checker.dart +++ b/dart/test/fake_platform_checker.dart @@ -20,9 +20,9 @@ class FakePlatformChecker implements PlatformChecker { _profileMode = true; } - bool _releaseMode; - bool _debugMode; - bool _profileMode; + late bool _releaseMode; + late bool _debugMode; + late bool _profileMode; @override bool isReleaseMode() { diff --git a/dart/test/http_client/sentry_http_client_test.dart b/dart/test/http_client/sentry_http_client_test.dart index e7193c9c8d..c3a6f8838c 100644 --- a/dart/test/http_client/sentry_http_client_test.dart +++ b/dart/test/http_client/sentry_http_client_test.dart @@ -7,7 +7,7 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/http_client/sentry_http_client.dart'; import 'package:test/test.dart'; -import '../mocks.dart'; +import '../mocks/mock_hub.dart'; void main() { group(SentryHttpClient, () { @@ -21,12 +21,11 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); - final response = await client.get('https://example.com'); + final response = await client.get(Uri.parse('https://example.com')); expect(response.statusCode, 200); - final breadcrumb = verify(mockHub.addBreadcrumb(captureAny)) - .captured - .single as Breadcrumb; + expect(mockHub.addBreadcrumbCalls.length, 1); + final breadcrumb = mockHub.addBreadcrumbCalls.first.crumb; expect(breadcrumb.type, 'http'); expect(breadcrumb.data, { @@ -47,12 +46,11 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); - final response = await client.get('https://example.com'); + final response = await client.get(Uri.parse('https://example.com')); expect(response.statusCode, 404); - final breadcrumb = verify(mockHub.addBreadcrumb(captureAny)) - .captured - .single as Breadcrumb; + expect(mockHub.addBreadcrumbCalls.length, 1); + final breadcrumb = mockHub.addBreadcrumbCalls.first.crumb; expect(breadcrumb.type, 'http'); expect(breadcrumb.data, { @@ -73,12 +71,11 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); - final response = await client.post('https://example.com'); + final response = await client.post(Uri.parse('https://example.com')); expect(response.statusCode, 200); - final breadcrumb = verify(mockHub.addBreadcrumb(captureAny)) - .captured - .single as Breadcrumb; + expect(mockHub.addBreadcrumbCalls.length, 1); + final breadcrumb = mockHub.addBreadcrumbCalls.first.crumb; expect(breadcrumb.type, 'http'); expect(breadcrumb.data, { @@ -98,12 +95,11 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); - final response = await client.put('https://example.com'); + final response = await client.put(Uri.parse('https://example.com')); expect(response.statusCode, 200); - final breadcrumb = verify(mockHub.addBreadcrumb(captureAny)) - .captured - .single as Breadcrumb; + expect(mockHub.addBreadcrumbCalls.length, 1); + final breadcrumb = mockHub.addBreadcrumbCalls.first.crumb; expect(breadcrumb.type, 'http'); expect(breadcrumb.data, { @@ -123,12 +119,11 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); - final response = await client.delete('https://example.com'); + final response = await client.delete(Uri.parse('https://example.com')); expect(response.statusCode, 200); - final breadcrumb = verify(mockHub.addBreadcrumb(captureAny)) - .captured - .single as Breadcrumb; + expect(mockHub.addBreadcrumbCalls.length, 1); + final breadcrumb = mockHub.addBreadcrumbCalls.first.crumb; expect(breadcrumb.type, 'http'); expect(breadcrumb.data, { @@ -156,15 +151,15 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); try { - await client.get('https://example.com'); + await client.get(Uri.parse('https://example.com')); fail('Method did not throw'); } on ClientException catch (e) { expect(e.message, 'test'); expect(e.uri, url); } - verifyNever(mockHub.addBreadcrumb(captureAny)); - verifyNever(mockHub.captureException(captureAny)); + expect(mockHub.addBreadcrumbCalls.length, 0); + expect(mockHub.captureExceptionCalls.length, 0); }); /// SocketException are only a thing on dart:io platforms. @@ -182,14 +177,14 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); try { - await client.get('https://example.com'); + await client.get(Uri.parse('https://example.com')); fail('Method did not throw'); } on SocketException catch (e) { expect(e.message, 'test'); } - verifyNever(mockHub.addBreadcrumb(captureAny)); - verifyNever(mockHub.captureException(captureAny)); + expect(mockHub.addBreadcrumbCalls.length, 0); + expect(mockHub.captureExceptionCalls.length, 0); }); test('close does get called for user defined client', () async { @@ -200,8 +195,8 @@ void main() { final client = SentryHttpClient(client: mockClient, hub: mockHub); client.close(); - verifyNever(mockHub.addBreadcrumb(captureAny)); - verifyNever(mockHub.captureException(captureAny)); + expect(mockHub.addBreadcrumbCalls.length, 0); + expect(mockHub.captureExceptionCalls.length, 0); verify(mockClient.close()); }); }); diff --git a/dart/test/http_transport_test.dart b/dart/test/http_transport_test.dart deleted file mode 100644 index 1cb3d6b75c..0000000000 --- a/dart/test/http_transport_test.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:sentry/src/transport/http_transport.dart'; -import 'package:test/test.dart'; - -void main() { - test("options can't be null", () { - expect(() => HttpTransport(null), throwsArgumentError); - }); -} diff --git a/dart/test/hub_test.dart b/dart/test/hub_test.dart index 8b18567828..2e1669efa1 100644 --- a/dart/test/hub_test.dart +++ b/dart/test/hub_test.dart @@ -1,17 +1,15 @@ -import 'dart:async'; - import 'package:collection/collection.dart'; -import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/hub.dart'; import 'package:test/test.dart'; import 'mocks.dart'; +import 'mocks/mock_sentry_client.dart'; void main() { - bool scopeEquals(Scope a, Scope b) { + bool scopeEquals(Scope? a, Scope b) { return identical(a, b) || - a.level == b.level && + a!.level == b.level && a.transaction == b.transaction && a.user == b.user && IterableEquality().equals(a.fingerprint, b.fingerprint) && @@ -21,16 +19,6 @@ void main() { } group('Hub instantiation', () { - test('should not instantiate without a sentryOptions', () { - Hub hub; - expect(() => hub = Hub(null), throwsArgumentError); - expect(hub, null); - }); - - test('should not instantiate without a dsn', () { - expect(() => Hub(SentryOptions()), throwsArgumentError); - }); - test('should instantiate with a dsn', () { final hub = Hub(SentryOptions(dsn: fakeDsn)); expect(hub.isEnabled, true); @@ -38,9 +26,9 @@ void main() { }); group('Hub captures', () { - Hub hub; - SentryOptions options; - MockSentryClient client; + var options = SentryOptions(dsn: fakeDsn); + var hub = Hub(options); + var client = MockSentryClient(); setUp(() { options = SentryOptions(dsn: fakeDsn); @@ -53,56 +41,52 @@ void main() { 'should capture event with the default scope', () async { await hub.captureEvent(fakeEvent); + + var scope = client.captureEventCalls.first.scope; + expect( - scopeEquals( - verify( - client.captureEvent( - fakeEvent, - scope: captureAnyNamed('scope'), - ), - ).captured.first, - Scope(options), - ), - true, + client.captureEventCalls.first.event, + fakeEvent, ); + + expect(scopeEquals(scope, Scope(options)), true); }, ); test('should capture exception', () async { await hub.captureException(fakeException); - verify(client.captureException(fakeException, scope: anyNamed('scope'))) - .called(1); + expect(client.captureExceptionCalls.length, 1); + expect( + client.captureExceptionCalls.first.throwable, + fakeException, + ); + expect(client.captureExceptionCalls.first.scope, isNotNull); }); test('should capture message', () async { - await hub.captureMessage(fakeMessage.formatted, - level: SentryLevel.warning); - verify( - client.captureMessage( - fakeMessage.formatted, - level: SentryLevel.warning, - scope: anyNamed('scope'), - ), - ).called(1); + await hub.captureMessage( + fakeMessage.formatted, + level: SentryLevel.warning, + ); + + expect(client.captureMessageCalls.length, 1); + expect(client.captureMessageCalls.first.formatted, fakeMessage.formatted); + expect(client.captureMessageCalls.first.level, SentryLevel.warning); + expect(client.captureMessageCalls.first.scope, isNotNull); }); test('should save the lastEventId', () async { final event = SentryEvent(); final eventId = event.eventId; - when(client.captureEvent( - event, - scope: anyNamed('scope'), - hint: anyNamed('hint'), - )).thenAnswer((_) => Future.value(event.eventId)); final returnedId = await hub.captureEvent(event); expect(eventId.toString(), returnedId.toString()); }); }); group('Hub scope', () { - Hub hub; - SentryClient client; + var hub = Hub(SentryOptions(dsn: fakeDsn)); + var client = MockSentryClient(); setUp(() { hub = Hub(SentryOptions(dsn: fakeDsn)); @@ -119,12 +103,10 @@ void main() { }); await hub.captureEvent(fakeEvent); - final scope = verify( - client.captureEvent( - fakeEvent, - scope: captureAnyNamed('scope'), - ), - ).captured.first as Scope; + expect(client.captureEventCalls.isNotEmpty, true); + expect(client.captureEventCalls.first.event, fakeEvent); + expect(client.captureEventCalls.first.scope, isNotNull); + final scope = client.captureEventCalls.first.scope; expect( scopeEquals( @@ -151,8 +133,8 @@ void main() { }); group('Hub Client', () { - Hub hub; - SentryClient client; + late Hub hub; + late SentryClient client; SentryOptions options; setUp(() { @@ -166,19 +148,16 @@ void main() { final client2 = MockSentryClient(); hub.bindClient(client2); await hub.captureEvent(fakeEvent); - verify( - client2.captureEvent( - fakeEvent, - scope: anyNamed('scope'), - ), - ).called(1); + expect(client2.captureEventCalls.length, 1); + expect(client2.captureEventCalls.first.event, fakeEvent); + expect(client2.captureEventCalls.first.scope, isNotNull); }); test('should close its client', () { hub.close(); expect(hub.isEnabled, false); - verify(client.close()).called(1); + expect((client as MockSentryClient).closeCalls, 1); }); }); diff --git a/dart/test/mocks.dart b/dart/test/mocks.dart index c26bbf75de..b64290a589 100644 --- a/dart/test/mocks.dart +++ b/dart/test/mocks.dart @@ -1,20 +1,15 @@ -import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/protocol.dart'; -class MockSentryClient extends Mock implements SentryClient {} - -class MockTransport extends Mock implements Transport {} - -class MockHub extends Mock implements Hub {} - -class MockIntegration extends Mock implements Integration {} - final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; final fakeException = Exception('Error'); -final fakeMessage = Message('message 1', template: 'message %d', params: ['1']); +final fakeMessage = Message( + 'message 1', + template: 'message %d', + params: ['1'], +); final fakeUser = User(id: '1', email: 'test@test'); @@ -32,28 +27,31 @@ final fakeEvent = SentryEvent( modules: const {'module1': 'factory'}, sdk: SdkVersion(name: 'sdk1', version: '1.0.0'), user: User( - id: '800', - username: 'first-user', - email: 'first@user.lan', - ipAddress: '127.0.0.1', - extras: {'first-sign-in': '2020-01-01'}), + id: '800', + username: 'first-user', + email: 'first@user.lan', + ipAddress: '127.0.0.1', + extras: {'first-sign-in': '2020-01-01'}, + ), breadcrumbs: [ Breadcrumb( - message: 'UI Lifecycle', - timestamp: DateTime.now().toUtc(), - category: 'ui.lifecycle', - type: 'navigation', - data: {'screen': 'MainActivity', 'state': 'created'}, - level: SentryLevel.info) + message: 'UI Lifecycle', + timestamp: DateTime.now().toUtc(), + category: 'ui.lifecycle', + type: 'navigation', + data: {'screen': 'MainActivity', 'state': 'created'}, + level: SentryLevel.info, + ) ], contexts: Contexts( operatingSystem: const OperatingSystem( - name: 'Android', - version: '5.0.2', - build: 'LRX22G.P900XXS0BPL2', - kernelVersion: - 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', - rooted: false), + name: 'Android', + version: '5.0.2', + build: 'LRX22G.P900XXS0BPL2', + kernelVersion: + 'Linux version 3.4.39-5726670 (dpi@SWHC3807) (gcc version 4.8 (GCC) ) #1 SMP PREEMPT Thu Dec 1 19:42:39 KST 2016', + rooted: false, + ), runtimes: [const SentryRuntime(name: 'ART', version: '5')], app: App( name: 'Example Dart App', diff --git a/dart/test/mocks/mock_hub.dart b/dart/test/mocks/mock_hub.dart new file mode 100644 index 0000000000..44ee762480 --- /dev/null +++ b/dart/test/mocks/mock_hub.dart @@ -0,0 +1,121 @@ +import 'package:sentry/sentry.dart'; + +class MockHub implements Hub { + List captureEventCalls = []; + List captureExceptionCalls = []; + List captureMessageCalls = []; + List addBreadcrumbCalls = []; + List bindClientCalls = []; + int closeCalls = 0; + + @override + void addBreadcrumb(Breadcrumb crumb, {dynamic hint}) { + addBreadcrumbCalls.add(AddBreadcrumbCall(crumb, hint)); + } + + @override + void bindClient(SentryClient client) { + bindClientCalls.add(client); + } + + @override + Future captureEvent( + SentryEvent event, { + dynamic stackTrace, + dynamic hint, + }) async { + captureEventCalls.add(CaptureEventCall(event, stackTrace, hint)); + return event.eventId; + } + + @override + Future captureException( + dynamic throwable, { + dynamic stackTrace, + dynamic hint, + }) async { + captureExceptionCalls + .add(CaptureExceptionCall(throwable, stackTrace, hint)); + return SentryId.newId(); + } + + @override + Future captureMessage( + String? message, { + SentryLevel? level = SentryLevel.info, + String? template, + List? params, + dynamic hint, + }) async { + captureMessageCalls + .add(CaptureMessageCall(message, level, template, params, hint)); + return SentryId.newId(); + } + + @override + Hub clone() { + // TODO: implement clone + throw UnimplementedError(); + } + + @override + void close() { + closeCalls = closeCalls + 1; + } + + @override + void configureScope(callback) { + // TODO: implement configureScope + } + + @override + // TODO: implement isEnabled + bool get isEnabled => throw UnimplementedError(); + + @override + // TODO: implement lastEventId + SentryId get lastEventId => throw UnimplementedError(); +} + +class CaptureEventCall { + final SentryEvent event; + final dynamic stackTrace; + final dynamic hint; + + CaptureEventCall(this.event, this.stackTrace, this.hint); +} + +class CaptureExceptionCall { + final dynamic throwable; + final dynamic stackTrace; + final dynamic hint; + + CaptureExceptionCall( + this.throwable, + this.stackTrace, + this.hint, + ); +} + +class CaptureMessageCall { + final String? message; + final SentryLevel? level; + final String? template; + final List? params; + final dynamic hint; + + CaptureMessageCall( + this.message, + this.level, + this.template, + this.params, + this.hint, + ); +} + +class AddBreadcrumbCall { + final Breadcrumb crumb; + final dynamic hint; + + AddBreadcrumbCall(this.crumb, this.hint); +} diff --git a/dart/test/mocks/mock_integration.dart b/dart/test/mocks/mock_integration.dart new file mode 100644 index 0000000000..150cd55ac2 --- /dev/null +++ b/dart/test/mocks/mock_integration.dart @@ -0,0 +1,18 @@ +import 'dart:async'; + +import 'package:sentry/sentry.dart'; + +class MockIntegration implements Integration { + int closeCalls = 0; + int callCalls = 0; + + @override + FutureOr call(Hub hub, SentryOptions options) async { + callCalls = callCalls + 1; + } + + @override + void close() { + closeCalls = closeCalls + 1; + } +} diff --git a/dart/test/mocks/mock_sentry_client.dart b/dart/test/mocks/mock_sentry_client.dart new file mode 100644 index 0000000000..d7e54fca23 --- /dev/null +++ b/dart/test/mocks/mock_sentry_client.dart @@ -0,0 +1,111 @@ +import 'package:sentry/sentry.dart'; + +class MockSentryClient implements SentryClient { + List captureEventCalls = []; + List captureExceptionCalls = []; + List captureMessageCalls = []; + int closeCalls = 0; + + @override + Future captureEvent( + SentryEvent event, { + Scope? scope, + dynamic stackTrace, + dynamic hint, + }) async { + captureEventCalls.add(CaptureEventCall( + event, + scope, + stackTrace, + hint, + )); + return event.eventId; + } + + @override + Future captureException( + dynamic throwable, { + dynamic stackTrace, + Scope? scope, + dynamic hint, + }) async { + captureExceptionCalls.add(CaptureExceptionCall( + throwable, + stackTrace, + scope, + hint, + )); + return SentryId.newId(); + } + + @override + Future captureMessage( + String? formatted, { + SentryLevel? level = SentryLevel.info, + String? template, + List? params, + Scope? scope, + dynamic hint, + }) async { + captureMessageCalls.add(CaptureMessageCall( + formatted, + level, + template, + params, + scope, + hint, + )); + return SentryId.newId(); + } + + @override + void close() { + closeCalls = closeCalls + 1; + } +} + +class CaptureEventCall { + final SentryEvent event; + final Scope? scope; + final dynamic stackTrace; + final dynamic hint; + + CaptureEventCall( + this.event, + this.scope, + this.stackTrace, + this.hint, + ); +} + +class CaptureExceptionCall { + final dynamic throwable; + final dynamic stackTrace; + final Scope? scope; + final dynamic hint; + + CaptureExceptionCall( + this.throwable, + this.stackTrace, + this.scope, + this.hint, + ); +} + +class CaptureMessageCall { + final String? formatted; + final SentryLevel? level; + final String? template; + final List? params; + final Scope? scope; + final dynamic hint; + + CaptureMessageCall( + this.formatted, + this.level, + this.template, + this.params, + this.scope, + this.hint, + ); +} diff --git a/dart/test/mocks/mock_transport.dart b/dart/test/mocks/mock_transport.dart new file mode 100644 index 0000000000..313ed7eb72 --- /dev/null +++ b/dart/test/mocks/mock_transport.dart @@ -0,0 +1,15 @@ +import 'package:sentry/sentry.dart'; + +class MockTransport implements Transport { + List events = []; + + bool called(int calls) { + return events.length == calls; + } + + @override + Future send(SentryEvent event) async { + events.add(event); + return event.eventId; + } +} diff --git a/dart/test/protocol/app_test.dart b/dart/test/protocol/app_test.dart index 89f0073787..fccfdcea19 100644 --- a/dart/test/protocol/app_test.dart +++ b/dart/test/protocol/app_test.dart @@ -39,7 +39,7 @@ void main() { }); } -App _generate({DateTime startTime}) => App( +App _generate({DateTime? startTime}) => App( name: 'name', version: 'version', identifier: 'identifier', diff --git a/dart/test/protocol/breadcrumb_test.dart b/dart/test/protocol/breadcrumb_test.dart index ddd47f3f25..2c0f6a97a7 100644 --- a/dart/test/protocol/breadcrumb_test.dart +++ b/dart/test/protocol/breadcrumb_test.dart @@ -37,7 +37,7 @@ void main() { }); } -Breadcrumb _generate({DateTime timestamp}) => Breadcrumb( +Breadcrumb _generate({DateTime? timestamp}) => Breadcrumb( message: 'message', timestamp: timestamp ?? DateTime.now(), data: {'key': 'value'}, diff --git a/dart/test/protocol/contexts_test.dart b/dart/test/protocol/contexts_test.dart index b25960078c..09893f4137 100644 --- a/dart/test/protocol/contexts_test.dart +++ b/dart/test/protocol/contexts_test.dart @@ -32,15 +32,15 @@ void main() { gpu: gpu, ); - expect(device.toJson(), copy.device.toJson()); - expect(os.toJson(), copy.operatingSystem.toJson()); + expect(device.toJson(), copy.device!.toJson()); + expect(os.toJson(), copy.operatingSystem!.toJson()); expect( ListEquality().equals(runtimes, copy.runtimes), true, ); - expect(app.toJson(), copy.app.toJson()); - expect(browser.toJson(), copy.browser.toJson()); - expect(gpu.toJson(), copy.gpu.toJson()); + expect(app.toJson(), copy.app!.toJson()); + expect(browser.toJson(), copy.browser!.toJson()); + expect(gpu.toJson(), copy.gpu!.toJson()); expect('value', copy['extra']); }); } diff --git a/dart/test/protocol/debug_meta_test.dart b/dart/test/protocol/debug_meta_test.dart index 0c07a47f63..b611bc9828 100644 --- a/dart/test/protocol/debug_meta_test.dart +++ b/dart/test/protocol/debug_meta_test.dart @@ -18,7 +18,7 @@ void main() { final newSdkInfo = SdkInfo( sdkName: 'sdkName1', ); - final newImageList = [DebugImage(uuid: 'uuid1')]; + final newImageList = [DebugImage(type: 'macho', uuid: 'uuid1')]; final copy = data.copyWith( sdk: newSdkInfo, @@ -30,7 +30,7 @@ void main() { true, ); expect( - MapEquality().equals(newSdkInfo.toJson(), copy.sdk.toJson()), + MapEquality().equals(newSdkInfo.toJson(), copy.sdk!.toJson()), true, ); }); @@ -40,5 +40,5 @@ DebugMeta _generate() => DebugMeta( sdk: SdkInfo( sdkName: 'sdkName', ), - images: [DebugImage(uuid: 'uuid')], + images: [DebugImage(type: 'macho', uuid: 'uuid')], ); diff --git a/dart/test/protocol/device_test.dart b/dart/test/protocol/device_test.dart index 3cc6d45fde..673be6c9f5 100644 --- a/dart/test/protocol/device_test.dart +++ b/dart/test/protocol/device_test.dart @@ -75,7 +75,7 @@ void main() { }); } -Device _generate({DateTime testBootTime}) => Device( +Device _generate({DateTime? testBootTime}) => Device( name: 'name', family: 'family', model: 'model', diff --git a/dart/test/protocol/sentry_exception_test.dart b/dart/test/protocol/sentry_exception_test.dart index 93a1008b64..cad8cd4cba 100644 --- a/dart/test/protocol/sentry_exception_test.dart +++ b/dart/test/protocol/sentry_exception_test.dart @@ -111,8 +111,8 @@ void main() { expect('value1', copy.value); expect('module1', copy.module); expect(2, copy.threadId); - expect(mechanism.toJson(), copy.mechanism.toJson()); - expect(stackTrace.toJson(), copy.stackTrace.toJson()); + expect(mechanism.toJson(), copy.mechanism!.toJson()); + expect(stackTrace.toJson(), copy.stackTrace!.toJson()); }); } diff --git a/dart/test/scope_test.dart b/dart/test/scope_test.dart index b0956734cf..f5c7252208 100644 --- a/dart/test/scope_test.dart +++ b/dart/test/scope_test.dart @@ -1,9 +1,13 @@ +import 'dart:async'; + import 'package:collection/collection.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; +import 'mocks.dart'; + void main() { - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); @@ -217,7 +221,7 @@ void main() { expect(sut.user, null); - expect(sut.fingerprint, null); + expect(sut.fingerprint.length, 0); expect(sut.tags.length, 0); @@ -258,7 +262,7 @@ void main() { tags: const {'etag': '987'}, extra: const {'e-infos': 'abc'}, ); - final scope = Scope(SentryOptions()) + final scope = Scope(SentryOptions(dsn: fakeDsn)) ..user = scopeUser ..fingerprint = ['example-dart'] ..addBreadcrumb(breadcrumb) @@ -268,21 +272,21 @@ void main() { ..setExtra('company-name', 'Dart Inc') ..setContexts('theme', 'material') ..addEventProcessor( - (event, {hint}) => event..tags.addAll({'page-locale': 'en-us'}), + (event, {hint}) => event..tags?.addAll({'page-locale': 'en-us'}), ); final updatedEvent = await scope.applyToEvent(event, null); - expect(updatedEvent.user, scopeUser); - expect(updatedEvent.transaction, '/example/app'); - expect(updatedEvent.fingerprint, ['example-dart']); - expect(updatedEvent.breadcrumbs, [breadcrumb]); - expect(updatedEvent.level, SentryLevel.warning); - expect(updatedEvent.tags, + expect(updatedEvent?.user, scopeUser); + expect(updatedEvent?.transaction, '/example/app'); + expect(updatedEvent?.fingerprint, ['example-dart']); + expect(updatedEvent?.breadcrumbs, [breadcrumb]); + expect(updatedEvent?.level, SentryLevel.warning); + expect(updatedEvent?.tags, {'etag': '987', 'build': '579', 'page-locale': 'en-us'}); expect( - updatedEvent.extra, {'e-infos': 'abc', 'company-name': 'Dart Inc'}); - expect(updatedEvent.contexts['theme'], {'value': 'material'}); + updatedEvent?.extra, {'e-infos': 'abc', 'company-name': 'Dart Inc'}); + expect(updatedEvent?.contexts['theme'], {'value': 'material'}); }); test('should not apply the scope properties when event already has it ', @@ -296,7 +300,7 @@ void main() { fingerprint: ['event-fingerprint'], breadcrumbs: [eventBreadcrumb], ); - final scope = Scope(SentryOptions()) + final scope = Scope(SentryOptions(dsn: fakeDsn)) ..user = scopeUser ..fingerprint = ['example-dart'] ..addBreadcrumb(breadcrumb) @@ -304,10 +308,10 @@ void main() { final updatedEvent = await scope.applyToEvent(event, null); - expect(updatedEvent.user, eventUser); - expect(updatedEvent.transaction, '/event/transaction'); - expect(updatedEvent.fingerprint, ['event-fingerprint']); - expect(updatedEvent.breadcrumbs, [eventBreadcrumb]); + expect(updatedEvent?.user, eventUser); + expect(updatedEvent?.transaction, '/event/transaction'); + expect(updatedEvent?.fingerprint, ['event-fingerprint']); + expect(updatedEvent?.breadcrumbs, [eventBreadcrumb]); }); test( @@ -323,7 +327,7 @@ void main() { operatingSystem: OperatingSystem(name: 'event-os'), ), ); - final scope = Scope(SentryOptions()) + final scope = Scope(SentryOptions(dsn: fakeDsn)) ..setContexts( Device.type, Device(name: 'context-device'), @@ -351,18 +355,18 @@ void main() { final updatedEvent = await scope.applyToEvent(event, null); - expect(updatedEvent.contexts[Device.type].name, 'event-device'); - expect(updatedEvent.contexts[App.type].name, 'event-app'); - expect(updatedEvent.contexts[Gpu.type].name, 'event-gpu'); - expect(updatedEvent.contexts[SentryRuntime.listType].first.name, + expect(updatedEvent?.contexts[Device.type].name, 'event-device'); + expect(updatedEvent?.contexts[App.type].name, 'event-app'); + expect(updatedEvent?.contexts[Gpu.type].name, 'event-gpu'); + expect(updatedEvent?.contexts[SentryRuntime.listType].first.name, 'event-runtime'); - expect(updatedEvent.contexts[Browser.type].name, 'event-browser'); - expect(updatedEvent.contexts[OperatingSystem.type].name, 'event-os'); + expect(updatedEvent?.contexts[Browser.type].name, 'event-browser'); + expect(updatedEvent?.contexts[OperatingSystem.type].name, 'event-os'); }); test('should apply the scope.contexts values ', () async { final event = SentryEvent(); - final scope = Scope(SentryOptions()) + final scope = Scope(SentryOptions(dsn: fakeDsn)) ..setContexts(Device.type, Device(name: 'context-device')) ..setContexts(App.type, App(name: 'context-app')) ..setContexts(Gpu.type, Gpu(name: 'context-gpu')) @@ -376,48 +380,48 @@ void main() { final updatedEvent = await scope.applyToEvent(event, null); - expect(updatedEvent.contexts[Device.type].name, 'context-device'); - expect(updatedEvent.contexts[App.type].name, 'context-app'); - expect(updatedEvent.contexts[Gpu.type].name, 'context-gpu'); + expect(updatedEvent?.contexts[Device.type].name, 'context-device'); + expect(updatedEvent?.contexts[App.type].name, 'context-app'); + expect(updatedEvent?.contexts[Gpu.type].name, 'context-gpu'); expect( - updatedEvent.contexts[SentryRuntime.listType].first.name, + updatedEvent?.contexts[SentryRuntime.listType].first.name, 'context-runtime', ); - expect(updatedEvent.contexts[Browser.type].name, 'context-browser'); - expect(updatedEvent.contexts[OperatingSystem.type].name, 'context-os'); - expect(updatedEvent.contexts['theme']['value'], 'material'); - expect(updatedEvent.contexts['version']['value'], 9); - expect(updatedEvent.contexts['location'], {'city': 'London'}); + expect(updatedEvent?.contexts[Browser.type].name, 'context-browser'); + expect(updatedEvent?.contexts[OperatingSystem.type].name, 'context-os'); + expect(updatedEvent?.contexts['theme']['value'], 'material'); + expect(updatedEvent?.contexts['version']['value'], 9); + expect(updatedEvent?.contexts['location'], {'city': 'London'}); }); test('should apply the scope level', () async { final event = SentryEvent(level: SentryLevel.warning); - final scope = Scope(SentryOptions())..level = SentryLevel.error; + final scope = Scope(SentryOptions(dsn: fakeDsn)) + ..level = SentryLevel.error; final updatedEvent = await scope.applyToEvent(event, null); - expect(updatedEvent.level, SentryLevel.error); + expect(updatedEvent?.level, SentryLevel.error); }); }); - - test("options can't be null", () { - expect(() => Scope(null), throwsArgumentError); - }); } class Fixture { Scope getSut({ int maxBreadcrumbs = 100, - BeforeBreadcrumbCallback beforeBreadcrumbCallback, + BeforeBreadcrumbCallback? beforeBreadcrumbCallback, }) { - final options = SentryOptions(); + final options = SentryOptions(dsn: fakeDsn); options.maxBreadcrumbs = maxBreadcrumbs; options.beforeBreadcrumb = beforeBreadcrumbCallback; return Scope(options); } - SentryEvent processor(SentryEvent event, {dynamic hint}) => null; + FutureOr processor(SentryEvent event, {dynamic hint}) { + return null; + } - Breadcrumb beforeBreadcrumbCallback(Breadcrumb breadcrumb, {dynamic hint}) => + Breadcrumb? beforeBreadcrumbCallback(Breadcrumb? breadcrumb, + {dynamic hint}) => null; } diff --git a/dart/test/sentry_client_test.dart b/dart/test/sentry_client_test.dart index 9042c847c2..0c99b0cd85 100644 --- a/dart/test/sentry_client_test.dart +++ b/dart/test/sentry_client_test.dart @@ -1,13 +1,13 @@ -import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:test/test.dart'; import 'mocks.dart'; +import 'mocks/mock_transport.dart'; void main() { group('SentryClient captures message', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); setUp(() { options = SentryOptions(dsn: fakeDsn); @@ -22,9 +22,7 @@ void main() { stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', ); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.stackTrace is SentryStackTrace, true); }); @@ -34,9 +32,7 @@ void main() { final event = SentryEvent(); await client.captureEvent(event); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.stackTrace is SentryStackTrace, true); }); @@ -46,9 +42,7 @@ void main() { final event = SentryEvent(); await client.captureEvent(event); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.stackTrace, isNull); }); @@ -68,12 +62,10 @@ void main() { stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', ); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.stackTrace, isNull); - expect(capturedEvent.exception.stackTrace, isNotNull); + expect(capturedEvent.exception!.stackTrace, isNotNull); }); test('should not attach event stacktrace if event has exception', () async { @@ -94,12 +86,10 @@ void main() { stackTrace: '#0 baz (file:///pathto/test.dart:50:3)', ); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.stackTrace, isNull); - expect(capturedEvent.exception.stackTrace, isNotNull); + expect(capturedEvent.exception!.stackTrace, isNotNull); }); test('should capture message', () async { @@ -111,13 +101,11 @@ void main() { level: SentryLevel.error, ); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; - expect(capturedEvent.message.formatted, 'simple message 1'); - expect(capturedEvent.message.template, 'simple message %d'); - expect(capturedEvent.message.params, [1]); + expect(capturedEvent.message!.formatted, 'simple message 1'); + expect(capturedEvent.message!.template, 'simple message %d'); + expect(capturedEvent.message!.params, [1]); expect(capturedEvent.level, SentryLevel.error); }); @@ -127,10 +115,7 @@ void main() { 'simple message 1', ); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; - + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.level, SentryLevel.info); }); @@ -138,16 +123,14 @@ void main() { final client = SentryClient(options..attachStacktrace = false); await client.captureMessage('message', level: SentryLevel.error); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.stackTrace, isNull); }); }); group('SentryClient captures exception', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); Error error; StackTrace stackTrace; @@ -168,18 +151,16 @@ void main() { final client = SentryClient(options); await client.captureException(error, stackTrace: stackTrace); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.throwable, error); expect(capturedEvent.exception is SentryException, true); - expect(capturedEvent.exception.stackTrace, isNotNull); + expect(capturedEvent.exception!.stackTrace, isNotNull); }); }); group('SentryClient captures exception and stacktrace', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); Error error; @@ -204,24 +185,22 @@ void main() { final client = SentryClient(options); await client.captureException(error, stackTrace: stacktrace); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.throwable, error); expect(capturedEvent.exception is SentryException, true); - expect(capturedEvent.exception.stackTrace, isNotNull); - expect(capturedEvent.exception.stackTrace.frames.first.fileName, + expect(capturedEvent.exception!.stackTrace, isNotNull); + expect(capturedEvent.exception!.stackTrace!.frames.first.fileName, 'test.dart'); - expect(capturedEvent.exception.stackTrace.frames.first.lineNo, 46); - expect(capturedEvent.exception.stackTrace.frames.first.colNo, 9); + expect(capturedEvent.exception!.stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exception!.stackTrace!.frames.first.colNo, 9); }); }); group('SentryClient captures exception and stacktrace', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); - Exception exception; + dynamic exception; setUp(() { options = SentryOptions(dsn: fakeDsn); @@ -244,16 +223,14 @@ void main() { final client = SentryClient(options); await client.captureException(exception, stackTrace: stacktrace); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.throwable, exception); expect(capturedEvent.exception is SentryException, true); - expect(capturedEvent.exception.stackTrace.frames.first.fileName, + expect(capturedEvent.exception!.stackTrace!.frames.first.fileName, 'test.dart'); - expect(capturedEvent.exception.stackTrace.frames.first.lineNo, 46); - expect(capturedEvent.exception.stackTrace.frames.first.colNo, 9); + expect(capturedEvent.exception!.stackTrace!.frames.first.lineNo, 46); + expect(capturedEvent.exception!.stackTrace!.frames.first.colNo, 9); }); test('should capture exception with Stackframe.current', () async { @@ -266,11 +243,9 @@ void main() { final client = SentryClient(options); await client.captureException(exception); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; - expect(capturedEvent.exception.stackTrace, isNotNull); + expect(capturedEvent.exception!.stackTrace, isNotNull); }); test('should capture exception without Stackframe.current', () async { @@ -283,11 +258,9 @@ void main() { final client = SentryClient(options..attachStacktrace = false); await client.captureException(exception); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; - expect(capturedEvent.exception.stackTrace, isNull); + expect(capturedEvent.exception!.stackTrace, isNull); }); test('should not capture sentry frames exception', () async { @@ -307,12 +280,10 @@ void main() { final client = SentryClient(options); await client.captureException(exception, stackTrace: stacktrace); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect( - capturedEvent.exception.stackTrace.frames + capturedEvent.exception!.stackTrace!.frames .every((frame) => frame.package != 'sentry'), true, ); @@ -320,8 +291,8 @@ void main() { }); group('SentryClient : apply scope to the captured event', () { - SentryOptions options; - Scope scope; + var options = SentryOptions(dsn: fakeDsn); + var scope = Scope(options); final level = SentryLevel.error; const transaction = '/test/scope'; @@ -362,15 +333,13 @@ void main() { final client = SentryClient(options); await client.captureEvent(event, scope: scope); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; expect(capturedEvent.user?.id, user.id); - expect(capturedEvent.level.name, SentryLevel.error.name); + expect(capturedEvent.level!.name, SentryLevel.error.name); expect(capturedEvent.transaction, transaction); expect(capturedEvent.fingerprint, fingerprint); - expect(capturedEvent.breadcrumbs.first, crumb); + expect(capturedEvent.breadcrumbs?.first, crumb); expect(capturedEvent.tags, { scopeTagKey: scopeTagValue, eventTagKey: eventTagValue, @@ -383,8 +352,8 @@ void main() { }); group('SentryClient : apply partial scope to the captured event', () { - SentryOptions options; - Scope scope; + var options = SentryOptions(dsn: fakeDsn); + var scope = Scope(options); final transaction = '/test/scope'; final eventTransaction = '/event/transaction'; @@ -417,12 +386,10 @@ void main() { final client = SentryClient(options); await client.captureEvent(event, scope: scope); - final capturedEvent = (verify( - options.transport.send(captureAny), - ).captured.first) as SentryEvent; + final capturedEvent = (options.transport as MockTransport).events.first; - expect(capturedEvent.user.id, eventUser.id); - expect(capturedEvent.level.name, SentryLevel.warning.name); + expect(capturedEvent.user!.id, eventUser.id); + expect(capturedEvent.level!.name, SentryLevel.warning.name); expect(capturedEvent.transaction, eventTransaction); expect(capturedEvent.fingerprint, eventFingerprint); expect(capturedEvent.breadcrumbs, eventCrumbs); @@ -430,7 +397,7 @@ void main() { }); group('SentryClient sampling', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); setUp(() { options = SentryOptions(dsn: fakeDsn); @@ -442,7 +409,7 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - verify(options.transport.send(any)).called(1); + expect((options.transport as MockTransport).called(1), true); }); test('do not capture event, sample rate is 0% disabled', () async { @@ -450,7 +417,7 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - verifyNever(options.transport.send(any)); + expect((options.transport as MockTransport).called(0), true); }); test('captures event, sample rate is null, disabled', () async { @@ -458,12 +425,12 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - verify(options.transport.send(any)).called(1); + expect((options.transport as MockTransport).called(1), true); }); }); group('SentryClient before send', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); setUp(() { options = SentryOptions(dsn: fakeDsn); @@ -475,7 +442,7 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - verifyNever(options.transport.send(any)); + expect((options.transport as MockTransport).called(0), true); }); test('before send returns an event and event is captured', () async { @@ -483,44 +450,39 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - final event = verify(options.transport.send(captureAny)).captured.first - as SentryEvent; + final event = (options.transport as MockTransport).events.first; - expect(event.tags.containsKey('theme'), true); - expect(event.extra.containsKey('host'), true); - expect(event.modules.containsKey('core'), true); - expect(event.sdk.integrations.contains('testIntegration'), true); + expect(event.tags!.containsKey('theme'), true); + expect(event.extra!.containsKey('host'), true); + expect(event.modules!.containsKey('core'), true); + expect(event.sdk!.integrations.contains('testIntegration'), true); expect( - event.sdk.packages.any((element) => element.name == 'test-pkg'), + event.sdk!.packages.any((element) => element.name == 'test-pkg'), true, ); expect( - event.breadcrumbs + event.breadcrumbs! .any((element) => element.message == 'processor crumb'), true, ); - expect(event.fingerprint.contains('process'), true); + expect(event.fingerprint!.contains('process'), true); }); }); - test("options can't be null", () { - expect(() => SentryClient(null), throwsArgumentError); - }); - group('EventProcessors', () { - SentryOptions options; + var options = SentryOptions(dsn: fakeDsn); setUp(() { options = SentryOptions(dsn: fakeDsn); options.addEventProcessor( (event, {hint}) => event - ..tags.addAll({'theme': 'material'}) - ..extra['host'] = '0.0.0.1' - ..modules.addAll({'core': '1.0'}) - ..breadcrumbs.add(Breadcrumb(message: 'processor crumb')) - ..fingerprint.add('process') - ..sdk.addIntegration('testIntegration') - ..sdk.addPackage('test-pkg', '1.0'), + ..tags!.addAll({'theme': 'material'}) + ..extra!['host'] = '0.0.0.1' + ..modules!.addAll({'core': '1.0'}) + ..breadcrumbs!.add(Breadcrumb(message: 'processor crumb')) + ..fingerprint!.add('process') + ..sdk!.addIntegration('testIntegration') + ..sdk!.addPackage('test-pkg', '1.0'), ); options.transport = MockTransport(); }); @@ -529,36 +491,35 @@ void main() { final client = SentryClient(options); await client.captureEvent(fakeEvent); - final event = verify(options.transport.send(captureAny)).captured.first - as SentryEvent; - expect(event.tags.containsKey('theme'), true); - expect(event.extra.containsKey('host'), true); - expect(event.modules.containsKey('core'), true); - expect(event.sdk.integrations.contains('testIntegration'), true); + final event = (options.transport as MockTransport).events.first; + expect(event.tags!.containsKey('theme'), true); + expect(event.extra!.containsKey('host'), true); + expect(event.modules!.containsKey('core'), true); + expect(event.sdk!.integrations.contains('testIntegration'), true); expect( - event.sdk.packages.any((element) => element.name == 'test-pkg'), + event.sdk!.packages.any((element) => element.name == 'test-pkg'), true, ); expect( - event.breadcrumbs + event.breadcrumbs! .any((element) => element.message == 'processor crumb'), true, ); - expect(event.fingerprint.contains('process'), true); + expect(event.fingerprint!.contains('process'), true); }); }); } -SentryEvent beforeSendCallbackDropEvent(SentryEvent event, {dynamic hint}) => +SentryEvent? beforeSendCallbackDropEvent(SentryEvent event, {dynamic hint}) => null; SentryEvent beforeSendCallback(SentryEvent event, {dynamic hint}) { return event - ..tags.addAll({'theme': 'material'}) - ..extra['host'] = '0.0.0.1' - ..modules.addAll({'core': '1.0'}) - ..breadcrumbs.add(Breadcrumb(message: 'processor crumb')) - ..fingerprint.add('process') - ..sdk.addIntegration('testIntegration') - ..sdk.addPackage('test-pkg', '1.0'); + ..tags!.addAll({'theme': 'material'}) + ..extra!['host'] = '0.0.0.1' + ..modules!.addAll({'core': '1.0'}) + ..breadcrumbs!.add(Breadcrumb(message: 'processor crumb')) + ..fingerprint!.add('process') + ..sdk!.addIntegration('testIntegration') + ..sdk!.addPackage('test-pkg', '1.0'); } diff --git a/dart/test/sentry_event_test.dart b/dart/test/sentry_event_test.dart index f6305ca309..ca8b628d8b 100644 --- a/dart/test/sentry_event_test.dart +++ b/dart/test/sentry_event_test.dart @@ -9,6 +9,8 @@ import 'package:sentry/src/utils.dart'; import 'package:sentry/src/version.dart'; import 'package:test/test.dart'; +import 'mocks.dart'; + void main() { group(SentryEvent, () { test('$Breadcrumb serializes', () { @@ -212,7 +214,7 @@ void main() { test('should not serialize stacktrace if not SentryStacktrace', () { final stacktrace = SentryStackTrace( - frames: SentryStackTraceFactory(SentryOptions()) + frames: SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) .getStackFrames('#0 baz (file:///pathto/test.dart:50:3)'), ); final serialized = SentryEvent(stackTrace: stacktrace).toJson(); @@ -260,7 +262,7 @@ void main() { test('should not serialize null or empty fields', () { final event = SentryEvent( - message: Message(null), + message: null, modules: {}, exception: SentryException(type: null, value: null), stackTrace: SentryStackTrace(frames: []), diff --git a/dart/test/sentry_options_test.dart b/dart/test/sentry_options_test.dart index feb64db507..720c002c69 100644 --- a/dart/test/sentry_options_test.dart +++ b/dart/test/sentry_options_test.dart @@ -3,20 +3,16 @@ import 'package:sentry/sentry.dart'; import 'package:sentry/src/noop_client.dart'; import 'package:test/test.dart'; +import 'mocks.dart'; + void main() { test('$Client is NoOp', () { - final options = SentryOptions(); - expect(NoOpClient(), options.httpClient); - }); - - test('$Client is NoOp if null is set', () { - final options = SentryOptions(); - options.httpClient = null; + final options = SentryOptions(dsn: fakeDsn); expect(NoOpClient(), options.httpClient); }); test('$Client sets a custom client', () { - final options = SentryOptions(); + final options = SentryOptions(dsn: fakeDsn); final client = Client(); options.httpClient = client; @@ -24,47 +20,26 @@ void main() { }); test('maxBreadcrumbs is 100 by default', () { - final options = SentryOptions(); - - expect(100, options.maxBreadcrumbs); - }); - - test('maxBreadcrumbs is default if null is set', () { - final options = SentryOptions(); - options.maxBreadcrumbs = null; - - expect(100, options.maxBreadcrumbs); - }); - - test('maxBreadcrumbs is default if negative number is set', () { - final options = SentryOptions(); - options.maxBreadcrumbs = -1; + final options = SentryOptions(dsn: fakeDsn); expect(100, options.maxBreadcrumbs); }); test('maxBreadcrumbs sets custom maxBreadcrumbs', () { - final options = SentryOptions(); + final options = SentryOptions(dsn: fakeDsn); options.maxBreadcrumbs = 200; expect(200, options.maxBreadcrumbs); }); test('$SentryLogger is NoOp by default', () { - final options = SentryOptions(); - - expect(noOpLogger, options.logger); - }); - - test('$SentryLogger is NoOp if null is set', () { - final options = SentryOptions(); - options.logger = null; + final options = SentryOptions(dsn: fakeDsn); expect(noOpLogger, options.logger); }); test('$SentryLogger sets a diagnostic logger', () { - final options = SentryOptions(); + final options = SentryOptions(dsn: fakeDsn); options.logger = dartLogger; expect(false, options.logger == noOpLogger); diff --git a/dart/test/sentry_test.dart b/dart/test/sentry_test.dart index 010bc4d495..abea5b3884 100644 --- a/dart/test/sentry_test.dart +++ b/dart/test/sentry_test.dart @@ -1,17 +1,18 @@ -import 'package:mockito/mockito.dart'; import 'package:sentry/sentry.dart'; import 'package:test/test.dart'; import 'mocks.dart'; import 'fake_platform_checker.dart'; +import 'mocks/mock_integration.dart'; +import 'mocks/mock_sentry_client.dart'; -Function appRunner = () {}; +AppRunner appRunner = () {}; void main() { group('Sentry capture methods', () { - SentryClient client; + var client = MockSentryClient(); - Exception anException; + var anException = Exception(); setUp(() async { await Sentry.init((options) => options.dsn = fakeDsn); @@ -26,50 +27,34 @@ void main() { test('should capture the event', () async { await Sentry.captureEvent(fakeEvent); - verify( - client.captureEvent( - fakeEvent, - scope: anyNamed('scope'), - ), - ).called(1); - }); - test('should not capture a null event', () async { - await Sentry.captureEvent(null); - verifyNever(client.captureEvent(fakeEvent)); + expect(client.captureEventCalls.length, 1); + expect(client.captureEventCalls.first.event, fakeEvent); + expect(client.captureEventCalls.first.scope, isNotNull); }); test('should not capture a null exception', () async { await Sentry.captureException(null); - verifyNever( - client.captureException( - any, - stackTrace: anyNamed('stackTrace'), - ), - ); + expect(client.captureExceptionCalls.length, 0); }); test('should capture the exception', () async { await Sentry.captureException(anException); - verify( - client.captureException( - anException, - stackTrace: null, - scope: anyNamed('scope'), - ), - ).called(1); + expect(client.captureExceptionCalls.length, 1); + expect(client.captureExceptionCalls.first.throwable, anException); + expect(client.captureExceptionCalls.first.stackTrace, isNull); + expect(client.captureExceptionCalls.first.scope, isNotNull); }); test('should capture message', () async { - await Sentry.captureMessage(fakeMessage.formatted, - level: SentryLevel.warning); - verify( - client.captureMessage( - fakeMessage.formatted, - level: SentryLevel.warning, - scope: anyNamed('scope'), - ), - ).called(1); + await Sentry.captureMessage( + fakeMessage.formatted, + level: SentryLevel.warning, + ); + + expect(client.captureMessageCalls.length, 1); + expect(client.captureMessageCalls.first.formatted, fakeMessage.formatted); + expect(client.captureMessageCalls.first.level, SentryLevel.warning); }); }); @@ -77,6 +62,7 @@ void main() { tearDown(() { Sentry.close(); }); + test('null DSN', () { expect( () async => await Sentry.init((options) => options.dsn = null), @@ -106,7 +92,7 @@ void main() { }, ); - verify(integration(any, any)).called(1); + expect(integration.callCalls, 1); }); test('close disables the SDK', () async { @@ -137,11 +123,11 @@ void main() { }, ); - verify(integration(any, any)).called(1); + expect(integration.callCalls, 1); }); test('should add default integrations', () async { - SentryOptions optionsReference; + late SentryOptions optionsReference; await Sentry.init( (options) { options.dsn = fakeDsn; @@ -187,24 +173,13 @@ void main() { Sentry.close(); - verify(integration(any, any)).called(1); - verify(integration.close()).called(1); + expect(integration.callCalls, 1); + expect(integration.closeCalls, 1); }); }); - test( - "options can't be null", - () { - expect( - () async => await Sentry.init( - (options) => options = null, - ), - throwsArgumentError); - }, - ); - test('options.environment debug', () async { - final sentryOptions = SentryOptions() + final sentryOptions = SentryOptions(dsn: fakeDsn) ..platformChecker = FakePlatformChecker.debugMode(); await Sentry.init((options) { @@ -214,7 +189,7 @@ void main() { }); test('options.environment profile', () async { - final sentryOptions = SentryOptions() + final sentryOptions = SentryOptions(dsn: fakeDsn) ..platformChecker = FakePlatformChecker.profileMode(); await Sentry.init((options) { options.dsn = fakeDsn; @@ -223,7 +198,7 @@ void main() { }); test('options.environment production (defaultEnvironment)', () async { - final sentryOptions = SentryOptions() + final sentryOptions = SentryOptions(dsn: fakeDsn) ..platformChecker = FakePlatformChecker.releaseMode(); await Sentry.init((options) { options.dsn = fakeDsn; diff --git a/dart/test/stack_trace_test.dart b/dart/test/stack_trace_test.dart index 82c1fb514c..dbc9943b32 100644 --- a/dart/test/stack_trace_test.dart +++ b/dart/test/stack_trace_test.dart @@ -9,14 +9,16 @@ import 'package:sentry/src/sentry_stack_trace_factory.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:test/test.dart'; +import 'mocks.dart'; + void main() { group('encodeStackTraceFrame', () { test('marks dart: frames as not app frames', () { final frame = Frame(Uri.parse('dart:core'), 1, 2, 'buzz'); expect( - SentryStackTraceFactory(SentryOptions()) - .encodeStackTraceFrame(frame) + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .encodeStackTraceFrame(frame)! .toJson(), { 'abs_path': '${eventOrigin}dart:core', @@ -32,8 +34,8 @@ void main() { test('cleanses absolute paths', () { final frame = Frame(Uri.parse('file://foo/bar/baz.dart'), 1, 2, 'buzz'); expect( - SentryStackTraceFactory(SentryOptions()) - .encodeStackTraceFrame(frame) + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .encodeStackTraceFrame(frame)! .toJson()['abs_path'], '${eventOrigin}baz.dart', ); @@ -41,123 +43,136 @@ void main() { test('send exception package', () { final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = - SentryStackTraceFactory(SentryOptions()..addInAppExclude('toolkit')) - .encodeStackTraceFrame(frame) - .toJson(); + final serializedFrame = SentryStackTraceFactory( + SentryOptions(dsn: fakeDsn)..addInAppExclude('toolkit')) + .encodeStackTraceFrame(frame)! + .toJson(); expect(serializedFrame['package'], 'toolkit'); }); test('apply inAppExcludes', () { final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = - SentryStackTraceFactory(SentryOptions()..addInAppExclude('toolkit')) - .encodeStackTraceFrame(frame) - .toJson(); + final serializedFrame = SentryStackTraceFactory( + SentryOptions(dsn: fakeDsn)..addInAppExclude('toolkit')) + .encodeStackTraceFrame(frame)! + .toJson(); expect(serializedFrame['in_app'], false); }); test('apply inAppIncludes', () { final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = - SentryStackTraceFactory(SentryOptions()..addInAppInclude('toolkit')) - .encodeStackTraceFrame(frame) - .toJson(); + final serializedFrame = SentryStackTraceFactory( + SentryOptions(dsn: fakeDsn)..addInAppInclude('toolkit')) + .encodeStackTraceFrame(frame)! + .toJson(); expect(serializedFrame['in_app'], true); }); test('flutter package is not inApp', () { final frame = Frame(Uri.parse('package:flutter/material.dart'), 1, 2, 'buzz'); - final serializedFrame = SentryStackTraceFactory(SentryOptions()) - .encodeStackTraceFrame(frame) - .toJson(); + final serializedFrame = + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .encodeStackTraceFrame(frame)! + .toJson(); expect(serializedFrame['in_app'], false); }); test('apply inAppIncludes with precedence', () { final frame = Frame(Uri.parse('package:toolkit/baz.dart'), 1, 2, 'buzz'); - final serializedFrame = SentryStackTraceFactory(SentryOptions() - ..addInAppInclude('toolkit') - ..addInAppExclude('toolkit')) - .encodeStackTraceFrame(frame) - .toJson(); + final serializedFrame = + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn) + ..addInAppInclude('toolkit') + ..addInAppExclude('toolkit')) + .encodeStackTraceFrame(frame)! + .toJson(); expect(serializedFrame['in_app'], true); }); }); group('encodeStackTrace', () { test('encodes a simple stack trace', () { - expect(SentryStackTraceFactory(SentryOptions()).getStackFrames(''' + expect( + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .getStackFrames(''' #0 baz (file:///pathto/test.dart:50:3) #1 bar (file:///pathto/test.dart:46:9) - ''').map((frame) => frame.toJson()), [ - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'bar', - 'lineno': 46, - 'colno': 9, - 'in_app': true, - 'filename': 'test.dart' - }, - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'baz', - 'lineno': 50, - 'colno': 3, - 'in_app': true, - 'filename': 'test.dart' - }, - ]); + ''').map((frame) => frame.toJson()), + [ + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'bar', + 'lineno': 46, + 'colno': 9, + 'in_app': true, + 'filename': 'test.dart' + }, + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'baz', + 'lineno': 50, + 'colno': 3, + 'in_app': true, + 'filename': 'test.dart' + }, + ]); }); test('encodes an asynchronous stack trace', () { - expect(SentryStackTraceFactory(SentryOptions()).getStackFrames(''' + expect( + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .getStackFrames(''' #0 baz (file:///pathto/test.dart:50:3) #1 bar (file:///pathto/test.dart:46:9) - ''').map((frame) => frame.toJson()), [ - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'bar', - 'lineno': 46, - 'colno': 9, - 'in_app': true, - 'filename': 'test.dart' - }, - { - 'abs_path': '', - }, - { - 'abs_path': '${eventOrigin}test.dart', - 'function': 'baz', - 'lineno': 50, - 'colno': 3, - 'in_app': true, - 'filename': 'test.dart' - }, - ]); + ''').map((frame) => frame.toJson()), + [ + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'bar', + 'lineno': 46, + 'colno': 9, + 'in_app': true, + 'filename': 'test.dart' + }, + { + 'abs_path': '', + }, + { + 'abs_path': '${eventOrigin}test.dart', + 'function': 'baz', + 'lineno': 50, + 'colno': 3, + 'in_app': true, + 'filename': 'test.dart' + }, + ]); }); test('sets instruction_addr if stack trace violates dart standard', () { - expect(SentryStackTraceFactory(SentryOptions()).getStackFrames(''' + expect( + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .getStackFrames(''' warning: This VM has been configured to produce stack traces that violate the Dart standard. unparsed #00 abs 000000723d6346d7 virt 00000000001ed6d7 _kDartIsolateSnapshotInstructions+0x1e26d7 unparsed #01 abs 000000723d637527 virt 00000000001f0527 _kDartIsolateSnapshotInstructions+0x1e5527 - ''').map((frame) => frame.toJson()), [ - { - 'platform': 'native', - 'instruction_addr': '0x000000723d637527', - }, - { - 'platform': 'native', - 'instruction_addr': '0x000000723d6346d7', - }, - ]); + ''').map((frame) => frame.toJson()), + [ + { + 'platform': 'native', + 'instruction_addr': '0x000000723d637527', + }, + { + 'platform': 'native', + 'instruction_addr': '0x000000723d6346d7', + }, + ]); }); test('sets instruction_addr and ignores noise', () { - expect(SentryStackTraceFactory(SentryOptions()).getStackFrames(''' + expect( + SentryStackTraceFactory(SentryOptions(dsn: fakeDsn)) + .getStackFrames(''' warning: This VM has been configured to produce stack traces that violate the Dart standard. *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** unparsed pid: 30930, tid: 30990, name 1.ui @@ -166,16 +181,17 @@ void main() { unparsed isolate_instructions: 723d452000, vm_instructions: 723d449000 unparsed #00 abs 000000723d6346d7 virt 00000000001ed6d7 _kDartIsolateSnapshotInstructions+0x1e26d7 unparsed #01 abs 000000723d637527 virt 00000000001f0527 _kDartIsolateSnapshotInstructions+0x1e5527 - ''').map((frame) => frame.toJson()), [ - { - 'platform': 'native', - 'instruction_addr': '0x000000723d637527', - }, - { - 'platform': 'native', - 'instruction_addr': '0x000000723d6346d7', - }, - ]); + ''').map((frame) => frame.toJson()), + [ + { + 'platform': 'native', + 'instruction_addr': '0x000000723d637527', + }, + { + 'platform': 'native', + 'instruction_addr': '0x000000723d6346d7', + }, + ]); }); }); } diff --git a/dart/test/test_utils.dart b/dart/test/test_utils.dart index cce40388f3..9f99b2125e 100644 --- a/dart/test/test_utils.dart +++ b/dart/test/test_utils.dart @@ -19,9 +19,9 @@ const String _testDsnWithPort = 'https://public:secret@sentry.example.com:8888/1'; void testHeaders( - Map headers, + Map? headers, ClockProvider fakeClockProvider, { - String sdkName, + String? sdkName, bool withUserAgent = true, bool compressPayload = true, bool withSecret = true, @@ -34,10 +34,11 @@ void testHeaders( }; if (withSecret) { - expectedHeaders['X-Sentry-Auth'] += 'sentry_secret=secret, '; + expectedHeaders['X-Sentry-Auth'] = + expectedHeaders['X-Sentry-Auth']! + 'sentry_secret=secret, '; } - expectedHeaders['X-Sentry-Auth'] += + expectedHeaders['X-Sentry-Auth'] = expectedHeaders['X-Sentry-Auth']! + 'sentry_timestamp=${fakeClockProvider().millisecondsSinceEpoch}'; if (withUserAgent) { @@ -53,17 +54,17 @@ void testHeaders( Future testCaptureException( bool compressPayload, - Codec, List> gzip, + Codec, List?>? gzip, bool isWeb, ) async { final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - String postUri; - Map headers; - List body; + Uri? postUri; + Map? headers; + List? body; final httpMock = MockClient((http.Request request) async { if (request.method == 'POST') { - postUri = request.url.toString(); + postUri = request.url; headers = request.headers; body = request.bodyBytes; return http.Response('{"id": "test-event-id"}', 200); @@ -89,7 +90,7 @@ Future testCaptureException( expect('$sentryId', 'testeventid'); } - final dsn = Dsn.parse(options.dsn); + final dsn = Dsn.parse(options.dsn!); expect(postUri, dsn.postUri); testHeaders( @@ -100,15 +101,16 @@ Future testCaptureException( sdkName: sdkName, ); - Map data; + Map? data; if (compressPayload) { - data = json.decode(utf8.decode(gzip.decode(body))) as Map; + data = + json.decode(utf8.decode(gzip!.decode(body))) as Map?; } else { - data = json.decode(utf8.decode(body)) as Map; + data = json.decode(utf8.decode(body!)) as Map?; } // so we assert the generated and returned id - data['event_id'] = sentryId.toString(); + data!['event_id'] = sentryId.toString(); final stacktrace = data['exception']['values'].first['stacktrace']; @@ -181,17 +183,17 @@ Future testCaptureException( client.close(); } -void runTest({Codec, List> gzip, bool isWeb = false}) { +void runTest({Codec, List?>? gzip, bool isWeb = false}) { test('can parse DSN', () async { final options = SentryOptions(dsn: testDsn); final client = SentryClient(options); - final dsn = Dsn.parse(options.dsn); + final dsn = Dsn.parse(options.dsn!); expect(dsn.uri, Uri.parse(testDsn)); expect( dsn.postUri, - 'https://sentry.example.com/api/1/store/', + Uri.parse('https://sentry.example.com/api/1/store/'), ); expect(dsn.publicKey, 'public'); expect(dsn.secretKey, 'secret'); @@ -203,12 +205,12 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { final options = SentryOptions(dsn: _testDsnWithoutSecret); final client = SentryClient(options); - final dsn = Dsn.parse(options.dsn); + final dsn = Dsn.parse(options.dsn!); expect(dsn.uri, Uri.parse(_testDsnWithoutSecret)); expect( dsn.postUri, - 'https://sentry.example.com/api/1/store/', + Uri.parse('https://sentry.example.com/api/1/store/'), ); expect(dsn.publicKey, 'public'); expect(dsn.secretKey, null); @@ -220,12 +222,12 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { final options = SentryOptions(dsn: _testDsnWithPath); final client = SentryClient(options); - final dsn = Dsn.parse(options.dsn); + final dsn = Dsn.parse(options.dsn!); expect(dsn.uri, Uri.parse(_testDsnWithPath)); expect( dsn.postUri, - 'https://sentry.example.com/path/api/1/store/', + Uri.parse('https://sentry.example.com/path/api/1/store/'), ); expect(dsn.publicKey, 'public'); expect(dsn.secretKey, 'secret'); @@ -236,12 +238,12 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { final options = SentryOptions(dsn: _testDsnWithPort); final client = SentryClient(options); - final dsn = Dsn.parse(options.dsn); + final dsn = Dsn.parse(options.dsn!); expect(dsn.uri, Uri.parse(_testDsnWithPort)); expect( dsn.postUri, - 'https://sentry.example.com:8888/api/1/store/', + Uri.parse('https://sentry.example.com:8888/api/1/store/'), ); expect(dsn.publicKey, 'public'); expect(dsn.secretKey, 'secret'); @@ -251,7 +253,7 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { test('sends client auth header without secret', () async { final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - Map headers; + Map? headers; final httpMock = MockClient((http.Request request) async { if (request.method == 'POST') { @@ -259,7 +261,8 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { return http.Response('{"id": "testeventid"}', 200); } fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock'); + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); }); final client = SentryClient( @@ -312,7 +315,8 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { }); } fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock'); + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); }); final client = SentryClient( @@ -341,19 +345,24 @@ void runTest({Codec, List> gzip, bool isWeb = false}) { test('$SentryEvent user overrides client', () async { final fakeClockProvider = () => DateTime.utc(2017, 1, 2); - String loggedUserId; // used to find out what user context was sent + String? loggedUserId; // used to find out what user context was sent final httpMock = MockClient((http.Request request) async { if (request.method == 'POST') { final bodyData = request.bodyBytes; final decoded = const Utf8Codec().decode(bodyData); final dynamic decodedJson = jsonDecode(decoded); - loggedUserId = decodedJson['user']['id'] as String; - return http.Response('', 401, headers: { - 'x-sentry-error': 'Invalid api key', - }); + loggedUserId = decodedJson['user']['id'] as String?; + return http.Response( + '', + 401, + headers: { + 'x-sentry-error': 'Invalid api key', + }, + ); } fail( - 'Unexpected request on ${request.method} ${request.url} in HttpMock'); + 'Unexpected request on ${request.method} ${request.url} in HttpMock', + ); }); final clientUser = User( diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 1e5ab156ee..045c0f691c 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -306,7 +306,7 @@ Future makeWebRequest(BuildContext context) async { final client = SentryHttpClient(); // We don't do any exception handling here. // In case of an exception, let it get caught and reported to Sentry - final response = await client.get('https://flutter.dev/'); + final response = await client.get(Uri.parse('https://flutter.dev/')); await showDialog( context: context, diff --git a/flutter/lib/src/default_integrations.dart b/flutter/lib/src/default_integrations.dart index 20ab9aa927..b63a4f1c1c 100644 --- a/flutter/lib/src/default_integrations.dart +++ b/flutter/lib/src/default_integrations.dart @@ -13,7 +13,7 @@ import 'widgets_binding_observer.dart'; class WidgetsFlutterBindingIntegration extends Integration { WidgetsFlutterBindingIntegration( - [WidgetsBinding Function() ensureInitialized]) + [WidgetsBinding Function()? ensureInitialized]) : _ensureInitialized = ensureInitialized ?? WidgetsFlutterBinding.ensureInitialized; @@ -104,7 +104,7 @@ class LoadContextsIntegration extends Integration { (event, {hint}) async { try { final infos = Map.from( - await _channel.invokeMethod('loadContexts'), + await (_channel.invokeMethod('loadContexts')), ); if (infos['contexts'] != null) { final contexts = Contexts.fromJson( @@ -137,7 +137,7 @@ class LoadContextsIntegration extends Integration { if (infos['package'] != null) { final package = Map.from(infos['package'] as Map); final sdk = event.sdk ?? options.sdk; - sdk.addPackage(package['sdk_name'], package['version']); + sdk.addPackage(package['sdk_name']!, package['version']!); event = event.copyWith(sdk: sdk); } } catch (error) { @@ -201,7 +201,7 @@ class NativeSdkIntegration extends Integration { /// - [SentryWidgetsBindingObserver] /// - [WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html) class WidgetsBindingIntegration extends Integration { - SentryWidgetsBindingObserver _observer; + SentryWidgetsBindingObserver? _observer; @override FutureOr call(Hub hub, SentryFlutterOptions options) { @@ -215,7 +215,7 @@ class WidgetsBindingIntegration extends Integration { // If the instance is not created, we skip it to keep going. final instance = WidgetsBinding.instance; if (instance != null) { - instance.addObserver(_observer); + instance.addObserver(_observer!); options.sdk.addIntegration('widgetsBindingIntegration'); } else { options.logger( @@ -228,8 +228,8 @@ class WidgetsBindingIntegration extends Integration { @override void close() { final instance = WidgetsBinding.instance; - if (instance != null) { - instance.removeObserver(_observer); + if (instance != null && _observer != null) { + instance.removeObserver(_observer!); } } } @@ -246,10 +246,8 @@ class LoadAndroidImageListIntegration options.addEventProcessor( (event, {hint}) async { try { - if (event.exception != null && - event.exception.stackTrace != null && - event.exception.stackTrace.frames != null) { - final needsSymbolication = event.exception.stackTrace.frames + if (event.exception != null && event.exception!.stackTrace != null) { + final needsSymbolication = event.exception!.stackTrace!.frames .any((element) => 'native' == element.platform); // if there are no frames that require symbolication, we don't @@ -264,7 +262,7 @@ class LoadAndroidImageListIntegration // we call on every event because the loaded image list is cached // and it could be changed on the Native side. final imageList = List>.from( - await _channel.invokeMethod('loadImageList'), + await (_channel.invokeMethod('loadImageList')), ); if (imageList.isEmpty) { @@ -274,13 +272,13 @@ class LoadAndroidImageListIntegration final newDebugImages = []; for (final item in imageList) { - final codeFile = item['code_file'] as String; - final codeId = item['code_id'] as String; - final imageAddr = item['image_addr'] as String; - final imageSize = item['image_size'] as int; + final codeFile = item['code_file'] as String?; + final codeId = item['code_id'] as String?; + final imageAddr = item['image_addr'] as String?; + final imageSize = item['image_size'] as int?; final type = item['type'] as String; - final debugId = item['debug_id'] as String; - final debugFile = item['debug_file'] as String; + final debugId = item['debug_id'] as String?; + final debugFile = item['debug_file'] as String?; final image = DebugImage( type: type, @@ -326,10 +324,6 @@ class LoadReleaseIntegration extends Integration { FutureOr call(Hub hub, SentryFlutterOptions options) async { try { if (!kIsWeb) { - if (_packageLoader == null) { - options.logger(SentryLevel.debug, 'Package loader is null.'); - return; - } final packageInfo = await _packageLoader(); final release = '${packageInfo.packageName}@${packageInfo.version}+${packageInfo.buildNumber}'; diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index c249b9b2c0..917bc67c08 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -35,26 +35,22 @@ const _navigationKey = 'navigation'; /// - [RouteObserver](https://api.flutter.dev/flutter/widgets/RouteObserver-class.html) /// - [Navigating with arguments](https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments) class SentryNavigatorObserver extends RouteObserver> { - factory SentryNavigatorObserver({Hub hub}) { - return SentryNavigatorObserver._(hub ?? HubAdapter()); - } - - SentryNavigatorObserver._(this.hub) : assert(hub != null); + SentryNavigatorObserver({Hub? hub}) : hub = hub ?? HubAdapter(); final Hub hub; @override - void didPush(Route route, Route previousRoute) { + void didPush(Route route, Route? previousRoute) { super.didPush(route, previousRoute); _addBreadcrumb( type: 'didPush', from: previousRoute?.settings, - to: route?.settings, + to: route.settings, ); } @override - void didReplace({Route newRoute, Route oldRoute}) { + void didReplace({Route? newRoute, Route? oldRoute}) { super.didReplace(newRoute: newRoute, oldRoute: oldRoute); _addBreadcrumb( @@ -65,20 +61,20 @@ class SentryNavigatorObserver extends RouteObserver> { } @override - void didPop(Route route, Route previousRoute) { + void didPop(Route route, Route? previousRoute) { super.didPop(route, previousRoute); _addBreadcrumb( type: 'didPop', - from: route?.settings, + from: route.settings, to: previousRoute?.settings, ); } void _addBreadcrumb({ - String type, - RouteSettings from, - RouteSettings to, + required String type, + RouteSettings? from, + RouteSettings? to, }) { hub.addBreadcrumb(RouteObserverBreadcrumb( navigationType: type, @@ -98,10 +94,10 @@ class RouteObserverBreadcrumb extends Breadcrumb { factory RouteObserverBreadcrumb({ /// This should correspond to Flutters navigation events. /// See https://api.flutter.dev/flutter/widgets/RouteObserver-class.html - @required String navigationType, - RouteSettings from, - RouteSettings to, - SentryLevel level, + required String navigationType, + RouteSettings? from, + RouteSettings? to, + SentryLevel? level, }) { final dynamic fromArgs = _formatArgs(from?.arguments); final dynamic toArgs = _formatArgs(to?.arguments); @@ -116,26 +112,25 @@ class RouteObserverBreadcrumb extends Breadcrumb { } RouteObserverBreadcrumb._({ - @required String navigationType, - String from, + required String navigationType, + String? from, dynamic fromArgs, - String to, + String? to, dynamic toArgs, - SentryLevel level, - }) : assert(navigationType != null), - super( + SentryLevel? level, + }) : super( category: _navigationKey, type: _navigationKey, level: level, data: { - if (navigationType != null) 'state': navigationType, + 'state': navigationType, if (from != null) 'from': from, if (fromArgs != null) 'from_arguments': fromArgs, if (to != null) 'to': to, if (toArgs != null) 'to_arguments': toArgs, }); - static dynamic _formatArgs(Object args) { + static dynamic _formatArgs(Object? args) { if (args == null) { return null; } diff --git a/flutter/lib/src/sentry_flutter.dart b/flutter/lib/src/sentry_flutter.dart index d5969bd047..f23746f355 100644 --- a/flutter/lib/src/sentry_flutter.dart +++ b/flutter/lib/src/sentry_flutter.dart @@ -25,16 +25,12 @@ mixin SentryFlutter { static Future init( FlutterOptionsConfiguration optionsConfiguration, { - AppRunner appRunner, + AppRunner? appRunner, PackageLoader packageLoader = _loadPackageInfo, iOSPlatformChecker isIOSChecker = isIOS, AndroidPlatformChecker isAndroidChecker = isAndroid, MethodChannel channel = _channel, }) async { - if (optionsConfiguration == null) { - throw ArgumentError('OptionsConfiguration is required.'); - } - final flutterOptions = SentryFlutterOptions(); // first step is to install the native integration and set default values, @@ -53,7 +49,7 @@ mixin SentryFlutter { await Sentry.init( (options) async { - await optionsConfiguration(options); + await optionsConfiguration(options as SentryFlutterOptions); }, appRunner: appRunner, options: flutterOptions, diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index bcc45dafe5..f5b7e1836c 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -5,19 +5,10 @@ import 'package:sentry/sentry.dart'; /// Note that some of these options require native Sentry integration, which is /// not available on all platforms. class SentryFlutterOptions extends SentryOptions { - SentryFlutterOptions() : super(); - - bool _enableAutoSessionTracking = true; + SentryFlutterOptions({String? dsn}) : super(dsn: dsn); /// Enable or disable the Auto session tracking on the Native SDKs (Android/iOS) - bool get enableAutoSessionTracking => _enableAutoSessionTracking; - - set enableAutoSessionTracking(bool value) { - assert(value != null); - _enableAutoSessionTracking = value ?? _enableAutoSessionTracking; - } - - bool _enableNativeCrashHandling = true; + bool enableAutoSessionTracking = true; /// Enable or disable the Crash handling on the Native SDKs, e.g., /// UncaughtExceptionHandler and [anrEnabled] for Android. @@ -28,12 +19,7 @@ class SentryFlutterOptions extends SentryOptions { /// /// Disabling this feature affects the [enableAutoSessionTracking] /// feature, as this is required to mark Sessions as Crashed. - bool get enableNativeCrashHandling => _enableNativeCrashHandling; - - set enableNativeCrashHandling(bool value) { - assert(value != null); - _enableNativeCrashHandling = value ?? _enableNativeCrashHandling; - } + bool enableNativeCrashHandling = true; int _autoSessionTrackingIntervalMillis = 30000; @@ -44,25 +30,16 @@ class SentryFlutterOptions extends SentryOptions { _autoSessionTrackingIntervalMillis; set autoSessionTrackingIntervalMillis(int value) { - assert(value != null); - _autoSessionTrackingIntervalMillis = (value != null && value >= 0) - ? value - : _autoSessionTrackingIntervalMillis; + _autoSessionTrackingIntervalMillis = + value >= 0 ? value : _autoSessionTrackingIntervalMillis; } - bool _anrEnabled = false; - /// Enable or disable ANR (Application Not Responding) Default is enabled Used by AnrIntegration. /// Available only for Android. /// Disabled by default as the stack trace most of the time is hanging on /// the MessageChannel from Flutter, but you can enable it if you have /// Java/Kotlin code as well. - bool get anrEnabled => _anrEnabled; - - set anrEnabled(bool value) { - assert(value != null); - _anrEnabled = value ?? _anrEnabled; - } + bool anrEnabled = false; int _anrTimeoutIntervalMillis = 5000; @@ -72,24 +49,15 @@ class SentryFlutterOptions extends SentryOptions { int get anrTimeoutIntervalMillis => _anrTimeoutIntervalMillis; set anrTimeoutIntervalMillis(int value) { - assert(value != null); - _anrTimeoutIntervalMillis = - (value != null && value >= 0) ? value : _anrTimeoutIntervalMillis; + _anrTimeoutIntervalMillis = value >= 0 ? value : _anrTimeoutIntervalMillis; } - bool _enableAutoNativeBreadcrumbs = true; - /// Enable or disable the Automatic breadcrumbs on the Native platforms (Android/iOS) /// Screen's lifecycle, App's lifecycle, System events, etc... /// /// If you only want to record breadcrumbs inside the Flutter environment /// consider using [useFlutterBreadcrumbTracking]. - bool get enableAutoNativeBreadcrumbs => _enableAutoNativeBreadcrumbs; - - set enableAutoNativeBreadcrumbs(bool value) { - assert(value != null); - _enableAutoNativeBreadcrumbs = value ?? _enableAutoNativeBreadcrumbs; - } + bool enableAutoNativeBreadcrumbs = true; int _cacheDirSize = 30; @@ -98,15 +66,14 @@ class SentryFlutterOptions extends SentryOptions { int get cacheDirSize => _cacheDirSize; set cacheDirSize(int value) { - assert(value != null); - _cacheDirSize = (value != null && value >= 0) ? value : _cacheDirSize; + _cacheDirSize = value >= 0 ? value : _cacheDirSize; } @Deprecated( 'Use enableAppLifecycleBreadcrumbs instead. ' 'This option gets removed in Sentry 5.0.0', ) - bool get enableLifecycleBreadcrumbs => _enableAppLifecycleBreadcrumbs; + bool get enableLifecycleBreadcrumbs => enableAppLifecycleBreadcrumbs; @Deprecated( 'Use enableAppLifecycleBreadcrumbs instead. ' @@ -131,83 +98,36 @@ class SentryFlutterOptions extends SentryOptions { /// [lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle). /// However because an iOS Flutter application lives inside a single /// `UIViewController` this is an application wide lifecycle event. - bool get enableAppLifecycleBreadcrumbs => _enableAppLifecycleBreadcrumbs; - - set enableAppLifecycleBreadcrumbs(bool value) { - assert(value != null); - _enableAppLifecycleBreadcrumbs = value ?? _enableAppLifecycleBreadcrumbs; - } - - bool _enableAppLifecycleBreadcrumbs = false; + bool enableAppLifecycleBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record window metric events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableWindowMetricBreadcrumbs => _enableWindowMetricBreadcrumbs; - - set enableWindowMetricBreadcrumbs(bool value) { - assert(value != null); - _enableWindowMetricBreadcrumbs = value ?? _enableWindowMetricBreadcrumbs; - } - - bool _enableWindowMetricBreadcrumbs = false; + bool enableWindowMetricBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record brightness change events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableBrightnessChangeBreadcrumbs => - _enableBrightnessChangeBreadcrumbs; - - set enableBrightnessChangeBreadcrumbs(bool value) { - assert(value != null); - _enableBrightnessChangeBreadcrumbs = - value ?? _enableBrightnessChangeBreadcrumbs; - } - - bool _enableBrightnessChangeBreadcrumbs = false; + bool enableBrightnessChangeBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record text scale change events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableTextScaleChangeBreadcrumbs => - _enableTextScaleChangeBreadcrumbs; - - set enableTextScaleChangeBreadcrumbs(bool value) { - assert(value != null); - _enableTextScaleChangeBreadcrumbs = - value ?? _enableTextScaleChangeBreadcrumbs; - } - - bool _enableTextScaleChangeBreadcrumbs = false; + bool enableTextScaleChangeBreadcrumbs = false; /// Consider disabling [enableAutoNativeBreadcrumbs] if you /// enable this. Otherwise you might record memory pressure events twice. /// Also consider using [enableBreadcrumbTrackingForCurrentPlatform] /// instead for more sensible defaults. - bool get enableMemoryPressureBreadcrumbs => _enableMemoryPressureBreadcrumbs; - - set enableMemoryPressureBreadcrumbs(bool value) { - assert(value != null); - _enableMemoryPressureBreadcrumbs = - value ?? _enableMemoryPressureBreadcrumbs; - } - - bool _enableMemoryPressureBreadcrumbs = false; + bool enableMemoryPressureBreadcrumbs = false; /// By default, we don't report [FlutterErrorDetails.silent] errors, /// but you can by enabling this flag. /// See https://api.flutter.dev/flutter/foundation/FlutterErrorDetails/silent.html - bool get reportSilentFlutterErrors => _reportSilentFlutterErrors; - - set reportSilentFlutterErrors(bool value) { - assert(value != null); - _reportSilentFlutterErrors = value ?? _reportSilentFlutterErrors; - } - - bool _reportSilentFlutterErrors = false; + bool reportSilentFlutterErrors = false; /// By using this, you are disabling native [Breadcrumb] tracking and instead /// you are just tracking [Breadcrumb]s which result from events available @@ -263,8 +183,6 @@ class SentryFlutterOptions extends SentryOptions { @foundation.visibleForTesting void configureBreadcrumbTrackingForPlatform( foundation.TargetPlatform platform) { - assert(platform != null); - // Bacause platform reports the Operating System and not if it is running // in a browser. So we have to check if this is Flutter for web. // See https://github.com/flutter/flutter/blob/c5a69b9b8ad186e9fce017fd4bfb8ce63f9f4d13/packages/flutter/lib/src/foundation/_platform_web.dart diff --git a/flutter/lib/src/widgets_binding_observer.dart b/flutter/lib/src/widgets_binding_observer.dart index 72916b1d23..6a591bce9f 100644 --- a/flutter/lib/src/widgets_binding_observer.dart +++ b/flutter/lib/src/widgets_binding_observer.dart @@ -19,16 +19,13 @@ import '../sentry_flutter.dart'; /// - [WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html) class SentryWidgetsBindingObserver with WidgetsBindingObserver { SentryWidgetsBindingObserver({ - Hub hub, - @required SentryFlutterOptions options, - }) { - _hub = hub ?? HubAdapter(); - assert(options != null); - _options = options; - } + Hub? hub, + required SentryFlutterOptions options, + }) : _hub = hub ?? HubAdapter(), + _options = options; - Hub _hub; - SentryFlutterOptions _options; + final Hub _hub; + final SentryFlutterOptions _options; /// This method records lifecycle events. /// It tries to mimic the behavior of ActivityBreadcrumbsIntegration of Sentry @@ -66,15 +63,15 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { if (!_options.enableWindowMetricBreadcrumbs) { return; } - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance?.window; _hub.addBreadcrumb(Breadcrumb( message: 'Screen size changed', category: 'device.screen', type: 'navigation', data: { - 'new_pixel_ratio': window.devicePixelRatio, - 'new_height': window.physicalSize.height, - 'new_width': window.physicalSize.width, + 'new_pixel_ratio': window?.devicePixelRatio, + 'new_height': window?.physicalSize.height, + 'new_width': window?.physicalSize.width, }, )); } @@ -86,7 +83,7 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { if (!_options.enableBrightnessChangeBreadcrumbs) { return; } - final brightness = WidgetsBinding.instance.window.platformBrightness; + final brightness = WidgetsBinding.instance?.window.platformBrightness; final brightnessDescription = brightness == Brightness.dark ? 'dark' : 'light'; @@ -107,7 +104,7 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { if (!_options.enableTextScaleChangeBreadcrumbs) { return; } - final newTextScaleFactor = WidgetsBinding.instance.window.textScaleFactor; + final newTextScaleFactor = WidgetsBinding.instance?.window.textScaleFactor; _hub.addBreadcrumb(Breadcrumb( message: 'Text scale factor changed to $newTextScaleFactor.', type: 'system', @@ -155,10 +152,9 @@ class SentryWidgetsBindingObserver with WidgetsBindingObserver { case AppLifecycleState.detached: return 'detached'; } - return ''; } - /* +/* These are also methods of `WidgetsBindingObserver` but are currently not implemented because I'm not sure what to do with them. See the reasoning for each method. If these methods are implemented the class definition should diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 7a354cc5ec..4f902dc6f3 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://docs.sentry.io/platforms/flutter/ repository: https://github.com/getsentry/sentry-dart environment: - sdk: ">=2.8.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' flutter: ">=1.17.0" dependencies: @@ -13,16 +13,17 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - sentry: ">=4.0.0 <5.0.0" - package_info: ^0.4.0 + # this should point to pub + sentry: ^4.0.5 + package_info: ^2.0.0 dev_dependencies: flutter_test: sdk: flutter - mockito: ^4.1.3 - test: ^1.15.7 - yaml: ^2.2.1 # needed for version match (code and pubspec) - pedantic: ^1.9.2 + mockito: ^5.0.0 + yaml: ^3.0.0 # needed for version match (code and pubspec) + pedantic: ^1.10.0 + build_runner: ^1.11.5 dependency_overrides: sentry: diff --git a/flutter/test/default_integrations_test.dart b/flutter/test/default_integrations_test.dart index 9518f06366..44c2468586 100644 --- a/flutter/test/default_integrations_test.dart +++ b/flutter/test/default_integrations_test.dart @@ -7,14 +7,14 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; void main() { const _channel = MethodChannel('sentry_flutter'); TestWidgetsFlutterBinding.ensureInitialized(); - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); @@ -26,13 +26,16 @@ void main() { void _reportError({ bool silent = false, - FlutterExceptionHandler handler, + FlutterExceptionHandler? handler, dynamic exception, }) { // replace default error otherwise it fails on testing FlutterError.onError = handler ?? (FlutterErrorDetails errorDetails) async {}; + when(fixture.hub.captureEvent(captureAny)) + .thenAnswer((_) => Future.value(SentryId.empty())); + FlutterErrorIntegration()(fixture.hub, fixture.options); final throwable = exception ?? StateError('error'); @@ -106,7 +109,7 @@ void main() { test('nativeSdkIntegration do not throw', () async { _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw null; + throw Exception(); }); final integration = NativeSdkIntegration(_channel); @@ -142,7 +145,7 @@ void main() { var called = false; var ensureInitialized = () { called = true; - return WidgetsBinding.instance; + return WidgetsBinding.instance!; }; final integration = WidgetsFlutterBindingIntegration(ensureInitialized); await integration(fixture.hub, fixture.options); diff --git a/flutter/test/file_system_transport_test.dart b/flutter/test/file_system_transport_test.dart index fa86bcef6f..08a5f29957 100644 --- a/flutter/test/file_system_transport_test.dart +++ b/flutter/test/file_system_transport_test.dart @@ -10,7 +10,7 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); @@ -33,7 +33,7 @@ void main() { test('FileSystemTransport returns emptyId if channel throws', () async { _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw null; + throw Exception(); }); final transport = fixture.getSut(_channel); @@ -84,7 +84,7 @@ void main() { class Fixture { FileSystemTransport getSut(MethodChannel channel) { - final options = SentryOptions(); + final options = SentryOptions(dsn: ''); return FileSystemTransport(channel, options); } } diff --git a/flutter/test/load_android_image_list_test.dart b/flutter/test/load_android_image_list_test.dart index 918ce6e798..36cf5c8292 100644 --- a/flutter/test/load_android_image_list_test.dart +++ b/flutter/test/load_android_image_list_test.dart @@ -88,10 +88,10 @@ void main() { LoadAndroidImageListIntegration(_channel)(hub, options); final ep = options.eventProcessors.first; - var event = getEvent(); + SentryEvent? event = getEvent(); event = await ep(event); - expect(1, event.debugMeta.images.length); + expect(1, event!.debugMeta!.images.length); }); test('Event processor asserts image list', () async { @@ -100,10 +100,10 @@ void main() { LoadAndroidImageListIntegration(_channel)(hub, options); final ep = options.eventProcessors.first; - var event = getEvent(); + SentryEvent? event = getEvent(); event = await ep(event); - final image = event.debugMeta.images.first; + final image = event!.debugMeta!.images.first; expect('/apex/com.android.art/javalib/arm64/boot.oat', image.codeFile); expect('13577ce71153c228ecf0eb73fc39f45010d487f8', image.codeId); diff --git a/flutter/test/load_contexts_integrations_test.dart b/flutter/test/load_contexts_integrations_test.dart index fc60a89d72..64b9e646db 100644 --- a/flutter/test/load_contexts_integrations_test.dart +++ b/flutter/test/load_contexts_integrations_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -44,22 +46,23 @@ void main() { expect(options.eventProcessors.length, 1); final e = SentryEvent(); - final event = await options.eventProcessors.first(e); + final event = + await (options.eventProcessors.first(e) as FutureOr); expect(called, true); - expect(event.contexts.device.name, 'Device1'); - expect(event.contexts.app.name, 'test-app'); - expect(event.contexts.operatingSystem.name, 'os1'); - expect(event.contexts.gpu.name, 'gpu1'); - expect(event.contexts.browser.name, 'browser1'); + expect(event.contexts.device!.name, 'Device1'); + expect(event.contexts.app!.name, 'test-app'); + expect(event.contexts.operatingSystem!.name, 'os1'); + expect(event.contexts.gpu!.name, 'gpu1'); + expect(event.contexts.browser!.name, 'browser1'); expect( event.contexts.runtimes.any((element) => element.name == 'RT1'), true); expect(event.contexts['theme'], 'material'); expect( - event.sdk.packages.any((element) => element.name == 'native-package'), + event.sdk!.packages.any((element) => element.name == 'native-package'), true, ); - expect(event.sdk.integrations.contains('NativeIntegration'), true); + expect(event.sdk!.integrations.contains('NativeIntegration'), true); }); test( @@ -81,14 +84,15 @@ void main() { runtimes: [const SentryRuntime(name: 'eRT')]) ..['theme'] = 'cuppertino'; final e = SentryEvent(contexts: eventContexts); - final event = await options.eventProcessors.first(e); + final event = + await (options.eventProcessors.first(e) as FutureOr); expect(called, true); - expect(event.contexts.device.name, 'eDevice'); - expect(event.contexts.app.name, 'eApp'); - expect(event.contexts.operatingSystem.name, 'eOS'); - expect(event.contexts.gpu.name, 'eGpu'); - expect(event.contexts.browser.name, 'eBrowser'); + expect(event.contexts.device!.name, 'eDevice'); + expect(event.contexts.app!.name, 'eApp'); + expect(event.contexts.operatingSystem!.name, 'eOS'); + expect(event.contexts.gpu!.name, 'eGpu'); + expect(event.contexts.browser!.name, 'eBrowser'); expect( event.contexts.runtimes.any((element) => element.name == 'RT1'), true); expect( @@ -111,24 +115,25 @@ void main() { packages: const [SentryPackage('event-package', '2.0')], ); final e = SentryEvent(sdk: eventSdk); - final event = await options.eventProcessors.first(e); + final event = + await (options.eventProcessors.first(e) as FutureOr); expect( - event.sdk.packages.any((element) => element.name == 'native-package'), + event.sdk!.packages.any((element) => element.name == 'native-package'), true, ); expect( - event.sdk.packages.any((element) => element.name == 'event-package'), + event.sdk!.packages.any((element) => element.name == 'event-package'), true, ); - expect(event.sdk.integrations.contains('NativeIntegration'), true); - expect(event.sdk.integrations.contains('EventIntegration'), true); + expect(event.sdk!.integrations.contains('NativeIntegration'), true); + expect(event.sdk!.integrations.contains('EventIntegration'), true); }, ); test('should not throw on loadContextsIntegration exception', () async { _channel.setMockMethodCallHandler((MethodCall methodCall) async { - throw null; + throw Exception(); }); final options = SentryFlutterOptions()..dsn = fakeDsn; final hub = Hub(options); diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 52e59d29ff..787cc98bd5 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -1,8 +1,7 @@ -import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; import 'package:sentry/sentry.dart'; -class MockHub extends Mock implements Hub {} - -class MockTransport extends Mock implements Transport {} - const fakeDsn = 'https://abc@def.ingest.sentry.io/1234567'; + +@GenerateMocks([Hub, Transport]) +void main() {} diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart new file mode 100644 index 0000000000..d81a53242f --- /dev/null +++ b/flutter/test/mocks.mocks.dart @@ -0,0 +1,101 @@ +// Mocks generated by Mockito 5.0.0-nullsafety.7 from annotations +// in sentry_flutter/test/mocks.dart. +// Do not manually edit this file. + +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:sentry/src/hub.dart' as _i3; +import 'package:sentry/src/protocol/breadcrumb.dart' as _i7; +import 'package:sentry/src/protocol/sentry_event.dart' as _i5; +import 'package:sentry/src/protocol/sentry_id.dart' as _i2; +import 'package:sentry/src/protocol/sentry_level.dart' as _i6; +import 'package:sentry/src/sentry_client.dart' as _i8; +import 'package:sentry/src/transport/transport.dart' as _i9; + +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis + +class _FakeSentryId extends _i1.Fake implements _i2.SentryId {} + +class _FakeHub extends _i1.Fake implements _i3.Hub {} + +/// A class which mocks [Hub]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHub extends _i1.Mock implements _i3.Hub { + MockHub() { + _i1.throwOnMissingStub(this); + } + + @override + bool get isEnabled => + (super.noSuchMethod(Invocation.getter(#isEnabled), returnValue: false) + as bool); + @override + _i2.SentryId get lastEventId => + (super.noSuchMethod(Invocation.getter(#lastEventId), + returnValue: _FakeSentryId()) as _i2.SentryId); + @override + _i4.Future<_i2.SentryId> captureEvent(_i5.SentryEvent? event, + {dynamic stackTrace, dynamic hint}) => + (super.noSuchMethod( + Invocation.method( + #captureEvent, [event], {#stackTrace: stackTrace, #hint: hint}), + returnValue: + Future.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); + @override + _i4.Future<_i2.SentryId> captureException(dynamic throwable, + {dynamic stackTrace, dynamic hint}) => + (super.noSuchMethod( + Invocation.method(#captureException, [throwable], + {#stackTrace: stackTrace, #hint: hint}), + returnValue: Future.value(_FakeSentryId())) + as _i4.Future<_i2.SentryId>); + @override + _i4.Future<_i2.SentryId> captureMessage(String? message, + {_i6.SentryLevel? level, + String? template, + List? params, + dynamic hint}) => + (super.noSuchMethod( + Invocation.method(#captureMessage, [ + message + ], { + #level: level, + #template: template, + #params: params, + #hint: hint + }), + returnValue: Future.value(_FakeSentryId())) + as _i4.Future<_i2.SentryId>); + @override + void addBreadcrumb(_i7.Breadcrumb? crumb, {dynamic hint}) => super + .noSuchMethod(Invocation.method(#addBreadcrumb, [crumb], {#hint: hint}), + returnValueForMissingStub: null); + @override + void bindClient(_i8.SentryClient? client) => + super.noSuchMethod(Invocation.method(#bindClient, [client]), + returnValueForMissingStub: null); + @override + _i3.Hub clone() => (super.noSuchMethod(Invocation.method(#clone, []), + returnValue: _FakeHub()) as _i3.Hub); + @override + void configureScope(_i3.ScopeCallback? callback) => + super.noSuchMethod(Invocation.method(#configureScope, [callback]), + returnValueForMissingStub: null); +} + +/// A class which mocks [Transport]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockTransport extends _i1.Mock implements _i9.Transport { + MockTransport() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Future<_i2.SentryId> send(_i5.SentryEvent? event) => (super.noSuchMethod( + Invocation.method(#send, [event]), + returnValue: Future.value(_FakeSentryId())) as _i4.Future<_i2.SentryId>); +} diff --git a/flutter/test/not_initialized_widgets_binding_test.dart b/flutter/test/not_initialized_widgets_binding_test.dart index fb4e8bc5ec..7ffebc982a 100644 --- a/flutter/test/not_initialized_widgets_binding_test.dart +++ b/flutter/test/not_initialized_widgets_binding_test.dart @@ -2,12 +2,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; /// Tests that require `WidgetsFlutterBinding.ensureInitialized();` not /// being called at all. void main() { - Fixture fixture; + late Fixture fixture; setUp(() { fixture = Fixture(); diff --git a/flutter/test/sentry_flutter_test.dart b/flutter/test/sentry_flutter_test.dart index 0ea57a7231..88fa44c7a5 100644 --- a/flutter/test/sentry_flutter_test.dart +++ b/flutter/test/sentry_flutter_test.dart @@ -6,6 +6,7 @@ import 'package:sentry/sentry.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'mocks.dart'; +import 'mocks.mocks.dart'; import 'sentry_flutter_util.dart'; void main() { @@ -75,8 +76,8 @@ void main() { final event = verify(transport.send(captureAny)).captured.first as SentryEvent; - expect(event.sdk.integrations.length, 7); - expect(event.sdk.integrations.contains('loadContextsIntegration'), true); + expect(event.sdk!.integrations.length, 7); + expect(event.sdk!.integrations.contains('loadContextsIntegration'), true); }); test('should not add loadContextsIntegration if not ios', () async { @@ -94,8 +95,9 @@ void main() { final event = verify(transport.send(captureAny)).captured.first as SentryEvent; - expect(event.sdk.integrations.length, 6); - expect(event.sdk.integrations.contains('loadContextsIntegration'), false); + expect(event.sdk!.integrations.length, 6); + expect( + event.sdk!.integrations.contains('loadContextsIntegration'), false); }); test('should not add loadAndroidImageListIntegration if not Android', @@ -114,8 +116,9 @@ void main() { final event = verify(transport.send(captureAny)).captured.first as SentryEvent; - expect(event.sdk.integrations.length, 6); - expect(event.sdk.integrations.contains('loadAndroidImageListIntegration'), + expect(event.sdk!.integrations.length, 6); + expect( + event.sdk!.integrations.contains('loadAndroidImageListIntegration'), false); }); }); diff --git a/flutter/test/sentry_flutter_web_test.dart b/flutter/test/sentry_flutter_web_test.dart index 3a8dafe31b..2c453b2c39 100644 --- a/flutter/test/sentry_flutter_web_test.dart +++ b/flutter/test/sentry_flutter_web_test.dart @@ -2,7 +2,7 @@ // import 'package:sentry_flutter/sentry_flutter.dart'; @TestOn('browser') -import 'package:test/test.dart'; +import 'package:flutter_test/flutter_test.dart'; // import 'sentry_flutter_util_test.dart'; diff --git a/flutter/test/sentry_navigator_observer_test.dart b/flutter/test/sentry_navigator_observer_test.dart index c07dff64d1..fd08c2a61d 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; void main() { group('RouteObserverBreadcrumb', () { @@ -135,12 +135,12 @@ void main() { }); group('SentryNavigatorObserver', () { - PageRoute route(RouteSettings settings) => PageRouteBuilder( - pageBuilder: (_, __, ___) => null, + PageRoute route(RouteSettings? settings) => PageRouteBuilder( + pageBuilder: (_, __, ___) => Container(), settings: settings, ); - RouteSettings routeSettings(String name, [Object arguments]) => + RouteSettings routeSettings(String name, [Object? arguments]) => RouteSettings(name: name, arguments: arguments); test('Test recording of Breadcrumbs', () { @@ -206,7 +206,7 @@ void main() { test('No RouteSettings', () { PageRoute route() => PageRouteBuilder( - pageBuilder: (_, __, ___) => null, + pageBuilder: (_, __, ___) => Container(), ); final hub = MockHub(); diff --git a/flutter/test/version_test.dart b/flutter/test/version_test.dart index 73ce0c4a9a..c909fa778d 100644 --- a/flutter/test/version_test.dart +++ b/flutter/test/version_test.dart @@ -6,8 +6,8 @@ import 'dart:io'; +import 'package:flutter_test/flutter_test.dart'; import 'package:sentry_flutter/src/version.dart'; -import 'package:test/test.dart'; import 'package:yaml/yaml.dart' as yaml; void main() { diff --git a/flutter/test/widgets_binding_observer_test.dart b/flutter/test/widgets_binding_observer_test.dart index ac413d8286..501980701c 100644 --- a/flutter/test/widgets_binding_observer_test.dart +++ b/flutter/test/widgets_binding_observer_test.dart @@ -6,12 +6,12 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/sentry_flutter_options.dart'; import 'package:sentry_flutter/src/widgets_binding_observer.dart'; -import 'mocks.dart'; +import 'mocks.mocks.dart'; void main() { group('WidgetsBindingObserver', () { - SentryFlutterOptions flutterTrackingEnabledOptions; - SentryFlutterOptions flutterTrackingDisabledOptions; + late SentryFlutterOptions flutterTrackingEnabledOptions; + late SentryFlutterOptions flutterTrackingDisabledOptions; setUp(() { WidgetsFlutterBinding.ensureInitialized(); @@ -30,12 +30,12 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); final message = const JSONMessageCodec() .encodeMessage({'type': 'memoryPressure'}); - await WidgetsBinding.instance.defaultBinaryMessenger + await WidgetsBinding.instance!.defaultBinaryMessenger .handlePlatformMessage('flutter/system', message, (_) {}); final breadcrumb = @@ -51,7 +51,7 @@ void main() { expect(breadcrumb.type, 'system'); expect(breadcrumb.category, 'device.event'); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable memory pressure breadcrumb', @@ -62,22 +62,22 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); final message = const JSONMessageCodec() .encodeMessage({'type': 'memoryPressure'}); - await WidgetsBinding.instance.defaultBinaryMessenger + await WidgetsBinding.instance!.defaultBinaryMessenger .handlePlatformMessage('flutter/system', message, (_) {}); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('lifecycle breadcrumbs', (WidgetTester tester) async { Future sendLifecycle(String event) async { - final messenger = ServicesBinding.instance.defaultBinaryMessenger; + final messenger = ServicesBinding.instance!.defaultBinaryMessenger; final message = const StringCodec().encodeMessage('AppLifecycleState.$event'); await messenger.handlePlatformMessage( @@ -94,7 +94,7 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); // paused lifecycle event await sendLifecycle('paused'); @@ -136,12 +136,12 @@ void main() { expect(breadcrumb.data, mapForLifecycle('detached')); expect(breadcrumb.level, SentryLevel.info); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable lifecycle breadcrumbs', (WidgetTester tester) async { Future sendLifecycle(String event) async { - final messenger = ServicesBinding.instance.defaultBinaryMessenger; + final messenger = ServicesBinding.instance!.defaultBinaryMessenger; final message = const StringCodec().encodeMessage('AppLifecycleState.$event'); await messenger.handlePlatformMessage( @@ -154,13 +154,13 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); await sendLifecycle('paused'); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('metrics changed breadcrumb', (WidgetTester tester) async { @@ -170,11 +170,11 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onMetricsChanged(); + window.onMetricsChanged!(); final breadcrumb = verify(hub.addBreadcrumb(captureAny)).captured.single as Breadcrumb; @@ -189,7 +189,7 @@ void main() { 'new_width': window.physicalSize.width, }); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable metrics changed breadcrumb', @@ -200,15 +200,15 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onMetricsChanged(); + window.onMetricsChanged!(); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('platform brightness breadcrumb', (WidgetTester tester) async { @@ -218,13 +218,13 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onPlatformBrightnessChanged(); + window.onPlatformBrightnessChanged!(); - final brightness = WidgetsBinding.instance.window.platformBrightness; + final brightness = WidgetsBinding.instance!.window.platformBrightness; final brightnessDescription = brightness == Brightness.dark ? 'dark' : 'light'; @@ -241,7 +241,7 @@ void main() { 'action': 'BRIGHTNESS_CHANGED_TO_${brightnessDescription.toUpperCase()}' }); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable platform brightness breadcrumb', @@ -252,15 +252,15 @@ void main() { hub: hub, options: flutterTrackingDisabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onPlatformBrightnessChanged(); + window.onPlatformBrightnessChanged!(); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('text scale factor brightness changed breadcrumb', @@ -271,13 +271,14 @@ void main() { hub: hub, options: flutterTrackingEnabledOptions, ); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onTextScaleFactorChanged(); + window.onTextScaleFactorChanged!(); - final newTextScaleFactor = WidgetsBinding.instance.window.textScaleFactor; + final newTextScaleFactor = + WidgetsBinding.instance!.window.textScaleFactor; final breadcrumb = verify(hub.addBreadcrumb(captureAny)).captured.single as Breadcrumb; @@ -291,7 +292,7 @@ void main() { 'action': 'TEXT_SCALE_CHANGED_TO_$newTextScaleFactor' }); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); testWidgets('disable text scale factor brightness changed breadcrumb', @@ -300,15 +301,15 @@ void main() { final observer = SentryWidgetsBindingObserver( hub: hub, options: flutterTrackingDisabledOptions); - WidgetsBinding.instance.addObserver(observer); + WidgetsBinding.instance!.addObserver(observer); - final window = WidgetsBinding.instance.window; + final window = WidgetsBinding.instance!.window; - window.onTextScaleFactorChanged(); + window.onTextScaleFactorChanged!(); verifyNever(hub.addBreadcrumb(captureAny)); - WidgetsBinding.instance.removeObserver(observer); + WidgetsBinding.instance!.removeObserver(observer); }); }); }