| // 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. |
| |
| // Dart test program for testing dart:ffi async callbacks. |
| // |
| // VMOptions= |
| // VMOptions=--stacktrace-every=100 |
| // VMOptions=--use-slow-path |
| // VMOptions=--use-slow-path --stacktrace-every=100 |
| // VMOptions=--dwarf_stack_traces --no-retain_function_objects --no-retain_code_objects |
| // VMOptions=--test_il_serialization |
| // VMOptions=--profiler --profile_vm=true |
| // VMOptions=--profiler --profile_vm=false |
| // SharedObjects=ffi_test_functions |
| |
| import 'dart:async'; |
| import 'dart:ffi'; |
| import 'dart:isolate'; |
| |
| import "package:expect/expect.dart"; |
| |
| import 'dylib_utils.dart'; |
| |
| main(args, message) async { |
| testNativeCallableStatic(); |
| testNativeCallableClosure(); |
| testNativeCallableDoubleCloseError(); |
| testNativeCallableNestedCloseCallStatic(); |
| testNativeCallableNestedCloseCallClosure(); |
| testNativeCallableExceptionalReturnStatic(); |
| testNativeCallableExceptionalReturnClosure(); |
| await testNativeCallableDontKeepAliveStatic(); |
| await testNativeCallableDontKeepAliveClosure(); |
| testNativeCallableKeepAliveGetter(); |
| print("All tests completed :)"); |
| } |
| |
| final ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions"); |
| |
| typedef TwoIntFnNativeType = Int32 Function(Pointer, Int32, Int32); |
| typedef TwoIntFnType = int Function(Pointer, int, int); |
| final callTwoIntFunction = ffiTestFunctions |
| .lookupFunction<TwoIntFnNativeType, TwoIntFnType>("CallTwoIntFunction"); |
| |
| typedef CallbackNativeType = Int32 Function(Int32, Int32); |
| int add(int a, int b) { |
| return a + b; |
| } |
| |
| testNativeCallableStatic() { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| add, |
| exceptionalReturn: 0, |
| ); |
| |
| Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234)); |
| |
| callback.close(); |
| } |
| |
| testNativeCallableClosure() { |
| int c = 70000; |
| |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| (int a, int b) => a + b + c, |
| exceptionalReturn: 0, |
| ); |
| |
| Expect.equals(71234, callTwoIntFunction(callback.nativeFunction, 1000, 234)); |
| |
| c = 80000; |
| Expect.equals(81234, callTwoIntFunction(callback.nativeFunction, 1000, 234)); |
| |
| callback.close(); |
| } |
| |
| testNativeCallableDoubleCloseError() { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| add, |
| exceptionalReturn: 0, |
| ); |
| Expect.notEquals(nullptr, callback.nativeFunction); |
| callback.close(); |
| |
| Expect.throwsStateError(() { |
| final _ = callback.nativeFunction; |
| }); |
| |
| // Expect that these do not throw. |
| callback.close(); |
| callback.keepIsolateAlive = true; |
| Expect.isFalse(callback.keepIsolateAlive); |
| } |
| |
| late NativeCallable selfClosingStaticCallback; |
| int selfClosingStatic(int a, int b) { |
| selfClosingStaticCallback.close(); |
| return a + b; |
| } |
| |
| testNativeCallableNestedCloseCallStatic() { |
| selfClosingStaticCallback = NativeCallable<CallbackNativeType>.isolateLocal( |
| selfClosingStatic, |
| exceptionalReturn: 0, |
| ); |
| |
| Expect.equals( |
| 1234, |
| callTwoIntFunction(selfClosingStaticCallback.nativeFunction, 1000, 234), |
| ); |
| |
| // The callback is already closed. |
| Expect.throwsStateError(() { |
| final _ = selfClosingStaticCallback.nativeFunction; |
| }); |
| } |
| |
| testNativeCallableNestedCloseCallClosure() { |
| late NativeCallable callback; |
| |
| callback = NativeCallable<CallbackNativeType>.isolateLocal((int a, int b) { |
| callback.close(); |
| return a + b; |
| }, exceptionalReturn: 0); |
| |
| Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234)); |
| |
| // The callback is already closed. |
| Expect.throwsStateError(() { |
| final _ = callback.nativeFunction; |
| }); |
| } |
| |
| int throwerCallback(int a, int b) { |
| if (a != 1000) { |
| throw "Oh no!"; |
| } |
| return a + b; |
| } |
| |
| testNativeCallableExceptionalReturnStatic() { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| throwerCallback, |
| exceptionalReturn: 5678, |
| ); |
| |
| Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234)); |
| Expect.equals(5678, callTwoIntFunction(callback.nativeFunction, 0, 0)); |
| |
| callback.close(); |
| } |
| |
| testNativeCallableExceptionalReturnClosure() { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal(( |
| int a, |
| int b, |
| ) { |
| if (a != 1000) { |
| throw "Oh no!"; |
| } |
| return a + b; |
| }, exceptionalReturn: 5678); |
| |
| Expect.equals(1234, callTwoIntFunction(callback.nativeFunction, 1000, 234)); |
| Expect.equals(5678, callTwoIntFunction(callback.nativeFunction, 0, 0)); |
| |
| callback.close(); |
| } |
| |
| Future<void> testNativeCallableDontKeepAliveStatic() async { |
| final exitPort = ReceivePort(); |
| await Isolate.spawn( |
| (_) async { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| add, |
| exceptionalReturn: 0, |
| ); |
| |
| Expect.equals( |
| 1234, |
| callTwoIntFunction(callback.nativeFunction, 1000, 234), |
| ); |
| |
| callback.keepIsolateAlive = false; |
| }, |
| null, |
| onExit: exitPort.sendPort, |
| ); |
| await exitPort.first; |
| exitPort.close(); |
| } |
| |
| Future<void> testNativeCallableDontKeepAliveClosure() async { |
| int c = 70000; |
| |
| final exitPort = ReceivePort(); |
| await Isolate.spawn( |
| (_) async { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| (int a, int b) => a + b + c, |
| exceptionalReturn: 0, |
| ); |
| |
| Expect.equals( |
| 71234, |
| callTwoIntFunction(callback.nativeFunction, 1000, 234), |
| ); |
| |
| callback.keepIsolateAlive = false; |
| }, |
| null, |
| onExit: exitPort.sendPort, |
| ); |
| await exitPort.first; |
| exitPort.close(); |
| } |
| |
| testNativeCallableKeepAliveGetter() { |
| final callback = NativeCallable<CallbackNativeType>.isolateLocal( |
| add, |
| exceptionalReturn: 0, |
| ); |
| // Check that only the flag changes are counted by decrementing and |
| // incrementing a lot, and by different amounts. |
| for (int i = 0; i < 100; ++i) { |
| callback.keepIsolateAlive = false; |
| Expect.isFalse(callback.keepIsolateAlive); |
| } |
| for (int i = 0; i < 200; ++i) { |
| callback.keepIsolateAlive = true; |
| Expect.isTrue(callback.keepIsolateAlive); |
| } |
| callback.close(); |
| } |