builder: enforce target, source sync, and timeout contracts
This commit is contained in:
@@ -33,6 +33,41 @@ async fn discovers_workspace_and_skips_target() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn copies_only_changed_and_new_sources() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let world = crate::world::World::open(temp.path())?;
|
||||
for name in ["unchanged.rs", "changed.rs"] {
|
||||
files::write(
|
||||
world.root.as_ref(),
|
||||
&format!("/project/{name}"),
|
||||
b"initial".to_vec(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let copy = temp.path().join("copy");
|
||||
let before = files::copy_out(world.root.as_ref(), "/project", ©).await?;
|
||||
files::write(
|
||||
world.root.as_ref(),
|
||||
"/project/unchanged.rs",
|
||||
b"concurrent edit".to_vec(),
|
||||
)
|
||||
.await?;
|
||||
std::fs::write(copy.join("changed.rs"), b"changed by command")?;
|
||||
std::fs::write(copy.join("Cargo.lock"), b"created by command")?;
|
||||
files::copy_back(world.root.as_ref(), ©, "/project", Some(&before)).await?;
|
||||
for (name, expected) in [
|
||||
("unchanged.rs", "concurrent edit"),
|
||||
("changed.rs", "changed by command"),
|
||||
("Cargo.lock", "created by command"),
|
||||
] {
|
||||
assert_eq!(
|
||||
files::read(world.root.as_ref(), &format!("/project/{name}")).await?,
|
||||
expected.as_bytes()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn clean_removes_only_deleted_artifacts() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let world = crate::world::World::open(temp.path())?;
|
||||
@@ -69,6 +104,58 @@ fn relative_host_directory_uses_absolute_namespace_paths() -> Result<()> {
|
||||
assert!(builder.directory.is_absolute());
|
||||
Ok(())
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn target_options_preserve_the_platform_target_and_cache() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let world = crate::world::World::open(temp.path())?;
|
||||
let builder = Builder::new(world.root, &world.host_dir)?;
|
||||
let (tx, _rx) = mpsc::channel(1);
|
||||
for options in [
|
||||
vec!["--target", "aarch64-unknown-linux-gnu"],
|
||||
vec!["--target=aarch64-unknown-linux-gnu"],
|
||||
vec!["--target-dir", "/other"],
|
||||
vec!["--target-dir=/other"],
|
||||
] {
|
||||
let error = builder
|
||||
.execute(
|
||||
Request {
|
||||
argv: std::iter::once("build")
|
||||
.chain(options)
|
||||
.map(str::to_string)
|
||||
.collect(),
|
||||
cwd: "/".into(),
|
||||
timeout_secs: None,
|
||||
},
|
||||
&tx,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains("target"), "{error}");
|
||||
}
|
||||
for (action, options) in [
|
||||
("run", vec!["--target", "wasm32-wasip2"]),
|
||||
("run", vec!["--target=wasm32-wasip2"]),
|
||||
("run", vec!["--", "--target-dir", "/argument"]),
|
||||
("tree", vec!["--target", "all"]),
|
||||
] {
|
||||
let error = builder
|
||||
.execute(
|
||||
Request {
|
||||
argv: std::iter::once(action)
|
||||
.chain(options)
|
||||
.map(str::to_string)
|
||||
.collect(),
|
||||
cwd: "/".into(),
|
||||
timeout_secs: None,
|
||||
},
|
||||
&tx,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_eq!(error.to_string(), "no Cargo.toml above cwd");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn test_filter_is_not_a_build_argument() {
|
||||
let args = ["test", "-p", "example", "filter", "--release"].map(str::to_string);
|
||||
@@ -96,6 +183,36 @@ fn disk_cap_removes_workspaces() -> Result<()> {
|
||||
assert_eq!(files::size(&builder.directory)?, 32);
|
||||
Ok(())
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
#[tokio::test]
|
||||
async fn timeout_kills_before_output_can_drain() -> Result<()> {
|
||||
use std::os::fd::{FromRawFd, OwnedFd};
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let output = tx.clone();
|
||||
let mut cmd = Command::new("sh");
|
||||
cmd.args(["-c", "echo $$; exec sleep 1000"]);
|
||||
let task =
|
||||
tokio::spawn(
|
||||
async move { run(cmd, &output, Instant::now() + Duration::from_secs(1)).await },
|
||||
);
|
||||
let Some(Event::Stdout { bytes }) =
|
||||
tokio::time::timeout(Duration::from_secs(2), rx.recv()).await?
|
||||
else {
|
||||
anyhow::bail!("missing native PID");
|
||||
};
|
||||
let pid: u32 = std::str::from_utf8(&bytes)?.trim().parse()?;
|
||||
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) };
|
||||
ensure!(fd >= 0, "pidfd_open: {}", std::io::Error::last_os_error());
|
||||
let exited = tokio::io::unix::AsyncFd::new(unsafe { OwnedFd::from_raw_fd(fd as i32) })?;
|
||||
// Fill the output channel, so the timeout message cannot enter it.
|
||||
tx.send(Event::Stdout { bytes: Vec::new() }).await?;
|
||||
let _ready = tokio::time::timeout(Duration::from_secs(2), exited.readable()).await??;
|
||||
assert!(matches!(rx.recv().await, Some(Event::Stdout { .. })));
|
||||
assert!(matches!(rx.recv().await, Some(Event::Stderr { bytes })
|
||||
if bytes.as_slice() == b"cargo: command timed out\n"));
|
||||
assert_eq!(task.await??.0, 124);
|
||||
Ok(())
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn closed_receiver_cancels_command() -> Result<()> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
|
||||
Reference in New Issue
Block a user