mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
repl_test: start re-writing the Wasm tests to use roc_wasm_interp
This commit is contained in:
parent
e6325fa78f
commit
eaa3f14fb0
7 changed files with 204 additions and 3 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3494,12 +3494,15 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
|
|||
name = "repl_test"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"roc_build",
|
||||
"roc_cli",
|
||||
"roc_repl_cli",
|
||||
"roc_test_utils",
|
||||
"roc_wasm_interp",
|
||||
"roc_wasm_module",
|
||||
"strip-ansi-escapes",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
|
|
|
@ -10,16 +10,19 @@ description = "Tests the roc REPL."
|
|||
roc_cli = {path = "../cli"}
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
lazy_static = "1.4.0" # TODO: delete this, it was only for wasmer
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.7"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
wasmer-wasi = "2.2.1"
|
||||
bumpalo.workspace = true
|
||||
|
||||
roc_build = { path = "../compiler/build" }
|
||||
roc_repl_cli = {path = "../repl_cli"}
|
||||
roc_test_utils = {path = "../test_utils"}
|
||||
roc_wasm_interp = {path = "../wasm_interp"}
|
||||
roc_wasm_module = {path = "../wasm_module"} # todo: would be nicer not to need this
|
||||
|
||||
# Wasmer singlepass compiler only works on x86_64.
|
||||
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
|
||||
|
|
24
crates/repl_test/build.rs
Normal file
24
crates/repl_test/build.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
if std::env::var("CARGO_FEATURE_WASM").is_ok() {
|
||||
Command::new("cargo")
|
||||
.args([
|
||||
"build",
|
||||
"--target",
|
||||
"wasm32-wasi",
|
||||
"-p",
|
||||
"roc_repl_wasm",
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
"wasi_test",
|
||||
"--release",
|
||||
"--target-dir",
|
||||
"../../.ignore/repl_test_target",
|
||||
])
|
||||
.spawn()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
|
@ -14,3 +14,6 @@ mod cli;
|
|||
|
||||
#[cfg(all(test, feature = "wasm"))]
|
||||
mod wasm;
|
||||
|
||||
#[cfg(all(test, feature = "wasm"))]
|
||||
mod wasm_interp;
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::cli::{expect_failure, expect_success, repl_eval};
|
|||
|
||||
#[cfg(feature = "wasm")]
|
||||
#[allow(unused_imports)]
|
||||
use crate::wasm::{expect_failure, expect_success};
|
||||
use crate::wasm_interp::{expect_failure, expect_success};
|
||||
|
||||
#[test]
|
||||
fn literal_0() {
|
||||
|
|
168
crates/repl_test/src/wasm_interp.rs
Normal file
168
crates/repl_test/src/wasm_interp.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
use bumpalo::Bump;
|
||||
use roc_wasm_interp::{
|
||||
wasi, DefaultImportDispatcher, ImportDispatcher, Instance, WasiDispatcher, DEFAULT_IMPORTS,
|
||||
};
|
||||
use roc_wasm_module::{Value, WasmModule};
|
||||
|
||||
const COMPILER_BYTES: &[u8] =
|
||||
include_bytes!("../../../.ignore/repl_test_target/wasm32-wasi/release/roc_repl_wasm.wasm");
|
||||
|
||||
struct CompilerDispatcher<'a> {
|
||||
arena: &'a Bump,
|
||||
src: &'a str,
|
||||
answer: String,
|
||||
wasi: WasiDispatcher<'a>,
|
||||
app: Option<(WasmModule<'a>, Instance<'a, DefaultImportDispatcher<'a>>)>,
|
||||
result_addr: Option<i32>,
|
||||
}
|
||||
|
||||
impl<'a> ImportDispatcher for CompilerDispatcher<'a> {
|
||||
fn dispatch(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
function_name: &str,
|
||||
arguments: &[Value],
|
||||
compiler_memory: &mut [u8],
|
||||
) -> Option<Value> {
|
||||
let unknown = || {
|
||||
panic!(
|
||||
"TestDispatcher does not implement {}.{}",
|
||||
module_name, function_name
|
||||
)
|
||||
};
|
||||
|
||||
if module_name == wasi::MODULE_NAME {
|
||||
self.wasi
|
||||
.dispatch(function_name, arguments, compiler_memory)
|
||||
} else if module_name == "env" {
|
||||
match function_name {
|
||||
"test_create_app" => {
|
||||
// Get some bytes from the compiler Wasm instance and create the app Wasm instance
|
||||
// fn test_create_app(app_bytes_ptr: *const u8, app_bytes_len: usize) -> u32;
|
||||
assert_eq!(arguments.len(), 2);
|
||||
let app_bytes_ptr = arguments[0].expect_i32().unwrap() as usize;
|
||||
let app_bytes_len = arguments[1].expect_i32().unwrap() as usize;
|
||||
let app_bytes = &compiler_memory[app_bytes_ptr..][..app_bytes_len];
|
||||
|
||||
let require_reloc = false;
|
||||
let module = WasmModule::preload(self.arena, app_bytes, require_reloc).unwrap();
|
||||
|
||||
let is_debug_mode = false;
|
||||
let instance =
|
||||
Instance::for_module(self.arena, &module, DEFAULT_IMPORTS, is_debug_mode)
|
||||
.unwrap();
|
||||
|
||||
self.app = Some((module, instance));
|
||||
let ok = Value::I32(true as i32);
|
||||
Some(ok)
|
||||
}
|
||||
"test_run_app" => {
|
||||
// fn test_run_app() -> usize;
|
||||
assert_eq!(arguments.len(), 0);
|
||||
match &mut self.app {
|
||||
Some((module, instance)) => {
|
||||
let result_addr = instance
|
||||
.call_export(module, "wrapper", [])
|
||||
.unwrap()
|
||||
.expect("No return address from wrapper")
|
||||
.expect_i32()
|
||||
.unwrap();
|
||||
self.result_addr = Some(result_addr);
|
||||
let memory_size = instance.memory.len();
|
||||
Some(Value::I32(memory_size as i32))
|
||||
}
|
||||
None => panic!("Trying to run the app but it hasn't been created"),
|
||||
}
|
||||
}
|
||||
"test_get_result_and_memory" => {
|
||||
// Copy the app's entire memory buffer into the compiler's memory,
|
||||
// and return the location in that buffer where we can find the app result.
|
||||
// fn test_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize;
|
||||
assert_eq!(arguments.len(), 1);
|
||||
let buffer_alloc_addr = arguments[0].expect_i32().unwrap() as usize;
|
||||
match &self.app {
|
||||
Some((_, instance)) => {
|
||||
let len = instance.memory.len();
|
||||
compiler_memory[buffer_alloc_addr..len]
|
||||
.copy_from_slice(&instance.memory);
|
||||
self.result_addr.map(Value::I32)
|
||||
}
|
||||
None => panic!("Trying to get result and memory but there is no app"),
|
||||
}
|
||||
}
|
||||
"test_copy_input_string" => {
|
||||
// Copy the Roc source code from the test into the compiler Wasm instance
|
||||
// fn test_copy_input_string(src_buffer_addr: *mut u8);
|
||||
assert_eq!(arguments.len(), 1);
|
||||
let src_buffer_addr = arguments[0].expect_i32().unwrap() as usize;
|
||||
let len = self.src.len();
|
||||
compiler_memory[src_buffer_addr..][..len].copy_from_slice(self.src.as_bytes());
|
||||
None
|
||||
}
|
||||
"test_copy_output_string" => {
|
||||
// The REPL now has a string representing the answer. Make it available to the test code.
|
||||
// fn test_copy_output_string(output_ptr: *const u8, output_len: usize);
|
||||
assert_eq!(arguments.len(), 2);
|
||||
let output_ptr = arguments[0].expect_i32().unwrap() as usize;
|
||||
let output_len = arguments[1].expect_i32().unwrap() as usize;
|
||||
match std::str::from_utf8(&compiler_memory[output_ptr..][..output_len]) {
|
||||
Ok(answer) => {
|
||||
self.answer = answer.into();
|
||||
}
|
||||
Err(e) => panic!("{:?}", e),
|
||||
}
|
||||
None
|
||||
}
|
||||
"now" => Some(Value::F64(0.0)),
|
||||
_ => unknown(),
|
||||
}
|
||||
} else {
|
||||
unknown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(src: &'static str) -> Result<String, String> {
|
||||
let arena = Bump::new();
|
||||
|
||||
let require_reloc = false;
|
||||
let module = WasmModule::preload(&arena, COMPILER_BYTES, require_reloc).unwrap();
|
||||
|
||||
let mut instance = {
|
||||
let dispatcher = CompilerDispatcher {
|
||||
arena: &arena,
|
||||
src,
|
||||
answer: String::new(),
|
||||
wasi: WasiDispatcher { args: &[] },
|
||||
app: None,
|
||||
result_addr: None,
|
||||
};
|
||||
|
||||
let is_debug_mode = false;
|
||||
Instance::for_module(&arena, &module, dispatcher, is_debug_mode).unwrap()
|
||||
};
|
||||
|
||||
let wasm_ok: i32 = instance
|
||||
.call_export(&module, "entrypoint_from_test", [])
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.expect_i32()
|
||||
.unwrap();
|
||||
let answer_str = instance.import_dispatcher.answer.to_owned();
|
||||
|
||||
if wasm_ok == 0 {
|
||||
Err(answer_str)
|
||||
} else {
|
||||
Ok(answer_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_success(input: &'static str, expected: &str) {
|
||||
assert_eq!(run(input), Ok(expected.into()));
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_failure(input: &'static str, expected: &str) {
|
||||
assert_eq!(run(input), Err(expected.into()));
|
||||
}
|
|
@ -50,7 +50,7 @@ pub struct Instance<'a, I: ImportDispatcher> {
|
|||
/// Cache for branching instructions
|
||||
branch_cache: Vec<'a, BranchCacheEntry>,
|
||||
/// Import dispatcher from user code
|
||||
import_dispatcher: I,
|
||||
pub import_dispatcher: I,
|
||||
/// Temporary storage for import arguments
|
||||
import_arguments: Vec<'a, Value>,
|
||||
/// temporary storage for output using the --debug option
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue