Skip to content

Commit 4c640cb

Browse files
committed
Tests where two commands have their stdin/stdout piped together
1 parent 1d46cc3 commit 4c640cb

6 files changed

Lines changed: 407 additions & 30 deletions

File tree

crates/test-programs/artifacts/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ fn build_and_generate_tests() {
7474
s if s.starts_with("cli_") => "cli",
7575
s if s.starts_with("api_") => "api",
7676
s if s.starts_with("nn_") => "nn",
77+
s if s.starts_with("piped_") => "piped",
7778
// If you're reading this because you hit this panic, either add it
7879
// to a test suite above or add a new "suite". The purpose of the
7980
// categorization above is to have a static assertion that tests
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use test_programs::wasi::cli::{stdin, stdout};
2+
3+
fn main() {
4+
match std::env::var("PIPED_SIDE")
5+
.expect("piped tests require the PIPED_SIDE env var")
6+
.as_str()
7+
{
8+
"PRODUCER" => producer(),
9+
"CONSUMER" => consumer(),
10+
side => panic!("unknown piped test side: {side}"),
11+
}
12+
}
13+
14+
fn producer() {
15+
let out = stdout::get_stdout();
16+
let out_pollable = out.subscribe();
17+
18+
for i in 1..100 {
19+
let message = format!("{i}");
20+
loop {
21+
let available = out.check_write().unwrap() as usize;
22+
if available >= message.len() {
23+
break;
24+
}
25+
26+
out_pollable.block();
27+
assert!(out_pollable.ready());
28+
}
29+
30+
out.write(message.as_bytes()).unwrap()
31+
}
32+
33+
drop(out_pollable);
34+
}
35+
36+
fn consumer() {
37+
let stdin = stdin::get_stdin();
38+
let stdin_pollable = stdin.subscribe();
39+
40+
for i in 1..100 {
41+
let expected = format!("{i}");
42+
43+
stdin_pollable.block();
44+
assert!(stdin_pollable.ready());
45+
46+
let bytes = stdin.read(expected.len() as u64).unwrap();
47+
assert_eq!(&bytes, expected.as_bytes());
48+
}
49+
50+
drop(stdin_pollable);
51+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use test_programs::wasi::cli::{stdin, stdout};
2+
3+
fn main() {
4+
match std::env::var("PIPED_SIDE")
5+
.expect("piped tests require the PIPED_SIDE env var")
6+
.as_str()
7+
{
8+
"PRODUCER" => producer(),
9+
"CONSUMER" => consumer(),
10+
side => panic!("unknown piped test side: {side}"),
11+
}
12+
}
13+
14+
const CHUNK: &[u8] = &[b'a'; 50];
15+
16+
fn producer() {
17+
let out = stdout::get_stdout();
18+
let n = out.check_write().unwrap() as usize;
19+
assert!(n > CHUNK.len());
20+
out.write(CHUNK).unwrap();
21+
}
22+
23+
fn consumer() {
24+
let stdin = stdin::get_stdin();
25+
let stdin_pollable = stdin.subscribe();
26+
stdin_pollable.block();
27+
assert!(stdin_pollable.ready());
28+
let bytes = stdin.read(CHUNK.len() as u64).unwrap();
29+
assert_eq!(&bytes, CHUNK);
30+
}

crates/wasi/src/preview2/stdio.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ pub trait StdinStream: Send + Sync {
3636
fn isatty(&self) -> bool;
3737
}
3838

39+
impl<T: StdinStream + ?Sized> StdinStream for Box<T> {
40+
fn stream(&self) -> Box<dyn HostInputStream> {
41+
self.as_ref().stream()
42+
}
43+
44+
fn isatty(&self) -> bool {
45+
self.as_ref().isatty()
46+
}
47+
}
48+
3949
impl StdinStream for pipe::MemoryInputPipe {
4050
fn stream(&self) -> Box<dyn HostInputStream> {
4151
Box::new(self.clone())
@@ -79,6 +89,16 @@ pub trait StdoutStream: Send + Sync {
7989
fn isatty(&self) -> bool;
8090
}
8191

92+
impl<T: StdoutStream + ?Sized> StdoutStream for Box<T> {
93+
fn stream(&self) -> Box<dyn HostOutputStream> {
94+
self.as_ref().stream()
95+
}
96+
97+
fn isatty(&self) -> bool {
98+
self.as_ref().isatty()
99+
}
100+
}
101+
82102
impl StdoutStream for pipe::MemoryOutputPipe {
83103
fn stream(&self) -> Box<dyn HostOutputStream> {
84104
Box::new(self.clone())

crates/wasi/tests/all/main.rs

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use wasmtime::{
77
use wasmtime_wasi::preview2::{
88
pipe::MemoryOutputPipe,
99
preview1::{WasiPreview1Adapter, WasiPreview1View},
10-
DirPerms, FilePerms, WasiCtx, WasiCtxBuilder, WasiView,
10+
DirPerms, FilePerms, StdinStream, StdoutStream, WasiCtx, WasiCtxBuilder, WasiView,
1111
};
1212

1313
struct Ctx {
@@ -48,40 +48,98 @@ fn prepare_workspace(exe_name: &str) -> Result<TempDir> {
4848
Ok(tempdir)
4949
}
5050

51-
fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store<Ctx>, TempDir)> {
52-
let stdout = MemoryOutputPipe::new(4096);
53-
let stderr = MemoryOutputPipe::new(4096);
54-
let workspace = prepare_workspace(name)?;
55-
56-
// Create our wasi context.
57-
let mut builder = WasiCtxBuilder::new();
58-
if inherit_stdio {
59-
builder.inherit_stdio();
60-
} else {
61-
builder.stdout(stdout.clone()).stderr(stderr.clone());
51+
struct StoreBuilder {
52+
builder: WasiCtxBuilder,
53+
inherit_stdio: bool,
54+
workspace: TempDir,
55+
stdin: Option<Box<dyn StdinStream>>,
56+
stdout: Option<Box<dyn StdoutStream>>,
57+
stderr: Option<Box<dyn StdoutStream>>,
58+
}
59+
60+
impl StoreBuilder {
61+
fn new(name: &str) -> Result<Self> {
62+
let mut builder = WasiCtxBuilder::new();
63+
64+
builder
65+
.args(&[name, "."])
66+
.inherit_network()
67+
.allow_ip_name_lookup(true);
68+
69+
let workspace = prepare_workspace(name)?;
70+
71+
Ok(Self {
72+
builder,
73+
inherit_stdio: false,
74+
workspace,
75+
stdin: None,
76+
stdout: None,
77+
stderr: None,
78+
})
6279
}
6380

64-
builder
65-
.args(&[name, "."])
66-
.inherit_network()
67-
.allow_ip_name_lookup(true);
68-
println!("preopen: {:?}", workspace);
69-
let preopen_dir =
70-
cap_std::fs::Dir::open_ambient_dir(workspace.path(), cap_std::ambient_authority())?;
71-
builder.preopened_dir(preopen_dir, DirPerms::all(), FilePerms::all(), ".");
72-
for (var, val) in test_programs_artifacts::wasi_tests_environment() {
73-
builder.env(var, val);
81+
fn stdout(&mut self, stdout: Box<dyn StdoutStream>) -> &mut Self {
82+
self.stdout.replace(stdout);
83+
self
7484
}
7585

76-
let ctx = Ctx {
77-
table: ResourceTable::new(),
78-
wasi: builder.build(),
79-
stderr,
80-
stdout,
81-
adapter: WasiPreview1Adapter::new(),
82-
};
86+
fn stdin(&mut self, stdin: Box<dyn StdinStream>) -> &mut Self {
87+
self.stdin.replace(stdin);
88+
self
89+
}
90+
91+
fn build(mut self, engine: &Engine) -> Result<(Store<Ctx>, TempDir)> {
92+
let stdout = MemoryOutputPipe::new(4096);
93+
let stderr = MemoryOutputPipe::new(4096);
94+
95+
// Create our wasi context.
96+
if self.inherit_stdio {
97+
self.builder.inherit_stdio();
98+
} else {
99+
if let Some(stdin) = self.stdin {
100+
self.builder.stdin(stdin);
101+
}
83102

84-
Ok((Store::new(&engine, ctx), workspace))
103+
if let Some(stdout) = self.stdout {
104+
self.builder.stdout(stdout);
105+
} else {
106+
self.builder.stdout(stdout.clone());
107+
}
108+
109+
if let Some(stderr) = self.stderr {
110+
self.builder.stderr(stderr);
111+
} else {
112+
self.builder.stderr(stderr.clone());
113+
}
114+
}
115+
116+
println!("preopen: {:?}", self.workspace);
117+
let preopen_dir = cap_std::fs::Dir::open_ambient_dir(
118+
self.workspace.path(),
119+
cap_std::ambient_authority(),
120+
)?;
121+
self.builder
122+
.preopened_dir(preopen_dir, DirPerms::all(), FilePerms::all(), ".");
123+
for (var, val) in test_programs_artifacts::wasi_tests_environment() {
124+
self.builder.env(var, val);
125+
}
126+
127+
let ctx = Ctx {
128+
table: ResourceTable::new(),
129+
wasi: self.builder.build(),
130+
stderr,
131+
stdout,
132+
adapter: WasiPreview1Adapter::new(),
133+
};
134+
135+
Ok((Store::new(&engine, ctx), self.workspace))
136+
}
137+
}
138+
139+
fn store(engine: &Engine, name: &str, inherit_stdio: bool) -> Result<(Store<Ctx>, TempDir)> {
140+
let mut builder = StoreBuilder::new(name)?;
141+
builder.inherit_stdio = inherit_stdio;
142+
builder.build(engine)
85143
}
86144

87145
impl Drop for Ctx {
@@ -108,5 +166,6 @@ macro_rules! assert_test_exists {
108166

109167
mod api;
110168
mod async_;
169+
mod piped;
111170
mod preview1;
112171
mod sync;

0 commit comments

Comments
 (0)