Skip to content

Commit

Permalink
Refactor integration tests
Browse files Browse the repository at this point in the history
This commit refactors the integration tests to use the `javy-runner`
crate.

The main motivation for this change is to reduce duplication and make it
easier to test more complex scenarios in the future e.g., the upcoming
changes in bytecodealliance#702
  • Loading branch information
saulecabrera committed Aug 14, 2024
1 parent 644a502 commit b0784eb
Show file tree
Hide file tree
Showing 16 changed files with 529 additions and 459 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 0 additions & 60 deletions crates/cli/tests/common/mod.rs

This file was deleted.

166 changes: 30 additions & 136 deletions crates/cli/tests/dylib_test.rs
Original file line number Diff line number Diff line change
@@ -1,169 +1,63 @@
use anyhow::Result;
use std::boxed::Box;
use javy_runner::{Runner, RunnerError};
use std::path::{Path, PathBuf};
use std::str;
use wasi_common::{pipe::WritePipe, sync::WasiCtxBuilder, WasiCtx, WasiFile};
use wasmtime::{AsContextMut, Engine, Instance, Linker, Store};

mod common;
static ROOT: &str = env!("CARGO_MANIFEST_DIR");

#[test]
fn test_dylib() -> Result<()> {
let js_src = "console.log(42);";
let stderr = WritePipe::new_in_memory();
run_js_src(js_src, &stderr)?;
let mut runner = Runner::with_dylib(provider_module()?)?;

let output = stderr.try_into_inner().unwrap().into_inner();
assert_eq!("42\n", str::from_utf8(&output)?);
let (_, logs, _) = runner.exec_through_dylib(&js_src, None)?;
assert_eq!("42\n", str::from_utf8(&logs)?);

Ok(())
}

#[test]
fn test_dylib_with_error() -> Result<()> {
let js_src = "function foo() { throw new Error('foo error'); } foo();";
let stderr = WritePipe::new_in_memory();
let result = run_js_src(js_src, &stderr);

assert!(result.is_err());
let output = stderr.try_into_inner().unwrap().into_inner();
let mut runner = Runner::with_dylib(provider_module()?)?;

let res = runner.exec_through_dylib(&js_src, None);

assert!(res.is_err());

let e = res.err().unwrap();
let expected_log_output = "Error:1:24 foo error\n at foo (function.mjs:1:24)\n at <anonymous> (function.mjs:1:50)\n\n";
assert_eq!(expected_log_output, str::from_utf8(&output)?);
assert_eq!(
expected_log_output,
String::from_utf8(e.downcast_ref::<RunnerError>().unwrap().stderr.clone())?
);

Ok(())
}

#[test]
fn test_dylib_with_exported_func() -> Result<()> {
let js_src = "export function foo() { console.log('In foo'); }; console.log('Toplevel');";
let stderr = WritePipe::new_in_memory();
run_invoke(js_src, "foo", &stderr)?;

let output = stderr.try_into_inner().unwrap().into_inner();
assert_eq!("Toplevel\nIn foo\n", str::from_utf8(&output)?);
let mut runner = Runner::with_dylib(provider_module()?)?;

Ok(())
}
let (_, logs, _) = runner.exec_through_dylib(&js_src, Some("foo"))?;
assert_eq!("Toplevel\nIn foo\n", str::from_utf8(&logs)?);

fn run_js_src<T: WasiFile + Clone + 'static>(js_src: &str, stderr: &T) -> Result<()> {
let (instance, mut store) = create_wasm_env(stderr)?;

let eval_bytecode_func =
instance.get_typed_func::<(u32, u32), ()>(store.as_context_mut(), "eval_bytecode")?;
let (bytecode_ptr, bytecode_len) =
compile_src(js_src.as_bytes(), &instance, store.as_context_mut())?;
eval_bytecode_func.call(store.as_context_mut(), (bytecode_ptr, bytecode_len))?;
Ok(())
}

fn run_invoke<T: WasiFile + Clone + 'static>(
js_src: &str,
fn_to_invoke: &str,
stderr: &T,
) -> Result<()> {
let (instance, mut store) = create_wasm_env(stderr)?;

let invoke_func =
instance.get_typed_func::<(u32, u32, u32, u32), ()>(store.as_context_mut(), "invoke")?;
let (bytecode_ptr, bytecode_len) =
compile_src(js_src.as_bytes(), &instance, store.as_context_mut())?;
let (fn_name_ptr, fn_name_len) =
copy_func_name(fn_to_invoke, &instance, store.as_context_mut())?;
invoke_func.call(
store.as_context_mut(),
(bytecode_ptr, bytecode_len, fn_name_ptr, fn_name_len),
)?;
Ok(())
}

fn create_wasm_env<T: WasiFile + Clone + 'static>(
stderr: &T,
) -> Result<(Instance, Store<WasiCtx>)> {
let engine = Engine::default();
let mut linker = Linker::new(&engine);
wasi_common::sync::add_to_linker(&mut linker, |s| s)?;
let wasi = WasiCtxBuilder::new()
.stderr(Box::new(stderr.clone()))
.build();
let module = common::create_quickjs_provider_module(&engine)?;

let mut store = Store::new(&engine, wasi);
let instance = linker.instantiate(store.as_context_mut(), &module)?;

Ok((instance, store))
}

fn compile_src(
js_src: &[u8],
instance: &Instance,
mut store: impl AsContextMut,
) -> Result<(u32, u32)> {
let memory = instance
.get_memory(store.as_context_mut(), "memory")
.unwrap();
let compile_src_func =
instance.get_typed_func::<(u32, u32), u32>(store.as_context_mut(), "compile_src")?;

let js_src_ptr = allocate_memory(
instance,
store.as_context_mut(),
1,
js_src.len().try_into()?,
)?;
memory.write(store.as_context_mut(), js_src_ptr.try_into()?, js_src)?;

let ret_ptr = compile_src_func.call(
store.as_context_mut(),
(js_src_ptr, js_src.len().try_into()?),
)?;
let mut ret_buffer = [0; 8];
memory.read(store.as_context(), ret_ptr.try_into()?, &mut ret_buffer)?;
let bytecode_ptr = u32::from_le_bytes(ret_buffer[0..4].try_into()?);
let bytecode_len = u32::from_le_bytes(ret_buffer[4..8].try_into()?);

Ok((bytecode_ptr, bytecode_len))
}

fn copy_func_name(
fn_name: &str,
instance: &Instance,
mut store: impl AsContextMut,
) -> Result<(u32, u32)> {
let memory = instance
.get_memory(store.as_context_mut(), "memory")
.unwrap();
let fn_name_bytes = fn_name.as_bytes();
let fn_name_ptr = allocate_memory(
instance,
store.as_context_mut(),
1,
fn_name_bytes.len().try_into()?,
)?;
memory.write(
store.as_context_mut(),
fn_name_ptr.try_into()?,
fn_name_bytes,
)?;

Ok((fn_name_ptr, fn_name_bytes.len().try_into()?))
}
fn provider_module() -> Result<Vec<u8>> {
let mut lib_path = PathBuf::from(ROOT);
lib_path.pop();
lib_path.pop();
lib_path = lib_path.join(
Path::new("target")
.join("wasm32-wasi")
.join("release")
.join("javy_quickjs_provider_wizened.wasm"),
);

fn allocate_memory(
instance: &Instance,
mut store: impl AsContextMut,
alignment: u32,
new_size: u32,
) -> Result<u32> {
let realloc_func = instance.get_typed_func::<(u32, u32, u32, u32), u32>(
store.as_context_mut(),
"canonical_abi_realloc",
)?;
let orig_ptr = 0;
let orig_size = 0;
realloc_func
.call(
store.as_context_mut(),
(orig_ptr, orig_size, alignment, new_size),
)
.map_err(Into::into)
std::fs::read(lib_path).map_err(Into::into)
}
1 change: 1 addition & 0 deletions crates/cli/tests/dynamic-linking-scripts/console.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(42);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function foo() {
throw "Error";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package local:main

world foo-test {
export foo: func()
}
1 change: 1 addition & 0 deletions crates/cli/tests/dynamic-linking-scripts/javy-json-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log(Javy.JSON.toStdout(Javy.JSON.fromStdin()));
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default () => console.log(42)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package local:test

world exported-arrow {
export default: func()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function foo() {
console.log('In foo');
};

console.log('Toplevel');
5 changes: 5 additions & 0 deletions crates/cli/tests/dynamic-linking-scripts/linking-with-func.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function fooBar() {
console.log('In foo');
};

console.log('Toplevel');
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package local:main

world foo-test {
export foo-bar: func()
}
Loading

0 comments on commit b0784eb

Please sign in to comment.