//! 真实命令的字节管道、非零退出、超时和输出预算;所有写入都在当前测试目录。 use super::{Asset, Bundle, Directory, Limits, Runtime}; use dever_runtime::worker::command::{self, Limits as CommandLimits}; use sha2::{Digest, Sha256}; use std::{fs, path::PathBuf, process::Command, time::Duration}; #[test] fn command_runs_owned_native_fixture_without_shell_and_drains_on_failure() { let _execution = super::EXECUTOR.lock().unwrap(); let directory = Directory::new(); let fixture = directory.0.join("fixture"); let source = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../../test/rust/runtime/worker/fixture.rs"); let result = Command::new("rustup") .args(["run", "1.95.0", "rustc", "--edition=2024", "-O"]) .arg(source) .arg("-o") .arg(&fixture) .output() .unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); let runner = directory.0.join("runner"); fs::copy(std::env::current_exe().unwrap(), &runner).unwrap(); let result = Command::new(runner) .args(["--exact", "command::owned_command_case", "--nocapture"]) .env_clear() .env("DEVER_COMMAND_FIXTURE", fixture) .output() .unwrap(); assert!( result.status.success(), "{}\n{}", String::from_utf8_lossy(&result.stdout), String::from_utf8_lossy(&result.stderr) ); } #[test] fn owned_command_case() { let Some(fixture) = std::env::var_os("DEVER_COMMAND_FIXTURE") else { return; }; let bytes = fs::read(&fixture).unwrap(); fs::remove_file(fixture).unwrap(); let entry = "module/demo/tool/command"; let launch = serde_json::to_vec(&serde_json::json!({"format":"dever-command-launch-v1","ecosystem":"command","entry":entry,"executable":entry,"arguments":[],"working_directory":"module/demo/tool"})).unwrap(); let files = [ (entry, bytes, true), ("module/demo/tool/command.dever-command.json", launch, false), ] .map(|(path, bytes, executable)| Asset { path, sha256: Box::leak(format!("{:x}", Sha256::digest(&bytes)).into_boxed_str()), bytes: Box::leak(bytes.into_boxed_slice()), executable, }); let bundle: &'static Bundle = Box::leak(Box::new(Bundle { files: Box::leak(Box::new(files)), })); let root = std::env::current_exe() .unwrap() .parent() .unwrap() .to_owned(); let runtime = Runtime::new(Limits { threads: 2, workers: 1, tasks: 32, }) .unwrap(); runtime .run(async |scope| { let arguments = ["--echo", "a b", "$(touch never)", "\"quoted\""].map(str::to_owned); let output = command::run( scope, bundle, entry, &arguments, Some(&[0, 255, 1]), CommandLimits::default(), ) .await .unwrap(); assert_eq!(output.code, 7); assert_eq!(output.stdout, [0, 255, 1]); assert_eq!(output.stderr, b"a b\n$(touch never)\n\"quoted\""); let empty = command::run( scope, bundle, entry, &["--echo".into()], None, CommandLimits::default(), ) .await .unwrap(); assert!(empty.stdout.is_empty()); let error = command::run( scope, bundle, entry, &["--flood".into()], None, CommandLimits { timeout_ms: 1000, output_limit: 100, }, ) .await .err() .unwrap(); assert!(error.contains("limit")); let marker = root.join("timed-pid"); let error = command::run( scope, bundle, entry, &["--hang".into(), marker.to_str().unwrap().into()], None, CommandLimits { timeout_ms: 100, output_limit: 100, }, ) .await .err() .unwrap(); assert!(error.contains("timed out")); let pid = fs::read_to_string(marker).unwrap(); assert!(!PathBuf::from(format!("/proc/{pid}")).exists()); let marker = root.join("cancelled-pid"); let arguments = ["--hang".into(), marker.to_str().unwrap().into()]; let running = command::run( scope, bundle, entry, &arguments, None, CommandLimits::default(), ); tokio::pin!(running); tokio::select! { _ = &mut running => panic!("hanging command completed before cancellation"), ready = tokio::time::timeout(Duration::from_secs(3), async { while !marker.is_file() { tokio::time::sleep(Duration::from_millis(5)).await; } }) => ready.unwrap(), } }) .unwrap(); let pid = fs::read_to_string(root.join("cancelled-pid")).unwrap(); assert!(!PathBuf::from(format!("/proc/{pid}")).exists()); runtime .run(async |scope| { let (started, ready) = tokio::sync::oneshot::channel(); let (release, receive) = std::sync::mpsc::channel(); let blocker = scope .spawn_sync(move || { started.send(()).unwrap(); receive.recv().unwrap(); }) .unwrap(); ready.await.unwrap(); let queued = scope .spawn(move |child| async move { command::run( &child, bundle, entry, &["--echo".into()], None, CommandLimits::default(), ) .await }) .unwrap(); let stopped = tokio::time::timeout(Duration::from_millis(300), queued.timeout(30)).await; release.send(()).unwrap(); blocker.wait().await.unwrap(); assert!(matches!( stopped, Ok(Ok(dever_runtime::task::Timed::Expired(None))) )); worker_finish(scope).await; }) .unwrap(); runtime .run(async |scope| { let marker = root.join("child-cancelled-pid"); let arguments = vec!["--hang".into(), marker.to_str().unwrap().into()]; let child = scope .spawn(move |child| async move { command::run( &child, bundle, entry, &arguments, None, CommandLimits::default(), ) .await }) .unwrap(); tokio::time::timeout(Duration::from_secs(3), async { while !marker.is_file() { tokio::time::sleep(Duration::from_millis(5)).await; } }) .await .unwrap(); child.stop().await.unwrap(); let pid = fs::read_to_string(marker).unwrap(); assert!(!PathBuf::from(format!("/proc/{pid}")).exists()); worker_finish(scope).await; }) .unwrap(); } async fn worker_finish(scope: &dever_runtime::task::Scope<'_>) { dever_runtime::worker::finish(scope, Ok(())).await.unwrap(); }