diff --git a/crates/cli/tests/integration_test.rs b/crates/cli/tests/integration_test.rs index 529f8c16..519dbd4c 100644 --- a/crates/cli/tests/integration_test.rs +++ b/crates/cli/tests/integration_test.rs @@ -17,7 +17,7 @@ fn test_identity() { let _guard = EXCLUSIVE_TEST.lock(); let mut runner = Runner::default(); - let output = run::<_, u32>(&mut runner, &42); + let (output, _) = run::<_, u32>(&mut runner, &42); assert_eq!(42, output); } @@ -26,7 +26,7 @@ fn test_fib() { let _guard = EXCLUSIVE_TEST.lock(); let mut runner = Runner::new("fib.js"); - let output = run::<_, u32>(&mut runner, &5); + let (output, _) = run::<_, u32>(&mut runner, &5); assert_eq!(8, output); } @@ -35,7 +35,7 @@ fn test_recursive_fib() { let _guard = EXCLUSIVE_TEST.lock(); let mut runner = Runner::new("recursive-fib.js"); - let output = run::<_, u32>(&mut runner, &5); + let (output, _) = run::<_, u32>(&mut runner, &5); assert_eq!(8, output); } @@ -44,7 +44,7 @@ fn test_str() { let _guard = EXCLUSIVE_TEST.lock(); let mut runner = Runner::new("str.js"); - let output = run::<_, String>(&mut runner, &"hello".to_string()); + let (output, _) = run::<_, String>(&mut runner, &"hello".to_string()); assert_eq!("world", output.as_str()); } @@ -53,29 +53,43 @@ fn test_big_ints() { let _guard = EXCLUSIVE_TEST.lock(); let mut runner = Runner::new("big-ints.js"); - let output = run::<_, String>(&mut runner, &42); + let (output, _) = run::<_, String>(&mut runner, &42); assert_eq!("a", output.as_str()); - let output = run::<_, String>(&mut runner, &i64::MAX); + let (output, _) = run::<_, String>(&mut runner, &i64::MAX); assert_eq!("b", output.as_str()); - let output = run::<_, String>(&mut runner, &i64::MIN); + let (output, _) = run::<_, String>(&mut runner, &i64::MIN); assert_eq!("c", output.as_str()); - let output = run::<_, String>(&mut runner, &u64::MAX); + let (output, _) = run::<_, String>(&mut runner, &u64::MAX); assert_eq!("d", output.as_str()); - let output = run::<_, String>(&mut runner, &u64::MIN); + let (output, _) = run::<_, String>(&mut runner, &u64::MIN); assert_eq!("e", output.as_str()); } -fn run(r: &mut Runner, i: &I) -> O +#[test] +fn test_logging() { + let _guard = EXCLUSIVE_TEST.lock(); + let mut runner = Runner::new("logging.js"); + + let (output, logs) = run::<_, u32>(&mut runner, &42); + assert_eq!(42, output); + assert_eq!( + "hello world from console.log\nhello world from console.error\n", + logs.as_str(), + ); +} + +fn run(r: &mut Runner, i: &I) -> (O, String) where I: Serialize, O: DeserializeOwned, { let input = rmp_serde::to_vec(i).unwrap(); - let output = r.exec(input).unwrap(); + let (output, logs) = r.exec(input).unwrap(); let output = rmp_serde::from_slice::(&output).unwrap(); - output + let logs = String::from_utf8(logs).unwrap(); + (output, logs) } diff --git a/crates/cli/tests/runner/mod.rs b/crates/cli/tests/runner/mod.rs index 0ff99df4..a5bb17b1 100644 --- a/crates/cli/tests/runner/mod.rs +++ b/crates/cli/tests/runner/mod.rs @@ -16,14 +16,21 @@ pub struct Runner { struct StoreContext { wasi_output: WritePipe>>, wasi: WasiCtx, + log_stream: WritePipe>>, } impl Default for StoreContext { fn default() -> Self { let wasi_output = WritePipe::new_in_memory(); + let log_stream = WritePipe::new_in_memory(); let mut wasi = WasiCtxBuilder::new().inherit_stdio().build(); wasi.set_stdout(Box::new(wasi_output.clone())); - Self { wasi, wasi_output } + wasi.set_stderr(Box::new(log_stream.clone())); + Self { + wasi, + wasi_output, + log_stream, + } } } @@ -31,11 +38,14 @@ impl StoreContext { fn new(input: Vec) -> Self { let mut wasi = WasiCtxBuilder::new().inherit_stdio().build(); let wasi_output = WritePipe::new_in_memory(); + let log_stream = WritePipe::new_in_memory(); wasi.set_stdout(Box::new(wasi_output.clone())); wasi.set_stdin(Box::new(ReadPipe::from(input.clone()))); + wasi.set_stderr(Box::new(log_stream.clone())); Self { wasi, wasi_output, + log_stream, ..Default::default() } } @@ -78,7 +88,7 @@ impl Runner { Self { wasm, linker } } - pub fn exec(&mut self, input: Vec) -> Result> { + pub fn exec(&mut self, input: Vec) -> Result<(Vec, Vec)> { let mut store = Store::new(self.linker.engine(), StoreContext::new(input)); let module = Module::from_binary(self.linker.engine(), &self.wasm)?; @@ -89,11 +99,17 @@ impl Runner { run.call(&mut store, ())?; let store_context = store.into_data(); drop(store_context.wasi); - Ok(store_context + let logs = store_context + .log_stream + .try_into_inner() + .expect("log stream reference still exists") + .into_inner(); + let output = store_context .wasi_output .try_into_inner() .expect("Output stream reference still exists") - .into_inner()) + .into_inner(); + Ok((output, logs)) } } diff --git a/crates/cli/tests/sample-scripts/logging.js b/crates/cli/tests/sample-scripts/logging.js new file mode 100644 index 00000000..449694fd --- /dev/null +++ b/crates/cli/tests/sample-scripts/logging.js @@ -0,0 +1,7 @@ +var Shopify = { + main: (i) => { + console.log("hello world from console.log"); + console.error("hello world from console.error"); + return i; + } +} diff --git a/crates/core/src/main.rs b/crates/core/src/main.rs index c43d3595..c0c070b4 100644 --- a/crates/core/src/main.rs +++ b/crates/core/src/main.rs @@ -23,7 +23,9 @@ static SCRIPT_NAME: &str = "script.js"; pub extern "C" fn init() { unsafe { let mut context = Context::default(); - context.register_globals(io::stdout()).unwrap(); + context + .register_globals(io::stderr(), io::stderr()) + .unwrap(); let mut contents = String::new(); io::stdin().read_to_string(&mut contents).unwrap(); diff --git a/crates/quickjs-wasm-rs/src/js_binding/context.rs b/crates/quickjs-wasm-rs/src/js_binding/context.rs index e33eba54..c35357e7 100644 --- a/crates/quickjs-wasm-rs/src/js_binding/context.rs +++ b/crates/quickjs-wasm-rs/src/js_binding/context.rs @@ -143,14 +143,16 @@ impl Context { Value::new(self.inner, raw) } - pub fn register_globals(&mut self, log_stream: T) -> Result<()> + pub fn register_globals(&mut self, log_stream: T, error_stream: T) -> Result<()> where T: Write, { let console_log_callback = unsafe { self.new_callback(console_log_to(log_stream))? }; + let console_error_callback = unsafe { self.new_callback(console_log_to(error_stream))? }; let global_object = self.global_object()?; let console_object = self.object_value()?; console_object.set_property("log", console_log_callback)?; + console_object.set_property("error", console_error_callback)?; global_object.set_property("console", console_object)?; Ok(()) } @@ -348,7 +350,7 @@ mod tests { let mut stream = SharedStream::default(); let mut ctx = Context::default(); - ctx.register_globals(stream.clone())?; + ctx.register_globals(stream.clone(), stream.clone())?; ctx.eval_global("main", "console.log(\"hello world\");")?; assert_eq!(b"hello world\n", stream.0.borrow().as_slice()); @@ -359,4 +361,21 @@ mod tests { assert_eq!(b"bonjour le monde\n", stream.0.borrow().as_slice()); Ok(()) } + + #[test] + fn test_console_error() -> Result<()> { + let mut stream = SharedStream::default(); + + let mut ctx = Context::default(); + ctx.register_globals(stream.clone(), stream.clone())?; + + ctx.eval_global("main", "console.error(\"hello world\");")?; + assert_eq!(b"hello world\n", stream.0.borrow().as_slice()); + + stream.clear(); + + ctx.eval_global("main", "console.error(\"bonjour\", \"le\", \"monde\")")?; + assert_eq!(b"bonjour le monde\n", stream.0.borrow().as_slice()); + Ok(()) + } }