Files
core/test/dever-cli-tests/native_release/ecosystems.rs
T

433 lines
17 KiB
Rust

//! Synthetic runtime closures exercise the production maker without host interpreters.
use std::io::Read;
use super::*;
fn add_file(author: &mut Author, ecosystem: &str, path: &str, bytes: &[u8]) {
let source = format!("inputs/{ecosystem}/{path}");
let file = author.root.join(&source);
fs::create_dir_all(file.parent().unwrap()).unwrap();
fs::write(&file, bytes).unwrap();
author.settings["runtimes"][ecosystem]["files"]
.as_array_mut()
.unwrap()
.push(json!({"source": source, "path": path, "sha256": sha256_file(&file).unwrap()}));
}
fn set_manifest(author: &mut Author, ecosystem: &str, change: impl FnOnce(&mut Value)) {
let input = author.settings["runtimes"][ecosystem]["files"]
.as_array_mut()
.unwrap()
.iter_mut()
.find(|input| input["path"] == "dever-runtime.json")
.unwrap();
let path = author.root.join(input["source"].as_str().unwrap());
let mut manifest: Value = serde_json::from_slice(&fs::read(&path).unwrap()).unwrap();
change(&mut manifest);
fs::write(&path, serde_json::to_vec(&manifest).unwrap()).unwrap();
input["sha256"] = json!(sha256_file(&path).unwrap());
}
fn runtime_author() -> Author {
let mut author = Author::new();
for (ecosystem, name, version) in [
("pip", "cpython", "3.12.0"),
("npm", "node", "24.0.0"),
("go", "go", "1.25.0"),
] {
author.settings["runtimes"][ecosystem] = json!({"name":name,"version":version,"files":[]});
let mut descriptor = json!({"format":"dever-worker-runtime-v1","ecosystem":ecosystem,"target":platform_identity()});
if ecosystem == "go" {
let arch = if std::env::consts::ARCH == "x86_64" {
"amd64"
} else {
"arm64"
};
descriptor["build"] = json!({"host":platform_identity(),"compiler":"bin/compile","linker":"bin/link","analyzer":"bin/analyze","stdlib_importcfg":"stdlib/importcfg","goos":"linux","goarch":arch,"go_version":format!("go version go1.25.0 linux/{arch}")});
for path in ["bin/compile", "bin/link", "bin/analyze"] {
add_file(
&mut author,
ecosystem,
path,
&native_elf::static_executable(),
);
}
let mut importcfg = String::new();
for name in ["runtime", "context", "encoding/json"] {
let path = format!("stdlib/{name}.a");
let object = format!("go object linux {arch} go1.25.0 fixture\n");
let archive = format!(
"!<arch>\n{:<16}{:<12}{:<6}{:<6}{:<8}{:<10}`\n{object}",
"__.PKGDEF",
0,
0,
0,
"644",
object.len()
);
add_file(&mut author, ecosystem, &path, archive.as_bytes());
importcfg.push_str(&format!("packagefile {name}={path}\n"));
}
add_file(
&mut author,
ecosystem,
"stdlib/importcfg",
importcfg.as_bytes(),
);
} else {
descriptor["executable"] = json!(format!("bin/{name}"));
descriptor["arguments"] = if ecosystem == "pip" {
json!(["-I", "-S", "-B"])
} else {
json!([])
};
add_file(
&mut author,
ecosystem,
&format!("bin/{name}"),
&native_elf::static_executable(),
);
}
add_file(
&mut author,
ecosystem,
"dever-runtime.json",
&serde_json::to_vec(&descriptor).unwrap(),
);
}
author.settings["runtimes"]["pip"]["python_markers"] = json!({
"implementation_name":"cpython","implementation_version":"3.12.0","os_name":"posix",
"platform_machine":std::env::consts::ARCH,"platform_python_implementation":"CPython",
"platform_release":"","platform_system":"Linux","platform_version":"",
"python_full_version":"3.12.0","python_version":"3.12","sys_platform":"linux"
});
author.settings["runtimes"]["pip"]["python_wheel_tags"] = json!(["py3-none-any"]);
let markers = author.settings["runtimes"]["pip"]["python_markers"].clone();
set_manifest(&mut author, "pip", |manifest| {
manifest["python_markers"] = markers;
manifest["python_wheel_tags"] = json!(["py3-none-any"]);
manifest["python_extension_suffixes"] = json!([".abi3.so", ".so"]);
});
author
}
#[test]
fn ecosystem_archives_are_deterministic_and_signed_with_computed_identities() {
let mut author = runtime_author();
author.save();
let first = author.output("first");
let manifest = packaging::create(&author.root, &first).unwrap();
// Input order and filesystem mode are not archive metadata.
for ecosystem in ["pip", "npm", "go"] {
author.settings["runtimes"][ecosystem]["files"]
.as_array_mut()
.unwrap()
.reverse();
}
fs::set_permissions(
author.root.join("inputs/npm/bin/node"),
fs::Permissions::from_mode(0o700),
)
.unwrap();
author.save();
let second = author.output("second");
assert_eq!(packaging::create(&author.root, &second).unwrap(), manifest);
assert_eq!(contents(&first), contents(&second));
assert_signed(&author, &first);
for ecosystem in ["pip", "npm", "go"] {
let prefix = format!("runtime/{ecosystem}/{}", platform_identity());
let metadata_path = format!("{prefix}/manifest.json");
let pack_path = format!("{prefix}/runtime.pack");
let metadata: Value =
serde_json::from_slice(&fs::read(first.join(&metadata_path)).unwrap()).unwrap();
assert_eq!(metadata["format"], "dever-registry-runtime-v1");
assert_eq!(
metadata["runtime"]["pack"]["sha256"],
sha256_file(&first.join(&pack_path)).unwrap()
);
for path in [&metadata_path, &pack_path] {
let signed = manifest
.extensions
.iter()
.flat_map(|extension| &extension.artifacts)
.find(|artifact| &artifact.path == path)
.unwrap();
assert_eq!(signed.sha256, sha256_file(&first.join(path)).unwrap());
}
let bytes = fs::read(first.join(&pack_path)).unwrap();
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(bytes.as_slice()));
let mut names = Vec::new();
for entry in archive.entries().unwrap() {
let mut entry = entry.unwrap();
let path = entry.path().unwrap().to_str().unwrap().to_owned();
assert_eq!(entry.header().mtime().unwrap(), 0);
assert_eq!(entry.header().uid().unwrap(), 0);
assert_eq!(entry.header().gid().unwrap(), 0);
assert_eq!(
entry.header().mode().unwrap(),
if path.starts_with("bin/") {
0o755
} else {
0o644
}
);
let mut actual = Vec::new();
entry.read_to_end(&mut actual).unwrap();
assert_eq!(
actual,
fs::read(author.root.join(format!("inputs/{ecosystem}/{path}"))).unwrap()
);
names.push(path);
}
assert!(names.windows(2).all(|pair| pair[0] < pair[1]));
}
let before = contents(&first);
fs::write(author.root.join("inputs/npm/bin/node"), b"changed").unwrap();
assert_eq!(contents(&first), before);
}
#[test]
fn rejects_incomplete_or_mismatched_runtime_descriptors() {
for ecosystem in ["pip", "npm", "go"] {
for field in ["format", "ecosystem", "target"] {
let mut author = runtime_author();
set_manifest(&mut author, ecosystem, |manifest| {
manifest[field] = json!("wrong")
});
assert!(author.reject().contains("identity"));
}
let mut author = runtime_author();
set_manifest(&mut author, ecosystem, |manifest| {
manifest["unknown"] = json!(true)
});
assert!(author.reject().contains("manifest"));
let mut author = runtime_author();
author.settings["runtimes"][ecosystem]["files"]
.as_array_mut()
.unwrap()
.retain(|input| input["path"] != "dever-runtime.json");
assert!(author.reject().contains("dever-runtime.json"));
}
for (ecosystem, field, value) in [
("pip", "arguments", json!(["-I", "-B"])),
("npm", "executable", json!("bin/missing")),
("npm", "arguments", json!(["source.js"])),
("go", "executable", json!("bin/compile")),
("go", "build", Value::Null),
] {
let mut author = runtime_author();
set_manifest(&mut author, ecosystem, |manifest| manifest[field] = value);
author.reject();
}
for (field, value) in [
("goarch", "unknown"),
("compiler", "bin/missing"),
("stdlib_importcfg", "missing"),
] {
let mut author = runtime_author();
set_manifest(&mut author, "go", |manifest| {
manifest["build"][field] = json!(value)
});
author.reject();
}
let mut author = runtime_author();
author.settings["runtimes"]["go"]["files"]
.as_array_mut()
.unwrap()
.retain(|input| input["path"] != "stdlib/context.a");
assert!(author.reject().contains("archive"));
}
#[test]
fn go_cross_pack_binds_host_tools_and_arm_standard_library_separately() {
for wrong_stdlib in [false, true] {
let mut author = runtime_author();
set_manifest(&mut author, "go", |manifest| {
manifest["target"] = json!("linux-aarch64");
manifest["build"]["goarch"] = json!("arm64");
});
for input in author.settings["runtimes"]["go"]["files"]
.as_array_mut()
.unwrap()
{
if !input["path"].as_str().unwrap().ends_with(".a") {
continue;
}
let path = author.root.join(input["source"].as_str().unwrap());
let architecture = if wrong_stdlib { "amd64" } else { "arm64" };
let object = format!("go object linux {architecture} go1.25.0 fixture\n");
let archive = format!(
"!<arch>\n{:<16}{:<12}{:<6}{:<6}{:<8}{:<10}`\n{object}",
"__.PKGDEF",
0,
0,
0,
"644",
object.len()
);
fs::write(&path, archive).unwrap();
input["sha256"] = json!(sha256_file(&path).unwrap());
}
let input = author.settings["runtimes"]
.as_object_mut()
.unwrap()
.remove("go")
.unwrap();
author.settings["ecosystem_targets"] = json!({"linux-aarch64":{"go":input}});
author.save();
let result = packaging::create(&author.root, &author.output("cross-go"));
if wrong_stdlib {
assert!(
result
.unwrap_err()
.contains("differs from its target or compiler version")
);
} else {
let manifest = result.unwrap();
assert!(
manifest
.extensions
.iter()
.flat_map(|extension| &extension.artifacts)
.any(|file| file.path == "runtime/go/linux-aarch64/runtime.pack")
);
}
}
}
#[test]
#[cfg(target_arch = "x86_64")]
fn sandbox_cross_assets_preserve_loader_mode_and_validate_deployment_target() {
let mut files = vec![
("bin/bwrap".into(), native_elf::static_executable()),
("bin/guard".into(), native_elf::static_executable()),
(
"lib/ld-linux-aarch64.so.1".into(),
native_elf::library("ld-linux-aarch64.so.1", &[]),
),
(
"lib/libc.so.6".into(),
native_elf::library("libc.so.6", &[]),
),
];
for (_, bytes) in &mut files {
bytes[18..20].copy_from_slice(&183u16.to_le_bytes());
}
dever_sandbox::validate_assets_for_target(&files, "linux-aarch64").unwrap();
let mut author = super::targets::cross_author();
let inputs = files
.iter()
.map(|(path, bytes)| {
let source = format!("inputs/arm-sandbox/{path}");
let file = author.root.join(&source);
fs::create_dir_all(file.parent().unwrap()).unwrap();
fs::write(&file, bytes).unwrap();
fs::set_permissions(&file, fs::Permissions::from_mode(0o600)).unwrap();
json!({"source":source,"path":path,"sha256":sha256_file(&file).unwrap()})
})
.collect::<Vec<_>>();
author.settings["sandbox_targets"] = json!({"linux-aarch64":inputs});
author.save();
let output = author.output("cross-sandbox");
packaging::create(&author.root, &output).unwrap();
for (path, mode) in [
("bin/bwrap", 0o755),
("bin/guard", 0o755),
("lib/ld-linux-aarch64.so.1", 0o755),
("lib/libc.so.6", 0o644),
] {
assert_eq!(
fs::metadata(output.join("sandbox/linux-aarch64").join(path))
.unwrap()
.permissions()
.mode()
& 0o777,
mode,
"incorrect packaged mode for {path}"
);
}
files[1].1[18..20].copy_from_slice(&62u16.to_le_bytes());
assert!(
dever_sandbox::validate_assets_for_target(&files, "linux-aarch64")
.unwrap_err()
.contains("incompatible target")
);
}
#[test]
fn rejects_invalid_registry_metadata_and_unknown_ecosystems() {
for (ecosystem, field, value) in [
("pip", "python_markers", Value::Null),
("pip", "python_wheel_tags", json!([])),
("pip", "python_wheel_tags", json!(["invalid"])),
(
"pip",
"python_wheel_tags",
json!(["py3-none-any", "py3-none-any"]),
),
("npm", "python_wheel_tags", json!(["py3-none-any"])),
("npm", "name", json!("")),
("npm", "name", json!("../node")),
("go", "version", json!("latest")),
("go", "unknown", json!(true)),
] {
let mut author = runtime_author();
author.settings["runtimes"][ecosystem][field] = value;
author.reject();
}
let mut author = runtime_author();
author.settings["runtimes"]["npm"]["python_markers"] =
author.settings["runtimes"]["pip"]["python_markers"].clone();
author.reject();
let mut author = runtime_author();
author.settings["runtimes"]["exec"] = author.settings["runtimes"]["npm"].clone();
author.reject();
let author = runtime_author();
author.save();
let path = author.root.join("config/setting.json");
let config = fs::read_to_string(&path).unwrap();
let duplicate = config.replacen("\"runtimes\":{", "\"runtimes\":{\"npm\":null,", 1);
assert_ne!(duplicate, config);
fs::write(path, duplicate).unwrap();
assert!(
packaging::create(&author.root, &author.output("duplicate"))
.unwrap_err()
.contains("invalid author configuration")
);
assert_eq!(fs::read_dir(author.directory.path()).unwrap().count(), 1);
}
#[test]
fn rejects_runtime_path_conflicts_drift_and_private_key_aliases() {
for path in [
"../outside",
"bin",
"bin/node/child",
"runtime.pack",
"bin/node",
] {
let mut author = runtime_author();
let mut input = author.settings["runtimes"]["npm"]["files"][0].clone();
input["path"] = json!(path);
author.settings["runtimes"]["npm"]["files"]
.as_array_mut()
.unwrap()
.push(input);
author.reject();
}
let author = runtime_author();
fs::write(author.root.join("inputs/npm/bin/node"), b"changed").unwrap();
assert!(author.reject().contains("SHA-256"));
let mut author = self::runtime_author();
let alias = author.root.join("inputs/key-alias");
fs::hard_link(author.root.join("signing.pk8"), &alias).unwrap();
author.settings["runtimes"]["npm"]["files"].as_array_mut().unwrap().push(json!({"source":"inputs/key-alias","path":"secrets/key","sha256":sha256_file(&alias).unwrap()}));
assert!(author.reject().contains("cannot be included"));
let mut author = self::runtime_author();
symlink("node", author.root.join("inputs/npm/bin/link")).unwrap();
author.settings["runtimes"]["npm"]["files"][0]["source"] = json!("inputs/npm/bin/link");
author.reject();
}