summaryrefslogtreecommitdiff
path: root/test_ffi/tests/ffi_callback_errors.ts
diff options
context:
space:
mode:
Diffstat (limited to 'test_ffi/tests/ffi_callback_errors.ts')
-rw-r--r--test_ffi/tests/ffi_callback_errors.ts141
1 files changed, 141 insertions, 0 deletions
diff --git a/test_ffi/tests/ffi_callback_errors.ts b/test_ffi/tests/ffi_callback_errors.ts
new file mode 100644
index 000000000..dda4de5fb
--- /dev/null
+++ b/test_ffi/tests/ffi_callback_errors.ts
@@ -0,0 +1,141 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
+const [libPrefix, libSuffix] = {
+ darwin: ["lib", "dylib"],
+ linux: ["lib", "so"],
+ windows: ["", "dll"],
+}[Deno.build.os];
+const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
+
+const dylib = Deno.dlopen(
+ libPath,
+ {
+ store_function_2: {
+ parameters: ["function"],
+ result: "void",
+ },
+ call_stored_function_2: {
+ parameters: ["u8"],
+ result: "void",
+ },
+ call_stored_function_2_from_other_thread: {
+ name: "call_stored_function_2",
+ parameters: ["u8"],
+ result: "void",
+ nonblocking: true,
+ },
+ call_stored_function_2_thread_safe: {
+ parameters: ["u8"],
+ result: "void",
+ },
+ } as const,
+);
+
+globalThis.addEventListener("error", (data) => {
+ console.log("Unhandled error");
+ data.preventDefault();
+});
+globalThis.onerror = (data) => {
+ console.log("Unhandled error");
+ if (typeof data !== "string") {
+ data.preventDefault();
+ }
+};
+
+globalThis.addEventListener("unhandledrejection", (data) => {
+ console.log("Unhandled rejection");
+ data.preventDefault();
+});
+
+const timer = setTimeout(() => {
+ console.error(
+ "Test failed, final callback did not get picked up by Deno event loop",
+ );
+ Deno.exit(-1);
+}, 5_000);
+
+Deno.unrefTimer(timer);
+
+enum CallCase {
+ SyncSelf,
+ SyncFfi,
+ AsyncSelf,
+ AsyncSyncFfi,
+ AsyncFfi,
+}
+type U8CallCase = Deno.NativeU8Enum<CallCase>;
+
+const throwCb = (c: CallCase): number => {
+ console.log("CallCase:", CallCase[c]);
+ if (c === CallCase.AsyncFfi) {
+ cb.unref();
+ }
+ throw new Error("Error");
+};
+
+const THROW_CB_DEFINITION = {
+ parameters: ["u8" as U8CallCase],
+ result: "u8",
+} as const;
+
+const cb = new Deno.UnsafeCallback(THROW_CB_DEFINITION, throwCb);
+
+try {
+ const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, THROW_CB_DEFINITION);
+
+ fnPointer.call(CallCase.SyncSelf);
+} catch (_err) {
+ console.log(
+ "Throwing errors from an UnsafeCallback called from a synchronous UnsafeFnPointer works. Terribly excellent.",
+ );
+}
+
+dylib.symbols.store_function_2(cb.pointer);
+try {
+ dylib.symbols.call_stored_function_2(CallCase.SyncFfi);
+} catch (_err) {
+ console.log(
+ "Throwing errors from an UnsafeCallback called from a synchronous FFI symbol works. Terribly excellent.",
+ );
+}
+
+try {
+ const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, {
+ ...THROW_CB_DEFINITION,
+ nonblocking: true,
+ });
+ await fnPointer.call(CallCase.AsyncSelf);
+} catch (err) {
+ throw new Error(
+ "Nonblocking UnsafeFnPointer should not be threading through a JS error thrown on the other side of the call",
+ {
+ cause: err,
+ },
+ );
+}
+
+try {
+ await dylib.symbols.call_stored_function_2_from_other_thread(
+ CallCase.AsyncSyncFfi,
+ );
+} catch (err) {
+ throw new Error(
+ "Nonblocking symbol call should not be threading through a JS error thrown on the other side of the call",
+ {
+ cause: err,
+ },
+ );
+}
+try {
+ // Ref the callback to make sure we do not exit before the call is done.
+ cb.ref();
+ dylib.symbols.call_stored_function_2_thread_safe(CallCase.AsyncFfi);
+} catch (err) {
+ throw new Error(
+ "Blocking symbol call should not be travelling 1.5 seconds forward in time to figure out that it call will trigger a JS error to be thrown",
+ {
+ cause: err,
+ },
+ );
+}