157 lines
5.1 KiB
Rust
157 lines
5.1 KiB
Rust
#[cfg(any(feature = "jpeg2000", feature = "rust-j2k"))]
|
|
use crate::assets::AssetTexture;
|
|
use crate::assets::{AssetMesh, AssetMutable, AssetSound};
|
|
use crate::{
|
|
AssetCache, AssetCacheComputeAssetCacheFilenameDelegate, Error, GridClient, ImageCodec,
|
|
};
|
|
#[cfg(any(feature = "jpeg2000", feature = "rust-j2k"))]
|
|
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(any(feature = "jpeg2000", feature = "rust-j2k"))]
|
|
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();
|
|
}
|