Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,252 @@
|
||||
//! 真实受管 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<u8>) -> &'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<Reply, String> {
|
||||
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::<u64>()
|
||||
);
|
||||
}
|
||||
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")
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user