fix: release Dever 0.1.6 with shared runtime assets and unbounded workers
This commit is contained in:
1 parent
a900d7e8db
commit
5589b657db
17 files changed
+357
-49
No files matched your search
@@ -144,6 +144,116 @@ fn signed_extension_preparation_is_shared_immutable_and_concurrent() {
|
||||
assert_eq!(source.requests.load(Ordering::Relaxed), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_resources_cross_machine_roots_without_sharing_the_cache() {
|
||||
let (root, layout, manager, key) = machine();
|
||||
release(&layout, &key, "1.0.0");
|
||||
let version = manager.install_requested("1.0.0").unwrap();
|
||||
let mut daemon = TestDaemon::start(&layout);
|
||||
let client = Layout::new(root.path().join("client"));
|
||||
client.initialize().unwrap();
|
||||
fs::copy(
|
||||
layout.state().join("trusted-release-key"),
|
||||
client.state().join("trusted-release-key"),
|
||||
)
|
||||
.unwrap();
|
||||
let source = layout.versions().join(version.as_str());
|
||||
let manifest: ReleaseManifest =
|
||||
serde_json::from_slice(&fs::read(source.join("manifest.json")).unwrap()).unwrap();
|
||||
for path in manifest
|
||||
.artifacts
|
||||
.iter()
|
||||
.map(|artifact| artifact.path.as_str())
|
||||
.chain(["manifest.json", "manifest.sig"])
|
||||
{
|
||||
let destination = client.downloads().join(version.as_str()).join(path);
|
||||
fs::create_dir_all(destination.parent().unwrap()).unwrap();
|
||||
fs::copy(source.join(path), destination).unwrap();
|
||||
}
|
||||
MachineManager::new(client.clone())
|
||||
.install_requested(version.as_str())
|
||||
.unwrap();
|
||||
fs::hard_link(layout.service_socket(), client.service_socket()).unwrap();
|
||||
fs::copy(layout.service_token(), client.service_token()).unwrap();
|
||||
let resources = SignedResources::load(&client, &version).unwrap();
|
||||
let extension = id(Ecosystem::Pip);
|
||||
let logical = logical_path(&extension);
|
||||
assert!(
|
||||
resources
|
||||
.read_shared(&logical)
|
||||
.unwrap_err()
|
||||
.contains("not installed")
|
||||
);
|
||||
assert!(!layout.extensions().exists());
|
||||
let source = Source::new(false);
|
||||
prepare_extension_with(&layout, &version, &extension, &source).unwrap();
|
||||
assert_eq!(resources.read_shared(&logical).unwrap(), b"1.0.0");
|
||||
assert_eq!(resources.read_shared(&logical).unwrap(), b"1.0.0");
|
||||
assert_eq!(source.requests.load(Ordering::Relaxed), 1);
|
||||
assert!(!client.extensions().exists());
|
||||
assert!(
|
||||
resources
|
||||
.read(&logical)
|
||||
.unwrap_err()
|
||||
.contains("not installed")
|
||||
);
|
||||
assert!(
|
||||
resources
|
||||
.read_shared(&logical_path(&id(Ecosystem::Npm)))
|
||||
.unwrap_err()
|
||||
.contains("not installed")
|
||||
);
|
||||
assert!(
|
||||
resources
|
||||
.read_shared("../../state/service-token")
|
||||
.unwrap_err()
|
||||
.contains("does not declare")
|
||||
);
|
||||
let path = SignedResources::load(&layout, &version)
|
||||
.unwrap()
|
||||
.path(&logical)
|
||||
.unwrap();
|
||||
fs::write(&path, b"other").unwrap();
|
||||
assert!(
|
||||
resources
|
||||
.read_shared(&logical)
|
||||
.unwrap_err()
|
||||
.contains("integrity")
|
||||
);
|
||||
daemon.stop();
|
||||
assert!(resources.read_shared(&logical).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_resource_ipc_rejects_unbound_receipts_catalogs_and_paths() {
|
||||
let (_root, layout, manager, key) = machine();
|
||||
release(&layout, &key, "1.0.0");
|
||||
let version = manager.install_requested("1.0.0").unwrap();
|
||||
prepare_extension_with(&layout, &version, &id(Ecosystem::Pip), &Source::new(false)).unwrap();
|
||||
let _daemon = TestDaemon::start(&layout);
|
||||
let token = fs::read_to_string(layout.service_token()).unwrap();
|
||||
let catalog = hex(&Sha256::digest(
|
||||
fs::read(layout.versions().join("1.0.0/manifest.json")).unwrap(),
|
||||
));
|
||||
let request = serde_json::json!({"version":"1.0.0", "catalog":catalog,
|
||||
"extension":id(Ecosystem::Pip), "artifact":{"sha256":hex(&Sha256::digest(b"1.0.0")), "bytes":5}});
|
||||
for field in ["catalog", "artifact", "extension", "path"] {
|
||||
let mut invalid = request.clone();
|
||||
invalid[field] = match field {
|
||||
"catalog" => serde_json::json!("0".repeat(64)),
|
||||
"artifact" => serde_json::json!({"sha256":"0".repeat(64),"bytes":u64::MAX}),
|
||||
"extension" => serde_json::Value::Null,
|
||||
_ => serde_json::json!("../../state/service-token"),
|
||||
};
|
||||
let result = raw_operation(
|
||||
&layout,
|
||||
token.trim(),
|
||||
serde_json::json!({"resource_get":invalid}),
|
||||
);
|
||||
assert_eq!(result["ok"], false, "{field}: {result}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_prepares_only_installed_extensions_and_preserves_active_on_failure() {
|
||||
let (_root, layout, manager, key) = machine();
|
||||
|
||||
@@ -135,13 +135,14 @@ fn handle_call(call: &Value, mode: &str, hello: &Value) -> Result<(), String> {
|
||||
"payload": {},
|
||||
}))
|
||||
}
|
||||
"timeout" => {
|
||||
let cancel = read_json()?;
|
||||
require_kind(&cancel, "cancel")?;
|
||||
if field(&cancel, "id")? != id {
|
||||
return Err("cancel id does not match timed out call".into());
|
||||
}
|
||||
write_json(&json!({"kind": "result", "id": id, "payload": {}}))
|
||||
"delayed" => {
|
||||
let delay = call
|
||||
.pointer("/payload/value")
|
||||
.and_then(Value::as_u64)
|
||||
.filter(|delay| *delay <= 35_000)
|
||||
.ok_or("invalid fixture delay")?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(delay));
|
||||
write_json(&json!({"kind": "result", "id": id, "payload": field(call, "payload")?}))
|
||||
}
|
||||
"crash" => std::process::exit(7),
|
||||
"duplicate" => write_raw(&format!(
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Fixture {
|
||||
capabilities: vec!["file".into(), "process".into()],
|
||||
operations: vec!["execute".into()],
|
||||
setting: None,
|
||||
timeout_ms: 30_000,
|
||||
protocol_timeout_ms: 30_000,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const OPERATIONS: &[&str] = &[
|
||||
"echo",
|
||||
"fail",
|
||||
"wait",
|
||||
"timeout",
|
||||
"delayed",
|
||||
"crash",
|
||||
"duplicate",
|
||||
"out_of_order",
|
||||
@@ -23,6 +23,28 @@ const OPERATIONS: &[&str] = &[
|
||||
"truncated",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn component_call_can_run_longer_than_thirty_seconds() {
|
||||
let fixture = install_fixture();
|
||||
dever_runtime::task::run_entry(async {
|
||||
fixture.prepare()?;
|
||||
component::start(definition("long_call", fixture.entry, "normal", 2_000)).await?;
|
||||
let result = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(40),
|
||||
component::call("long_call", "delayed", &payload(31_000)),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "long-call fixture did not finish")??;
|
||||
match result {
|
||||
Reply::Result(body) => assert_eq!(body, r#"{"value":31000}"#),
|
||||
Reply::Error { .. } => return Err("long call returned an error".into()),
|
||||
}
|
||||
component::shutdown().await
|
||||
})
|
||||
.expect("long external invocation has no implicit execution deadline");
|
||||
assert!(fixture.path.with_extension("shutdown").is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_protocol_and_lifecycle_are_bounded() {
|
||||
let fixture = install_fixture();
|
||||
@@ -159,14 +181,20 @@ async fn normal_lifecycle(entry: &'static str) -> Result<(), String> {
|
||||
.await
|
||||
.map_err(|error| format!("cancel wait: {error}"))?;
|
||||
|
||||
let timeout = component::call("normal", "timeout", &payload(0))
|
||||
.await
|
||||
.expect_err("timeout operation must reach the deadline");
|
||||
assert_eq!(timeout, "external Adapter call timed out");
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
component::call("normal", "delayed", &payload(150)),
|
||||
)
|
||||
.await
|
||||
.expect("fixture call must finish")?
|
||||
{
|
||||
Reply::Result(body) => assert_eq!(body, r#"{"value":150}"#),
|
||||
Reply::Error { .. } => return Err("delayed call returned a business error".into()),
|
||||
}
|
||||
assert!(matches!(
|
||||
component::call("normal", "echo", &payload(3))
|
||||
.await
|
||||
.map_err(|error| format!("echo after timeout: {error}"))?,
|
||||
.map_err(|error| format!("echo after delayed call: {error}"))?,
|
||||
Reply::Result(_)
|
||||
));
|
||||
|
||||
@@ -248,7 +276,7 @@ fn definition(
|
||||
key: &'static str,
|
||||
entry: &'static str,
|
||||
mode: &'static str,
|
||||
timeout_ms: u64,
|
||||
protocol_timeout_ms: u64,
|
||||
) -> Definition {
|
||||
let (_, setting) = component::adapter_settings()
|
||||
.unwrap()
|
||||
@@ -266,7 +294,7 @@ fn definition(
|
||||
capabilities: vec!["network".into(), "file".into()],
|
||||
operations: OPERATIONS.iter().map(|name| (*name).into()).collect(),
|
||||
setting: Some(setting.to_string()),
|
||||
timeout_ms,
|
||||
protocol_timeout_ms,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user