use std::fmt;
use std::path::PathBuf;
use crate::BundleError;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RuntimeRef {
namespace: Option<String>,
name: String,
version: String,
}
impl RuntimeRef {
pub fn parse(s: &str) -> Result<Self, BundleError> {
let segments: Vec<&str> = s.split('/').collect();
let (namespace, name, version) = match segments.as_slice() {
[name, version] => (None, *name, *version),
[ns, name, version] => (Some(*ns), *name, *version),
_ => {
return Err(BundleError::Runtime(format!(
"{s:?} is not a resolvable runtime reference (expected \
\"<name>/<version>\" or \"<namespace>/<name>/<version>\"; \
\"self\" is resolved inside the bundle, not here)"
)))
}
};
for seg in namespace.into_iter().chain([name, version]) {
check_segment(seg, s)?;
}
Ok(Self {
namespace: namespace.map(str::to_string),
name: name.to_string(),
version: version.to_string(),
})
}
pub fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> &str {
&self.version
}
pub fn as_wire(&self) -> String {
match &self.namespace {
Some(ns) => format!("{ns}/{}/{}", self.name, self.version),
None => format!("{}/{}", self.name, self.version),
}
}
pub fn cache_rel_bin(&self, triple: &str, bin: &str) -> PathBuf {
self.cache_rel_dir(triple).join(bin)
}
pub fn cache_rel_dir(&self, triple: &str) -> PathBuf {
let mut p = PathBuf::from("runtimes");
if let Some(ns) = &self.namespace {
p.push(ns);
}
p.push(&self.name);
p.push(&self.version);
p.push(triple);
p
}
pub fn cache_rel_serve(&self, triple: &str) -> PathBuf {
self.cache_rel_bin(triple, SERVE_BIN)
}
pub fn asset_manifest_prefix(&self) -> String {
let mut key = String::from("runtimes/");
if let Some(ns) = &self.namespace {
key.push_str(ns);
key.push('/');
}
key.push_str(&self.name);
key.push('/');
key.push_str(&self.version);
key.push('/');
key
}
pub fn asset_manifest_key(&self, triple: &str) -> String {
let mut key = String::from("runtimes/");
if let Some(ns) = &self.namespace {
key.push_str(ns);
key.push('/');
}
key.push_str(&self.name);
key.push('/');
key.push_str(&self.version);
key.push('/');
key.push_str(triple);
key.push_str(".toml");
key
}
}
impl fmt::Display for RuntimeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.as_wire())
}
}
pub const SERVE_BIN: &str = "serve";
pub fn check_bin_name(bin: &str) -> Result<(), BundleError> {
check_segment(bin, bin).map_err(|_| {
BundleError::Runtime(format!(
"runtime asset binary name {bin:?} is not a plain filename \
(must be non-empty [A-Za-z0-9._+-] and never \".\" or \"..\")"
))
})
}
fn check_segment(seg: &str, whole: &str) -> Result<(), BundleError> {
let ok = !seg.is_empty()
&& seg != "."
&& seg != ".."
&& seg
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'_' | b'+' | b'-'));
if ok {
return Ok(());
}
Err(BundleError::Runtime(format!(
"runtime reference {whole:?} has an unusable segment {seg:?} \
(segments must be non-empty [A-Za-z0-9._+-] and never \".\" or \"..\")"
)))
}
#[cfg(feature = "store")]
pub use asset::{
ensure_runtime_asset, publish_runtime_asset, published_runtime_assets, require_runtime_contract,
PublishedRuntime, RuntimeAsset, RuntimeAssetManifest, RUNTIME_ASSET_SCHEMA_VERSION,
};
pub const FEED_BIN: &str = "almanac-feed";
#[cfg(feature = "store")]
mod asset {
use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use yah_object_store::ObjectStore;
use super::{check_bin_name, RuntimeRef};
use crate::contract::{describe_contracts, ContractRequirement, ContractVersion};
use crate::store::{io_ctx, store_err, touch_access};
use crate::{blob_key, BundleError, BundleHash};
pub const RUNTIME_ASSET_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeAssetManifest {
pub schema_version: u32,
pub runtime: String,
pub triple: String,
#[serde(default = "default_bin")]
pub bin: String,
#[serde(default = "default_contract")]
pub contract: Vec<ContractVersion>,
pub serve: BundleHash,
}
fn default_bin() -> String {
super::SERVE_BIN.to_string()
}
fn default_contract() -> Vec<ContractVersion> {
vec![1]
}
impl RuntimeAssetManifest {
pub fn from_toml_str(s: &str) -> Result<Self, BundleError> {
let m: RuntimeAssetManifest =
toml::from_str(s).map_err(|e| BundleError::Manifest(e.to_string()))?;
if m.schema_version != RUNTIME_ASSET_SCHEMA_VERSION {
return Err(BundleError::SchemaVersion {
found: m.schema_version,
});
}
Ok(m)
}
pub fn to_toml_string(&self) -> Result<String, BundleError> {
toml::to_string_pretty(self).map_err(|e| BundleError::Manifest(e.to_string()))
}
}
#[derive(Debug, Clone)]
pub struct RuntimeAsset {
pub manifest: RuntimeAssetManifest,
pub manifest_key: String,
pub blob_key: String,
pub blob_uploaded: bool,
}
pub fn publish_runtime_asset(
store: &dyn ObjectStore,
runtime: &RuntimeRef,
triple: &str,
bin: &str,
contract: &[ContractVersion],
serve_bin: &Path,
) -> Result<RuntimeAsset, BundleError> {
check_bin_name(bin)?;
if contract.is_empty() {
return Err(BundleError::Manifest(format!(
"refusing to publish {} as {runtime}/{triple} advertising no contract version at \
all — nothing could ever resolve it",
serve_bin.display()
)));
}
let bytes = fs::read(serve_bin).map_err(io_ctx(serve_bin))?;
let hash = BundleHash::of(&bytes);
let bkey = blob_key(&hash);
let blob_uploaded = if store.head(&bkey).map_err(store_err)? {
false
} else {
store.put(&bkey, bytes).map_err(store_err)?;
true
};
let manifest = RuntimeAssetManifest {
schema_version: RUNTIME_ASSET_SCHEMA_VERSION,
runtime: runtime.as_wire(),
triple: triple.to_string(),
bin: bin.to_string(),
contract: contract.to_vec(),
serve: hash,
};
let mkey = runtime.asset_manifest_key(triple);
store
.put(&mkey, manifest.to_toml_string()?.into_bytes())
.map_err(store_err)?;
Ok(RuntimeAsset {
manifest,
manifest_key: mkey,
blob_key: bkey,
blob_uploaded,
})
}
pub fn ensure_runtime_asset(
store: &dyn ObjectStore,
cache_dir: &Path,
runtime: &RuntimeRef,
triple: &str,
bin: &str,
requires: ContractRequirement,
) -> Result<PathBuf, BundleError> {
check_bin_name(bin)?;
let dir = cache_dir.join(runtime.cache_rel_dir(triple));
let dest = dir.join(bin);
if dest.is_file() {
let _ = touch_access(&dir);
return Ok(dest);
}
let mkey = runtime.asset_manifest_key(triple);
let manifest_bytes = store
.get(&mkey)
.map_err(store_err)?
.ok_or_else(|| BundleError::RuntimeAssetMissing {
runtime: runtime.as_wire(),
triple: triple.to_string(),
location: store.locate(&mkey),
})?;
let manifest_text = String::from_utf8(manifest_bytes).map_err(|e| {
BundleError::Manifest(format!("runtime asset manifest {mkey} not utf-8: {e}"))
})?;
let manifest = RuntimeAssetManifest::from_toml_str(&manifest_text)?;
if manifest.runtime != runtime.as_wire() || manifest.triple != triple {
return Err(BundleError::Manifest(format!(
"runtime asset at {mkey} declares {}/{} but was published under {}/{triple} \
— refusing to serve a bundle with a binary built for something else",
manifest.runtime,
manifest.triple,
runtime.as_wire(),
)));
}
if manifest.bin != bin {
return Err(BundleError::Manifest(format!(
"runtime asset at {mkey} publishes the binary {:?} but {} was asked for \
{bin:?} — refusing to fork a binary under a name it was not published as",
manifest.bin,
runtime.as_wire(),
)));
}
if !requires.satisfied_by(&manifest.contract) {
let ContractRequirement::Version(required) = requires else {
unreachable!("Unchecked is satisfied by every advertised set")
};
return Err(BundleError::ContractUnsatisfied {
runtime: runtime.as_wire(),
triple: triple.to_string(),
required,
advertised: describe_contracts(&manifest.contract),
});
}
let bkey = blob_key(&manifest.serve);
let bytes = store
.get(&bkey)
.map_err(store_err)?
.ok_or_else(|| BundleError::MissingBlob {
key: store.locate(&bkey),
path: format!("{}/{triple}/{bin}", runtime.as_wire()),
})?;
let actual = BundleHash::of(&bytes);
if actual != manifest.serve {
return Err(BundleError::HashMismatch {
what: format!("runtime asset {}/{triple}", runtime.as_wire()),
expected: manifest.serve.to_string(),
actual: actual.to_string(),
});
}
fs::create_dir_all(&dir).map_err(io_ctx(&dir))?;
let staging = dir.join(format!(".staging-{}", manifest.serve.as_str()));
fs::write(&staging, &bytes).map_err(io_ctx(&staging))?;
set_executable(&staging)?;
fs::rename(&staging, &dest).map_err(io_ctx(&dest))?;
let _ = touch_access(&dir);
Ok(dest)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PublishedRuntime {
pub triple: String,
pub contract: Vec<ContractVersion>,
}
pub fn published_runtime_assets(
store: &dyn ObjectStore,
runtime: &RuntimeRef,
) -> Result<Vec<PublishedRuntime>, BundleError> {
let prefix = runtime.asset_manifest_prefix();
let mut out = Vec::new();
for key in store.list_prefix(&prefix).map_err(store_err)? {
let Some(rest) = key.strip_prefix(&prefix) else {
continue;
};
let Some(triple) = rest.strip_suffix(".toml") else {
continue;
};
if triple.contains('/') || triple.is_empty() {
continue;
}
let bytes = store.get(&key).map_err(store_err)?.ok_or_else(|| {
BundleError::Manifest(format!("runtime asset {key} was listed but could not be read"))
})?;
let text = String::from_utf8(bytes).map_err(|e| {
BundleError::Manifest(format!("runtime asset manifest {key} not utf-8: {e}"))
})?;
let manifest = RuntimeAssetManifest::from_toml_str(&text)?;
out.push(PublishedRuntime {
triple: triple.to_string(),
contract: manifest.contract,
});
}
out.sort_by(|a, b| a.triple.cmp(&b.triple));
Ok(out)
}
pub fn require_runtime_contract(
store: &dyn ObjectStore,
runtime: &RuntimeRef,
required: ContractVersion,
) -> Result<Vec<String>, BundleError> {
let published = published_runtime_assets(store, runtime)?;
let requirement = ContractRequirement::Version(required);
for asset in &published {
if !requirement.satisfied_by(&asset.contract) {
return Err(BundleError::ContractUnsatisfied {
runtime: runtime.as_wire(),
triple: asset.triple.clone(),
required,
advertised: describe_contracts(&asset.contract),
});
}
}
Ok(published.into_iter().map(|a| a.triple).collect())
}
#[cfg(unix)]
fn set_executable(path: &Path) -> Result<(), BundleError> {
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path).map_err(io_ctx(path))?.permissions();
perms.set_mode(0o755);
fs::set_permissions(path, perms).map_err(io_ctx(path))
}
#[cfg(not(unix))]
fn set_executable(_path: &Path) -> Result<(), BundleError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stock_is_the_unnamespaced_case_and_custom_is_namespaced() {
let stock = RuntimeRef::parse("mesofact/0.8.20").unwrap();
assert_eq!(stock.namespace(), None);
assert_eq!(stock.name(), "mesofact");
assert_eq!(stock.version(), "0.8.20");
assert_eq!(stock.as_wire(), "mesofact/0.8.20");
let custom = RuntimeRef::parse("acme/site/1.2.3").unwrap();
assert_eq!(custom.namespace(), Some("acme"));
assert_eq!(custom.name(), "site");
assert_eq!(custom.version(), "1.2.3");
assert_eq!(custom.as_wire(), "acme/site/1.2.3");
}
#[test]
fn cache_layout_is_namespace_name_version_triple() {
let triple = "x86_64-unknown-linux-musl";
assert_eq!(
RuntimeRef::parse("mesofact/0.8.20")
.unwrap()
.cache_rel_serve(triple),
PathBuf::from("runtimes/mesofact/0.8.20/x86_64-unknown-linux-musl/serve")
);
assert_eq!(
RuntimeRef::parse("acme/site/1.2.3")
.unwrap()
.cache_rel_serve(triple),
PathBuf::from("runtimes/acme/site/1.2.3/x86_64-unknown-linux-musl/serve")
);
assert_eq!(
RuntimeRef::parse("mesofact/0.8.20")
.unwrap()
.asset_manifest_key(triple),
"runtimes/mesofact/0.8.20/x86_64-unknown-linux-musl.toml"
);
}
#[test]
fn traversal_and_junk_segments_are_rejected() {
assert!(RuntimeRef::parse("mesofact/../../etc").is_err());
assert!(RuntimeRef::parse("../mesofact/0.1.0").is_err());
assert!(RuntimeRef::parse("mesofact/.").is_err());
assert!(RuntimeRef::parse("mesofact/").is_err());
assert!(RuntimeRef::parse("/0.1.0").is_err());
assert!(RuntimeRef::parse("a/b/c/d").is_err());
assert!(RuntimeRef::parse("mesofact").is_err());
assert!(RuntimeRef::parse("self").is_err());
assert!(RuntimeRef::parse("mesofact/0.8 20").is_err());
assert!(RuntimeRef::parse("mesofact/$(id)").is_err());
}
#[cfg(feature = "store")]
mod asset {
use super::*;
use crate::runtime::{
ensure_runtime_asset, publish_runtime_asset, published_runtime_assets,
require_runtime_contract, PublishedRuntime, RuntimeAssetManifest,
};
use crate::{
blob_key, BundleError, BundleHash, ContractRequirement, ContractVersion,
BUNDLE_CONTRACT_VERSION, IMPLEMENTED_CONTRACTS,
};
use tempfile::TempDir;
use yah_object_store::{InMemoryObjectStore, ObjectStore};
const TRIPLE: &str = "x86_64-unknown-linux-musl";
const V1: &[ContractVersion] = IMPLEMENTED_CONTRACTS;
const REQ1: ContractRequirement = ContractRequirement::Version(BUNDLE_CONTRACT_VERSION);
fn write_bin(dir: &TempDir, bytes: &[u8]) -> std::path::PathBuf {
let p = dir.path().join("mesofact-serve");
std::fs::write(&p, bytes).unwrap();
p
}
#[test]
fn publish_then_resolve_round_trips_and_lands_executable() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
let published =
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, V1, &write_bin(&src, b"ELF serve")).unwrap();
assert!(published.blob_uploaded);
let resolved = ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).unwrap();
assert_eq!(
resolved,
cache.path().join("runtimes/mesofact/0.8.20").join(TRIPLE).join("serve")
);
assert_eq!(std::fs::read(&resolved).unwrap(), b"ELF serve");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(&resolved).unwrap().permissions().mode();
assert!(mode & 0o111 != 0, "a runtime asset must land forkable, mode={mode:o}");
}
}
#[test]
fn a_second_resolve_at_the_same_version_is_a_cache_hit() {
struct CountingStore {
inner: InMemoryObjectStore,
gets: std::sync::atomic::AtomicUsize,
}
impl ObjectStore for CountingStore {
fn put(&self, key: &str, data: Vec<u8>) -> Result<(), yah_object_store::Error> {
self.inner.put(key, data)
}
fn get(&self, key: &str) -> Result<Option<Vec<u8>>, yah_object_store::Error> {
self.gets.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
self.inner.get(key)
}
fn delete(&self, key: &str) -> Result<(), yah_object_store::Error> {
self.inner.delete(key)
}
fn list_prefix(&self, p: &str) -> Result<Vec<String>, yah_object_store::Error> {
self.inner.list_prefix(p)
}
}
let store = CountingStore {
inner: InMemoryObjectStore::new(),
gets: std::sync::atomic::AtomicUsize::new(0),
};
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, V1, &write_bin(&src, b"ELF serve")).unwrap();
ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).unwrap();
let after_cold = store.gets.load(std::sync::atomic::Ordering::SeqCst);
assert!(after_cold >= 2, "cold resolve fetches manifest + blob");
ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).unwrap();
assert_eq!(
store.gets.load(std::sync::atomic::Ordering::SeqCst),
after_cold,
"a second bundle at the same runtime version must fetch nothing"
);
}
#[test]
fn versions_do_not_alias() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let old = RuntimeRef::parse("mesofact/0.8.19").unwrap();
let new = RuntimeRef::parse("mesofact/0.8.20").unwrap();
publish_runtime_asset(&store, &old, TRIPLE, SERVE_BIN, V1, &write_bin(&src, b"old serve")).unwrap();
ensure_runtime_asset(&store, cache.path(), &old, TRIPLE, SERVE_BIN, REQ1).unwrap();
let err = ensure_runtime_asset(&store, cache.path(), &new, TRIPLE, SERVE_BIN, REQ1).unwrap_err();
assert!(
matches!(err, BundleError::RuntimeAssetMissing { .. }),
"got {err:?}"
);
}
#[test]
fn an_unpublished_version_fails_naming_version_and_location() {
let store = InMemoryObjectStore::new();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/9.9.9").unwrap();
let err = ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("mesofact/9.9.9"), "got {msg}");
assert!(msg.contains(TRIPLE), "got {msg}");
assert!(
msg.contains("runtimes/mesofact/9.9.9/x86_64-unknown-linux-musl.toml"),
"the message must name what it looked for, got {msg}"
);
assert!(
!cache.path().join("runtimes").exists(),
"a failed resolve must not leave a partial runtime dir"
);
}
#[test]
fn tampered_bytes_are_refused_and_never_written() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
let published =
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, V1, &write_bin(&src, b"ELF serve")).unwrap();
store.put(&published.blob_key, b"malware".to_vec()).unwrap();
let err = ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).unwrap_err();
assert!(matches!(err, BundleError::HashMismatch { .. }), "got {err:?}");
assert!(
!cache.path().join(rref.cache_rel_serve(TRIPLE)).exists(),
"unverified bytes must never reach the resolved path"
);
}
#[test]
fn a_misfiled_asset_manifest_is_refused() {
let store = InMemoryObjectStore::new();
let cache = TempDir::new().unwrap();
let asked = RuntimeRef::parse("mesofact/0.8.20").unwrap();
let bytes = b"ELF serve".to_vec();
let hash = BundleHash::of(&bytes);
store.put(&blob_key(&hash), bytes).unwrap();
let manifest = RuntimeAssetManifest {
schema_version: 1,
runtime: "mesofact/0.8.19".to_string(),
triple: TRIPLE.to_string(),
bin: SERVE_BIN.to_string(),
contract: V1.to_vec(),
serve: hash,
};
store
.put(
&asked.asset_manifest_key(TRIPLE),
manifest.to_toml_string().unwrap().into_bytes(),
)
.unwrap();
let err = ensure_runtime_asset(&store, cache.path(), &asked, TRIPLE, SERVE_BIN, REQ1).unwrap_err();
assert!(err.to_string().contains("0.8.19"), "got {err}");
}
#[test]
fn identical_binaries_dedupe_against_the_bundle_blob_space() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
let bin = write_bin(&src, b"ELF serve");
let first = publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, V1, &bin).unwrap();
assert!(first.blob_uploaded);
let other = RuntimeRef::parse("acme/site/1.0.0").unwrap();
let second = publish_runtime_asset(&store, &other, TRIPLE, SERVE_BIN, V1, &bin).unwrap();
assert!(!second.blob_uploaded, "identical bytes must not re-upload");
assert_eq!(first.blob_key, second.blob_key);
}
#[test]
fn a_sidecar_asset_resolves_under_its_own_binary_name() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let feed = RuntimeRef::parse("almanac-feed/0.8.22").unwrap();
publish_runtime_asset(&store, &feed, TRIPLE, FEED_BIN, V1, &write_bin(&src, b"ELF feed"))
.unwrap();
let resolved = ensure_runtime_asset(&store, cache.path(), &feed, TRIPLE, FEED_BIN, REQ1)
.unwrap();
assert_eq!(
resolved,
cache
.path()
.join("runtimes/almanac-feed/0.8.22")
.join(TRIPLE)
.join(FEED_BIN)
);
assert_eq!(std::fs::read(&resolved).unwrap(), b"ELF feed");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = std::fs::metadata(&resolved).unwrap().permissions().mode();
assert!(mode & 0o111 != 0, "a sidecar asset must land forkable");
}
}
#[test]
fn an_asset_published_under_another_binary_name_is_refused() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.22").unwrap();
publish_runtime_asset(&store, &rref, TRIPLE, FEED_BIN, V1, &write_bin(&src, b"ELF feed"))
.unwrap();
let err =
ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).unwrap_err();
let msg = err.to_string();
assert!(msg.contains(FEED_BIN) && msg.contains(SERVE_BIN), "got {msg}");
assert!(
!cache.path().join(rref.cache_rel_serve(TRIPLE)).exists(),
"a refused resolve must leave nothing forkable behind"
);
}
#[test]
fn serve_and_feed_assets_do_not_share_a_cache_slot() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let serve_ref = RuntimeRef::parse("mesofact/0.8.22").unwrap();
let feed_ref = RuntimeRef::parse("almanac-feed/0.8.22").unwrap();
publish_runtime_asset(&store, &serve_ref, TRIPLE, SERVE_BIN, V1, &write_bin(&src, b"S"))
.unwrap();
let feed_src = TempDir::new().unwrap();
publish_runtime_asset(&store, &feed_ref, TRIPLE, FEED_BIN, V1, &write_bin(&feed_src, b"F"))
.unwrap();
let s = ensure_runtime_asset(&store, cache.path(), &serve_ref, TRIPLE, SERVE_BIN, REQ1)
.unwrap();
let f =
ensure_runtime_asset(&store, cache.path(), &feed_ref, TRIPLE, FEED_BIN, REQ1).unwrap();
assert_ne!(s, f);
assert_eq!(std::fs::read(&s).unwrap(), b"S");
assert_eq!(std::fs::read(&f).unwrap(), b"F");
}
#[test]
fn a_traversing_binary_name_is_refused() {
for bad in ["../serve", "a/b", "..", ".", "", "serve bin"] {
assert!(
crate::runtime::check_bin_name(bad).is_err(),
"{bad:?} must not become a cache path"
);
}
assert!(crate::runtime::check_bin_name(FEED_BIN).is_ok());
assert!(crate::runtime::check_bin_name(SERVE_BIN).is_ok());
}
#[test]
fn a_runtime_that_does_not_implement_the_required_contract_is_refused() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, &[1], &write_bin(&src, b"ELF"))
.unwrap();
let err = ensure_runtime_asset(
&store,
cache.path(),
&rref,
TRIPLE,
SERVE_BIN,
ContractRequirement::Version(2),
)
.unwrap_err();
let msg = err.to_string();
assert!(matches!(err, BundleError::ContractUnsatisfied { .. }), "got {err:?}");
assert!(msg.contains("mesofact/0.8.20") && msg.contains(TRIPLE), "got {msg}");
assert!(msg.contains('1') && msg.contains('2'), "both versions, got {msg}");
assert!(
!cache.path().join(rref.cache_rel_serve(TRIPLE)).exists(),
"a refused resolve must leave nothing forkable behind"
);
}
#[test]
fn a_runtime_advertising_a_set_serves_every_version_in_it() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.9.0").unwrap();
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, &[1, 2], &write_bin(&src, b"ELF"))
.unwrap();
for required in [1, 2] {
let cache = TempDir::new().unwrap();
assert!(
ensure_runtime_asset(
&store,
cache.path(),
&rref,
TRIPLE,
SERVE_BIN,
ContractRequirement::Version(required),
)
.is_ok(),
"contract {required} is advertised and must resolve"
);
}
}
#[test]
fn an_unchecked_requirement_resolves_a_sidecar_whatever_it_advertises() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let feed = RuntimeRef::parse("almanac-feed/0.8.22").unwrap();
publish_runtime_asset(&store, &feed, TRIPLE, FEED_BIN, &[99], &write_bin(&src, b"F"))
.unwrap();
assert!(ensure_runtime_asset(
&store,
cache.path(),
&feed,
TRIPLE,
FEED_BIN,
ContractRequirement::Unchecked,
)
.is_ok());
}
#[test]
fn an_asset_manifest_without_a_contract_field_advertises_contract_1() {
let store = InMemoryObjectStore::new();
let cache = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.18").unwrap();
let bytes = b"ELF serve".to_vec();
let hash = BundleHash::of(&bytes);
store.put(&blob_key(&hash), bytes).unwrap();
store
.put(
&rref.asset_manifest_key(TRIPLE),
format!(
"schema_version = 1\nruntime = \"mesofact/0.8.18\"\n\
triple = \"{TRIPLE}\"\nbin = \"serve\"\nserve = \"{hash}\"\n"
)
.into_bytes(),
)
.unwrap();
let parsed = RuntimeAssetManifest::from_toml_str(
&String::from_utf8(store.get(&rref.asset_manifest_key(TRIPLE)).unwrap().unwrap())
.unwrap(),
)
.unwrap();
assert_eq!(parsed.contract, vec![1]);
assert!(
ensure_runtime_asset(&store, cache.path(), &rref, TRIPLE, SERVE_BIN, REQ1).is_ok(),
"a pre-F6 asset must keep serving a contract-1 bundle"
);
}
#[test]
fn publishing_an_asset_that_advertises_no_contract_is_refused() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
let err =
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, &[], &write_bin(&src, b"E"))
.unwrap_err();
assert!(err.to_string().contains("no contract version"), "got {err}");
assert!(
store.list_prefix(&rref.asset_manifest_prefix()).unwrap().is_empty(),
"a refused publish must write no manifest"
);
}
#[test]
fn the_survey_reports_every_published_triple_for_a_runtime() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
publish_runtime_asset(&store, &rref, "x86_64-unknown-linux-musl", SERVE_BIN, &[1], &write_bin(&src, b"x")).unwrap();
let src2 = TempDir::new().unwrap();
publish_runtime_asset(&store, &rref, "aarch64-unknown-linux-musl", SERVE_BIN, &[1, 2], &write_bin(&src2, b"a")).unwrap();
let other = RuntimeRef::parse("mesofact/0.8.2").unwrap();
let src3 = TempDir::new().unwrap();
publish_runtime_asset(&store, &other, "x86_64-unknown-linux-musl", SERVE_BIN, &[9], &write_bin(&src3, b"o")).unwrap();
assert_eq!(
published_runtime_assets(&store, &rref).unwrap(),
vec![
PublishedRuntime {
triple: "aarch64-unknown-linux-musl".to_string(),
contract: vec![1, 2],
},
PublishedRuntime {
triple: "x86_64-unknown-linux-musl".to_string(),
contract: vec![1],
},
]
);
}
#[test]
fn the_apply_gate_refuses_a_runtime_that_cannot_serve_the_bundle() {
let store = InMemoryObjectStore::new();
let src = TempDir::new().unwrap();
let rref = RuntimeRef::parse("mesofact/0.8.20").unwrap();
publish_runtime_asset(&store, &rref, TRIPLE, SERVE_BIN, &[1], &write_bin(&src, b"x"))
.unwrap();
assert_eq!(
require_runtime_contract(&store, &rref, 1).unwrap(),
vec![TRIPLE.to_string()]
);
let err = require_runtime_contract(&store, &rref, 2).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("mesofact/0.8.20"), "got {msg}");
assert!(msg.contains(TRIPLE), "got {msg}");
assert!(msg.contains('1') && msg.contains('2'), "both versions, got {msg}");
}
#[test]
fn one_stale_triple_fails_the_apply_for_all_of_them() {
let store = InMemoryObjectStore::new();
let rref = RuntimeRef::parse("mesofact/0.9.0").unwrap();
let a = TempDir::new().unwrap();
let b = TempDir::new().unwrap();
publish_runtime_asset(&store, &rref, "x86_64-unknown-linux-musl", SERVE_BIN, &[1, 2], &write_bin(&a, b"new")).unwrap();
publish_runtime_asset(&store, &rref, "aarch64-unknown-linux-musl", SERVE_BIN, &[1], &write_bin(&b, b"old")).unwrap();
let err = require_runtime_contract(&store, &rref, 2).unwrap_err();
assert!(
err.to_string().contains("aarch64-unknown-linux-musl"),
"the refusal must name the triple that is behind, got {err}"
);
}
#[test]
fn an_unpublished_runtime_surveys_empty_rather_than_failing_the_gate() {
let store = InMemoryObjectStore::new();
let rref = RuntimeRef::parse("mesofact/9.9.9").unwrap();
assert!(require_runtime_contract(&store, &rref, 1).unwrap().is_empty());
}
}
}