//! 真实受管 Worker 与隔离资源目录;不访问已安装服务或生产缓存。 use dever_runtime::{ task::{Limits, Runtime}, wire::json::Json, worker::{self, Asset, Bundle, Reply, Setting}, }; use sha2::{Digest, Sha256}; use std::{ fs, os::unix::fs::{DirBuilderExt, PermissionsExt}, path::PathBuf, process::Command, time::Duration, }; const ENTRY: &str = "module/demo/test/worker"; static EXECUTOR: std::sync::Mutex<()> = std::sync::Mutex::new(()); mod command; mod lifecycle; mod performance; const CONTRACT: &str = r#"{"port":"demo.test","adapter":"default","ecosystem":"exec","entry":"module/demo/test/worker","schema":"owned-fixture","capabilities":[],"operations":["bad","crash","echo","error","extra_field","fragment","hang","ignore","measure","missing_error","null_error","shutdown_bad"],"inputs":{},"outputs":{},"errors":{"demo.test.Error.Failed":{}},"setting":null}"#; struct Directory(PathBuf); impl Directory { fn new() -> Self { let path = std::env::temp_dir().join(format!( "dever-worker-{}-{}", std::process::id(), std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_nanos() )); fs::DirBuilder::new().mode(0o700).create(&path).unwrap(); Self(path) } } impl Drop for Directory { fn drop(&mut self) { fs::remove_dir_all(&self.0).unwrap(); } } fn bundle(bytes: Vec) -> &'static Bundle { let launch = serde_json::to_vec(&serde_json::json!({"format":"dever-worker-launch-v1","ecosystem":"exec","entry":ENTRY,"executable":ENTRY,"arguments":[],"working_directory":"module/demo/test"})).unwrap(); let files = [ (ENTRY, bytes, true), ("module/demo/test/worker.dever-worker.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, }); Box::leak(Box::new(Bundle { files: Box::leak(Box::new(files)), })) } fn pid(reply: Reply) -> u32 { let Reply::Result(value) = reply else { panic!("expected result") }; let value: serde_json::Value = serde_json::from_str(&value).unwrap(); assert_eq!(value["value"], 42); value["pid"].as_u64().unwrap() as u32 } async fn call( scope: &dever_runtime::task::Scope<'_>, bundle: &'static Bundle, operation: &str, ) -> Result { worker::call( scope, bundle, ENTRY, CONTRACT, &Setting::empty(), operation, Json::parse("{}").unwrap(), ) .await? .resolve(Ok) .await } #[test] fn real_worker_runs_from_a_relocated_owned_executable() { let _execution = 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", "owned_process_protocol_case", "--nocapture"]) .env_clear() .env("DEVER_WORKER_FIXTURE", &fixture) .output() .unwrap(); assert!( result.status.success(), "{}\n{}", String::from_utf8_lossy(&result.stdout), String::from_utf8_lossy(&result.stderr) ); } #[test] fn owned_process_protocol_case() { let Some(fixture) = std::env::var_os("DEVER_WORKER_FIXTURE") else { return; }; let bundle = bundle(fs::read(&fixture).unwrap()); fs::remove_file(fixture).unwrap(); let runtime = Runtime::new(Limits { threads: 2, workers: 1, tasks: 32, }) .unwrap(); let process = runtime .run(async |scope| { let first = pid(call(scope, bundle, "echo").await.unwrap()); assert_eq!(first, pid(call(scope, bundle, "echo").await.unwrap())); for source in [ "null", " \n{\"text\":\"雪\\u96ea\\n🙂\",\"value\":1e+02} \t", ] { let response = worker::call( scope, bundle, ENTRY, CONTRACT, &Setting::empty(), "measure", Json::parse(source).unwrap(), ) .await .unwrap() .resolve(Ok) .await .unwrap(); let Reply::Result(payload) = response else { panic!("expected measured result") }; let measured: serde_json::Value = serde_json::from_str(&payload).unwrap(); let expected = source.trim_ascii(); assert_eq!(measured["length"], expected.len()); assert_eq!( measured["checksum"], expected.bytes().map(u64::from).sum::() ); } let Reply::Error { identity, payload } = call(scope, bundle, "error").await.unwrap() else { panic!("business error") }; assert_eq!(identity, "demo.test.Error.Failed"); assert_eq!(payload, "{\"message\":\"failed\"}"); for operation in ["hang", "fragment"] { assert!( tokio::time::timeout(Duration::from_millis(30), call(scope, bundle, operation)) .await .is_err() ); assert_eq!(first, pid(call(scope, bundle, "echo").await.unwrap())); } assert!(call(scope, bundle, "crash").await.is_err()); let second = pid(call(scope, bundle, "echo").await.unwrap()); assert_ne!(first, second); assert!(call(scope, bundle, "bad").await.is_err()); let third = pid(call(scope, bundle, "echo").await.unwrap()); assert_ne!(second, third); worker::finish(scope, Ok(third)).await.unwrap() }) .unwrap(); assert!(!PathBuf::from(format!("/proc/{process}")).exists()); for operation in ["null_error", "missing_error", "extra_field"] { runtime .run(async |scope| { let process = pid(call(scope, bundle, "echo").await.unwrap()); assert!(call(scope, bundle, operation).await.is_err()); assert!(!PathBuf::from(format!("/proc/{process}")).exists()); worker::finish(scope, Ok(())).await.unwrap(); }) .unwrap(); } } #[test] fn resource_publication_reuses_only_exact_owned_bytes_and_modes() { let directory = Directory::new(); let resources = bundle(b"not executed".to_vec()); fs::create_dir(directory.0.join("data")).unwrap(); fs::set_permissions(directory.0.join("data"), fs::Permissions::from_mode(0o755)).unwrap(); let root = resources.prepare_in(&directory.0).unwrap(); assert_eq!(root, resources.prepare_in(&directory.0).unwrap()); fs::write(root.join("unlocked.py"), b"not part of bundle").unwrap(); assert!( resources .prepare_in(&directory.0) .unwrap_err() .contains("unexpected") ); fs::remove_file(root.join("unlocked.py")).unwrap(); fs::set_permissions(root.join(ENTRY), fs::Permissions::from_mode(0o700)).unwrap(); assert!( resources .prepare_in(&directory.0) .unwrap_err() .contains("mode") ); fs::write(root.join(ENTRY), b"bad resource").unwrap(); fs::set_permissions(root.join(ENTRY), fs::Permissions::from_mode(0o500)).unwrap(); assert!(resources.prepare_in(&directory.0).is_err()); let duplicate = Bundle { files: Box::leak( vec![Asset { path: "../escape", bytes: b"", sha256: "", executable: false, }] .into_boxed_slice(), ), }; assert!( duplicate .prepare_in(&directory.0) .unwrap_err() .contains("path") ); }