From 8951a2f5d45556d4e4a9029944fbc1abf2ec292f Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Fri, 19 Jul 2024 14:13:58 +0200 Subject: [PATCH 1/6] wip: test native integration --- .../src/native/java/sentry_native_java.dart | 13 +- flutter/test/mocks.dart | 30 ++ flutter/test/mocks.mocks.dart | 498 ++++++++++-------- flutter/test/replay/replay_native_test.dart | 58 ++ 4 files changed, 358 insertions(+), 241 deletions(-) create mode 100644 flutter/test/replay/replay_native_test.dart diff --git a/flutter/lib/src/native/java/sentry_native_java.dart b/flutter/lib/src/native/java/sentry_native_java.dart index 0ce7117afd..8690a5f30b 100644 --- a/flutter/lib/src/native/java/sentry_native_java.dart +++ b/flutter/lib/src/native/java/sentry_native_java.dart @@ -14,7 +14,6 @@ import '../sentry_native_channel.dart'; @internal class SentryNativeJava extends SentryNativeChannel { ScreenshotRecorder? _replayRecorder; - late final SentryFlutterOptions _options; SentryNativeJava(super.options, super.channel); @override @@ -22,8 +21,6 @@ class SentryNativeJava extends SentryNativeChannel { // We only need these when replay is enabled (session or error capture) // so let's set it up conditionally. This allows Dart to trim the code. if (options.experimental.replay.isEnabled) { - _options = options; - // We only need the integration when error-replay capture is enabled. if ((options.experimental.replay.errorSampleRate ?? 0) > 0) { options.addEventProcessor(ReplayEventProcessor(this)); @@ -89,7 +86,7 @@ class SentryNativeJava extends SentryNativeChannel { final timestamp = DateTime.now().millisecondsSinceEpoch; final filePath = "$cacheDir/$timestamp.png"; - _options.logger( + options.logger( SentryLevel.debug, 'Replay: Saving screenshot to $filePath (' '${image.width}x${image.height} pixels, ' @@ -102,7 +99,7 @@ class SentryNativeJava extends SentryNativeChannel { {'path': filePath, 'timestamp': timestamp}, ); } catch (error, stackTrace) { - _options.logger( + options.logger( SentryLevel.error, 'Native call `addReplayScreenshot` failed', exception: error, @@ -112,10 +109,6 @@ class SentryNativeJava extends SentryNativeChannel { } }; - _replayRecorder = ScreenshotRecorder( - config, - callback, - _options, - )..start(); + _replayRecorder = ScreenshotRecorder(config, callback, options)..start(); } } diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index f5e5cb65ce..ff1aaa5b23 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/src/widgets/binding.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'package:sentry/src/platform/platform.dart'; import 'package:sentry/src/sentry_tracer.dart'; @@ -206,3 +207,32 @@ final fakeFrameDurations = [ Duration(milliseconds: 40), Duration(milliseconds: 710), ]; + +@GenerateMocks([Callbacks]) +abstract class Callbacks { + Future? methodCallHandler(MethodCall message); +} + +class NativeChannelFixture { + late final MethodChannel channel; + late final Future? Function(MethodCall?) handler; + + NativeChannelFixture() { + TestWidgetsFlutterBinding.ensureInitialized(); + channel = MockMethodChannel(); + when(channel.name).thenReturn('test.channel'); + when(channel.binaryMessenger).thenReturn( + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger); + handler = MockCallbacks().methodCallHandler; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, handler); + } + + // Mock this call as if it was invoked by the native side. + Future invokeFromNative(String method, [dynamic arguments]) async { + final call = + StandardMethodCodec().encodeMethodCall(MethodCall(method, arguments)); + return TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage(channel.name, call, (ByteData? data) {}); + } +} diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 01d2127efe..5f728c707a 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -3,27 +3,22 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; -import 'dart:typed_data' as _i16; +import 'dart:async' as _i7; +import 'dart:typed_data' as _i12; -import 'package:flutter/src/services/binary_messenger.dart' as _i6; -import 'package:flutter/src/services/message_codec.dart' as _i5; -import 'package:flutter/src/services/platform_channel.dart' as _i12; +import 'package:flutter/services.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i10; -import 'package:sentry/sentry.dart' as _i2; -import 'package:sentry/src/metrics/metric.dart' as _i19; -import 'package:sentry/src/metrics/metrics_api.dart' as _i7; -import 'package:sentry/src/profiling.dart' as _i11; -import 'package:sentry/src/protocol.dart' as _i3; -import 'package:sentry/src/sentry_envelope.dart' as _i9; -import 'package:sentry/src/sentry_tracer.dart' as _i4; -import 'package:sentry_flutter/sentry_flutter.dart' as _i14; -import 'package:sentry_flutter/src/native/native_app_start.dart' as _i15; -import 'package:sentry_flutter/src/native/native_frames.dart' as _i17; -import 'package:sentry_flutter/src/native/sentry_native_binding.dart' as _i13; - -import 'mocks.dart' as _i18; +import 'package:mockito/src/dummies.dart' as _i8; +import 'package:sentry/src/metrics/metric.dart' as _i14; +import 'package:sentry/src/metrics/metrics_api.dart' as _i5; +import 'package:sentry/src/profiling.dart' as _i9; +import 'package:sentry/src/sentry_tracer.dart' as _i3; +import 'package:sentry_flutter/sentry_flutter.dart' as _i2; +import 'package:sentry_flutter/src/native/native_app_start.dart' as _i11; +import 'package:sentry_flutter/src/native/native_frames.dart' as _i13; +import 'package:sentry_flutter/src/native/sentry_native_binding.dart' as _i10; + +import 'mocks.dart' as _i6; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -70,7 +65,7 @@ class _FakeISentrySpan_2 extends _i1.SmartFake implements _i2.ISentrySpan { } class _FakeSentryTraceHeader_3 extends _i1.SmartFake - implements _i3.SentryTraceHeader { + implements _i2.SentryTraceHeader { _FakeSentryTraceHeader_3( Object parent, Invocation parentInvocation, @@ -80,7 +75,7 @@ class _FakeSentryTraceHeader_3 extends _i1.SmartFake ); } -class _FakeSentryTracer_4 extends _i1.SmartFake implements _i4.SentryTracer { +class _FakeSentryTracer_4 extends _i1.SmartFake implements _i3.SentryTracer { _FakeSentryTracer_4( Object parent, Invocation parentInvocation, @@ -90,7 +85,7 @@ class _FakeSentryTracer_4 extends _i1.SmartFake implements _i4.SentryTracer { ); } -class _FakeSentryId_5 extends _i1.SmartFake implements _i3.SentryId { +class _FakeSentryId_5 extends _i1.SmartFake implements _i2.SentryId { _FakeSentryId_5( Object parent, Invocation parentInvocation, @@ -100,7 +95,7 @@ class _FakeSentryId_5 extends _i1.SmartFake implements _i3.SentryId { ); } -class _FakeContexts_6 extends _i1.SmartFake implements _i3.Contexts { +class _FakeContexts_6 extends _i1.SmartFake implements _i2.Contexts { _FakeContexts_6( Object parent, Invocation parentInvocation, @@ -111,7 +106,7 @@ class _FakeContexts_6 extends _i1.SmartFake implements _i3.Contexts { } class _FakeSentryTransaction_7 extends _i1.SmartFake - implements _i3.SentryTransaction { + implements _i2.SentryTransaction { _FakeSentryTransaction_7( Object parent, Invocation parentInvocation, @@ -121,7 +116,7 @@ class _FakeSentryTransaction_7 extends _i1.SmartFake ); } -class _FakeMethodCodec_8 extends _i1.SmartFake implements _i5.MethodCodec { +class _FakeMethodCodec_8 extends _i1.SmartFake implements _i4.MethodCodec { _FakeMethodCodec_8( Object parent, Invocation parentInvocation, @@ -132,7 +127,7 @@ class _FakeMethodCodec_8 extends _i1.SmartFake implements _i5.MethodCodec { } class _FakeBinaryMessenger_9 extends _i1.SmartFake - implements _i6.BinaryMessenger { + implements _i4.BinaryMessenger { _FakeBinaryMessenger_9( Object parent, Invocation parentInvocation, @@ -152,7 +147,7 @@ class _FakeSentryOptions_10 extends _i1.SmartFake implements _i2.SentryOptions { ); } -class _FakeMetricsApi_11 extends _i1.SmartFake implements _i7.MetricsApi { +class _FakeMetricsApi_11 extends _i1.SmartFake implements _i5.MetricsApi { _FakeMetricsApi_11( Object parent, Invocation parentInvocation, @@ -182,6 +177,22 @@ class _FakeHub_13 extends _i1.SmartFake implements _i2.Hub { ); } +/// A class which mocks [Callbacks]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCallbacks extends _i1.Mock implements _i6.Callbacks { + MockCallbacks() { + _i1.throwOnMissingStub(this); + } + + @override + _i7.Future? methodCallHandler(_i4.MethodCall? message) => + (super.noSuchMethod(Invocation.method( + #methodCallHandler, + [message], + )) as _i7.Future?); +} + /// A class which mocks [Transport]. /// /// See the documentation for Mockito's code generation for more information. @@ -191,20 +202,20 @@ class MockTransport extends _i1.Mock implements _i2.Transport { } @override - _i8.Future<_i3.SentryId?> send(_i9.SentryEnvelope? envelope) => + _i7.Future<_i2.SentryId?> send(_i2.SentryEnvelope? envelope) => (super.noSuchMethod( Invocation.method( #send, [envelope], ), - returnValue: _i8.Future<_i3.SentryId?>.value(), - ) as _i8.Future<_i3.SentryId?>); + returnValue: _i7.Future<_i2.SentryId?>.value(), + ) as _i7.Future<_i2.SentryId?>); } /// A class which mocks [SentryTracer]. /// /// See the documentation for Mockito's code generation for more information. -class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { +class MockSentryTracer extends _i1.Mock implements _i3.SentryTracer { MockSentryTracer() { _i1.throwOnMissingStub(this); } @@ -212,7 +223,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { @override String get name => (super.noSuchMethod( Invocation.getter(#name), - returnValue: _i10.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#name), ), @@ -228,15 +239,15 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ); @override - _i3.SentryTransactionNameSource get transactionNameSource => + _i2.SentryTransactionNameSource get transactionNameSource => (super.noSuchMethod( Invocation.getter(#transactionNameSource), - returnValue: _i3.SentryTransactionNameSource.custom, - ) as _i3.SentryTransactionNameSource); + returnValue: _i2.SentryTransactionNameSource.custom, + ) as _i2.SentryTransactionNameSource); @override set transactionNameSource( - _i3.SentryTransactionNameSource? _transactionNameSource) => + _i2.SentryTransactionNameSource? _transactionNameSource) => super.noSuchMethod( Invocation.setter( #transactionNameSource, @@ -246,7 +257,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ); @override - set profiler(_i11.SentryProfiler? _profiler) => super.noSuchMethod( + set profiler(_i9.SentryProfiler? _profiler) => super.noSuchMethod( Invocation.setter( #profiler, _profiler, @@ -255,7 +266,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ); @override - set profileInfo(_i11.SentryProfileInfo? _profileInfo) => super.noSuchMethod( + set profileInfo(_i9.SentryProfileInfo? _profileInfo) => super.noSuchMethod( Invocation.setter( #profileInfo, _profileInfo, @@ -303,10 +314,10 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ) as bool); @override - List<_i3.SentrySpan> get children => (super.noSuchMethod( + List<_i2.SentrySpan> get children => (super.noSuchMethod( Invocation.getter(#children), - returnValue: <_i3.SentrySpan>[], - ) as List<_i3.SentrySpan>); + returnValue: <_i2.SentrySpan>[], + ) as List<_i2.SentrySpan>); @override set throwable(dynamic throwable) => super.noSuchMethod( @@ -318,7 +329,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ); @override - set status(_i3.SpanStatus? status) => super.noSuchMethod( + set status(_i2.SpanStatus? status) => super.noSuchMethod( Invocation.setter( #status, status, @@ -339,8 +350,8 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ) as Map); @override - _i8.Future finish({ - _i3.SpanStatus? status, + _i7.Future finish({ + _i2.SpanStatus? status, DateTime? endTimestamp, }) => (super.noSuchMethod( @@ -352,9 +363,9 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { #endTimestamp: endTimestamp, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void removeData(String? key) => super.noSuchMethod( @@ -436,7 +447,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { @override _i2.ISentrySpan startChildWithParentSpanId( - _i3.SpanId? parentSpanId, + _i2.SpanId? parentSpanId, String? operation, { String? description, DateTime? startTimestamp, @@ -470,7 +481,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { ) as _i2.ISentrySpan); @override - _i3.SentryTraceHeader toSentryTrace() => (super.noSuchMethod( + _i2.SentryTraceHeader toSentryTrace() => (super.noSuchMethod( Invocation.method( #toSentryTrace, [], @@ -482,7 +493,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { [], ), ), - ) as _i3.SentryTraceHeader); + ) as _i2.SentryTraceHeader); @override void setMeasurement( @@ -516,7 +527,7 @@ class MockSentryTracer extends _i1.Mock implements _i4.SentryTracer { /// /// See the documentation for Mockito's code generation for more information. // ignore: must_be_immutable -class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { +class MockSentryTransaction extends _i1.Mock implements _i2.SentryTransaction { MockSentryTransaction() { _i1.throwOnMissingStub(this); } @@ -540,13 +551,13 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { ); @override - List<_i3.SentrySpan> get spans => (super.noSuchMethod( + List<_i2.SentrySpan> get spans => (super.noSuchMethod( Invocation.getter(#spans), - returnValue: <_i3.SentrySpan>[], - ) as List<_i3.SentrySpan>); + returnValue: <_i2.SentrySpan>[], + ) as List<_i2.SentrySpan>); @override - set spans(List<_i3.SentrySpan>? _spans) => super.noSuchMethod( + set spans(List<_i2.SentrySpan>? _spans) => super.noSuchMethod( Invocation.setter( #spans, _spans, @@ -555,13 +566,13 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { ); @override - _i4.SentryTracer get tracer => (super.noSuchMethod( + _i3.SentryTracer get tracer => (super.noSuchMethod( Invocation.getter(#tracer), returnValue: _FakeSentryTracer_4( this, Invocation.getter(#tracer), ), - ) as _i4.SentryTracer); + ) as _i3.SentryTracer); @override Map get measurements => (super.noSuchMethod( @@ -580,7 +591,7 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { ); @override - set metricSummaries(Map>? _metricSummaries) => + set metricSummaries(Map>? _metricSummaries) => super.noSuchMethod( Invocation.setter( #metricSummaries, @@ -590,7 +601,7 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { ); @override - set transactionInfo(_i3.SentryTransactionInfo? _transactionInfo) => + set transactionInfo(_i2.SentryTransactionInfo? _transactionInfo) => super.noSuchMethod( Invocation.setter( #transactionInfo, @@ -612,22 +623,22 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { ) as bool); @override - _i3.SentryId get eventId => (super.noSuchMethod( + _i2.SentryId get eventId => (super.noSuchMethod( Invocation.getter(#eventId), returnValue: _FakeSentryId_5( this, Invocation.getter(#eventId), ), - ) as _i3.SentryId); + ) as _i2.SentryId); @override - _i3.Contexts get contexts => (super.noSuchMethod( + _i2.Contexts get contexts => (super.noSuchMethod( Invocation.getter(#contexts), returnValue: _FakeContexts_6( this, Invocation.getter(#contexts), ), - ) as _i3.Contexts); + ) as _i2.Contexts); @override Map toJson() => (super.noSuchMethod( @@ -639,8 +650,8 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { ) as Map); @override - _i3.SentryTransaction copyWith({ - _i3.SentryId? eventId, + _i2.SentryTransaction copyWith({ + _i2.SentryId? eventId, DateTime? timestamp, String? platform, String? logger, @@ -649,26 +660,26 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { String? dist, String? environment, Map? modules, - _i3.SentryMessage? message, + _i2.SentryMessage? message, String? transaction, dynamic throwable, - _i3.SentryLevel? level, + _i2.SentryLevel? level, String? culprit, Map? tags, Map? extra, List? fingerprint, - _i3.SentryUser? user, - _i3.Contexts? contexts, - List<_i3.Breadcrumb>? breadcrumbs, - _i3.SdkVersion? sdk, - _i3.SentryRequest? request, - _i3.DebugMeta? debugMeta, - List<_i3.SentryException>? exceptions, - List<_i3.SentryThread>? threads, + _i2.SentryUser? user, + _i2.Contexts? contexts, + List<_i2.Breadcrumb>? breadcrumbs, + _i2.SdkVersion? sdk, + _i2.SentryRequest? request, + _i2.DebugMeta? debugMeta, + List<_i2.SentryException>? exceptions, + List<_i2.SentryThread>? threads, String? type, Map? measurements, - Map>? metricSummaries, - _i3.SentryTransactionInfo? transactionInfo, + Map>? metricSummaries, + _i2.SentryTransactionInfo? transactionInfo, }) => (super.noSuchMethod( Invocation.method( @@ -744,13 +755,13 @@ class MockSentryTransaction extends _i1.Mock implements _i3.SentryTransaction { }, ), ), - ) as _i3.SentryTransaction); + ) as _i2.SentryTransaction); } /// A class which mocks [SentrySpan]. /// /// See the documentation for Mockito's code generation for more information. -class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { +class MockSentrySpan extends _i1.Mock implements _i2.SentrySpan { MockSentrySpan() { _i1.throwOnMissingStub(this); } @@ -762,16 +773,16 @@ class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { ) as bool); @override - _i4.SentryTracer get tracer => (super.noSuchMethod( + _i3.SentryTracer get tracer => (super.noSuchMethod( Invocation.getter(#tracer), returnValue: _FakeSentryTracer_4( this, Invocation.getter(#tracer), ), - ) as _i4.SentryTracer); + ) as _i3.SentryTracer); @override - set status(_i3.SpanStatus? status) => super.noSuchMethod( + set status(_i2.SpanStatus? status) => super.noSuchMethod( Invocation.setter( #status, status, @@ -834,8 +845,8 @@ class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { ) as Map); @override - _i8.Future finish({ - _i3.SpanStatus? status, + _i7.Future finish({ + _i2.SpanStatus? status, DateTime? endTimestamp, }) => (super.noSuchMethod( @@ -847,9 +858,9 @@ class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { #endTimestamp: endTimestamp, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void removeData(String? key) => super.noSuchMethod( @@ -939,7 +950,7 @@ class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { ) as Map); @override - _i3.SentryTraceHeader toSentryTrace() => (super.noSuchMethod( + _i2.SentryTraceHeader toSentryTrace() => (super.noSuchMethod( Invocation.method( #toSentryTrace, [], @@ -951,7 +962,7 @@ class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { [], ), ), - ) as _i3.SentryTraceHeader); + ) as _i2.SentryTraceHeader); @override void setMeasurement( @@ -984,7 +995,7 @@ class MockSentrySpan extends _i1.Mock implements _i3.SentrySpan { /// A class which mocks [MethodChannel]. /// /// See the documentation for Mockito's code generation for more information. -class MockMethodChannel extends _i1.Mock implements _i12.MethodChannel { +class MockMethodChannel extends _i1.Mock implements _i4.MethodChannel { MockMethodChannel() { _i1.throwOnMissingStub(this); } @@ -992,32 +1003,32 @@ class MockMethodChannel extends _i1.Mock implements _i12.MethodChannel { @override String get name => (super.noSuchMethod( Invocation.getter(#name), - returnValue: _i10.dummyValue( + returnValue: _i8.dummyValue( this, Invocation.getter(#name), ), ) as String); @override - _i5.MethodCodec get codec => (super.noSuchMethod( + _i4.MethodCodec get codec => (super.noSuchMethod( Invocation.getter(#codec), returnValue: _FakeMethodCodec_8( this, Invocation.getter(#codec), ), - ) as _i5.MethodCodec); + ) as _i4.MethodCodec); @override - _i6.BinaryMessenger get binaryMessenger => (super.noSuchMethod( + _i4.BinaryMessenger get binaryMessenger => (super.noSuchMethod( Invocation.getter(#binaryMessenger), returnValue: _FakeBinaryMessenger_9( this, Invocation.getter(#binaryMessenger), ), - ) as _i6.BinaryMessenger); + ) as _i4.BinaryMessenger); @override - _i8.Future invokeMethod( + _i7.Future invokeMethod( String? method, [ dynamic arguments, ]) => @@ -1029,11 +1040,11 @@ class MockMethodChannel extends _i1.Mock implements _i12.MethodChannel { arguments, ], ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future?> invokeListMethod( + _i7.Future?> invokeListMethod( String? method, [ dynamic arguments, ]) => @@ -1045,11 +1056,11 @@ class MockMethodChannel extends _i1.Mock implements _i12.MethodChannel { arguments, ], ), - returnValue: _i8.Future?>.value(), - ) as _i8.Future?>); + returnValue: _i7.Future?>.value(), + ) as _i7.Future?>); @override - _i8.Future?> invokeMapMethod( + _i7.Future?> invokeMapMethod( String? method, [ dynamic arguments, ]) => @@ -1061,12 +1072,12 @@ class MockMethodChannel extends _i1.Mock implements _i12.MethodChannel { arguments, ], ), - returnValue: _i8.Future?>.value(), - ) as _i8.Future?>); + returnValue: _i7.Future?>.value(), + ) as _i7.Future?>); @override void setMethodCallHandler( - _i8.Future Function(_i5.MethodCall)? handler) => + _i7.Future Function(_i4.MethodCall)? handler) => super.noSuchMethod( Invocation.method( #setMethodCallHandler, @@ -1080,44 +1091,44 @@ class MockMethodChannel extends _i1.Mock implements _i12.MethodChannel { /// /// See the documentation for Mockito's code generation for more information. class MockSentryNativeBinding extends _i1.Mock - implements _i13.SentryNativeBinding { + implements _i10.SentryNativeBinding { MockSentryNativeBinding() { _i1.throwOnMissingStub(this); } @override - _i8.Future init(_i14.SentryFlutterOptions? options) => + _i7.Future init(_i2.SentryFlutterOptions? options) => (super.noSuchMethod( Invocation.method( #init, [options], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future close() => (super.noSuchMethod( + _i7.Future close() => (super.noSuchMethod( Invocation.method( #close, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future<_i15.NativeAppStart?> fetchNativeAppStart() => (super.noSuchMethod( + _i7.Future<_i11.NativeAppStart?> fetchNativeAppStart() => (super.noSuchMethod( Invocation.method( #fetchNativeAppStart, [], ), - returnValue: _i8.Future<_i15.NativeAppStart?>.value(), - ) as _i8.Future<_i15.NativeAppStart?>); + returnValue: _i7.Future<_i11.NativeAppStart?>.value(), + ) as _i7.Future<_i11.NativeAppStart?>); @override - _i8.Future captureEnvelope( - _i16.Uint8List? envelopeData, + _i7.Future captureEnvelope( + _i12.Uint8List? envelopeData, bool? containsUnhandledException, ) => (super.noSuchMethod( @@ -1128,72 +1139,72 @@ class MockSentryNativeBinding extends _i1.Mock containsUnhandledException, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future beginNativeFrames() => (super.noSuchMethod( + _i7.Future beginNativeFrames() => (super.noSuchMethod( Invocation.method( #beginNativeFrames, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future<_i17.NativeFrames?> endNativeFrames(_i3.SentryId? id) => + _i7.Future<_i13.NativeFrames?> endNativeFrames(_i2.SentryId? id) => (super.noSuchMethod( Invocation.method( #endNativeFrames, [id], ), - returnValue: _i8.Future<_i17.NativeFrames?>.value(), - ) as _i8.Future<_i17.NativeFrames?>); + returnValue: _i7.Future<_i13.NativeFrames?>.value(), + ) as _i7.Future<_i13.NativeFrames?>); @override - _i8.Future setUser(_i3.SentryUser? user) => (super.noSuchMethod( + _i7.Future setUser(_i2.SentryUser? user) => (super.noSuchMethod( Invocation.method( #setUser, [user], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future addBreadcrumb(_i3.Breadcrumb? breadcrumb) => + _i7.Future addBreadcrumb(_i2.Breadcrumb? breadcrumb) => (super.noSuchMethod( Invocation.method( #addBreadcrumb, [breadcrumb], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future clearBreadcrumbs() => (super.noSuchMethod( + _i7.Future clearBreadcrumbs() => (super.noSuchMethod( Invocation.method( #clearBreadcrumbs, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future?> loadContexts() => (super.noSuchMethod( + _i7.Future?> loadContexts() => (super.noSuchMethod( Invocation.method( #loadContexts, [], ), - returnValue: _i8.Future?>.value(), - ) as _i8.Future?>); + returnValue: _i7.Future?>.value(), + ) as _i7.Future?>); @override - _i8.Future setContexts( + _i7.Future setContexts( String? key, dynamic value, ) => @@ -1205,22 +1216,22 @@ class MockSentryNativeBinding extends _i1.Mock value, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future removeContexts(String? key) => (super.noSuchMethod( + _i7.Future removeContexts(String? key) => (super.noSuchMethod( Invocation.method( #removeContexts, [key], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future setExtra( + _i7.Future setExtra( String? key, dynamic value, ) => @@ -1232,22 +1243,22 @@ class MockSentryNativeBinding extends _i1.Mock value, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future removeExtra(String? key) => (super.noSuchMethod( + _i7.Future removeExtra(String? key) => (super.noSuchMethod( Invocation.method( #removeExtra, [key], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future setTag( + _i7.Future setTag( String? key, String? value, ) => @@ -1259,50 +1270,50 @@ class MockSentryNativeBinding extends _i1.Mock value, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future removeTag(String? key) => (super.noSuchMethod( + _i7.Future removeTag(String? key) => (super.noSuchMethod( Invocation.method( #removeTag, [key], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - int? startProfiler(_i3.SentryId? traceId) => + int? startProfiler(_i2.SentryId? traceId) => (super.noSuchMethod(Invocation.method( #startProfiler, [traceId], )) as int?); @override - _i8.Future discardProfiler(_i3.SentryId? traceId) => + _i7.Future discardProfiler(_i2.SentryId? traceId) => (super.noSuchMethod( Invocation.method( #discardProfiler, [traceId], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future displayRefreshRate() => (super.noSuchMethod( + _i7.Future displayRefreshRate() => (super.noSuchMethod( Invocation.method( #displayRefreshRate, [], ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future?> collectProfile( - _i3.SentryId? traceId, + _i7.Future?> collectProfile( + _i2.SentryId? traceId, int? startTimeNs, int? endTimeNs, ) => @@ -1315,37 +1326,62 @@ class MockSentryNativeBinding extends _i1.Mock endTimeNs, ], ), - returnValue: _i8.Future?>.value(), - ) as _i8.Future?>); + returnValue: _i7.Future?>.value(), + ) as _i7.Future?>); @override - _i8.Future?> loadDebugImages() => (super.noSuchMethod( + _i7.Future?> loadDebugImages() => (super.noSuchMethod( Invocation.method( #loadDebugImages, [], ), - returnValue: _i8.Future?>.value(), - ) as _i8.Future?>); + returnValue: _i7.Future?>.value(), + ) as _i7.Future?>); @override - _i8.Future pauseAppHangTracking() => (super.noSuchMethod( + _i7.Future pauseAppHangTracking() => (super.noSuchMethod( Invocation.method( #pauseAppHangTracking, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future resumeAppHangTracking() => (super.noSuchMethod( + _i7.Future resumeAppHangTracking() => (super.noSuchMethod( Invocation.method( #resumeAppHangTracking, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + + @override + _i7.Future<_i2.SentryId> sendReplayForEvent( + _i2.SentryId? eventId, + bool? isCrash, + ) => + (super.noSuchMethod( + Invocation.method( + #sendReplayForEvent, + [ + eventId, + isCrash, + ], + ), + returnValue: _i7.Future<_i2.SentryId>.value(_FakeSentryId_5( + this, + Invocation.method( + #sendReplayForEvent, + [ + eventId, + isCrash, + ], + ), + )), + ) as _i7.Future<_i2.SentryId>); } /// A class which mocks [Hub]. @@ -1366,13 +1402,13 @@ class MockHub extends _i1.Mock implements _i2.Hub { ) as _i2.SentryOptions); @override - _i7.MetricsApi get metricsApi => (super.noSuchMethod( + _i5.MetricsApi get metricsApi => (super.noSuchMethod( Invocation.getter(#metricsApi), returnValue: _FakeMetricsApi_11( this, Invocation.getter(#metricsApi), ), - ) as _i7.MetricsApi); + ) as _i5.MetricsApi); @override bool get isEnabled => (super.noSuchMethod( @@ -1381,13 +1417,13 @@ class MockHub extends _i1.Mock implements _i2.Hub { ) as bool); @override - _i3.SentryId get lastEventId => (super.noSuchMethod( + _i2.SentryId get lastEventId => (super.noSuchMethod( Invocation.getter(#lastEventId), returnValue: _FakeSentryId_5( this, Invocation.getter(#lastEventId), ), - ) as _i3.SentryId); + ) as _i2.SentryId); @override _i2.Scope get scope => (super.noSuchMethod( @@ -1399,7 +1435,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { ) as _i2.Scope); @override - set profilerFactory(_i11.SentryProfilerFactory? value) => super.noSuchMethod( + set profilerFactory(_i9.SentryProfilerFactory? value) => super.noSuchMethod( Invocation.setter( #profilerFactory, value, @@ -1408,8 +1444,8 @@ class MockHub extends _i1.Mock implements _i2.Hub { ); @override - _i8.Future<_i3.SentryId> captureEvent( - _i3.SentryEvent? event, { + _i7.Future<_i2.SentryId> captureEvent( + _i2.SentryEvent? event, { dynamic stackTrace, _i2.Hint? hint, _i2.ScopeCallback? withScope, @@ -1424,7 +1460,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { #withScope: withScope, }, ), - returnValue: _i8.Future<_i3.SentryId>.value(_FakeSentryId_5( + returnValue: _i7.Future<_i2.SentryId>.value(_FakeSentryId_5( this, Invocation.method( #captureEvent, @@ -1436,10 +1472,10 @@ class MockHub extends _i1.Mock implements _i2.Hub { }, ), )), - ) as _i8.Future<_i3.SentryId>); + ) as _i7.Future<_i2.SentryId>); @override - _i8.Future<_i3.SentryId> captureException( + _i7.Future<_i2.SentryId> captureException( dynamic throwable, { dynamic stackTrace, _i2.Hint? hint, @@ -1455,7 +1491,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { #withScope: withScope, }, ), - returnValue: _i8.Future<_i3.SentryId>.value(_FakeSentryId_5( + returnValue: _i7.Future<_i2.SentryId>.value(_FakeSentryId_5( this, Invocation.method( #captureException, @@ -1467,12 +1503,12 @@ class MockHub extends _i1.Mock implements _i2.Hub { }, ), )), - ) as _i8.Future<_i3.SentryId>); + ) as _i7.Future<_i2.SentryId>); @override - _i8.Future<_i3.SentryId> captureMessage( + _i7.Future<_i2.SentryId> captureMessage( String? message, { - _i3.SentryLevel? level, + _i2.SentryLevel? level, String? template, List? params, _i2.Hint? hint, @@ -1490,7 +1526,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { #withScope: withScope, }, ), - returnValue: _i8.Future<_i3.SentryId>.value(_FakeSentryId_5( + returnValue: _i7.Future<_i2.SentryId>.value(_FakeSentryId_5( this, Invocation.method( #captureMessage, @@ -1504,22 +1540,22 @@ class MockHub extends _i1.Mock implements _i2.Hub { }, ), )), - ) as _i8.Future<_i3.SentryId>); + ) as _i7.Future<_i2.SentryId>); @override - _i8.Future captureUserFeedback(_i2.SentryUserFeedback? userFeedback) => + _i7.Future captureUserFeedback(_i2.SentryUserFeedback? userFeedback) => (super.noSuchMethod( Invocation.method( #captureUserFeedback, [userFeedback], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.Future addBreadcrumb( - _i3.Breadcrumb? crumb, { + _i7.Future addBreadcrumb( + _i2.Breadcrumb? crumb, { _i2.Hint? hint, }) => (super.noSuchMethod( @@ -1528,9 +1564,9 @@ class MockHub extends _i1.Mock implements _i2.Hub { [crumb], {#hint: hint}, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void bindClient(_i2.SentryClient? client) => super.noSuchMethod( @@ -1557,21 +1593,21 @@ class MockHub extends _i1.Mock implements _i2.Hub { ) as _i2.Hub); @override - _i8.Future close() => (super.noSuchMethod( + _i7.Future close() => (super.noSuchMethod( Invocation.method( #close, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i8.FutureOr configureScope(_i2.ScopeCallback? callback) => + _i7.FutureOr configureScope(_i2.ScopeCallback? callback) => (super.noSuchMethod(Invocation.method( #configureScope, [callback], - )) as _i8.FutureOr); + )) as _i7.FutureOr); @override _i2.ISentrySpan startTransaction( @@ -1604,7 +1640,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { #customSamplingContext: customSamplingContext, }, ), - returnValue: _i18.startTransactionShim( + returnValue: _i6.startTransactionShim( name, operation, description: description, @@ -1662,8 +1698,8 @@ class MockHub extends _i1.Mock implements _i2.Hub { ) as _i2.ISentrySpan); @override - _i8.Future<_i3.SentryId> captureTransaction( - _i3.SentryTransaction? transaction, { + _i7.Future<_i2.SentryId> captureTransaction( + _i2.SentryTransaction? transaction, { _i2.SentryTraceContextHeader? traceContext, }) => (super.noSuchMethod( @@ -1672,7 +1708,7 @@ class MockHub extends _i1.Mock implements _i2.Hub { [transaction], {#traceContext: traceContext}, ), - returnValue: _i8.Future<_i3.SentryId>.value(_FakeSentryId_5( + returnValue: _i7.Future<_i2.SentryId>.value(_FakeSentryId_5( this, Invocation.method( #captureTransaction, @@ -1680,24 +1716,24 @@ class MockHub extends _i1.Mock implements _i2.Hub { {#traceContext: traceContext}, ), )), - ) as _i8.Future<_i3.SentryId>); + ) as _i7.Future<_i2.SentryId>); @override - _i8.Future<_i3.SentryId> captureMetrics( - Map>? metricsBuckets) => + _i7.Future<_i2.SentryId> captureMetrics( + Map>? metricsBuckets) => (super.noSuchMethod( Invocation.method( #captureMetrics, [metricsBuckets], ), - returnValue: _i8.Future<_i3.SentryId>.value(_FakeSentryId_5( + returnValue: _i7.Future<_i2.SentryId>.value(_FakeSentryId_5( this, Invocation.method( #captureMetrics, [metricsBuckets], ), )), - ) as _i8.Future<_i3.SentryId>); + ) as _i7.Future<_i2.SentryId>); @override void setSpanContext( diff --git a/flutter/test/replay/replay_native_test.dart b/flutter/test/replay/replay_native_test.dart new file mode 100644 index 0000000000..ff4320fc3c --- /dev/null +++ b/flutter/test/replay/replay_native_test.dart @@ -0,0 +1,58 @@ +// ignore_for_file: inference_failure_on_function_invocation + +@TestOn('vm') +library flutter_test; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:sentry_flutter/src/event_processor/replay_event_processor.dart'; +import 'package:sentry_flutter/src/native/factory.dart'; +import 'package:sentry_flutter/src/native/sentry_native_binding.dart'; +import '../mocks.dart'; +import '../sentry_flutter_test.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + for (var mockPlatform in [ + MockPlatform.android(), + ]) { + group('$SentryNativeBinding ($mockPlatform)', () { + late SentryNativeBinding sut; + late NativeChannelFixture native; + late SentryFlutterOptions options; + + setUp(() { + options = SentryFlutterOptions( + dsn: fakeDsn, checker: getPlatformChecker(platform: mockPlatform)) + // ignore: invalid_use_of_internal_member + ..automatedTestMode = true; + + native = NativeChannelFixture(); + when(native.channel.invokeMethod('initNativeSdk', any)) + .thenAnswer((_) => Future.value()); + + sut = createBinding(options, channel: native.channel); + }); + + test('init sets $ReplayEventProcessor when error replay is enabled', + () async { + options.experimental.replay.errorSampleRate = 0.1; + await sut.init(options); + + expect(options.eventProcessors.map((e) => e.runtimeType.toString()), + contains('$ReplayEventProcessor')); + }); + + test( + 'init does not set $ReplayEventProcessor when error replay is disabled', + () async { + await sut.init(options); + + expect(options.eventProcessors.map((e) => e.runtimeType.toString()), + isNot(contains('$ReplayEventProcessor'))); + }); + }); + } +} From 6707457e8abfc5ac7679abbd657d23799e6f8b73 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 22 Jul 2024 21:25:53 +0200 Subject: [PATCH 2/6] test: native replay binding --- flutter/example/lib/main.dart | 4 +- .../integrations/native_sdk_integration.dart | 2 +- .../src/native/java/sentry_native_java.dart | 26 ++- .../lib/src/native/sentry_native_binding.dart | 2 +- .../lib/src/native/sentry_native_channel.dart | 3 +- flutter/lib/src/replay/recorder.dart | 8 +- flutter/lib/src/sentry_flutter_options.dart | 5 + flutter/pubspec.yaml | 1 + .../integrations/init_native_sdk_test.dart | 5 +- .../native_sdk_integration_test.dart | 2 +- flutter/test/mocks.dart | 28 ++-- flutter/test/mocks.mocks.dart | 15 +- flutter/test/replay/replay_native_test.dart | 150 ++++++++++++++++-- 13 files changed, 209 insertions(+), 42 deletions(-) diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index f31e5487c5..99431f8549 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -90,8 +90,8 @@ Future setupSentry( options.maxResponseBodySize = MaxResponseBodySize.always; options.navigatorKey = navigatorKey; - options.experimental.replay.sessionSampleRate = 1.0; - options.experimental.replay.errorSampleRate = 1.0; + // options.experimental.replay.sessionSampleRate = 1.0; + // options.experimental.replay.errorSampleRate = 1.0; _isIntegrationTest = isIntegrationTest; if (_isIntegrationTest) { diff --git a/flutter/lib/src/integrations/native_sdk_integration.dart b/flutter/lib/src/integrations/native_sdk_integration.dart index 7178883d73..ad77711b63 100644 --- a/flutter/lib/src/integrations/native_sdk_integration.dart +++ b/flutter/lib/src/integrations/native_sdk_integration.dart @@ -20,7 +20,7 @@ class NativeSdkIntegration implements Integration { } try { - await _native.init(options); + await _native.init(hub); options.sdk.addIntegration('nativeSdkIntegration'); } catch (exception, stackTrace) { options.logger( diff --git a/flutter/lib/src/native/java/sentry_native_java.dart b/flutter/lib/src/native/java/sentry_native_java.dart index 8690a5f30b..30c157e3ad 100644 --- a/flutter/lib/src/native/java/sentry_native_java.dart +++ b/flutter/lib/src/native/java/sentry_native_java.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'dart:ui'; import 'package:meta/meta.dart'; @@ -17,7 +16,7 @@ class SentryNativeJava extends SentryNativeChannel { SentryNativeJava(super.options, super.channel); @override - Future init(SentryFlutterOptions options) async { + Future init(Hub hub) async { // We only need these when replay is enabled (session or error capture) // so let's set it up conditionally. This allows Dart to trim the code. if (options.experimental.replay.isEnabled) { @@ -41,7 +40,7 @@ class SentryNativeJava extends SentryNativeChannel { ), ); - Sentry.configureScope((s) { + hub.configureScope((s) { // ignore: invalid_use_of_internal_member s.replayId = replayId; }); @@ -51,7 +50,7 @@ class SentryNativeJava extends SentryNativeChannel { await _replayRecorder?.stop(); _replayRecorder = null; - Sentry.configureScope((s) { + hub.configureScope((s) { // ignore: invalid_use_of_internal_member s.replayId = null; }); @@ -69,7 +68,14 @@ class SentryNativeJava extends SentryNativeChannel { }); } - return super.init(options); + return super.init(hub); + } + + @override + Future close() async { + await _replayRecorder?.stop(); + _replayRecorder = null; + return super.close(); } void _startRecorder(String cacheDir, ScreenshotRecorderConfig config) { @@ -91,9 +97,11 @@ class SentryNativeJava extends SentryNativeChannel { 'Replay: Saving screenshot to $filePath (' '${image.width}x${image.height} pixels, ' '${imageData.lengthInBytes} bytes)'); - await File(filePath).writeAsBytes(imageData.buffer.asUint8List()); - try { + await options.fileSystem + .file(filePath) + .writeAsBytes(imageData.buffer.asUint8List(), flush: true); + await channel.invokeMethod( 'addReplayScreenshot', {'path': filePath, 'timestamp': timestamp}, @@ -105,6 +113,10 @@ class SentryNativeJava extends SentryNativeChannel { exception: error, stackTrace: stackTrace, ); + // ignore: invalid_use_of_internal_member + if (options.automatedTestMode) { + rethrow; + } } } }; diff --git a/flutter/lib/src/native/sentry_native_binding.dart b/flutter/lib/src/native/sentry_native_binding.dart index 20afea7b4c..15769c97d3 100644 --- a/flutter/lib/src/native/sentry_native_binding.dart +++ b/flutter/lib/src/native/sentry_native_binding.dart @@ -10,7 +10,7 @@ import 'native_frames.dart'; /// Provide typed methods to access native layer. @internal abstract class SentryNativeBinding { - Future init(SentryFlutterOptions options); + Future init(Hub hub); Future close(); diff --git a/flutter/lib/src/native/sentry_native_channel.dart b/flutter/lib/src/native/sentry_native_channel.dart index e8f3bb1404..20d0794f31 100644 --- a/flutter/lib/src/native/sentry_native_channel.dart +++ b/flutter/lib/src/native/sentry_native_channel.dart @@ -29,8 +29,7 @@ class SentryNativeChannel : channel = SentrySafeMethodChannel(channel, options); @override - Future init(SentryFlutterOptions options) async { - assert(this.options == options); + Future init(Hub hub) async { return channel.invokeMethod('initNativeSdk', { 'dsn': options.dsn, 'debug': options.debug, diff --git a/flutter/lib/src/replay/recorder.dart b/flutter/lib/src/replay/recorder.dart index ff64711d05..e17167f87f 100644 --- a/flutter/lib/src/replay/recorder.dart +++ b/flutter/lib/src/replay/recorder.dart @@ -19,13 +19,16 @@ class ScreenshotRecorder { final ScreenshotRecorderCallback _callback; final SentryLogger _logger; final SentryReplayOptions _options; + final bool rethrowExceptions; WidgetFilter? _widgetFilter; late final Scheduler _scheduler; bool warningLogged = false; ScreenshotRecorder(this._config, this._callback, SentryFlutterOptions options) : _logger = options.logger, - _options = options.experimental.replay { + _options = options.experimental.replay, + // ignore: invalid_use_of_internal_member + rethrowExceptions = options.automatedTestMode { final frameDuration = Duration(milliseconds: 1000 ~/ _config.frameRate); _scheduler = Scheduler(frameDuration, _capture, options.bindingUtils.instance!.addPostFrameCallback); @@ -121,6 +124,9 @@ class ScreenshotRecorder { } catch (e, stackTrace) { _logger(SentryLevel.error, "Replay: failed to capture screenshot.", exception: e, stackTrace: stackTrace); + if (rethrowExceptions) { + rethrow; + } } } diff --git a/flutter/lib/src/sentry_flutter_options.dart b/flutter/lib/src/sentry_flutter_options.dart index 9fd7c60737..8b6f7ed491 100644 --- a/flutter/lib/src/sentry_flutter_options.dart +++ b/flutter/lib/src/sentry_flutter_options.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:file/file.dart'; +import 'package:file/local.dart'; import 'package:meta/meta.dart' as meta; import 'package:sentry/sentry.dart'; import 'package:flutter/widgets.dart'; @@ -329,6 +331,9 @@ class SentryFlutterOptions extends SentryOptions { /// The [navigatorKey] is used to add information of the currently used locale to the contexts. GlobalKey? navigatorKey; + @meta.internal + FileSystem fileSystem = LocalFileSystem(); + /// Configuration of experimental features that may change or be removed /// without prior notice. Additionally, these features may not be ready for /// production use yet. diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index f98b8b6ab6..7af30527f2 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: package_info_plus: '>=1.0.0' meta: ^1.3.0 ffi: ^2.0.0 + file: ^6.1.4 dev_dependencies: build_runner: ^2.4.2 diff --git a/flutter/test/integrations/init_native_sdk_test.dart b/flutter/test/integrations/init_native_sdk_test.dart index fd96ad2127..b33f852de0 100644 --- a/flutter/test/integrations/init_native_sdk_test.dart +++ b/flutter/test/integrations/init_native_sdk_test.dart @@ -8,6 +8,7 @@ import 'package:sentry_flutter/src/native/sentry_native_channel.dart'; import 'package:sentry_flutter/src/version.dart'; import '../mocks.dart'; +import '../mocks.mocks.dart'; void main() { late Fixture fixture; @@ -25,7 +26,7 @@ void main() { }); var sut = fixture.getSut(channel); - await sut.init(fixture.options); + await sut.init(MockHub()); channel.setMethodCallHandler(null); @@ -115,7 +116,7 @@ void main() { fixture.options.sdk.addIntegration('foo'); fixture.options.sdk.addPackage('bar', '1'); - await sut.init(fixture.options); + await sut.init(MockHub()); channel.setMethodCallHandler(null); diff --git a/flutter/test/integrations/native_sdk_integration_test.dart b/flutter/test/integrations/native_sdk_integration_test.dart index f94f88698c..5c244b3d66 100644 --- a/flutter/test/integrations/native_sdk_integration_test.dart +++ b/flutter/test/integrations/native_sdk_integration_test.dart @@ -60,7 +60,7 @@ void main() { class _ThrowingMockSentryNative extends MockSentryNativeBinding { @override - Future init(SentryFlutterOptions? options) async { + Future init(Hub? hub) async { throw Exception(); } } diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index ff1aaa5b23..e697095a27 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -4,7 +4,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/src/widgets/binding.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; import 'package:sentry/src/platform/platform.dart'; import 'package:sentry/src/sentry_tracer.dart'; @@ -210,29 +209,36 @@ final fakeFrameDurations = [ @GenerateMocks([Callbacks]) abstract class Callbacks { - Future? methodCallHandler(MethodCall message); + Future? methodCallHandler(String method, [dynamic arguments]); } class NativeChannelFixture { late final MethodChannel channel; - late final Future? Function(MethodCall?) handler; + late final Future? Function(String method, [dynamic arguments]) + handler; + static TestDefaultBinaryMessenger get _messenger => + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger; NativeChannelFixture() { TestWidgetsFlutterBinding.ensureInitialized(); - channel = MockMethodChannel(); - when(channel.name).thenReturn('test.channel'); - when(channel.binaryMessenger).thenReturn( - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger); + channel = MethodChannel('test.channel', StandardMethodCodec(), _messenger); + // channel = MockMethodChannel(); + // when(channel.name).thenReturn('test.channel'); + // when(channel.binaryMessenger).thenReturn(_messenger); handler = MockCallbacks().methodCallHandler; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler(channel, handler); + // _messenger.setMessageHandler(channel.name, (ByteData? data) async { + // final call = StandardMethodCodec().decodeMethodCall(data); + // await handler(call); + // }); + _messenger.setMockMethodCallHandler( + channel, (call) => handler(call.method, call.arguments)); } // Mock this call as if it was invoked by the native side. Future invokeFromNative(String method, [dynamic arguments]) async { final call = StandardMethodCodec().encodeMethodCall(MethodCall(method, arguments)); - return TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .handlePlatformMessage(channel.name, call, (ByteData? data) {}); + return _messenger.handlePlatformMessage( + channel.name, call, (ByteData? data) {}); } } diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 5f728c707a..3632555cc7 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -186,10 +186,16 @@ class MockCallbacks extends _i1.Mock implements _i6.Callbacks { } @override - _i7.Future? methodCallHandler(_i4.MethodCall? message) => + _i7.Future? methodCallHandler( + String? method, [ + dynamic arguments, + ]) => (super.noSuchMethod(Invocation.method( #methodCallHandler, - [message], + [ + method, + arguments, + ], )) as _i7.Future?); } @@ -1097,11 +1103,10 @@ class MockSentryNativeBinding extends _i1.Mock } @override - _i7.Future init(_i2.SentryFlutterOptions? options) => - (super.noSuchMethod( + _i7.Future init(_i2.Hub? hub) => (super.noSuchMethod( Invocation.method( #init, - [options], + [hub], ), returnValue: _i7.Future.value(), returnValueForMissingStub: _i7.Future.value(), diff --git a/flutter/test/replay/replay_native_test.dart b/flutter/test/replay/replay_native_test.dart index ff4320fc3c..e49d0b9f09 100644 --- a/flutter/test/replay/replay_native_test.dart +++ b/flutter/test/replay/replay_native_test.dart @@ -1,16 +1,22 @@ -// ignore_for_file: inference_failure_on_function_invocation +// ignore_for_file: invalid_use_of_internal_member @TestOn('vm') library flutter_test; +import 'dart:async'; + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:sentry_flutter/src/event_processor/replay_event_processor.dart'; import 'package:sentry_flutter/src/native/factory.dart'; import 'package:sentry_flutter/src/native/sentry_native_binding.dart'; + import '../mocks.dart'; -import '../sentry_flutter_test.dart'; +import '../mocks.mocks.dart'; +import 'test_widget.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -22,24 +28,45 @@ void main() { late SentryNativeBinding sut; late NativeChannelFixture native; late SentryFlutterOptions options; + late MockHub hub; + late FileSystem fs; + late Directory replayDir; + final replayConfig = { + 'replayId': '123', + 'directory': 'dir', + 'width': 1000, + 'height': 1000, + 'frameRate': 1000, + }; setUp(() { - options = SentryFlutterOptions( - dsn: fakeDsn, checker: getPlatformChecker(platform: mockPlatform)) - // ignore: invalid_use_of_internal_member - ..automatedTestMode = true; + hub = MockHub(); + + fs = MemoryFileSystem.test(); + replayDir = fs.directory(replayConfig['directory']) + ..createSync(recursive: true); + + options = defaultTestOptions() + ..platformChecker = MockPlatformChecker(mockPlatform: mockPlatform) + ..fileSystem = fs; native = NativeChannelFixture(); - when(native.channel.invokeMethod('initNativeSdk', any)) + when(native.handler('initNativeSdk', any)) + .thenAnswer((_) => Future.value()); + when(native.handler('closeNativeSdk', any)) .thenAnswer((_) => Future.value()); sut = createBinding(options, channel: native.channel); }); + tearDown(() async { + await sut.close(); + }); + test('init sets $ReplayEventProcessor when error replay is enabled', () async { options.experimental.replay.errorSampleRate = 0.1; - await sut.init(options); + await sut.init(hub); expect(options.eventProcessors.map((e) => e.runtimeType.toString()), contains('$ReplayEventProcessor')); @@ -48,11 +75,116 @@ void main() { test( 'init does not set $ReplayEventProcessor when error replay is disabled', () async { - await sut.init(options); + await sut.init(hub); expect(options.eventProcessors.map((e) => e.runtimeType.toString()), isNot(contains('$ReplayEventProcessor'))); }); + + group('replay recorder', () { + setUp(() async { + options.experimental.replay.sessionSampleRate = 0.1; + options.experimental.replay.errorSampleRate = 0.1; + await sut.init(hub); + }); + + test('start() sets replay ID to context', () async { + // verify there was no scope configured before + verifyNever(hub.configureScope(any)); + + // emulate the native platform invoking the method + await native.invokeFromNative('ReplayRecorder.start', replayConfig); + + // verify the replay ID was set + final closure = + verify(hub.configureScope(captureAny)).captured.single; + final scope = Scope(options); + expect(scope.replayId, isNull); + await closure(scope); + expect(scope.replayId.toString(), replayConfig['replayId']); + }); + + test('stop() clears replay ID from context', () async { + // verify there was no scope configured before + verifyNever(hub.configureScope(any)); + + // emulate the native platform invoking the method + await native.invokeFromNative('ReplayRecorder.stop'); + + // verify the replay ID was cleared + final closure = + verify(hub.configureScope(captureAny)).captured.single; + final scope = Scope(options); + scope.replayId = SentryId.newId(); + expect(scope.replayId, isNotNull); + await closure(scope); + expect(scope.replayId, isNull); + }); + + testWidgets('captures images', (tester) async { + await tester.runAsync(() async { + var callbackFinished = Completer(); + + nextFrame({bool wait = true}) async { + tester.binding.scheduleFrame(); + await Future.delayed(const Duration(milliseconds: 100)); + await tester.pumpAndSettle(const Duration(seconds: 1)); + await callbackFinished.future.timeout( + Duration(milliseconds: wait ? 1000 : 100), onTimeout: () { + if (wait) { + fail('native callback not called'); + } + }); + callbackFinished = Completer(); + } + + imageInfo(File file) => file.readAsBytesSync().length; + + fileToImageMap(Iterable files) => + {for (var file in files) file.path: imageInfo(file)}; + + final capturedImages = {}; + when(native.handler('addReplayScreenshot', any)) + .thenAnswer((invocation) async { + callbackFinished.complete(); + final path = invocation.positionalArguments[1]["path"] as String; + capturedImages[path] = imageInfo(fs.file(path)); + return null; + }); + + fsImages() { + final files = replayDir.listSync().map((f) => f as File); + return fileToImageMap(files); + } + + await pumpTestElement(tester); + + await nextFrame(wait: false); + expect(fsImages(), isEmpty); + verifyNever(native.handler('addReplayScreenshot', any)); + + await native.invokeFromNative('ReplayRecorder.start', replayConfig); + + await nextFrame(); + expect(fsImages().values, [5378]); + expect(capturedImages, equals(fsImages())); + + await nextFrame(); + expect(fsImages().values, [5378, 5378]); + expect(capturedImages, equals(fsImages())); + + await native.invokeFromNative('ReplayRecorder.stop'); + + await nextFrame(wait: false); + expect(fsImages().values, [5378, 5378]); + expect(capturedImages, equals(fsImages())); + + await nextFrame(wait: false); + expect(fsImages().values, [5378, 5378]); + expect(capturedImages, equals(fsImages())); + }); + }, timeout: Timeout(Duration(seconds: 10))); + }); }); } } From 325087da437c08fe377fa49d4d4fbe29d4c92f6b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 23 Jul 2024 09:14:08 +0200 Subject: [PATCH 3/6] update example --- flutter/example/lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/example/lib/main.dart b/flutter/example/lib/main.dart index 99431f8549..f31e5487c5 100644 --- a/flutter/example/lib/main.dart +++ b/flutter/example/lib/main.dart @@ -90,8 +90,8 @@ Future setupSentry( options.maxResponseBodySize = MaxResponseBodySize.always; options.navigatorKey = navigatorKey; - // options.experimental.replay.sessionSampleRate = 1.0; - // options.experimental.replay.errorSampleRate = 1.0; + options.experimental.replay.sessionSampleRate = 1.0; + options.experimental.replay.errorSampleRate = 1.0; _isIntegrationTest = isIntegrationTest; if (_isIntegrationTest) { From e9d2c0510c7fbc73983908d266b90dac6fdfc261 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 23 Jul 2024 09:15:51 +0200 Subject: [PATCH 4/6] chore: update pubspec --- flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml index 7af30527f2..c17b488cdb 100644 --- a/flutter/pubspec.yaml +++ b/flutter/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: package_info_plus: '>=1.0.0' meta: ^1.3.0 ffi: ^2.0.0 - file: ^6.1.4 + file: '>=6.1.4' dev_dependencies: build_runner: ^2.4.2 From a8b33320688b44e0dc758f8cf3359ebf0245a963 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 23 Jul 2024 09:23:23 +0200 Subject: [PATCH 5/6] fixup tests --- flutter/test/replay/replay_native_test.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/flutter/test/replay/replay_native_test.dart b/flutter/test/replay/replay_native_test.dart index e49d0b9f09..afff3343ae 100644 --- a/flutter/test/replay/replay_native_test.dart +++ b/flutter/test/replay/replay_native_test.dart @@ -166,21 +166,24 @@ void main() { await native.invokeFromNative('ReplayRecorder.start', replayConfig); await nextFrame(); - expect(fsImages().values, [5378]); + expect(fsImages().values, isNotEmpty); + final size = fsImages().values.first; + expect(size, greaterThan(5000)); + expect(fsImages().values, [size]); expect(capturedImages, equals(fsImages())); await nextFrame(); - expect(fsImages().values, [5378, 5378]); + expect(fsImages().values, [size, size]); expect(capturedImages, equals(fsImages())); await native.invokeFromNative('ReplayRecorder.stop'); await nextFrame(wait: false); - expect(fsImages().values, [5378, 5378]); + expect(fsImages().values, [size, size]); expect(capturedImages, equals(fsImages())); await nextFrame(wait: false); - expect(fsImages().values, [5378, 5378]); + expect(fsImages().values, [size, size]); expect(capturedImages, equals(fsImages())); }); }, timeout: Timeout(Duration(seconds: 10))); From 7f45f7581be5b5ff28a0c26a43cd2087972a6778 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos <6349682+vaind@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:41:54 +0200 Subject: [PATCH 6/6] Update flutter/test/mocks.dart --- flutter/test/mocks.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index e697095a27..4e59f6d389 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -222,14 +222,7 @@ class NativeChannelFixture { NativeChannelFixture() { TestWidgetsFlutterBinding.ensureInitialized(); channel = MethodChannel('test.channel', StandardMethodCodec(), _messenger); - // channel = MockMethodChannel(); - // when(channel.name).thenReturn('test.channel'); - // when(channel.binaryMessenger).thenReturn(_messenger); handler = MockCallbacks().methodCallHandler; - // _messenger.setMessageHandler(channel.name, (ByteData? data) async { - // final call = StandardMethodCodec().decodeMethodCall(data); - // await handler(call); - // }); _messenger.setMockMethodCallHandler( channel, (call) => handler(call.method, call.arguments)); }