import 'dart:io'; import 'dart:isolate'; import 'package:jni/jni.dart'; import 'package:test/test.dart'; import 'test_util/test_util.dart'; void main() { if (!Platform.isAndroid) { checkDylibIsUpToDate(); spawnJvm(); } test('Debug mode captures stack trace on double release', () { Jni.captureStackTraceOnRelease = true; addTearDown(() { Jni.captureStackTraceOnRelease = false; }); final s = 'hello'.toJString(); s.release(); try { s.release(); // ignore: avoid_catching_errors } on DoubleReleaseError catch (e) { expect(e.toString(), contains('Object was released at:')); expect(e.toString(), contains('debug_release_test.dart')); } }); test('Debug mode captures stack trace on use after release', () { Jni.captureStackTraceOnRelease = true; addTearDown(() { Jni.captureStackTraceOnRelease = false; }); final s = 'hello'.toJString(); s.release(); try { s.toDartString(); // ignore: avoid_catching_errors } on UseAfterReleaseError catch (e) { expect(e.toString(), contains('Object was released at:')); expect(e.toString(), contains('debug_release_test.dart')); } }); test('Debug mode captures arena stack trace', () { Jni.captureStackTraceOnRelease = true; addTearDown(() { Jni.captureStackTraceOnRelease = false; }); final s = 'hello'.toJString(); s.release(); using(s.releasedBy); using(s.releasedBy); try { s.toDartString(); // ignore: avoid_catching_errors } on UseAfterReleaseError catch (e) { expect( e.toString(), stringContainsInOrder([ 'Object was released at:', 'Object was registered to be released', 'Object was registered to be released' ])); expect(e.toString(), contains('debug_release_test.dart')); } try { s.release(); // ignore: avoid_catching_errors } on DoubleReleaseError catch (e) { expect( e.toString(), stringContainsInOrder([ 'Object was released at:', 'Object was registered to be released', 'Object was registered to be released' ])); expect(e.toString(), contains('debug_release_test.dart')); } }); test('Hint is shown when debug mode is disabled', () { Jni.captureStackTraceOnRelease = false; final s = 'hello'.toJString(); s.release(); try { s.release(); // ignore: avoid_catching_errors } on DoubleReleaseError catch (e) { expect(e.toString(), contains('set `Jni.captureStackTraceOnRelease = true`')); } }); test('Debug mode flag is shared across isolates', () async { Jni.captureStackTraceOnRelease = true; addTearDown(() { Jni.captureStackTraceOnRelease = false; }); final isEnabled = await Isolate.run(() { return Jni.captureStackTraceOnRelease; }); expect(isEnabled, isTrue); Jni.captureStackTraceOnRelease = false; final isEnabled2 = await Isolate.run(() { return Jni.captureStackTraceOnRelease; }); expect(isEnabled2, isFalse); }); test('Debug mode captures stack trace in another isolate', () async { Jni.captureStackTraceOnRelease = true; addTearDown(() { Jni.captureStackTraceOnRelease = false; }); await Isolate.run(() { final s = 'hello'.toJString(); s.release(); try { s.release(); // ignore: avoid_catching_errors } on DoubleReleaseError catch (e) { if (!e.toString().contains('Object was released at:')) { throw StateError('Stack trace not captured in isolate'); } } }); }); test( 'Debug mode captures stack trace from another isolate' ' (cross-isolate release)', () async { Jni.captureStackTraceOnRelease = true; addTearDown(() { Jni.captureStackTraceOnRelease = false; }); final s = 'hello'.toJString(); s.release(); final (isReleased, doubleReleaseError, useAfterReleaseError) = await Isolate.run(() { String? doubleReleaseError; String? useAfterReleaseError; try { s.release(); // ignore: avoid_catching_errors } on DoubleReleaseError catch (e) { doubleReleaseError = e.toString(); } try { s.toString(); // ignore: avoid_catching_errors } on UseAfterReleaseError catch (e) { useAfterReleaseError = e.toString(); } return (s.isReleased, doubleReleaseError, useAfterReleaseError); }); expect(isReleased, isTrue); expect(doubleReleaseError, contains('Object was released at:')); expect(useAfterReleaseError, contains('Object was released at:')); }); }