blob: 6472985c9601eaae5ccdf75168c8b4d315ebcf1a [file]
// Copyright (c) 2023, 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.
import 'dart:io';
import 'package:test/test.dart';
import 'package:jni/jni.dart';
import '../test_util/callback_types.dart';
import 'c_based/dart_bindings/simple_package.dart';
const pi = 3.14159;
const fpDelta = 0.001;
const trillion = 1024 * 1024 * 1024 * 1024;
void registerTests(String groupName, TestRunnerCallback test) {
group(groupName, () {
test('static final fields - int', () {
expect(Example.ON, equals(1));
expect(Example.OFF, equals(0));
expect(Example.PI, closeTo(pi, fpDelta));
expect(Example.SEMICOLON, equals(';'));
expect(Example.SEMICOLON_STRING, equals(';'));
});
test('Static methods - primitive', () {
// same test can be run at a replicated (dart-only) test, check for both
// possible values.
expect(Example.getAmount(), isIn([1012, 500]));
Example.setAmount(1012);
expect(Example.getAmount(), equals(1012));
expect(Example.getAsterisk(), equals('*'.codeUnitAt(0)));
expect(C2.CONSTANT, equals(12));
});
test('Static fields & methods - string', () {
expect(
Example.getName().toDartString(deleteOriginal: true),
isIn(["Ragnar Lothbrok", "Theseus"]),
);
Example.setName("Theseus".toJString());
expect(
Example.getName().toDartString(deleteOriginal: true),
equals("Theseus"),
);
});
test('Static fields and methods - Object', () {
final nested = Example.getNestedInstance();
expect(nested.getValue(), isIn([true, false]));
nested.setValue(false);
expect(nested.getValue(), isFalse);
});
test('static methods with several arguments', () {
expect(Example.addInts(10, 15), equals(25));
expect(Example.max4(-1, 15, 30, 12), equals(30));
expect(Example.max8(1, 4, 8, 2, 4, 10, 8, 6), equals(10));
});
test('Instance methods (getters & setters)', () {
final e = Example();
expect(e.getNumber(), equals(0));
expect(e.getIsUp(), true);
expect(e.getCodename().toDartString(), equals("achilles"));
e.setNumber(1);
e.setUp(false);
e.setCodename("spartan".toJString());
expect(e.getIsUp(), false);
expect(e.getNumber(), 1);
expect(e.getCodename().toDartString(), equals("spartan"));
e.delete();
});
test('Instance methods with several arguments', () {
final e = Example();
expect(e.add4Longs(1, 2, 3, 4), equals(10));
expect(e.add8Longs(1, 1, 2, 2, 3, 3, 12, 24), equals(48));
expect(
e.add4Longs(trillion, trillion, trillion, trillion),
equals(4 * trillion),
);
expect(
e.add8Longs(trillion, -trillion, trillion, -trillion, trillion,
-trillion, -trillion, -trillion),
equals(2 * -trillion),
);
e.delete();
});
test('Misc. instance methods', () {
final e = Example();
final rand = e.getRandom();
expect(rand.isNull, isFalse);
final _ = e.getRandomLong();
final id =
e.getRandomNumericString(rand).toDartString(deleteOriginal: true);
expect(int.parse(id), lessThan(10000));
e.setNumber(145);
expect(
e.getSelf().getSelf().getSelf().getSelf().getNumber(),
equals(145),
);
e.delete();
});
test('Constructors', () {
final e0 = Example();
expect(e0.getNumber(), 0);
expect(e0.getIsUp(), true);
expect(e0.getCodename().toDartString(), equals('achilles'));
final e1 = Example.ctor1(111);
expect(e1.getNumber(), equals(111));
expect(e1.getIsUp(), true);
expect(e1.getCodename().toDartString(), "achilles");
final e2 = Example.ctor2(122, false);
expect(e2.getNumber(), equals(122));
expect(e2.getIsUp(), false);
expect(e2.getCodename().toDartString(), "achilles");
final e3 = Example.ctor3(133, false, "spartan".toJString());
expect(e3.getNumber(), equals(133));
expect(e3.getIsUp(), false);
expect(e3.getCodename().toDartString(), "spartan");
});
test('Static (non-final) fields', () {
// Other replica test may already have modified this, so assert both
// values.
expect(Fields.amount, isIn([500, 101]));
Fields.amount = 101;
expect(Fields.amount, equals(101));
expect(Fields.asterisk, equals('*'.codeUnitAt(0)));
expect(
Fields.name.toDartString(),
isIn(["Earl Haraldson", "Ragnar Lothbrok"]),
);
Fields.name = "Ragnar Lothbrok".toJString();
expect(Fields.name.toDartString(), equals("Ragnar Lothbrok"));
expect(Fields.pi, closeTo(pi, fpDelta));
});
test('Instance fields', () {
final f = Fields();
expect(f.trillion, equals(trillion));
expect(f.isAchillesDead, isFalse);
expect(f.bestFighterInGreece.toDartString(), equals("Achilles"));
// "For your glory walks hand-in-hand with your doom." - Thetis.
f.isAchillesDead = true;
// I don't know much Greek mythology. But Troy was released in 2004,
// and 300 was released in 2006, so it's Leonidas I.
f.bestFighterInGreece = "Leonidas I".toJString();
expect(f.isAchillesDead, isTrue);
expect(f.bestFighterInGreece.toDartString(), "Leonidas I");
});
test('Fields from nested class', () {
expect(Fields_Nested().hundred, equals(100));
// Hector of Troy may disagree.
expect(Fields_Nested.BEST_GOD.toDartString(), equals('Pallas Athena'));
});
test('static methods arrays', () {
final array = Example.getArr();
expect(array[0], 1);
expect(array[1], 2);
expect(array[2], 3);
expect(Example.addAll(array), 6);
array[0] = 4;
expect(Example.addAll(array), 9);
});
test('array of the class', () {
final ex1 = Example();
final ex2 = Example();
ex1.setNumber(1);
ex2.setNumber(2);
final array = JArray(Example.type, 2);
array[0] = ex1;
array[1] = ex2;
expect(array[0].getNumber(), 1);
expect(array[1].getNumber(), 2);
array.delete();
ex1.delete();
ex2.delete();
});
test("Check bindings for same-named classes", () {
expect(Example().whichExample(), 0);
expect(Example1().whichExample(), 1);
});
test('Unicode char', () {
expect(Fields.euroSymbol, equals('\u20AC'.codeUnitAt(0)));
});
group('exception tests', () {
void throwsException(void Function() f) {
expect(f, throwsA(isA<JniException>()));
}
test('Example throw exception', () {
throwsException(Example.throwException);
});
test('Exception from method returning Object', () {
throwsException(Exceptions.staticObjectMethod);
throwsException(Exceptions.staticObjectArrayMethod);
final x = Exceptions();
throwsException(x.objectMethod);
throwsException(x.objectArrayMethod);
});
test('Exception from method returning int', () {
throwsException(Exceptions.staticIntMethod);
throwsException(Exceptions.staticIntArrayMethod);
final x = Exceptions();
throwsException(x.intMethod);
throwsException(x.intArrayMethod);
});
test('Exception from constructor', () {
throwsException(() => Exceptions.ctor1(6.8));
throwsException(() => Exceptions.ctor2(1, 2, 3, 4, 5, 6));
});
test('Exception contains error message & stack trace', () {
try {
Exceptions.throwLoremIpsum();
} on JniException catch (e) {
expect(e.message, stringContainsInOrder(["Lorem Ipsum"]));
expect(
e.toString(),
stringContainsInOrder(["Lorem Ipsum", "throwLoremIpsum"]),
);
return;
}
throw AssertionError("No exception was thrown");
});
});
group('generics', () {
test('GrandParent constructor', () {
using((arena) {
final grandParent = GrandParent('Hello'.toJString()..deletedIn(arena))
..deletedIn(arena);
expect(grandParent, isA<GrandParent<JString>>());
expect(grandParent.$type, isA<$GrandParentType<JString>>());
expect(grandParent.value.toDartString(deleteOriginal: true), 'Hello');
});
});
test('MyStack<T>', () {
using((arena) {
final stack = MyStack(T: JString.type)..deletedIn(arena);
stack.push('Hello'.toJString()..deletedIn(arena));
stack.push('World'.toJString()..deletedIn(arena));
expect(stack.pop().toDartString(deleteOriginal: true), 'World');
expect(stack.pop().toDartString(deleteOriginal: true), 'Hello');
});
});
test('Different stacks have different types, same stacks have same types',
() {
using((arena) {
final aStringStack = MyStack(T: JString.type)..deletedIn(arena);
final anotherStringStack = MyStack(T: JString.type)..deletedIn(arena);
final anObjectStack = MyStack(T: JObject.type)..deletedIn(arena);
expect(aStringStack.$type, anotherStringStack.$type);
expect(
aStringStack.$type.hashCode,
anotherStringStack.$type.hashCode,
);
expect(aStringStack.$type, isNot(anObjectStack.$type));
expect(
aStringStack.$type.hashCode,
isNot(anObjectStack.$type.hashCode),
);
});
});
test('MyMap<K, V>', () {
using((arena) {
final map = MyMap(K: JString.type, V: Example.type)..deletedIn(arena);
final helloExample = Example.ctor1(1)..deletedIn(arena);
final worldExample = Example.ctor1(2)..deletedIn(arena);
map.put('Hello'.toJString()..deletedIn(arena), helloExample);
map.put('World'.toJString()..deletedIn(arena), worldExample);
expect(
(map.get0('Hello'.toJString()..deletedIn(arena))..deletedIn(arena))
.getNumber(),
1,
);
expect(
(map.get0('World'.toJString()..deletedIn(arena))..deletedIn(arena))
.getNumber(),
2,
);
expect(
((map.entryStack()..deletedIn(arena)).pop()..deletedIn(arena))
.key
.castTo(JString.type, deleteOriginal: true)
.toDartString(deleteOriginal: true),
anyOf('Hello', 'World'),
);
});
});
group('classes extending generics', () {
test('StringStack', () {
using((arena) {
final stringStack = StringStack()..deletedIn(arena);
stringStack.push('Hello'.toJString()..deletedIn(arena));
expect(
stringStack.pop().toDartString(deleteOriginal: true), 'Hello');
});
});
test('StringKeyedMap', () {
using((arena) {
final map = StringKeyedMap(V: Example.type)..deletedIn(arena);
final example = Example()..deletedIn(arena);
map.put('Hello'.toJString()..deletedIn(arena), example);
expect(
(map.get0('Hello'.toJString()..deletedIn(arena))
..deletedIn(arena))
.getNumber(),
0,
);
});
});
test('StringValuedMap', () {
using((arena) {
final map = StringValuedMap(K: Example.type)..deletedIn(arena);
final example = Example()..deletedIn(arena);
map.put(example, 'Hello'.toJString()..deletedIn(arena));
expect(
map.get0(example).toDartString(deleteOriginal: true),
'Hello',
);
});
});
test('StringMap', () {
using((arena) {
final map = StringMap()..deletedIn(arena);
map.put('hello'.toJString()..deletedIn(arena),
'world'.toJString()..deletedIn(arena));
expect(
map
.get0('hello'.toJString()..deletedIn(arena))
.toDartString(deleteOriginal: true),
'world',
);
});
});
});
test('superclass count', () {
expect(JObject.type.superCount, 0);
expect(MyMap.type(JObject.type, JObject.type).superCount, 1);
expect(StringKeyedMap.type(JObject.type).superCount, 2);
expect(StringValuedMap.type(JObject.type).superCount, 2);
expect(StringMap.type.superCount, 3);
});
test('nested generics', () {
using((arena) {
final grandParent =
GrandParent(T: JString.type, "!".toJString()..deletedIn(arena))
..deletedIn(arena);
expect(
grandParent.value.toDartString(deleteOriginal: true),
"!",
);
final strStaticParent = GrandParent.stringStaticParent()
..deletedIn(arena);
expect(
strStaticParent.value.toDartString(deleteOriginal: true),
"Hello",
);
final exampleStaticParent = GrandParent.varStaticParent(
S: Example.type, Example()..deletedIn(arena))
..deletedIn(arena);
expect(
(exampleStaticParent.value..deletedIn(arena)).getNumber(),
0,
);
final strParent = grandParent.stringParent()..deletedIn(arena);
expect(
strParent.parentValue
.castTo(JString.type, deleteOriginal: true)
.toDartString(deleteOriginal: true),
"!",
);
expect(
strParent.value.toDartString(deleteOriginal: true),
"Hello",
);
final exampleParent = grandParent.varParent(
S: Example.type, Example()..deletedIn(arena))
..deletedIn(arena);
expect(
exampleParent.parentValue
.castTo(JString.type, deleteOriginal: true)
.toDartString(deleteOriginal: true),
"!",
);
expect(
(exampleParent.value..deletedIn(arena)).getNumber(),
0,
);
// TODO(#139): test constructing Child, currently does not work due
// to a problem with C-bindings.
});
});
});
group('Generic type inference', () {
test('MyStack.of1', () {
using((arena) {
final emptyStack = MyStack(T: JString.type)..deletedIn(arena);
expect(emptyStack.size(), 0);
final stack = MyStack.of1(
"Hello".toJString()..deletedIn(arena),
)..deletedIn(arena);
expect(stack, isA<MyStack<JString>>());
expect(stack.$type, isA<$MyStackType<JString>>());
expect(
stack.pop().toDartString(deleteOriginal: true),
"Hello",
);
});
});
test('MyStack.of 2 strings', () {
using((arena) {
final stack = MyStack.of2(
"Hello".toJString()..deletedIn(arena),
"World".toJString()..deletedIn(arena),
)..deletedIn(arena);
expect(stack, isA<MyStack<JString>>());
expect(stack.$type, isA<$MyStackType<JString>>());
expect(
stack.pop().toDartString(deleteOriginal: true),
"World",
);
expect(
stack.pop().toDartString(deleteOriginal: true),
"Hello",
);
});
});
test('MyStack.of a string and an array', () {
using((arena) {
final array = JArray.filled(1, "World".toJString()..deletedIn(arena))
..deletedIn(arena);
final stack = MyStack.of2(
"Hello".toJString()..deletedIn(arena),
array,
)..deletedIn(arena);
expect(stack, isA<MyStack<JObject>>());
expect(stack.$type, isA<$MyStackType<JObject>>());
expect(
stack
.pop()
.castTo(JArray.type(JString.type), deleteOriginal: true)[0]
.toDartString(deleteOriginal: true),
"World",
);
expect(
stack
.pop()
.castTo(JString.type, deleteOriginal: true)
.toDartString(deleteOriginal: true),
"Hello",
);
});
});
test('MyStack.from array of string', () {
using((arena) {
final array = JArray.filled(1, "Hello".toJString()..deletedIn(arena))
..deletedIn(arena);
final stack = MyStack.fromArray(array)..deletedIn(arena);
expect(stack, isA<MyStack<JString>>());
expect(stack.$type, isA<$MyStackType<JString>>());
expect(
stack.pop().toDartString(deleteOriginal: true),
"Hello",
);
});
});
test('MyStack.fromArrayOfArrayOfGrandParents', () {
using((arena) {
final firstDimention = JArray.filled(
1,
GrandParent("Hello".toJString()..deletedIn(arena))
..deletedIn(arena),
)..deletedIn(arena);
final twoDimentionalArray = JArray.filled(1, firstDimention)
..deletedIn(arena);
final stack =
MyStack.fromArrayOfArrayOfGrandParents(twoDimentionalArray)
..deletedIn(arena);
expect(stack, isA<MyStack<JString>>());
expect(stack.$type, isA<$MyStackType<JString>>());
expect(
stack.pop().toDartString(deleteOriginal: true),
"Hello",
);
});
});
});
});
group('$groupName (load tests)', () {
const k4 = 4 * 1024; // This is a round number, unlike say 4000
const k256 = 256 * 1024;
test('create large number of JNI references without deleting', () {
for (int i = 0; i < k4; i++) {
final e = Example.ctor1(i);
expect(e.getNumber(), equals(i));
}
});
test('Create many JNI refs with scoped deletion', () {
for (int i = 0; i < k256; i++) {
using((arena) {
final e = Example.ctor1(i)..deletedIn(arena);
expect(e.getNumber(), equals(i));
});
}
});
test('Create many JNI refs with scoped deletion, in batches', () {
for (int i = 0; i < 256; i++) {
using((arena) {
for (int i = 0; i < 1024; i++) {
final e = Example.ctor1(i)..deletedIn(arena);
expect(e.getNumber(), equals(i));
}
});
}
});
test('Create large number of JNI refs with manual delete', () {
for (int i = 0; i < k256; i++) {
final e = Example.ctor1(i);
expect(e.getNumber(), equals(i));
e.delete();
}
});
test('Method returning primitive type does not create references', () {
using((arena) {
final e = Example.ctor1(64)..deletedIn(arena);
for (int i = 0; i < k256; i++) {
expect(e.getNumber(), equals(64));
}
});
});
test('Class references are cached', () {
final asterisk = '*'.codeUnitAt(0);
for (int i = 0; i < k256; i++) {
expect(Fields.asterisk, equals(asterisk));
}
});
void testPassageOfTime(int n) {
test('Refs are not inadvertently deleted after $n seconds', () {
final f = Fields();
expect(f.trillion, equals(trillion));
sleep(Duration(seconds: n));
expect(f.trillion, equals(trillion));
});
}
if (!Platform.isAndroid) {
testPassageOfTime(1);
testPassageOfTime(4);
}
});
}