// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. @Tags(['load_test']) library; import 'dart:ffi'; import 'dart:io'; import 'dart:math'; import 'package:ffi/ffi.dart'; import 'package:jni/jni.dart'; import 'package:jni/src/third_party/generated_bindings.dart'; import 'package:test/test.dart'; import 'test_util/test_util.dart'; /// Taken from /// https://github.com/dart-lang/ffigen/blob/master/test/native_objc_test/automated_ref_count_test.dart final executeInternalCommand = DynamicLibrary.process().lookupFunction< Void Function(Pointer, Pointer), void Function(Pointer, Pointer)>('Dart_ExecuteInternalCommand'); void doGC() { final gcNow = 'gc-now'.toNativeUtf8(); executeInternalCommand(gcNow.cast(), nullptr); calloc.free(gcNow); } void main() { if (!Platform.isAndroid) { checkDylibIsUpToDate(); spawnJvm(); } run(testRunner: test); } const k4 = 4 * 1024; const k256 = 256 * 1024; const secureRandomSeedBound = 4294967296; final random = Random.secure(); final randomClass = JClass.forName('java/util/Random'); JObject newRandom() => randomClass .constructorId('(J)V') .call(randomClass, [random.nextInt(secureRandomSeedBound)]); void run({required TestRunnerCallback testRunner}) { testRunner('Test 4K refs can be created in a row', () { final list = []; for (var i = 0; i < k4; i++) { list.add(newRandom()); } for (final jobject in list) { jobject.release(); } }); testRunner('Create and release 256K references in a loop using arena', () { for (var i = 0; i < k256; i++) { using((arena) { final random = newRandom()..releasedBy(arena); // The actual expect here does not matter. I am just being paranoid // against assigning to `_` because compiler may optimize it. (It has // side effect of calling FFI but still.) expect(random.reference.pointer, isNot(nullptr)); }); } }); testRunner('Create and release 256K references in a loop (explicit release)', () { for (var i = 0; i < k256; i++) { final random = newRandom(); expect(random.reference.pointer, isNot(nullptr)); random.release(); } }); testRunner('Create and release 64K references, in batches of 256', () { for (var i = 0; i < 64 * 4; i++) { using((arena) { for (var i = 0; i < 256; i++) { final r = newRandom()..releasedBy(arena); expect(r.reference.pointer, isNot(nullptr)); } }); } }); // We don't have a direct way to check if something creates JNI references. // So we are checking if we can run this for large number of times. testRunner('Verify a call returning primitive can be run any times', () { final random = newRandom(); final nextInt = randomClass.instanceMethodId('nextInt', '()I'); for (var i = 0; i < k256; i++) { final rInt = nextInt(random, jint.type, []); expect(rInt, isA()); } }); void testFinalizer() { testRunner('Finalizer correctly removes the references', () { // We are checking if we can run this for large number of times. // More than the number of available global references. for (var i = 0; i < k256; ++i) { final random = newRandom(); expect(random.reference.pointer, isNot(nullptr)); if (i % k4 == 0) { doGC(); } } }); } void testRefValidityAfterGC(int delayInSeconds) { testRunner('Validate reference after GC & ${delayInSeconds}s sleep', () { final random = newRandom(); final nextInt = randomClass.instanceMethodId('nextInt', '()I'); doGC(); sleep(Duration(seconds: delayInSeconds)); expect( nextInt(random, jint.type, []), isA(), ); expect( Jni.env.GetObjectRefType(random.reference.pointer), equals(JObjectRefType.JNIGlobalRefType), ); }); } // Dart_ExecuteInternalCommand doesn't exist in Android. if (!Platform.isAndroid) { testFinalizer(); testRefValidityAfterGC(1); testRefValidityAfterGC(10); } }