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

Commit 9fcea1c

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 9fcea1c

File tree

11 files changed

+191
-35
lines changed

11 files changed

+191
-35
lines changed

Cargo.lock

+25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ vdso = "0.1"
5555

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

build.rs

+12
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,17 @@ fn build_cc_tests(in_path: &Path, out_path: &Path) {
107107
}
108108
}
109109

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

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

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

internal/wasmldr/build.rs

-26
This file was deleted.

internal/wasmldr/fixtures/bundle/config.yaml

-8
This file was deleted.

internal/wasmldr/fixtures/bundle/stdin.txt

-1
This file was deleted.
File renamed without changes.
File renamed without changes.

tests/wasmldr_tests.rs

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

0 commit comments

Comments
 (0)