| 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:')); |
| }); |
| } |