Files
core/test/dever-cli-tests/shared_toolchain.rs
T

1380 lines
46 KiB
Rust

use std::collections::BTreeSet;
use std::ffi::OsString;
use std::fs;
#[cfg(unix)]
use std::io::Read;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};
use std::sync::atomic::{AtomicU64, Ordering};
use dever_cli::toolchain::{
Artifact, BuildIdentity, CacheStore, CommandResult, Layout, MachineManager, PlatformContract,
ReleaseManifest, Version, artifact_get, artifact_put, execute_in, platform_contract,
};
use ring::rand::SystemRandom;
use ring::signature::{Ed25519KeyPair, KeyPair};
use sha2::{Digest, Sha256};
static NEXT_TEMPORARY: AtomicU64 = AtomicU64::new(0);
#[cfg(unix)]
#[path = "shared_toolchain_extensions.rs"]
mod extensions;
struct TemporaryDirectory(PathBuf);
impl TemporaryDirectory {
fn new() -> Self {
loop {
let path = std::env::temp_dir().join(format!(
"dever-toolchain-test-{}-{}",
std::process::id(),
NEXT_TEMPORARY.fetch_add(1, Ordering::Relaxed),
));
match fs::create_dir(&path) {
Ok(()) => return Self(path),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => panic!("cannot create test directory: {error}"),
}
}
}
fn path(&self) -> &Path {
&self.0
}
}
impl Drop for TemporaryDirectory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[cfg(unix)]
struct TestDaemon(Child);
#[cfg(unix)]
impl TestDaemon {
fn start(layout: &Layout) -> Self {
let child = Command::new(env!("CARGO_BIN_EXE_deverd"))
.env_clear()
.args(["--root", layout.root().to_str().unwrap()])
.spawn()
.unwrap();
let mut daemon = Self(child);
for _ in 0..100 {
if dever_cli::toolchain::cache_status(layout).is_ok() {
return daemon;
}
assert!(
daemon.0.try_wait().unwrap().is_none(),
"deverd exited before becoming ready"
);
std::thread::sleep(std::time::Duration::from_millis(10));
}
panic!("deverd did not publish its endpoint");
}
fn stop(&mut self) {
self.0.kill().unwrap();
self.0.wait().unwrap();
}
}
#[cfg(unix)]
impl Drop for TestDaemon {
fn drop(&mut self) {
let _ = self.0.kill();
let _ = self.0.wait();
}
}
#[cfg(unix)]
fn raw_request(layout: &Layout, token: &str, operation: &str) -> serde_json::Value {
raw_operation(layout, token, serde_json::Value::String(operation.into()))
}
#[cfg(unix)]
fn raw_operation(layout: &Layout, token: &str, operation: serde_json::Value) -> serde_json::Value {
use std::os::unix::net::UnixStream;
let mut stream = UnixStream::connect(layout.service_socket()).unwrap();
stream
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
.unwrap();
let payload = serde_json::to_vec(&serde_json::json!({
"token": token,
"operation": operation,
"installed": [],
}))
.unwrap();
stream
.write_all(&(payload.len() as u32).to_be_bytes())
.unwrap();
stream.write_all(&payload).unwrap();
read_response(&mut stream)
}
#[cfg(unix)]
#[test]
fn compilation_control_rejects_client_ir_paths_and_oversized_bodies() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let _daemon = TestDaemon::start(&layout);
let token = fs::read_to_string(layout.service_token()).unwrap();
for extra in ["ir", "output", "cache_key", "compiler"] {
let mut operation = serde_json::json!({"compile":{"bytes":1}});
operation["compile"][extra] = serde_json::json!("caller-controlled");
let response = raw_operation(&layout, token.trim(), operation);
assert_eq!(response["ok"], false);
assert!(
response["error"]
.as_str()
.unwrap()
.contains("unknown field"),
"{response}"
);
}
for bytes in [0, 384 * 1024 * 1024 + 1, u64::MAX] {
let response = raw_operation(
&layout,
token.trim(),
serde_json::json!({"compile":{"bytes":bytes}}),
);
assert_eq!(response["ok"], false);
assert!(
response["error"]
.as_str()
.unwrap()
.contains("transfer limit")
);
}
assert_eq!(
dever_cli::toolchain::cache_status(&layout).unwrap().entries,
0
);
}
#[cfg(unix)]
fn read_response(stream: &mut std::os::unix::net::UnixStream) -> serde_json::Value {
let mut header = [0_u8; 4];
stream.read_exact(&mut header).unwrap();
let mut payload = vec![0; u32::from_be_bytes(header) as usize];
stream.read_exact(&mut payload).unwrap();
serde_json::from_slice(&payload).unwrap()
}
#[test]
fn runtime_accepts_only_an_exact_project_toolchain_version() {
let temporary = TemporaryDirectory::new();
let config = temporary.path().join("config");
fs::create_dir(&config).unwrap();
fs::write(
config.join("setting.json"),
r#"{"dever":{"version":"1.2.3"}}"#,
)
.unwrap();
let settings = dever_runtime::config::Settings::load_project(temporary.path()).unwrap();
assert_eq!(settings.dever_version(), Some("1.2.3"));
for invalid in [
r#"{"dever":{"version":"latest"}}"#,
r#"{"dever":{"version":"01.2.3"}}"#,
r#"{"dever":{"version":"1.2"}}"#,
r#"{"dever":{"version":"1.2.3","channel":"stable"}}"#,
r#"{"dever":{"version":"1.2.3","version":"2.0.0"}}"#,
] {
fs::write(config.join("setting.json"), invalid).unwrap();
assert!(
dever_runtime::config::Settings::load_project(temporary.path()).is_err(),
"accepted {invalid}"
);
}
assert!(Version::parse("1.10.0").unwrap() > Version::parse("1.2.0").unwrap());
}
#[test]
fn all_supported_platforms_have_one_machine_entry_and_service_contract() {
for (os, expected) in [
("linux", ("/opt/dever", "/usr/local/bin/dever")),
(
"macos",
("/Library/Application Support/Dever", "/usr/local/bin/dever"),
),
(
"windows",
(
r"C:\Program Files\Dever",
r"C:\Program Files\Dever\bin\dever.exe",
),
),
] {
let PlatformContract {
machine_root,
public_entry,
service_definition,
ipc_endpoint,
} = platform_contract(os).expect("supported platform contract");
assert_eq!((machine_root, public_entry), expected);
assert!(!service_definition.is_empty());
assert!(!ipc_endpoint.is_empty());
}
assert!(platform_contract("freebsd").is_none());
}
#[cfg(unix)]
#[test]
fn signed_versions_install_select_dispatch_update_and_rollback_atomically() {
use std::os::unix::fs::PermissionsExt;
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let key = signing_key();
fs::write(
layout.state().join("trusted-release-key"),
format!("{}\n", hex(key.public_key().as_ref())),
)
.unwrap();
let dispatch_log = temporary.path().join("dispatch.log");
for (version, healthy, valid_signature) in [
("1.0.0", true, true),
("2.0.0", true, true),
("3.0.0", false, true),
("4.0.0", true, false),
] {
write_release(
&layout,
&key,
version,
&dispatch_log,
healthy,
valid_signature,
);
}
fs::write(layout.downloads().join("latest"), "2.0.0\n").unwrap();
let manager = MachineManager::new(layout.clone());
fs::write(
layout.state().join("active-version"),
"dever-active-version-v1\n\npartial",
)
.unwrap();
assert_eq!(manager.active_version().unwrap(), None);
assert_eq!(
manager.install_requested("1.0.0").unwrap().as_str(),
"1.0.0"
);
assert_eq!(
fs::metadata(layout.versions().join("1.0.0/dever-core"))
.unwrap()
.permissions()
.mode()
& 0o777,
0o755
);
assert_eq!(
fs::metadata(layout.versions().join("1.0.0/runtime.pack"))
.unwrap()
.permissions()
.mode()
& 0o777,
0o644
);
assert_eq!(manager.active_version().unwrap().unwrap().as_str(), "1.0.0");
fs::OpenOptions::new()
.append(true)
.open(layout.state().join("active-version"))
.unwrap()
.write_all(b"\npartial-record")
.unwrap();
assert_eq!(manager.active_version().unwrap().unwrap().as_str(), "1.0.0");
manager.install_requested("2.0.0").unwrap();
assert_eq!(manager.active_version().unwrap().unwrap().as_str(), "1.0.0");
let skill_path = |layout: &Layout| {
let CommandResult::Message(path) =
execute_in(layout, &["skill".into(), "path".into()]).unwrap()
else {
panic!("skill path must return the active signed directory")
};
PathBuf::from(path)
};
assert_eq!(
fs::read_to_string(skill_path(&layout).join("SKILL.md")).unwrap(),
"skill 1.0.0\n"
);
let skill_entry = temporary.path().join("dever-language");
execute_in(
&layout,
&[
"skill".into(),
"install".into(),
skill_entry.as_os_str().into(),
],
)
.unwrap();
let loader = fs::read(skill_entry.join("SKILL.md")).unwrap();
assert!(String::from_utf8_lossy(&loader).contains("skill path"));
assert!(
execute_in(
&layout,
&[
"skill".into(),
"install".into(),
skill_entry.as_os_str().into()
]
)
.is_err()
);
assert_eq!(fs::read(skill_entry.join("SKILL.md")).unwrap(), loader);
let new_project = temporary.path().join("not-created-yet");
let CommandResult::Exited(code) = execute_in(
&layout,
&[
"new".into(),
new_project.as_os_str().into(),
"--markdown".into(),
],
)
.unwrap() else {
panic!("new must dispatch without project settings")
};
assert_eq!(code, 0);
assert_eq!(
fs::read_to_string(&dispatch_log).unwrap(),
format!("new\n{}\n--markdown\n", new_project.display())
);
let project = temporary.path().join("project");
fs::create_dir_all(project.join("config")).unwrap();
fs::write(
project.join("config/setting.json"),
r#"{"dever":{"version":"2.0.0"}}"#,
)
.unwrap();
let arguments = ["check", project.to_str().unwrap()].map(OsString::from);
let CommandResult::Message(pinned_skill) = execute_in(
&layout,
&["skill".into(), "path".into(), project.as_os_str().into()],
)
.unwrap() else {
panic!("project skill must match its selected compiler")
};
assert_eq!(
Path::new(&pinned_skill),
layout.versions().join("2.0.0/skills/dever-language")
);
let CommandResult::Exited(code) = execute_in(&layout, &arguments).unwrap() else {
panic!("project command must dispatch")
};
assert_eq!(code, 0);
assert_eq!(
fs::read_to_string(&dispatch_log).unwrap(),
format!("check\n{}\n", project.display())
);
let clean_arguments = ["clean", project.to_str().unwrap()].map(OsString::from);
let CommandResult::Exited(code) = execute_in(&layout, &clean_arguments).unwrap() else {
panic!("clean command must use the selected project core")
};
assert_eq!(code, 0);
assert_eq!(
fs::read_to_string(&dispatch_log).unwrap(),
format!("clean\n{}\n", project.display())
);
assert_eq!(manager.update().unwrap().as_str(), "2.0.0");
assert_eq!(manager.active_version().unwrap().unwrap().as_str(), "2.0.0");
assert_eq!(
fs::read_to_string(skill_path(&layout).join("SKILL.md")).unwrap(),
"skill 2.0.0\n"
);
assert_eq!(fs::read(skill_entry.join("SKILL.md")).unwrap(), loader);
fs::write(layout.downloads().join("latest"), "1.0.0\n").unwrap();
assert!(
manager
.update()
.unwrap_err()
.contains("refusing to downgrade")
);
assert_eq!(manager.active_version().unwrap().unwrap().as_str(), "2.0.0");
fs::write(layout.downloads().join("latest"), "2.0.0\n").unwrap();
assert!(
manager
.uninstall(&Version::parse("2.0.0").unwrap())
.unwrap_err()
.contains("active")
);
manager.activate(&Version::parse("1.0.0").unwrap()).unwrap();
assert_eq!(
fs::read_to_string(skill_path(&layout).join("SKILL.md")).unwrap(),
"skill 1.0.0\n"
);
manager
.uninstall(&Version::parse("2.0.0").unwrap())
.unwrap();
assert!(!layout.versions().join("2.0.0").exists());
let before = manager.active_version().unwrap();
assert!(
manager
.install_requested("3.0.0")
.unwrap_err()
.contains("health check")
);
assert_eq!(manager.active_version().unwrap(), before);
assert!(!layout.versions().join("3.0.0").exists());
assert!(
manager
.install_requested("4.0.0")
.unwrap_err()
.contains("signature")
);
assert_eq!(manager.active_version().unwrap(), before);
write_release(&layout, &key, "5.0.0", &dispatch_log, true, true);
fs::write(
layout.downloads().join("5.0.0/runtime.pack"),
"tampered runtime",
)
.unwrap();
assert!(
manager
.install_requested("5.0.0")
.unwrap_err()
.contains("integrity")
);
assert_eq!(fs::read_dir(layout.staging()).unwrap().count(), 0);
let install_lock = fs::OpenOptions::new()
.read(true)
.write(true)
.open(layout.state().join("install.lock"))
.unwrap();
fs2::FileExt::lock_exclusive(&install_lock).unwrap();
assert!(
manager
.install_requested("1.0.0")
.unwrap_err()
.contains("active")
);
fs2::FileExt::unlock(&install_lock).unwrap();
drop(install_lock);
fs::set_permissions(layout.state(), fs::Permissions::from_mode(0o755)).unwrap();
fs::write(
project.join("config/setting.json"),
r#"{"dever":{"version":"9.9.9"}}"#,
)
.unwrap();
let error = match execute_in(&layout, &arguments) {
Ok(_) => panic!("missing locked version must fail"),
Err(error) => error,
};
assert!(error.contains("dever install 9.9.9"), "{error}");
fs::set_permissions(
layout.versions().join("1.0.0/dever-core"),
fs::Permissions::from_mode(0o777),
)
.unwrap();
let error = manager
.resolve_core(&Version::parse("1.0.0").unwrap())
.unwrap_err();
assert!(
error.contains("not writable by group or other users"),
"{error}"
);
}
#[cfg(unix)]
#[test]
fn installed_launcher_is_one_shared_entry_for_multiple_projects() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let key = signing_key();
fs::write(
layout.state().join("trusted-release-key"),
format!("{}\n", hex(key.public_key().as_ref())),
)
.unwrap();
let dispatch_log = temporary.path().join("dispatch.log");
write_release(&layout, &key, "1.0.0", &dispatch_log, true, true);
MachineManager::new(layout.clone())
.install_requested("1.0.0")
.unwrap();
let launcher = layout.bin().join("dever");
fs::copy(env!("CARGO_BIN_EXE_dever-launcher"), &launcher).unwrap();
let version = Command::new(&launcher)
.env_clear()
.arg("version")
.output()
.unwrap();
assert!(
version.status.success(),
"{}",
String::from_utf8_lossy(&version.stderr)
);
assert_eq!(version.stdout, b"1.0.0\n");
for name in ["first-project", "second-project"] {
let project = temporary.path().join(name);
fs::create_dir_all(project.join("config")).unwrap();
fs::write(project.join("config/setting.json"), "{}\n").unwrap();
for prefix in [vec!["check"], vec!["lib", "list"], vec!["package", "list"]] {
let output = Command::new(&launcher)
.env_clear()
.env("DEVER_TEST_SENTINEL", "must-not-reach-version-core")
.args(&prefix)
.arg(&project)
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
fs::read_to_string(&dispatch_log).unwrap(),
format!("{}\n{}\n", prefix.join("\n"), project.display())
);
}
}
let project = temporary.path().join("exit-42");
fs::create_dir_all(project.join("config")).unwrap();
fs::write(project.join("config/setting.json"), "{}\n").unwrap();
let status = Command::new(&launcher)
.env_clear()
.args(["check", project.to_str().unwrap()])
.status()
.unwrap();
assert_eq!(status.code(), Some(42));
}
#[test]
fn shared_cache_uses_complete_crypto_identity_integrity_leases_and_safe_cleanup() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let version = Version::parse("1.0.0").unwrap();
let program = temporary.path().join("program");
fs::write(&program, b"verified executable bytes").unwrap();
let build = identity(&version, b"generated one", "linux-x86_64");
let service = CacheStore::new(&layout);
let keys = std::thread::scope(|scope| {
let first = scope.spawn(|| CacheStore::new(&layout).publish(&build, &program).unwrap());
let second = scope.spawn(|| CacheStore::new(&layout).publish(&build, &program).unwrap());
[first.join().unwrap(), second.join().unwrap()]
});
assert_eq!(keys[0], keys[1]);
let restored = service.restore(&build).unwrap().expect("cache hit");
assert_eq!(restored.as_bytes(), b"verified executable bytes");
assert!(
service
.restore(&identity(&version, b"generated two", "linux-x86_64"))
.unwrap()
.is_none()
);
for target in ["linux-aarch64", "windows-x86_64"] {
assert!(
service
.restore(&identity(&version, b"generated one", target))
.unwrap()
.is_none()
);
}
let status = service.status().unwrap();
assert_eq!((status.entries, status.corrupt_entries), (1, 0));
assert_eq!(status.versions.get("1.0.0"), Some(&1));
service.clean(&BTreeSet::new()).unwrap();
assert_eq!(
service.status().unwrap().entries,
1,
"leased entry was deleted"
);
drop(restored);
fs::create_dir_all(layout.native_cache().join("operations")).unwrap();
fs::write(
layout.native_cache().join("operations/active"),
"fixture operation",
)
.unwrap();
assert!(
service
.clean(&BTreeSet::new())
.unwrap_err()
.contains("busy")
);
fs::remove_file(layout.native_cache().join("operations/active")).unwrap();
service.clean(&BTreeSet::new()).unwrap();
assert_eq!(service.status().unwrap().entries, 0);
let key = service.publish(&build, &program).unwrap();
fs::write(
layout
.native_cache()
.join("entries")
.join(&key)
.join("program"),
b"tampered",
)
.unwrap();
let error = match service.restore(&build) {
Ok(_) => panic!("modified cache entry must fail"),
Err(error) => error,
};
assert!(error.contains("modified"), "{error}");
assert_eq!(service.status().unwrap().corrupt_entries, 1);
let installed = BTreeSet::from([version]);
service.clean(&installed).unwrap();
assert_eq!(service.status().unwrap().corrupt_entries, 0);
}
#[test]
fn launcher_does_not_bypass_an_unavailable_authenticated_cache_service() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
for arguments in [["cache", "status"], ["cache", "clean"]] {
let arguments = arguments.map(OsString::from);
let error = match execute_in(&layout, &arguments) {
Ok(_) => panic!("cache command must not open the service store directly"),
Err(error) => error,
};
assert!(error.contains("authenticated deverd"), "{error}");
}
}
#[cfg(unix)]
#[test]
fn deverd_authenticates_cache_commands_over_local_ipc() {
use std::os::unix::fs::PermissionsExt;
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let _daemon = TestDaemon::start(&layout);
let status = execute_in(
&layout,
&[OsString::from("cache"), OsString::from("status")],
)
.unwrap();
let CommandResult::Message(status) = status else {
panic!("cache status must return a message");
};
let status: serde_json::Value = serde_json::from_str(&status).unwrap();
assert_eq!(status["entries"], 0);
let version = Version::parse("1.0.0").unwrap();
let artifact = temporary.path().join("program");
fs::write(&artifact, b"authenticated cleanup fixture").unwrap();
CacheStore::new(&layout)
.publish(&identity(&version, b"auth", "linux-x86_64"), &artifact)
.unwrap();
let token = fs::read_to_string(layout.service_token()).unwrap();
let padded = format!("{}{}", token.trim(), "\0".repeat(256));
assert_eq!(raw_request(&layout, &padded, "clean")["ok"], false);
assert_eq!(
dever_cli::toolchain::cache_status(&layout).unwrap().entries,
1
);
assert_eq!(raw_request(&layout, token.trim(), "status")["ok"], true);
let moved = layout.state().join("deverd.token.saved");
fs::rename(layout.service_token(), &moved).unwrap();
assert_eq!(raw_request(&layout, "", "clean")["ok"], false);
assert!(dever_cli::toolchain::cache_clean(&layout, &BTreeSet::new()).is_err());
assert!(!layout.service_token().exists());
fs::rename(moved, layout.service_token()).unwrap();
fs::set_permissions(layout.service_token(), fs::Permissions::from_mode(0o644)).unwrap();
assert_eq!(raw_request(&layout, token.trim(), "clean")["ok"], false);
fs::set_permissions(layout.service_token(), fs::Permissions::from_mode(0o600)).unwrap();
assert_eq!(
dever_cli::toolchain::cache_status(&layout).unwrap().entries,
1
);
fs::write(layout.service_token(), format!("{token}tampered")).unwrap();
let error = execute_in(
&layout,
&[OsString::from("cache"), OsString::from("status")],
)
.unwrap_err();
assert!(
error.contains("authentication token") || error.contains("invalid"),
"{error}"
);
}
#[cfg(unix)]
#[test]
fn deverd_rejects_a_second_instance_and_recovers_its_socket_after_exit() {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let mut daemon = TestDaemon::start(&layout);
let socket_inode = fs::symlink_metadata(layout.service_socket()).unwrap().ino();
let second = Command::new(env!("CARGO_BIN_EXE_deverd"))
.args(["--root", layout.root().to_str().unwrap()])
.output()
.unwrap();
assert!(!second.status.success());
assert!(String::from_utf8_lossy(&second.stderr).contains("already running"));
assert_eq!(
fs::symlink_metadata(layout.service_socket()).unwrap().ino(),
socket_inode
);
assert!(dever_cli::toolchain::cache_status(&layout).is_ok());
daemon.stop();
assert!(layout.service_socket().exists());
let lock = layout.state().join("deverd.lock");
fs::set_permissions(&lock, fs::Permissions::from_mode(0o644)).unwrap();
let insecure = Command::new(env!("CARGO_BIN_EXE_deverd"))
.args(["--root", layout.root().to_str().unwrap()])
.output()
.unwrap();
assert!(!insecure.status.success());
assert!(String::from_utf8_lossy(&insecure.stderr).contains("deverd lock"));
fs::set_permissions(&lock, fs::Permissions::from_mode(0o600)).unwrap();
let _recovered = TestDaemon::start(&layout);
assert!(dever_cli::toolchain::cache_status(&layout).is_ok());
}
#[cfg(target_os = "linux")]
#[test]
fn deverd_bounds_workers_when_clients_stall() {
use std::os::unix::net::UnixStream;
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let daemon = TestDaemon::start(&layout);
let clients: Vec<_> = (0..48)
.map(|_| {
let mut stream = UnixStream::connect(layout.service_socket()).unwrap();
let _ = stream.write_all(&[0]);
stream
})
.collect();
let status = fs::read_to_string(format!("/proc/{}/status", daemon.0.id())).unwrap();
let threads: usize = status
.lines()
.find_map(|line| {
line.strip_prefix("Threads:")
.map(|value| value.trim().parse().unwrap())
})
.unwrap();
assert!(
threads <= 10,
"deverd created {threads} threads for stalled clients"
);
drop(clients);
let recovered = (0..100).any(|_| {
if dever_cli::toolchain::cache_status(&layout).is_ok() {
return true;
}
std::thread::sleep(std::time::Duration::from_millis(10));
false
});
assert!(recovered);
}
#[cfg(unix)]
#[test]
fn deverd_keeps_serving_after_bad_and_slow_clients() {
use std::net::Shutdown;
use std::os::unix::net::UnixStream;
use std::time::{Duration, Instant};
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let _daemon = TestDaemon::start(&layout);
let mut slow = UnixStream::connect(layout.service_socket()).unwrap();
slow.write_all(&[0]).unwrap();
let start = Instant::now();
assert!(dever_cli::toolchain::cache_status(&layout).is_ok());
assert!(start.elapsed() < Duration::from_secs(2));
let mut oversized = UnixStream::connect(layout.service_socket()).unwrap();
oversized
.set_read_timeout(Some(Duration::from_secs(2)))
.unwrap();
oversized.write_all(&u32::MAX.to_be_bytes()).unwrap();
assert_eq!(read_response(&mut oversized)["ok"], false);
let mut partial = UnixStream::connect(layout.service_socket()).unwrap();
partial
.set_read_timeout(Some(Duration::from_secs(2)))
.unwrap();
partial.write_all(&100_u32.to_be_bytes()).unwrap();
partial.shutdown(Shutdown::Write).unwrap();
assert_eq!(read_response(&mut partial)["ok"], false);
let mut disconnected = UnixStream::connect(layout.service_socket()).unwrap();
disconnected.write_all(&0_u32.to_be_bytes()).unwrap();
disconnected.shutdown(Shutdown::Both).unwrap();
drop(slow);
assert!(dever_cli::toolchain::cache_status(&layout).is_ok());
}
#[cfg(unix)]
#[test]
fn deverd_transfers_verified_artifacts_without_client_cache_paths() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let _daemon = TestDaemon::start(&layout);
// Larger than a transport scratch buffer; the daemon streams to staging.
let bytes = (0..262_147)
.map(|index| (index % 251) as u8)
.collect::<Vec<_>>();
let receipt = artifact_put(&layout, &bytes).unwrap();
assert_eq!(receipt.bytes, bytes.len() as u64);
assert_eq!(
artifact_get(&layout, &receipt.sha256, receipt.bytes).unwrap(),
bytes
);
std::thread::scope(|scope| {
for _ in 0..6 {
scope.spawn(|| assert_eq!(artifact_put(&layout, &bytes).unwrap(), receipt));
}
});
let status = dever_cli::toolchain::cache_status(&layout).unwrap();
assert_eq!(status.artifact_entries, 1);
assert_eq!(status.artifact_bytes, receipt.bytes);
assert_eq!(status.staging_entries, 0);
assert!(artifact_get(&layout, "../outside", 1).is_err());
assert!(artifact_get(&layout, &receipt.sha256, receipt.bytes + 1).is_err());
assert!(artifact_get(&layout, &receipt.sha256, 64 * 1024 * 1024 + 1).is_err());
fs::write(
layout
.native_cache()
.join("artifacts")
.join(&receipt.sha256),
b"tampered",
)
.unwrap();
assert!(artifact_get(&layout, &receipt.sha256, receipt.bytes).is_err());
assert_eq!(
dever_cli::toolchain::cache_status(&layout)
.unwrap()
.corrupt_artifacts,
1
);
dever_cli::toolchain::cache_clean(&layout, &BTreeSet::new()).unwrap();
assert!(artifact_get(&layout, &receipt.sha256, receipt.bytes).is_err());
}
#[cfg(unix)]
#[test]
fn deverd_rejects_bad_and_incomplete_artifact_uploads_before_publication() {
use std::os::unix::net::UnixStream;
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let _daemon = TestDaemon::start(&layout);
let token = fs::read_to_string(layout.service_token()).unwrap();
for (bytes, digest, body, complete) in [
(
4_u64,
hex(&Sha256::digest(b"good")),
b"bad!".as_slice(),
true,
),
(4, hex(&Sha256::digest(b"good")), b"go".as_slice(), false),
] {
let mut stream = UnixStream::connect(layout.service_socket()).unwrap();
stream
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
.unwrap();
let request = serde_json::to_vec(&serde_json::json!({
"token": token.trim(), "operation": {"artifact_put": {"sha256": digest, "bytes": bytes}}, "installed": []
})).unwrap();
stream
.write_all(&(request.len() as u32).to_be_bytes())
.unwrap();
stream.write_all(&request).unwrap();
assert_eq!(read_response(&mut stream)["ok"], true);
stream.write_all(body).unwrap();
if complete {
assert_eq!(read_response(&mut stream)["ok"], false);
}
drop(stream);
for attempt in 0..100 {
let status = dever_cli::toolchain::cache_status(&layout).unwrap();
if status.staging_entries == 0 {
assert_eq!(status.artifact_entries, 0);
break;
}
assert!(attempt < 99, "incomplete upload staging was not removed");
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
let receipt = artifact_put(&layout, b"good").unwrap();
assert_eq!(
artifact_get(&layout, &receipt.sha256, receipt.bytes).unwrap(),
b"good"
);
}
#[cfg(unix)]
#[test]
fn shared_artifact_quota_counts_bytes_and_entries_and_cleans_failed_staging() {
for (entries, artifact_bytes) in [(32_u32, 64 * 1024 * 1024), (4096, 0)] {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let artifacts = layout.native_cache().join("artifacts");
fs::create_dir(&artifacts).unwrap();
// Sparse owned fixtures test byte and entry quotas without allocating 2 GiB.
for index in 0_u32..entries {
let file = fs::File::create(artifacts.join(hex(&Sha256::digest(index.to_le_bytes()))))
.unwrap();
file.set_len(artifact_bytes).unwrap();
}
let body = b"new verified artifact";
let receipt = dever_cli::toolchain::ArtifactReceipt {
sha256: hex(&Sha256::digest(body)),
bytes: body.len() as u64,
};
let store = CacheStore::new(&layout);
assert!(
store
.publish_artifact(&receipt, &mut body.as_slice())
.unwrap_err()
.contains("cache is full")
);
assert!(!artifacts.join(&receipt.sha256).exists());
assert_eq!(
fs::read_dir(layout.native_cache().join("staging"))
.unwrap()
.count(),
0
);
assert_eq!(
fs::read_dir(layout.native_cache().join("operations"))
.unwrap()
.count(),
0
);
}
}
#[cfg(unix)]
#[test]
fn shared_cache_summary_cannot_claim_payload_integrity() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let store = CacheStore::new(&layout);
let bytes = b"good";
let receipt = dever_cli::toolchain::ArtifactReceipt {
sha256: hex(&Sha256::digest(bytes)),
bytes: bytes.len() as u64,
};
store
.publish_artifact(&receipt, &mut bytes.as_slice())
.unwrap();
fs::write(
layout
.native_cache()
.join("artifacts")
.join(&receipt.sha256),
b"bad!",
)
.unwrap();
let summary = store.summary().unwrap();
assert!(!summary.verified);
assert_eq!(summary.artifact_entries, 1);
assert_eq!(summary.corrupt_artifacts, 0);
let checked = store.status().unwrap();
assert!(checked.verified);
assert_eq!(checked.artifact_entries, 0);
assert_eq!(checked.corrupt_artifacts, 1);
assert!(store.artifact(&receipt).is_err());
}
#[test]
fn compiled_cache_has_independent_byte_entry_and_output_limits() {
for (count, bytes) in [(32, 64 * 1024 * 1024), (4096, 1)] {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let version = Version::parse("0.1.0").unwrap();
for index in 0..count {
let key = format!("{index:064x}");
let directory = layout.native_cache().join("entries").join(&key);
fs::create_dir_all(&directory).unwrap();
fs::File::create(directory.join("program"))
.unwrap()
.set_len(bytes)
.unwrap();
let entry = dever_cli::toolchain::CacheEntry {
format: "dever-machine-cache-entry-v1".into(),
key,
version: version.clone(),
target: "linux-x86_64".into(),
bytes,
sha256: "0".repeat(64),
};
fs::write(
directory.join("entry.json"),
serde_json::to_vec(&entry).unwrap(),
)
.unwrap();
}
let program = temporary.path().join("program");
fs::write(&program, b"new compiled output").unwrap();
let store = CacheStore::new(&layout);
assert!(
store
.publish(&identity(&version, b"new", "linux-x86_64"), &program)
.unwrap_err()
.contains("quota exceeded")
);
fs::File::create(&program)
.unwrap()
.set_len(320 * 1024 * 1024 + 1)
.unwrap();
assert!(
store
.publish(&identity(&version, b"large", "linux-x86_64"), &program)
.unwrap_err()
.contains("320 MiB")
);
assert_eq!(
fs::read_dir(layout.native_cache().join("staging"))
.unwrap()
.count(),
0
);
assert_eq!(
fs::read_dir(layout.native_cache().join("operations"))
.unwrap()
.count(),
0
);
}
}
#[cfg(target_os = "linux")]
#[test]
#[ignore = "requires root; drops only an owned skill client to an unprivileged uid"]
fn active_skill_is_readable_by_other_machine_users_without_install_lock_access() {
use std::os::unix::fs::PermissionsExt;
use std::os::unix::process::CommandExt;
assert_eq!(rustix::process::geteuid().as_raw(), 0);
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let key = signing_key();
fs::write(
layout.state().join("trusted-release-key"),
format!("{}\n", hex(key.public_key().as_ref())),
)
.unwrap();
write_release(
&layout,
&key,
"1.0.0",
&temporary.path().join("dispatch"),
true,
true,
);
MachineManager::new(layout.clone())
.install_requested("1.0.0")
.unwrap();
let launcher = layout.bin().join("dever");
fs::copy(env!("CARGO_BIN_EXE_dever-launcher"), &launcher).unwrap();
fs::set_permissions(&launcher, fs::Permissions::from_mode(0o755)).unwrap();
let output = Command::new(&launcher)
.args(["skill", "path"])
.uid(65534)
.gid(65534)
.env_clear()
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
let path = String::from_utf8(output.stdout).unwrap();
assert_eq!(
Path::new(path.trim()),
layout.versions().join("1.0.0/skills/dever-language")
);
let read = Command::new("/bin/cat")
.arg(Path::new(path.trim()).join("SKILL.md"))
.uid(65534)
.gid(65534)
.env_clear()
.output()
.unwrap();
assert!(read.status.success());
assert_eq!(read.stdout, b"skill 1.0.0\n");
}
#[cfg(unix)]
#[test]
fn corrupt_skill_cannot_be_published_or_resolved() {
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let key = signing_key();
fs::write(
layout.state().join("trusted-release-key"),
format!("{}\n", hex(key.public_key().as_ref())),
)
.unwrap();
for version in ["1.0.0", "2.0.0"] {
write_release(
&layout,
&key,
version,
&temporary.path().join("dispatch"),
true,
true,
);
}
let manager = MachineManager::new(layout.clone());
manager.install_requested("1.0.0").unwrap();
fs::write(
layout
.downloads()
.join("2.0.0/skills/dever-language/SKILL.md"),
"tampered",
)
.unwrap();
fs::write(layout.downloads().join("latest"), "2.0.0\n").unwrap();
assert!(manager.update().unwrap_err().contains("integrity"));
assert_eq!(manager.active_version().unwrap().unwrap().as_str(), "1.0.0");
assert!(!layout.versions().join("2.0.0").exists());
fs::write(
layout
.versions()
.join("1.0.0/skills/dever-language/SKILL.md"),
"tampered",
)
.unwrap();
assert!(
execute_in(&layout, &["skill".into(), "path".into()])
.unwrap_err()
.contains("integrity")
);
}
#[cfg(target_os = "linux")]
#[test]
#[ignore = "requires root; drops only owned test clients to an unprivileged uid"]
fn deverd_uses_kernel_credentials_for_other_machine_users() {
use std::os::unix::fs::PermissionsExt;
use std::os::unix::process::CommandExt;
assert_eq!(
rustix::process::geteuid().as_raw(),
0,
"this scoped acceptance requires root"
);
let temporary = TemporaryDirectory::new();
let layout = Layout::new(temporary.path().join("machine"));
layout.initialize().unwrap();
let _daemon = TestDaemon::start(&layout);
let launcher = layout.bin().join("dever");
fs::copy(env!("CARGO_BIN_EXE_dever-launcher"), &launcher).unwrap();
fs::set_permissions(&launcher, fs::Permissions::from_mode(0o755)).unwrap();
let status = Command::new(&launcher)
.args(["cache", "status"])
.uid(65534)
.gid(65534)
.env_clear()
.output()
.unwrap();
assert!(
status.status.success(),
"{}",
String::from_utf8_lossy(&status.stderr)
);
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&status.stdout).unwrap()["verified"],
false
);
let clean = Command::new(&launcher)
.args(["cache", "clean"])
.uid(65534)
.gid(65534)
.env_clear()
.output()
.unwrap();
assert!(!clean.status.success());
assert!(String::from_utf8_lossy(&clean.stderr).contains("service owner"));
let client = r#"
import hashlib, json, socket, struct, sys
endpoint, token_path = sys.argv[1:]
try:
open(token_path, 'rb')
except PermissionError:
pass
else:
raise AssertionError('foreign user read the owner token')
def response(peer):
def read(n):
out = b''
while len(out) < n:
part = peer.recv(n - len(out))
if not part: raise AssertionError('incomplete response')
out += part
return out
length, = struct.unpack('>I', read(4))
return json.loads(read(length))
held = []
try:
for _ in range(2):
peer = socket.socket(socket.AF_UNIX)
peer.settimeout(2)
peer.connect(endpoint)
held.append(peer)
with socket.socket(socket.AF_UNIX) as excess:
excess.settimeout(2)
excess.connect(endpoint)
assert excess.recv(1) == b'', 'foreign UID exceeded its two-client admission'
request = json.dumps(dict(operation='status')).encode()
for peer in held:
peer.sendall(struct.pack('>I', len(request)) + request)
assert response(peer)['status']['verified'] is False
finally:
for peer in held:
peer.close()
body = b'shared across machine users'
identity = dict(sha256=hashlib.sha256(body).hexdigest(), bytes=len(body))
for operation in ('artifact_put', 'artifact_get'):
with socket.socket(socket.AF_UNIX) as peer:
peer.settimeout(5)
peer.connect(endpoint)
request = json.dumps(dict(operation={operation: identity})).encode()
peer.sendall(struct.pack('>I', len(request)) + request)
assert response(peer)['artifact'] == identity
if operation == 'artifact_put':
peer.sendall(body)
assert response(peer)['artifact'] == identity
else:
read = b''
while len(read) < len(body):
part = peer.recv(len(body) - len(read))
assert part
read += part
assert read == body
"#;
let result = Command::new("/usr/bin/python3")
.args(["-I", "-c", client])
.arg(layout.service_socket())
.arg(layout.service_token())
.uid(65534)
.gid(65534)
.env_clear()
.output()
.unwrap();
assert!(
result.status.success(),
"{}",
String::from_utf8_lossy(&result.stderr)
);
let status = dever_cli::toolchain::cache_status(&layout).unwrap();
assert_eq!(status.artifact_entries, 1);
}
fn identity<'a>(version: &'a Version, generated: &'a [u8], target: &'a str) -> BuildIdentity<'a> {
BuildIdentity {
version,
compiler: b"compiler identity",
runtime: b"runtime identity",
target,
generated,
options: b"release options",
environment: b"backend environment",
}
}
fn signing_key() -> Ed25519KeyPair {
let document = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap();
Ed25519KeyPair::from_pkcs8(document.as_ref()).unwrap()
}
#[cfg(unix)]
fn write_release(
layout: &Layout,
key: &Ed25519KeyPair,
version: &str,
dispatch_log: &Path,
healthy: bool,
valid_signature: bool,
) {
use std::os::unix::fs::PermissionsExt;
let root = layout.downloads().join(version);
fs::create_dir(&root).unwrap();
let core = root.join("dever-core");
let log = dispatch_log.display().to_string().replace('\'', "'\\''");
fs::write(
&core,
format!(
"#!/bin/sh\nif [ \"${{DEVER_TEST_SENTINEL+x}}\" = x ]; then exit 98; fi\nif [ \"$1\" = \"--dever-health\" ]; then exit {}; fi\nif [ \"${{2##*/}}\" = \"exit-42\" ]; then exit 42; fi\nprintf '%s\\n' \"$@\" > '{}'\n",
if healthy { 0 } else { 9 },
log,
),
).unwrap();
fs::set_permissions(&core, fs::Permissions::from_mode(0o755)).unwrap();
let runtime = root.join("runtime.pack");
fs::write(&runtime, format!("runtime {version}")).unwrap();
fs::set_permissions(&runtime, fs::Permissions::from_mode(0o666)).unwrap();
fs::set_permissions(&core, fs::Permissions::from_mode(0o777)).unwrap();
let skill = root.join("skills/dever-language/SKILL.md");
fs::create_dir_all(skill.parent().unwrap()).unwrap();
fs::write(&skill, format!("skill {version}\n")).unwrap();
let manifest = ReleaseManifest::new(
Version::parse(version).unwrap(),
vec![
artifact(&core, "dever-core"),
artifact(&runtime, "runtime.pack"),
artifact(&skill, "skills/dever-language/SKILL.md"),
],
);
let bytes = serde_json::to_vec(&manifest).unwrap();
fs::write(root.join("manifest.json"), &bytes).unwrap();
let mut signature = key.sign(&bytes).as_ref().to_vec();
if !valid_signature {
signature[0] ^= 1;
}
fs::write(root.join("manifest.sig"), format!("{}\n", hex(&signature))).unwrap();
}
fn artifact(path: &Path, relative: &str) -> Artifact {
let bytes = fs::read(path).unwrap();
Artifact {
path: relative.into(),
bytes: bytes.len() as u64,
sha256: hex(&Sha256::digest(&bytes)),
}
}
fn hex(bytes: &[u8]) -> String {
const DIGITS: &[u8; 16] = b"0123456789abcdef";
let mut output = String::with_capacity(bytes.len() * 2);
for byte in bytes {
output.push(DIGITS[usize::from(byte >> 4)] as char);
output.push(DIGITS[usize::from(byte & 0x0f)] as char);
}
output
}