blob: 27cc6ad71fe0ec7d07ba42d989b4e09500d50d08 [file] [log] [blame]
// 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'])
import 'dart:io';
import 'dart:ffi';
import 'dart:math';
import 'package:ffi/ffi.dart';
import 'package:test/test.dart';
import 'package:jni/jni.dart';
import 'test_util/test_util.dart';
const maxLongInJava = 9223372036854775807;
/// 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<Char>, Pointer<Void>),
void Function(Pointer<Char>, Pointer<Void>)>('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 k64 = 64 * 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, const JObjectType(), [random.nextInt(secureRandomSeedBound)]);
void run({required TestRunnerCallback testRunner}) {
testRunner('Test 4K refs can be created in a row', () {
final list = <JObject>[];
for (int 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 (int 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 (int 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 (int i = 0; i < 64 * 4; i++) {
using((arena) {
for (int 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 (int i = 0; i < k256; i++) {
final rInt = nextInt(random, const jintType(), []);
expect(rInt, isA<int>());
}
});
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, const jintType(), []),
isA<int>(),
);
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);
}
}