console: piped lines run in order; cargo-valid: empty_world allows the first folder of the package path
The runner mounts the package at its host path, so / shows its first folder: tmp locally, build in the Nix sandbox.
This commit is contained in:
+6
-5
@@ -329,7 +329,7 @@ pub async fn run_input(host: Host) -> Result<()> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let mut printer = started.await??;
|
let mut printer = started.await??;
|
||||||
run_commands(host, lines, move |text| {
|
run_commands(host, lines, true, move |text| {
|
||||||
printer.print(format!("{text}\n"))?;
|
printer.print(format!("{text}\n"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -348,7 +348,7 @@ async fn run_plain(host: Host) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
run_commands(host, lines, |text| {
|
run_commands(host, lines, false, |text| {
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
writeln!(std::io::stdout(), "{text}")?;
|
writeln!(std::io::stdout(), "{text}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -356,10 +356,11 @@ async fn run_plain(host: Host) -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both input modes keep the prompt available and label asynchronous results.
|
// Both input modes label results. Only terminal input starts concurrent commands.
|
||||||
async fn run_commands(
|
async fn run_commands(
|
||||||
host: Host,
|
host: Host,
|
||||||
mut lines: tokio::sync::mpsc::UnboundedReceiver<Result<String>>,
|
mut lines: tokio::sync::mpsc::UnboundedReceiver<Result<String>>,
|
||||||
|
concurrent: bool,
|
||||||
mut print: impl FnMut(String) -> Result<()>,
|
mut print: impl FnMut(String) -> Result<()>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut notifications = host.subscribe();
|
let mut notifications = host.subscribe();
|
||||||
@@ -381,7 +382,7 @@ async fn run_commands(
|
|||||||
let text = result.unwrap_or_else(|error|format!("error: {error}"));
|
let text = result.unwrap_or_else(|error|format!("error: {error}"));
|
||||||
if !text.is_empty() { print(format!("> {line}\n{}",indent(&text)))?; }
|
if !text.is_empty() { print(format!("> {line}\n{}",indent(&text)))?; }
|
||||||
},
|
},
|
||||||
line = lines.recv(), if input_open => match line {
|
line = lines.recv(), if input_open && (concurrent || commands.is_empty()) => match line {
|
||||||
Some(line) => {
|
Some(line) => {
|
||||||
let line = line?;
|
let line = line?;
|
||||||
if line.trim()=="quit" { print("> quit\n Goodbye.".into())?; return Ok(()); }
|
if line.trim()=="quit" { print("> quit\n Goodbye.".into())?; return Ok(()); }
|
||||||
@@ -446,7 +447,7 @@ mod tests {
|
|||||||
let mut events = host.subscribe();
|
let mut events = host.subscribe();
|
||||||
let (sender, lines) = tokio::sync::mpsc::unbounded_channel();
|
let (sender, lines) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let (printed, mut output) = tokio::sync::mpsc::unbounded_channel();
|
let (printed, mut output) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let runner = tokio::spawn(run_commands(host.clone(), lines, move |text| {
|
let runner = tokio::spawn(run_commands(host.clone(), lines, true, move |text| {
|
||||||
printed.send(text).unwrap();
|
printed.send(text).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|||||||
+37
-34
@@ -8,7 +8,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
io::AsyncWriteExt,
|
||||||
process::{Child, Command},
|
process::{Child, Command},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -307,51 +307,54 @@ async fn console_error_stops_host() {
|
|||||||
assert!(stopped > 0);
|
assert!(stopped > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ps and kill remain available before a slow command returns.
|
/// Piped calls wait for activation, and EOF or quit preserves the last result.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn piped_console_accepts_commands_during_a_call() {
|
async fn piped_console_runs_dependent_commands_in_order() {
|
||||||
|
for ending in ["", "quit\n"] {
|
||||||
let (_folder, world) = world();
|
let (_folder, world) = world();
|
||||||
install_probe(&world);
|
std::fs::copy(probe(), world.join("candidate.wasm")).unwrap();
|
||||||
|
std::fs::create_dir_all(world.join("state/probe")).unwrap();
|
||||||
|
std::fs::write(world.join("state/probe/discover.json"), r#"{"methods":[]}"#).unwrap();
|
||||||
|
let activate = r#"core.activate {"plugin":"notes","wasm":"candidate.wasm","force":true}"#;
|
||||||
|
let call = r#"notes.echo {"ordered":true}"#;
|
||||||
let mut child = binary()
|
let mut child = binary()
|
||||||
.arg("run")
|
.arg("run")
|
||||||
.arg(world)
|
.arg(&world)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut input = child.stdin.take().unwrap();
|
child
|
||||||
let mut output = BufReader::new(child.stdout.take().unwrap()).lines();
|
.stdin
|
||||||
input.write_all(b"probe.busy\n").await.unwrap();
|
.take()
|
||||||
let mut pid = None;
|
.unwrap()
|
||||||
tokio::time::timeout(Duration::from_secs(15), async {
|
.write_all(format!("{activate}\n{call}\n{ending}").as_bytes())
|
||||||
while pid.is_none() {
|
|
||||||
input.write_all(b"ps\n").await.unwrap();
|
|
||||||
while let Some(line) = output.next_line().await.unwrap() {
|
|
||||||
if line.contains("probe busy console") {
|
|
||||||
pid = line.split_whitespace().next().map(str::to_owned);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if line.contains("PID PLUGIN METHOD") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
input
|
let output = tokio::time::timeout(Duration::from_secs(20), child.wait_with_output())
|
||||||
.write_all(format!("kill {}\n", pid.unwrap()).as_bytes())
|
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
tokio::time::timeout(Duration::from_secs(15), async {
|
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||||
while let Some(line) = output.next_line().await.unwrap() {
|
assert_eq!(
|
||||||
if line.starts_with("> kill ") {
|
output.status.code(),
|
||||||
break;
|
Some(0),
|
||||||
|
"{}",
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
);
|
||||||
|
assert!(!stdout.contains("error:"), "{stdout}");
|
||||||
|
assert!(stdout.contains(r#""ordered": true"#), "{stdout}");
|
||||||
|
assert!(
|
||||||
|
stdout.find(&format!("> {activate}")).unwrap()
|
||||||
|
< stdout.find(&format!("> {call}")).unwrap(),
|
||||||
|
"{stdout}"
|
||||||
|
);
|
||||||
|
if !ending.is_empty() {
|
||||||
|
assert!(
|
||||||
|
stdout.find(r#""ordered": true"#).unwrap() < stdout.find("> quit").unwrap(),
|
||||||
|
"{stdout}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
input.write_all(b"quit\n").await.unwrap();
|
|
||||||
assert_eq!(status(&mut child).await.code(), Some(0));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,11 +99,21 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|entry| entry.unwrap().file_name())
|
.map(|entry| entry.unwrap().file_name())
|
||||||
.collect();
|
.collect();
|
||||||
assert!(names.iter().all(|name| {
|
// The runner mounts the package folder at its host path, so the first
|
||||||
["proc", "self", "run", "tmp"]
|
// folder of that path also appears in `/` (for example `tmp` or `build`).
|
||||||
|
let package = env!("CARGO_MANIFEST_DIR")
|
||||||
|
.trim_start_matches('/')
|
||||||
|
.split('/')
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
assert!(
|
||||||
|
names.iter().all(|name| {
|
||||||
|
["proc", "self", "run", package]
|
||||||
.iter()
|
.iter()
|
||||||
.any(|allowed| name == allowed)
|
.any(|allowed| name == allowed)
|
||||||
}));
|
}),
|
||||||
|
"{names:?}"
|
||||||
|
);
|
||||||
for path in ["/proc", "/self", "/run"] {
|
for path in ["/proc", "/self", "/run"] {
|
||||||
assert!(std::path::Path::new(path).is_dir());
|
assert!(std::path::Path::new(path).is_dir());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user