Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ jobs:
- target: js
os: ubuntu
exclude:
# Keep Android on stable for the v9 maintenance line.
- target: android
sdk: beta
# Flutter WASM tests don't compile on beta currently and hangs on stable.
# https://github.com/getsentry/sentry-dart/issues/3477
- target: wasm
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/flutter_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ jobs:
strategy:
fail-fast: false
matrix:
sdk: [stable, beta]
# Keep Android on stable for the v9 maintenance line.
sdk: [stable]
steps:
- name: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
// ignore_for_file: invalid_use_of_internal_member, depend_on_referenced_packages

import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:jni/jni.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_flutter/src/native/java/binding.dart' as native;
import 'package:sentry_flutter/src/native/java/android_core_worker.dart';
import 'package:sentry_flutter/src/isolate/isolate_worker.dart';

import 'utils.dart';

Expand All @@ -17,8 +22,49 @@ void main() {
group('SentryFlutter native lifecycle', () {
tearDown(Sentry.close);

testWidgets('keeps native reporting enabled across detach and reattach',
(tester) async {
group('during worker handoff', () {
late _Fixture fixture;

setUp(() {
fixture = _Fixture();
});

for (final closeWhileDraining in [false, true]) {
testWidgets(
'keeps the native user cleared after retiring updates '
'(closing: $closeWhileDraining)', (tester) async {
await restoreFlutterOnErrorAfter(() async {
await SentryFlutter.init((options) {
options.dsn = fakeDsn;
}, appRunner: () async {});
});
final worker = fixture.getSut();
await worker.start();
final updating = worker.setUser(SentryUser(id: 'previous-user'));

worker.stop();
final clearing = worker.setUser(null);
final closing = closeWhileDraining ? worker.close() : null;
fixture.releaseUpdates();
await updating;
await clearing;
await closing;

using((arena) {
final adapter = native.ScopesAdapter.getInstance()!
..releasedBy(arena);
final scope = adapter.getScope()..releasedBy(arena);
final user = scope.as(native.Scope.type).getUser();
user?.releasedBy(arena);
expect(user, isNull);
});
}, skip: !Platform.isAndroid);
}
});

testWidgets('keeps native reporting enabled across detach and reattach', (
tester,
) async {
await restoreFlutterOnErrorAfter(() async {
await SentryFlutter.init((options) {
options.dsn = fakeDsn;
Expand All @@ -27,10 +73,10 @@ void main() {
});
expect(native.Sentry.isEnabled(), isTrue);

binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
binding.handleAppLifecycleStateChanged(AppLifecycleState.detached);
await _lifecycle(binding, AppLifecycleState.resumed);
await _lifecycle(binding, AppLifecycleState.detached);
await tester.pump();
binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
await _lifecycle(binding, AppLifecycleState.resumed);
await tester.pump();

expect(native.Sentry.isEnabled(), isTrue);
Expand All @@ -45,3 +91,66 @@ void main() {
}, skip: !Platform.isAndroid);
});
}

Future<void> _lifecycle(
IntegrationTestWidgetsFlutterBinding binding,
AppLifecycleState state,
) async {
await binding.defaultBinaryMessenger.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage('AppLifecycleState.${state.name}'),
(_) {},
);
}

class _Fixture {
final _updates = Completer<void>();

AndroidCoreWorker getSut() {
final worker = AndroidCoreWorker(
Sentry.currentHub.options,
spawn: (config, entry) async =>
_GatedWorker(await spawnWorker(config, entry), _updates.future),
);
addTearDown(() async {
releaseUpdates();
await worker.close();
});
return worker;
}

void releaseUpdates() {
if (!_updates.isCompleted) _updates.complete();
}
}

// Delay transport to the real isolate so an outstanding JNI write spans stop().
class _GatedWorker implements Worker {
final Worker _worker;
Future<void> _queue;

_GatedWorker(this._worker, this._queue);

@override
Future<Object?> request(Object? payload) {
final response = _queue.then((_) => _worker.request(payload));
_queue = response.then<void>((_) {}, onError: (Object _, StackTrace __) {});
return response;
}

@override
void send(Object? message) {
_queue = _queue.then((_) => _worker.send(message));
}

@override
void close() {
unawaited(_queue.then((_) => _worker.close()));
}

@override
void closeOnOwnerExit() => _worker.closeOnOwnerExit();

@override
SendPort get port => _worker.port;
}
Loading
Loading