Files
MetaCrate/crates/libremetaverse/src/asset_pipeline_semantics.rs
Chili Palmer c9a1170a27
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Complete first release candidate audit (#107)
2026-08-12 14:44:28 +00:00

157 lines
5.1 KiB
Rust

#[cfg(feature = "jpeg2000")]
use crate::assets::AssetTexture;
use crate::assets::{AssetMesh, AssetMutable, AssetSound};
use crate::{
AssetCache, AssetCacheComputeAssetCacheFilenameDelegate, Error, GridClient, ImageCodec,
};
#[cfg(feature = "jpeg2000")]
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_types::{AssetType, UUID};
use std::fs;
use std::path::PathBuf;
fn fixture(max_size: i64) -> (GridClient, AssetCache, PathBuf) {
let path =
std::env::temp_dir().join(format!("metacrate-asset-cache-{}", UUID::random().unwrap()));
let mut client = GridClient::new().unwrap();
let settings = client.settings();
settings.asset_cache_mut().dir = path.to_string_lossy().into_owned();
settings.asset_cache_mut().enabled = true;
settings.asset_cache_mut().max_size = max_size;
let cache = AssetCache::new(client.clone()).unwrap();
(client, cache, path)
}
#[test]
fn asset_models_retain_type_and_reject_malformed_meshes() {
let asset = AssetMutable::new_with_asset_type_uuid_bytes(
AssetType::Sound,
UUID::random().unwrap(),
vec![1, 2, 3],
)
.unwrap();
assert_eq!(asset.asset_type(), AssetType::Sound);
assert!(asset.decode().unwrap());
let malformed =
AssetMesh::new_with_uuid_bytes(UUID::random().unwrap(), b"not-llsd".to_vec()).unwrap();
assert!(!malformed.decode().unwrap());
}
#[test]
#[cfg(feature = "vorbis")]
fn sound_codec_produces_real_valid_payload() {
let ogg = AssetSound::pcm_to_ogg(vec![0_u8; 256 * 2], 44_100, 1, Some(16)).unwrap();
assert!(ogg.starts_with(b"OggS"));
}
#[test]
#[cfg(not(feature = "vorbis"))]
fn sound_codec_is_explicitly_feature_gated() {
assert_eq!(
AssetSound::pcm_to_ogg(vec![0_u8; 256 * 2], 44_100, 1, Some(16)),
Err(Error::InvalidOperation)
);
assert_eq!(
AssetSound::pcm_to_ogg(Vec::new(), 44_100, 1, Some(16)),
Err(Error::Argument)
);
}
#[test]
#[cfg(feature = "jpeg2000")]
fn texture_codec_produces_real_valid_payload() {
let mut image = ManagedImage::new(2, 2, ManagedImageImageChannels::COLOR).unwrap();
image.red.fill(255);
let texture = AssetTexture::new_with_managed_image(image).unwrap();
texture.encode().unwrap();
assert!(texture.decode().unwrap());
}
#[test]
fn cache_writes_are_atomic_and_corruption_is_typed() {
let (_client, cache, path) = fixture(1024);
let id = UUID::random().unwrap();
cache
.save_asset_to_cache_with_uuid_bytes(id, vec![1, 2, 3, 4])
.unwrap();
cache
.save_asset_to_cache_with_uuid_bytes(id, vec![9, 9, 9, 9])
.unwrap();
assert_eq!(
cache.get_cached_asset_bytes_with_uuid(id).unwrap(),
Some(vec![1, 2, 3, 4])
);
let image = cache.get_cached_image(id).unwrap().unwrap();
assert_eq!(image.codec, ImageCodec::J2C);
assert_eq!(image.base.asset_data, vec![1, 2, 3, 4]);
let corrupt = UUID::random().unwrap();
fs::write(path.join(corrupt.to_string()), []).unwrap();
assert_eq!(
cache.get_cached_asset_bytes_with_uuid(corrupt),
Err(Error::Argument)
);
fs::remove_dir_all(path).unwrap();
}
#[test]
fn custom_cache_naming_is_used_but_cannot_escape_the_cache_directory() {
let (_client, mut cache, path) = fixture(1024);
cache.compute_asset_cache_filename = Some(
AssetCacheComputeAssetCacheFilenameDelegate::from_handler(|directory, id| {
Ok(PathBuf::from(directory)
.join(format!("{id}.asset"))
.to_string_lossy()
.into_owned())
}),
);
let id = UUID::random().unwrap();
cache
.save_asset_to_cache_with_uuid_bytes(id, vec![7, 8, 9])
.unwrap();
assert_eq!(
cache.get_cached_asset_bytes_with_uuid(id).unwrap(),
Some(vec![7, 8, 9])
);
assert!(path.join(format!("{id}.asset")).is_file());
cache.clear().unwrap();
assert!(!path.join(format!("{id}.asset")).exists());
cache.compute_asset_cache_filename = Some(
AssetCacheComputeAssetCacheFilenameDelegate::from_handler(|directory, _| {
Ok(PathBuf::from(directory)
.join("..")
.join("escape")
.to_string_lossy()
.into_owned())
}),
);
assert_eq!(
cache.has_asset(UUID::random().unwrap()),
Err(Error::Argument)
);
fs::remove_dir_all(path).unwrap();
}
#[tokio::test]
async fn cache_pruning_reclaims_old_entries_to_below_the_limit() {
let (_client, cache, path) = fixture(12);
cache
.save_asset_to_cache_with_uuid_bytes(UUID::random().unwrap(), vec![1; 8])
.unwrap();
std::thread::sleep(std::time::Duration::from_millis(5));
cache
.save_asset_to_cache_with_uuid_bytes(UUID::random().unwrap(), vec![2; 8])
.unwrap();
cache.prune(None).await.unwrap();
let bytes: u64 = fs::read_dir(&path)
.unwrap()
.filter_map(Result::ok)
.filter_map(|entry| entry.metadata().ok())
.map(|metadata| metadata.len())
.sum();
assert!(bytes <= 10);
fs::remove_dir_all(path).unwrap();
}