Skip to content
This repository has been archived by the owner on Oct 26, 2021. It is now read-only.

Commit

Permalink
Add wasmldr integration test(s)
Browse files Browse the repository at this point in the history
Hooray! We can actually run wasm now!

 Here's what this patch does:

* Remove internal/wasmldr's build.rs
* Move wasmldr test sources to tests/wasm/*.wat
* Build tests/wasm/*.wat to OUT_DIR/bin/*.wasm in build.rs
* Add tests/wasmldr_tests.rs, which runs the .wasm test binaries

The tricky bit was getting Rust to pass open file descriptors to the
child process, since it *really* wants to set FD_CLOEXEC on everything.
This isn't pretty, but it's a start.

Signed-off-by: Will Woods <will@profian.com>
  • Loading branch information
wgwoods committed Oct 4, 2021
1 parent bcfcfa7 commit 9fcea1c
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 35 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ vdso = "0.1"

[build-dependencies]
cc = "1.0"
wat = "1.0"
walkdir = "2"
protobuf-codegen-pure = "2.25"
sallyport = { git = "https://github.com/enarx/sallyport", rev = "a567a22665c7e5ba88a8c4acd64ab43ee32b4681", features = [ "asm" ] }
Expand Down
12 changes: 12 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ fn build_cc_tests(in_path: &Path, out_path: &Path) {
}
}

fn build_wasm_tests(in_path: &Path, out_path: &Path) {
for wat in find_files_with_extensions(&["wat"], &in_path) {
let wasm = out_path
.join(wat.file_stem().unwrap())
.with_extension("wasm");
let bin = wat::parse_file(&wat).unwrap_or_else(|_| panic!("failed to compile {:?}", &wat));
std::fs::write(&wasm, &bin).unwrap_or_else(|_| panic!("failed to write {:?}", &wasm));
println!("cargo:rerun-if-changed={}", &wat.display());
}
}

// Build a binary named `bin_name` from the crate located at `in_dir`,
// targeting `target_name`, then strip the resulting binary and place it
// at `out_dir`/bin/`bin_name`.
Expand Down Expand Up @@ -235,6 +246,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

build_cc_tests(&Path::new(CRATE).join(TEST_BINS_IN), &out_dir_bin);
build_rs_tests(&Path::new(CRATE).join(TEST_BINS_IN), &out_dir_bin);
build_wasm_tests(&Path::new(CRATE).join("tests/wasm"), &out_dir_bin);

let target = "x86_64-unknown-linux-musl";

Expand Down
26 changes: 0 additions & 26 deletions internal/wasmldr/build.rs

This file was deleted.

8 changes: 0 additions & 8 deletions internal/wasmldr/fixtures/bundle/config.yaml

This file was deleted.

1 change: 0 additions & 1 deletion internal/wasmldr/fixtures/bundle/stdin.txt

This file was deleted.

File renamed without changes.
File renamed without changes.
153 changes: 153 additions & 0 deletions tests/wasmldr_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: Apache-2.0

use process_control::{ChildExt, Output, Timeout};
use std::fs::File;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::os::unix::process::CommandExt;
use std::path::Path;
use std::process::{Command, Stdio};

extern crate libc;
use libc::c_int;

use std::io;
use std::io::Write;
use std::time::Duration;

mod common;
use common::{check_output, CRATE, KEEP_BIN, OUT_DIR, TEST_BINS_OUT, TIMEOUT_SECS};

use serial_test::serial;

const MODULE_FD: RawFd = 3;

// wrap a libc call to return io::Result<c_int>
fn cvt(rv: c_int) -> io::Result<c_int> {
if rv == -1 {
Err(io::Error::last_os_error())
} else {
Ok(rv)
}
}

// wrap a libc call to return io::Result<()>
fn cv(rv: c_int) -> io::Result<()> {
cvt(rv).and(Ok(()))
}

trait CommandFdExt {
fn inherit_with_fd(&mut self, file: impl IntoRawFd, child_fd: RawFd) -> &mut Self;
}

impl CommandFdExt for Command {
fn inherit_with_fd(&mut self, file: impl IntoRawFd, child_fd: RawFd) -> &mut Self {
let fd = file.into_raw_fd();
if fd == child_fd {
unsafe {
self.pre_exec(move || cv(libc::fcntl(fd, libc::F_SETFD, 0)));
}
} else {
unsafe {
self.pre_exec(move || cv(libc::dup2(fd, child_fd)));
}
}
self
}
}

pub fn wasmldr_exec<'a>(wasm: &str, input: impl Into<Option<&'a [u8]>>) -> Output {
let wasm_path = Path::new(CRATE)
.join(OUT_DIR)
.join(TEST_BINS_OUT)
.join(wasm);
let wasm_file =
File::open(wasm_path).unwrap_or_else(|e| panic!("failed to open `{}`: {:#?}", wasm, e));

let mut child = Command::new(&String::from(KEEP_BIN))
.current_dir(CRATE)
.arg("exec")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.inherit_with_fd(wasm_file, MODULE_FD)
.spawn()
.unwrap_or_else(|e| panic!("failed to run `{}`: {:#?}", wasm, e));

if let Some(input) = input.into() {
child
.stdin
.as_mut()
.unwrap()
.write_all(input)
.expect("failed to write stdin to child");

drop(child.stdin.take());
}

let output = child
.with_output_timeout(Duration::from_secs(TIMEOUT_SECS))
.terminating()
.wait()
.unwrap_or_else(|e| panic!("failed to run `{}`: {:#?}", wasm, e))
.unwrap_or_else(|| panic!("process `{}` timed out", wasm));

assert!(
output.status.code().is_some(),
"process `{}` terminated by signal {:?}",
wasm,
output.status.signal()
);

output
}

fn run_wasm_test<'a>(
wasm: &str,
status: i32,
input: impl Into<Option<&'a [u8]>>,
expected_stdout: impl Into<Option<&'a [u8]>>,
expected_stderr: impl Into<Option<&'a [u8]>>,
) -> Output {
let output = wasmldr_exec(wasm, input);
check_output(&output, status, expected_stdout, expected_stderr);
output
}

#[test]
#[serial]
fn return_1() {
// This module does, in fact, return 1. But function return values
// are separate from setting the process exit status code, so
// we still expect a return code of '0' here.
run_wasm_test("return_1.wasm", 0, None, None, None);
}

#[test]
#[serial]
fn wasi_snapshot1() {
// This module uses WASI to return the number of commandline args.
// Since we don't currently do anything with the function return value,
// we don't get any output here, and we expect '0', as above.
run_wasm_test("wasi_snapshot1.wasm", 0, None, None, None);
}

#[test]
#[serial]
fn hello_wasi_snapshot1() {
// This module just prints "Hello, world!" to stdout. Hooray!
run_wasm_test(
"hello_wasi_snapshot1.wasm",
0,
None,
&b"Hello, world!\n"[..],
None,
);
}

#[test]
#[serial]
fn no_export() {
// This module has no exported functions, so we get Error::ExportNotFound,
// which wasmldr maps to EX_DATAERR (65) at process exit.
run_wasm_test("no_export.wasm", 65, None, None, None);
}

0 comments on commit 9fcea1c

Please sign in to comment.