173 lines
5.8 KiB
Rust
173 lines
5.8 KiB
Rust
//! 从合法 Dever Port 到实际嵌入 ELF;无 fails 的声明必须在检查期拒绝。
|
|
|
|
use super::{Fixture, app, command, write};
|
|
use dever::compiler::{
|
|
build::{self, BuildTarget, Resource},
|
|
project::Project,
|
|
};
|
|
use std::{fs, os::unix::fs::PermissionsExt, process::Command};
|
|
|
|
mod arguments;
|
|
mod command;
|
|
mod jobs;
|
|
mod roots;
|
|
|
|
#[test]
|
|
fn external_builds_and_executes_after_source_removal() {
|
|
let fixture = Fixture::new(&[]);
|
|
let root = &fixture.directory.0;
|
|
write(
|
|
root,
|
|
"module/demo/value/app.dever",
|
|
app("port.native(input) + input"),
|
|
);
|
|
write(root, "module/demo/value/api.dever", command());
|
|
write(
|
|
root,
|
|
"module/demo/value/port.dever",
|
|
"native(input: Int) (value: Int)\n",
|
|
);
|
|
write(
|
|
root,
|
|
"module/demo/value/adapter.dever",
|
|
"external exec \"worker\" {}\n",
|
|
);
|
|
let port = root.join("module/demo/value/port.dever");
|
|
assert!(
|
|
Project::load(root)
|
|
.unwrap()
|
|
.check()
|
|
.unwrap_err()
|
|
.contains("fails")
|
|
);
|
|
let source = fs::read_to_string(&port).unwrap().replace(
|
|
"native(input: Int) (value: Int)",
|
|
"native(input: Int) (value: Int) fails dever.api.Error",
|
|
);
|
|
fs::write(&port, source).unwrap();
|
|
roots::prepare(root);
|
|
jobs::prepare(root);
|
|
let project = Project::load(root).unwrap();
|
|
let program = project.check().unwrap();
|
|
let contract = program.external_workers().next().unwrap();
|
|
let manifest: serde_json::Value = serde_json::from_str(&contract.contract).unwrap();
|
|
let identity = "dever-component-v1;setting=none;operation=native;input=input:wire-v1:Output:Int;;output=value:wire-v1:Output:Int;;dever.api.Error.Invalid;dever.api.Error.Unauthorized;dever.api.Error.Forbidden;dever.api.Error.NotFound;dever.api.Error.Conflict;dever.api.Error.TooManyRequests;";
|
|
let checksum = identity.bytes().fold(0xcbf29ce484222325u64, |hash, byte| {
|
|
(hash ^ u64::from(byte)).wrapping_mul(0x100000001b3)
|
|
});
|
|
assert_eq!(manifest["schema"], format!("{checksum:016x}"));
|
|
let worker = root.join("fixture");
|
|
let source = std::path::Path::new(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(&worker)
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
let launch = serde_json::to_vec(&serde_json::json!({"format":"dever-worker-launch-v1","ecosystem":"exec","entry":contract.entry,"executable":contract.entry,"arguments":[],"working_directory":"module/demo/value"})).unwrap();
|
|
let resources = [
|
|
(contract.entry.clone(), fs::read(&worker).unwrap(), true),
|
|
(
|
|
format!("{}.dever-worker.json", contract.entry),
|
|
launch,
|
|
false,
|
|
),
|
|
]
|
|
.into_iter()
|
|
.map(|(path, bytes, executable)| Resource {
|
|
path,
|
|
bytes,
|
|
executable,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let binary = build::build_with_resources(
|
|
&program,
|
|
&project.sources,
|
|
"main",
|
|
&root.join("app"),
|
|
BuildTarget::host().unwrap(),
|
|
&resources,
|
|
)
|
|
.unwrap();
|
|
fs::remove_dir_all(root.join("module")).unwrap();
|
|
fs::remove_file(worker).unwrap();
|
|
fs::create_dir(root.join("data")).unwrap();
|
|
fs::set_permissions(root.join("data"), fs::Permissions::from_mode(0o755)).unwrap();
|
|
roots::commands(&binary);
|
|
roots::http(&binary, root);
|
|
jobs::run(&binary, root);
|
|
}
|
|
|
|
fn command_cases(binary: &std::path::Path, entry: &str) {
|
|
for _ in 0..2 {
|
|
let result = Command::new(binary)
|
|
.env_clear()
|
|
.args([entry, "{\"input\":8}"])
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
assert_eq!(
|
|
result.stdout,
|
|
b"{\"code\":0,\"message\":\"ok\",\"data\":50}\n"
|
|
);
|
|
}
|
|
for (input, expected) in [
|
|
(-1, "Worker output does not match checked Port"),
|
|
(-2, "Worker returned an undeclared business error"),
|
|
(-3, "Invalid"),
|
|
] {
|
|
let output = Command::new(binary)
|
|
.env_clear()
|
|
.args([entry, &format!("{{\"input\":{input}}}")])
|
|
.output()
|
|
.unwrap();
|
|
assert!(!output.status.success());
|
|
let error = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
error.contains(expected),
|
|
"input {input}, expected {expected}: {error}"
|
|
);
|
|
if input != -3 {
|
|
assert!(error.contains("adapter.dever:"), "{error}");
|
|
}
|
|
}
|
|
// Worker 已交付合法终态;根入口仍须等 shutdown,不能提前发布成功结果。
|
|
for input in [-4, -5] {
|
|
let output = Command::new(binary)
|
|
.env_clear()
|
|
.args([entry, &format!("{{\"input\":{input}}}")])
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
!output.status.success(),
|
|
"input {input}: shutdown failure was discarded"
|
|
);
|
|
assert!(
|
|
output.stdout.is_empty(),
|
|
"input {input}: CMD published output before cleanup"
|
|
);
|
|
let error = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
error.contains("invalid Worker shutdown acknowledgement"),
|
|
"input {input}: {error}"
|
|
);
|
|
if input == -5 {
|
|
assert!(
|
|
error.contains("dever.api.Error.Invalid") && error.contains("cleanup:"),
|
|
"business error and cleanup must both survive: {error}"
|
|
);
|
|
}
|
|
}
|
|
}
|