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

Commit 0ce7598

Browse files
committed
Add wasmldr integration test(s)
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 <[email protected]>
1 parent bcfcfa7 commit 0ce7598

File tree

11 files changed

+195
-36
lines changed

11 files changed

+195
-36
lines changed

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ default = ["backend-kvm", "backend-sgx", "wasmldr"]
2727

2828
backend-kvm = ["x86_64", "kvm-bindings", "kvm-ioctls"]
2929
backend-sgx = ["x86_64", "sgx"]
30-
wasmldr = []
30+
wasmldr = ["wat"]
3131

3232
[dependencies]
3333
sgx = { git = "https://github.com/enarx/sgx", rev = "57df3753a0ea1777963dbf3023452993df2edb8c", features = ["openssl"], optional = true }
@@ -55,6 +55,7 @@ vdso = "0.1"
5555

5656
[build-dependencies]
5757
cc = "1.0"
58+
wat = { version = "1.0", optional = true }
5859
walkdir = "2"
5960
protobuf-codegen-pure = "2.25"
6061
sallyport = { git = "https://github.com/enarx/sallyport", rev = "a567a22665c7e5ba88a8c4acd64ab43ee32b4681", features = [ "asm" ] }

build.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ fn build_cc_tests(in_path: &Path, out_path: &Path) {
107107
}
108108
}
109109

110+
#[cfg(feature = "wasmldr")]
111+
fn build_wasm_tests(in_path: &Path, out_path: &Path) {
112+
for wat in find_files_with_extensions(&["wat"], &in_path) {
113+
let wasm = out_path
114+
.join(wat.file_stem().unwrap())
115+
.with_extension("wasm");
116+
let bin = wat::parse_file(&wat).unwrap_or_else(|_| panic!("failed to compile {:?}", &wat));
117+
std::fs::write(&wasm, &bin).unwrap_or_else(|_| panic!("failed to write {:?}", &wasm));
118+
println!("cargo:rerun-if-changed={}", &wat.display());
119+
}
120+
}
121+
110122
// Build a binary named `bin_name` from the crate located at `in_dir`,
111123
// targeting `target_name`, then strip the resulting binary and place it
112124
// at `out_dir`/bin/`bin_name`.
@@ -235,6 +247,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
235247

236248
build_cc_tests(&Path::new(CRATE).join(TEST_BINS_IN), &out_dir_bin);
237249
build_rs_tests(&Path::new(CRATE).join(TEST_BINS_IN), &out_dir_bin);
250+
#[cfg(feature = "wasmldr")]
251+
build_wasm_tests(&Path::new(CRATE).join("tests/wasm"), &out_dir_bin);
238252

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

internal/wasmldr/build.rs

Lines changed: 0 additions & 26 deletions
This file was deleted.

internal/wasmldr/fixtures/bundle/config.yaml

Lines changed: 0 additions & 8 deletions
This file was deleted.

internal/wasmldr/fixtures/bundle/stdin.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.
File renamed without changes.
File renamed without changes.

tests/wasmldr_tests.rs

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
#![cfg(feature = "wasmldr")]
3+
4+
use process_control::{ChildExt, Output, Timeout};
5+
use std::fs::File;
6+
use std::os::unix::io::{IntoRawFd, RawFd};
7+
use std::os::unix::process::CommandExt;
8+
use std::path::Path;
9+
use std::process::{Command, Stdio};
10+
11+
extern crate libc;
12+
use libc::c_int;
13+
14+
use std::io;
15+
use std::io::Write;
16+
use std::time::Duration;
17+
18+
mod common;
19+
use common::{check_output, CRATE, KEEP_BIN, OUT_DIR, TEST_BINS_OUT, TIMEOUT_SECS};
20+
21+
use serial_test::serial;
22+
23+
const MODULE_FD: RawFd = 3;
24+
25+
// wrap a libc call to return io::Result<c_int>
26+
fn cvt(rv: c_int) -> io::Result<c_int> {
27+
if rv == -1 {
28+
Err(io::Error::last_os_error())
29+
} else {
30+
Ok(rv)
31+
}
32+
}
33+
34+
// wrap a libc call to return io::Result<()>
35+
fn cv(rv: c_int) -> io::Result<()> {
36+
cvt(rv).and(Ok(()))
37+
}
38+
39+
trait CommandFdExt {
40+
fn inherit_with_fd(&mut self, file: impl IntoRawFd, child_fd: RawFd) -> &mut Self;
41+
}
42+
43+
impl CommandFdExt for Command {
44+
fn inherit_with_fd(&mut self, file: impl IntoRawFd, child_fd: RawFd) -> &mut Self {
45+
let fd = file.into_raw_fd();
46+
if fd == child_fd {
47+
unsafe {
48+
self.pre_exec(move || cv(libc::fcntl(fd, libc::F_SETFD, 0)));
49+
}
50+
} else {
51+
unsafe {
52+
self.pre_exec(move || cv(libc::dup2(fd, child_fd)));
53+
}
54+
}
55+
self
56+
}
57+
}
58+
59+
pub fn wasmldr_exec<'a>(wasm: &str, input: impl Into<Option<&'a [u8]>>) -> Output {
60+
let wasm_path = Path::new(CRATE)
61+
.join(OUT_DIR)
62+
.join(TEST_BINS_OUT)
63+
.join(wasm);
64+
let wasm_file =
65+
File::open(wasm_path).unwrap_or_else(|e| panic!("failed to open `{}`: {:#?}", wasm, e));
66+
67+
let mut child = Command::new(&String::from(KEEP_BIN))
68+
.current_dir(CRATE)
69+
.arg("exec")
70+
.stdin(Stdio::piped())
71+
.stdout(Stdio::piped())
72+
.stderr(Stdio::piped())
73+
.inherit_with_fd(wasm_file, MODULE_FD)
74+
.spawn()
75+
.unwrap_or_else(|e| panic!("failed to run `{}`: {:#?}", wasm, e));
76+
77+
if let Some(input) = input.into() {
78+
child
79+
.stdin
80+
.as_mut()
81+
.unwrap()
82+
.write_all(input)
83+
.expect("failed to write stdin to child");
84+
85+
drop(child.stdin.take());
86+
}
87+
88+
let output = child
89+
.with_output_timeout(Duration::from_secs(TIMEOUT_SECS))
90+
.terminating()
91+
.wait()
92+
.unwrap_or_else(|e| panic!("failed to run `{}`: {:#?}", wasm, e))
93+
.unwrap_or_else(|| panic!("process `{}` timed out", wasm));
94+
95+
assert!(
96+
output.status.code().is_some(),
97+
"process `{}` terminated by signal {:?}",
98+
wasm,
99+
output.status.signal()
100+
);
101+
102+
output
103+
}
104+
105+
fn run_wasm_test<'a>(
106+
wasm: &str,
107+
status: i32,
108+
input: impl Into<Option<&'a [u8]>>,
109+
expected_stdout: impl Into<Option<&'a [u8]>>,
110+
expected_stderr: impl Into<Option<&'a [u8]>>,
111+
) -> Output {
112+
let output = wasmldr_exec(wasm, input);
113+
check_output(&output, status, expected_stdout, expected_stderr);
114+
output
115+
}
116+
117+
#[test]
118+
#[serial]
119+
fn return_1() {
120+
// This module does, in fact, return 1. But function return values
121+
// are separate from setting the process exit status code, so
122+
// we still expect a return code of '0' here.
123+
run_wasm_test("return_1.wasm", 0, None, None, None);
124+
}
125+
126+
#[test]
127+
#[serial]
128+
fn wasi_snapshot1() {
129+
// This module uses WASI to return the number of commandline args.
130+
// Since we don't currently do anything with the function return value,
131+
// we don't get any output here, and we expect '0', as above.
132+
run_wasm_test("wasi_snapshot1.wasm", 0, None, None, None);
133+
}
134+
135+
#[test]
136+
#[serial]
137+
fn hello_wasi_snapshot1() {
138+
// This module just prints "Hello, world!" to stdout. Hooray!
139+
run_wasm_test(
140+
"hello_wasi_snapshot1.wasm",
141+
0,
142+
None,
143+
&b"Hello, world!\n"[..],
144+
None,
145+
);
146+
}
147+
148+
#[test]
149+
#[serial]
150+
fn no_export() {
151+
// This module has no exported functions, so we get Error::ExportNotFound,
152+
// which wasmldr maps to EX_DATAERR (65) at process exit.
153+
run_wasm_test("no_export.wasm", 65, None, None, None);
154+
}

0 commit comments

Comments
 (0)