diff --git a/cli/tests/unit/dispatch_bin_test.ts b/cli/tests/unit/dispatch_bin_test.ts index ca18646219..54a379bc08 100644 --- a/cli/tests/unit/dispatch_bin_test.ts +++ b/cli/tests/unit/dispatch_bin_test.ts @@ -1,27 +1,16 @@ -import { - assert, - assertEquals, - assertMatch, - unitTest, - unreachable, -} from "./test_util.ts"; +import { assertStringIncludes, unitTest, unreachable } from "./test_util.ts"; -const readErrorStackPattern = new RegExp( - `^.* - at processErr \\(.*core\\.js:.*\\) - at opAsyncHandler \\(.*core\\.js:.*\\) - at handleAsyncMsgFromRust \\(.*core\\.js:.*\\).*$`, - "ms", -); - -unitTest(async function sendAsyncStackTrace(): Promise { +unitTest(async function sendAsyncStackTrace() { const buf = new Uint8Array(10); const rid = 10; try { await Deno.read(rid, buf); unreachable(); } catch (error) { - assertMatch(error.stack, readErrorStackPattern); + const s = error.stack.toString(); + console.log(s); + assertStringIncludes(s, "dispatch_bin_test.ts"); + assertStringIncludes(s, "read"); } }); diff --git a/core/core.js b/core/core.js index 3ec5c3e41c..3142aa93ae 100644 --- a/core/core.js +++ b/core/core.js @@ -65,7 +65,10 @@ function handleAsyncMsgFromRust() { for (let i = 0; i < arguments.length; i += 2) { - opAsyncHandler(arguments[i], arguments[i + 1]); + const promiseId = arguments[i]; + const res = arguments[i + 1]; + const promise = getPromise(promiseId); + promise.resolve(res); } } @@ -84,48 +87,31 @@ return errorMap[errorName] ?? [undefined, []]; } - function processResponse(res) { - if (!isErr(res)) { - return res; + function unwrapOpResult(res) { + // .$err_class_name is a special key that should only exist on errors + if (res?.$err_class_name) { + const className = res.$err_class_name; + const [ErrorClass, args] = getErrorClassAndArgs(className); + if (!ErrorClass) { + throw new Error( + `Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`, + ); + } + throw new ErrorClass(res.message, ...args); } - throw processErr(res); - } - - // .$err_class_name is a special key that should only exist on errors - function isErr(res) { - return !!(res && res.$err_class_name); - } - - function processErr(err) { - const className = err.$err_class_name; - const [ErrorClass, args] = getErrorClassAndArgs(className); - if (!ErrorClass) { - return new Error( - `Unregistered error class: "${className}"\n ${err.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`, - ); - } - return new ErrorClass(err.message, ...args); + return res; } function jsonOpAsync(opName, args = null, zeroCopy = null) { const promiseId = nextPromiseId++; const maybeError = dispatch(opName, promiseId, args, zeroCopy); // Handle sync error (e.g: error parsing args) - if (maybeError) processResponse(maybeError); - return setPromise(promiseId); + if (maybeError) return unwrapOpResult(maybeError); + return setPromise(promiseId).then(unwrapOpResult); } function jsonOpSync(opName, args = null, zeroCopy = null) { - return processResponse(dispatch(opName, null, args, zeroCopy)); - } - - function opAsyncHandler(promiseId, res) { - const promise = getPromise(promiseId); - if (!isErr(res)) { - promise.resolve(res); - } else { - promise.reject(processErr(res)); - } + return unwrapOpResult(dispatch(opName, null, args, zeroCopy)); } function binOpSync(opName, args = null, zeroCopy = null) { diff --git a/core/ops_json.rs b/core/ops_json.rs index 21c6b219fc..0efd44a907 100644 --- a/core/ops_json.rs +++ b/core/ops_json.rs @@ -108,3 +108,51 @@ where }, ) } + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn json_op_async_stack_trace() { + let mut runtime = crate::JsRuntime::new(Default::default()); + + async fn op_throw( + _state: Rc>, + msg: Option, + zero_copy: Option, + ) -> Result<(), AnyError> { + assert_eq!(msg.unwrap(), "hello"); + assert!(zero_copy.is_none()); + Err(crate::error::generic_error("foo")) + } + + runtime.register_op("op_throw", json_op_async(op_throw)); + runtime + .execute( + "", + r#" + // First we initialize the ops cache. This maps op names to their id's. + Deno.core.ops(); + // Register the error class. + Deno.core.registerErrorClass('Error', Error); + + async function f1() { + await Deno.core.jsonOpAsync('op_throw', 'hello'); + } + + async function f2() { + await f1(); + } + + f2(); + "#, + ) + .unwrap(); + let e = runtime.run_event_loop().await.unwrap_err().to_string(); + println!("{}", e); + assert!(e.contains("Error: foo")); + assert!(e.contains("at async f1 (:")); + assert!(e.contains("at async f2 (:")); + } +}