Files
MetaCrate/tests/compat/tests/inventory_store_semantics.rs

546 lines
20 KiB
Rust

// Exact Rust translations of LibreMetaverse.Tests/InventoryTests.cs.
// Source SHA-256: 378ab668bdea082d90fb0105e2bcd6b7734d7fe9eb9c1bf550554bd4ef700953
use libremetaverse::{GridClient, Inventory, InventoryFolder, InventoryItem, InventoryNode};
use libremetaverse_compat_tests::block_on;
use libremetaverse_types::{AssetType, UUID};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
fn uuid() -> UUID {
UUID::random().expect("UUID Random")
}
struct Fixture {
client: Arc<GridClient>,
inventory: Inventory,
}
impl Fixture {
fn new() -> Self {
let client = Arc::new(GridClient::new().expect("GridClient constructor"));
let inventory = Inventory::new_with_grid_client_uuid(Arc::clone(&client), uuid())
.expect("Inventory constructor");
Self { client, inventory }
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = self.client.dispose_with_method();
}
}
fn folder(name: &str, parent: UUID) -> InventoryFolder {
let mut folder = InventoryFolder::new(uuid()).expect("InventoryFolder constructor");
folder.base.set_name(name.into());
folder.base.set_parent_uuid(parent);
folder
}
fn item(name: &str, parent: UUID) -> InventoryItem {
let mut item = InventoryItem::new_with_uuid(uuid()).expect("InventoryItem constructor");
item.base.set_name(name.into());
item.base.set_parent_uuid(parent);
item
}
struct TempDirectory(PathBuf);
impl TempDirectory {
fn new() -> Self {
let path = std::env::temp_dir().join(format!("metacrate-inventory-{}", uuid().to_string()));
std::fs::create_dir(&path).expect("create isolated temporary directory");
Self(path)
}
fn file(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
impl Drop for TempDirectory {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn path_string(path: &Path) -> String {
path.to_string_lossy().into_owned()
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.AddAndGetItem::test 24702f0cf9215beee29123ee81dab4a7fe971beb197ecceac9a1761b695bce45 translated
#[test]
fn add_and_get_item() {
let fixture = Fixture::new();
let folder = folder("TestFolder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("Item1", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
assert!(fixture.inventory.contains_with_uuid(item_id).unwrap());
let mut found = None;
assert!(
fixture
.inventory
.try_get_value_with_uuid_inventory_base(item_id, &mut found)
.unwrap()
);
let found = found.expect("inventory item");
assert!(found.as_any().is::<InventoryItem>());
assert_eq!(found.inventory_base().name(), "Item1");
let contents = fixture
.inventory
.get_contents_with_inventory_folder(&folder)
.unwrap();
assert_eq!(contents.len(), 1);
assert_eq!(contents[0].inventory_base().uuid(), item_id);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateItemParent::test ea1a73e28fb92b873ee28102e81811d769b5cd815d2e1d756ece6ea635e5c014 translated
#[test]
fn update_item_parent() {
let fixture = Fixture::new();
let folder1 = folder("F1", UUID::zero());
let folder2 = folder("F2", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
let mut item = item("Item", folder1_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
assert_eq!(item.base.parent_uuid(), folder1_id);
item.base.set_parent_uuid(folder2_id);
fixture.inventory.update_node_for(&item).unwrap();
let contents1 = fixture
.inventory
.get_contents_with_inventory_folder(&folder1)
.unwrap();
let contents2 = fixture
.inventory
.get_contents_with_inventory_folder(&folder2)
.unwrap();
assert!(
contents1
.iter()
.all(|entry| entry.inventory_base().uuid() != item_id)
);
assert!(
contents2
.iter()
.any(|entry| entry.inventory_base().uuid() == item_id)
);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.InventoryNodeModifyTime_SelfLoop::test f8050cc284f7954c1301091ddc1cbf0fa76073806eee7de4f6cb21063591fbf1 translated
#[test]
fn inventory_node_modify_time_self_loop() {
let folder_id = uuid();
let mut folder = InventoryFolder::new(folder_id).unwrap();
folder.base.set_name("F1".into());
folder.base.set_parent_uuid(folder_id);
let node = InventoryNode::new_with_inventory_base(&folder).unwrap();
assert_eq!(node.modify_time(), std::time::SystemTime::UNIX_EPOCH);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateNodeFor_SelfLoop::test 14f42e5263c465e8534347c5a3195c195e2d90371dbc2f69d36a40362384505c translated
#[test]
fn update_node_for_self_loop() {
let fixture = Fixture::new();
let folder_id = uuid();
let mut folder = InventoryFolder::new(folder_id).unwrap();
folder.base.set_name("F1".into());
folder.base.set_parent_uuid(folder_id);
fixture.inventory.update_node_for(&folder).unwrap();
assert!(fixture.inventory.contains_with_uuid(folder_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.UpdateNodeFor_ComplexSelfLoop::test a2fc7b29b8a7a7767c42ea9c7fb9628451c88991c3eb1ecbd2ce0952ab0b5700 translated
#[test]
fn update_node_for_complex_self_loop() {
let fixture = Fixture::new();
let folder1_id = uuid();
let folder2_id = uuid();
let mut folder1 = InventoryFolder::new(folder1_id).unwrap();
folder1.base.set_name("F1".into());
folder1.base.set_parent_uuid(folder2_id);
let mut folder2 = InventoryFolder::new(folder2_id).unwrap();
folder2.base.set_name("F2".into());
folder2.base.set_parent_uuid(folder1_id);
fixture.inventory.update_node_for(&folder1).unwrap();
assert!(fixture.inventory.contains_with_uuid(folder1_id).unwrap());
assert!(fixture.inventory.contains_with_uuid(folder2_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.RemoveItem::test 1e72f9049b9114f0c56e152d84190d31bca7a862f7ad1459624d753beb6818b4 translated
#[test]
fn remove_item() {
let fixture = Fixture::new();
let folder = folder("Folder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("ToRemove", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
assert!(fixture.inventory.contains_with_uuid(item_id).unwrap());
fixture.inventory.remove_node_for(&item).unwrap();
assert!(!fixture.inventory.contains_with_uuid(item_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.SaveAndRestoreAsync::test bf23b1c67cc9618713d2d8e6653f0ffbbf4c24d7f2c59c5dd639047f7e0c8156 translated
#[test]
fn save_and_restore_async() {
let fixture = Fixture::new();
let folder = folder("FolderSave", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("SaveItem", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
let temporary = TempDirectory::new();
let file = path_string(&temporary.file("inventory.bin"));
block_on(
fixture
.inventory
.save_to_disk_with_string_cancellation_token(file.clone(), None),
)
.unwrap();
fixture.inventory.clear().unwrap();
assert!(!fixture.inventory.contains_with_uuid(item_id).unwrap());
let count = block_on(
fixture
.inventory
.restore_from_disk_with_string_cancellation_token(file, None),
)
.unwrap();
assert!(count >= 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.GetNodeOrDefault_ReturnsNullWhenMissing::test f5651b5a143470c3ab6ba1f835206a1a55a6218c28242dc12c5d6b5e89519596 translated
#[test]
fn get_node_or_default_returns_null_when_missing() {
assert!(
Fixture::new()
.inventory
.get_node_or_default(uuid())
.unwrap()
.is_none()
);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.TryGetValue_Generic::test e6f6d6535af9d011e65cb3c297fe99543f85b64382c47e1756f051d6031886f5 translated
#[test]
fn try_get_value_generic() {
let fixture = Fixture::new();
let folder = folder("GenFolder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let item = item("GenItem", folder_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&item).unwrap();
let mut found = None;
assert!(
fixture
.inventory
.try_get_value_with_uuid_t::<InventoryItem>(item_id, &mut found)
);
let found = found.expect("generic inventory item");
assert_eq!(found.base.uuid(), item_id);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.RemoveFolderRemovesAllDescendants::test b537223d271b75c11adfa6d87c1e79bb29366f294657ddf71f236fab0dee009f translated
#[test]
fn remove_folder_removes_all_descendants() {
let fixture = Fixture::new();
let parent = folder("Parent", UUID::zero());
let parent_id = parent.base.uuid();
let child = folder("ChildFolder", parent_id);
let child_id = child.base.uuid();
let item = item("NestedItem", child_id);
let item_id = item.base.uuid();
fixture.inventory.update_node_for(&parent).unwrap();
fixture.inventory.update_node_for(&child).unwrap();
fixture.inventory.update_node_for(&item).unwrap();
assert!(fixture.inventory.contains_with_uuid(parent_id).unwrap());
assert!(fixture.inventory.contains_with_uuid(child_id).unwrap());
assert!(fixture.inventory.contains_with_uuid(item_id).unwrap());
fixture.inventory.remove_node_for(&parent).unwrap();
assert!(!fixture.inventory.contains_with_uuid(parent_id).unwrap());
assert!(!fixture.inventory.contains_with_uuid(child_id).unwrap());
assert!(!fixture.inventory.contains_with_uuid(item_id).unwrap());
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.DescendentCountUpdatesOnAddAndRemove::test cfe6571887d0e992ff4d69ccbcdfb34012f9bb5d62118201c33d7d66cc9ddfd8 translated
#[test]
fn descendent_count_updates_on_add_and_remove() {
let fixture = Fixture::new();
let folder = folder("Folder", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().expect("folder node data");
let folder_data = data
.as_any()
.downcast_ref::<InventoryFolder>()
.expect("InventoryFolder data");
assert_eq!(folder_data.descendent_count(), 0);
let item = item("Item", folder_id);
fixture.inventory.update_node_for(&item).unwrap();
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().expect("folder node data after add");
let folder_data = data.as_any().downcast_ref::<InventoryFolder>().unwrap();
assert!(folder_data.descendent_count() > 0);
fixture.inventory.remove_node_for(&item).unwrap();
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().expect("folder node data after remove");
let folder_data = data.as_any().downcast_ref::<InventoryFolder>().unwrap();
assert_eq!(folder_data.descendent_count(), 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.MovingItemUpdatesAncestorDescendentCounts::test 1f4b4189694b9471d48562a434c91a9e455706b83b65bb855c0846388b44c759 translated
#[test]
fn moving_item_updates_ancestor_descendent_counts() {
let fixture = Fixture::new();
let folder1 = folder("F1", UUID::zero());
let folder2 = folder("F2", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
let mut item = item("Movable", folder1_id);
fixture.inventory.update_node_for(&item).unwrap();
let first = fixture.inventory.get_node_for(folder1_id).unwrap();
let first_data = first.data().unwrap();
let first_folder = first_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
let second = fixture.inventory.get_node_for(folder2_id).unwrap();
let second_data = second.data().unwrap();
let second_folder = second_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
assert!(first_folder.descendent_count() > 0);
assert_eq!(second_folder.descendent_count(), 0);
item.base.set_parent_uuid(folder2_id);
fixture.inventory.update_node_for(&item).unwrap();
let first = fixture.inventory.get_node_for(folder1_id).unwrap();
let first_data = first.data().unwrap();
let first_folder = first_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
let second = fixture.inventory.get_node_for(folder2_id).unwrap();
let second_data = second.data().unwrap();
let second_folder = second_data
.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap();
assert_eq!(first_folder.descendent_count(), 0);
assert!(second_folder.descendent_count() > 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.ConcurrentAddMoveRemoveStress::test 0b76b2fb62a6300e02a02859772f146f107caa77986f4d593e3a4d01966ab4e4 translated
#[test]
fn concurrent_add_move_remove_stress() {
const ITEM_COUNT: usize = 500;
let fixture = Fixture::new();
let folder1 = folder("Folder1", UUID::zero());
let folder2 = folder("Folder2", UUID::zero());
let folder3 = folder("Folder3", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
let folder3_id = folder3.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
fixture.inventory.update_node_for(&folder3).unwrap();
let items: Vec<_> = (0..ITEM_COUNT)
.map(|index| Arc::new(Mutex::new(item(&format!("Item{index}"), folder1_id))))
.collect();
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.update_node_for(&*item.lock().unwrap()).unwrap());
}
});
for item in &items {
assert!(
fixture
.inventory
.contains_with_uuid(item.lock().unwrap().base.uuid())
.unwrap()
);
}
std::thread::scope(|scope| {
for (index, item) in items.iter().enumerate() {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || {
let mut item = item.lock().unwrap();
item.base.set_parent_uuid(if index % 2 == 0 {
folder2_id
} else {
folder3_id
});
inventory.update_node_for(&*item).unwrap();
});
}
});
let count = |folder_id| {
let node = fixture.inventory.get_node_for(folder_id).unwrap();
let data = node.data().unwrap();
data.as_any()
.downcast_ref::<InventoryFolder>()
.unwrap()
.descendent_count()
};
assert_eq!(count(folder1_id), 0);
assert_eq!(
count(folder2_id) + count(folder3_id),
i32::try_from(ITEM_COUNT).unwrap()
);
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.remove_node_for(&*item.lock().unwrap()).unwrap());
}
});
for item in &items {
assert!(
!fixture
.inventory
.contains_with_uuid(item.lock().unwrap().base.uuid())
.unwrap()
);
}
assert_eq!(count(folder1_id), 0);
assert_eq!(count(folder2_id), 0);
assert_eq!(count(folder3_id), 0);
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.ConcurrentLinkAddRemoveStress::test eef3b4eb300427800757dc6cac2dbd9202ed1cf1aac42565b3c26eb11e71bbf1 translated
#[test]
fn concurrent_link_add_remove_stress() {
const ITEM_COUNT: usize = 200;
let fixture = Fixture::new();
let folder1 = folder("LinkFolder", UUID::zero());
let folder2 = folder("LinkFolder2", UUID::zero());
let folder1_id = folder1.base.uuid();
let folder2_id = folder2.base.uuid();
fixture.inventory.update_node_for(&folder1).unwrap();
fixture.inventory.update_node_for(&folder2).unwrap();
let asset_id = uuid();
let items: Vec<_> = (0..ITEM_COUNT)
.map(|index| {
let mut item = item(&format!("L{index}"), folder1_id);
item.set_asset_type(AssetType::Link);
item.set_asset_uuid(asset_id);
Arc::new(Mutex::new(item))
})
.collect();
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.update_node_for(&*item.lock().unwrap()).unwrap());
}
});
assert_eq!(
fixture.inventory.find_all_links(asset_id).unwrap().len(),
ITEM_COUNT
);
std::thread::scope(|scope| {
for (index, item) in items.iter().enumerate() {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || {
let mut item = item.lock().unwrap();
item.base.set_parent_uuid(if index % 2 == 0 {
folder2_id
} else {
folder1_id
});
inventory.update_node_for(&*item).unwrap();
});
}
});
assert_eq!(
fixture.inventory.find_all_links(asset_id).unwrap().len(),
ITEM_COUNT
);
std::thread::scope(|scope| {
for item in &items {
let item = Arc::clone(item);
let inventory = &fixture.inventory;
scope.spawn(move || inventory.remove_node_for(&*item.lock().unwrap()).unwrap());
}
});
assert!(
fixture
.inventory
.find_all_links(asset_id)
.unwrap()
.is_empty()
);
for item in &items {
assert!(
!fixture
.inventory
.contains_with_uuid(item.lock().unwrap().base.uuid())
.unwrap()
);
}
}
// parity-case: LibreMetaverse.Tests/InventoryTests.cs::InventoryTests.LinkIndexUpdateOnAssetChange::test d3e0066e06ab1c48520e5e0bbba8069b74f1a38d37e24d914392406b41b2ea0c translated
#[test]
fn link_index_updates_on_asset_change() {
let fixture = Fixture::new();
let folder = folder("LUpdate", UUID::zero());
let folder_id = folder.base.uuid();
fixture.inventory.update_node_for(&folder).unwrap();
let asset1 = uuid();
let asset2 = uuid();
let mut item = item("LinkItem", folder_id);
let item_id = item.base.uuid();
item.set_asset_type(AssetType::Link);
item.set_asset_uuid(asset1);
fixture.inventory.update_node_for(&item).unwrap();
let found1 = fixture.inventory.find_all_links(asset1).unwrap();
assert_eq!(found1.len(), 1);
let data = found1[0].data().expect("link node data");
assert_eq!(data.inventory_base().uuid(), item_id);
item.set_asset_uuid(asset2);
fixture.inventory.update_node_for(&item).unwrap();
let found1 = fixture.inventory.find_all_links(asset1).unwrap();
let found2 = fixture.inventory.find_all_links(asset2).unwrap();
assert!(found1.is_empty());
assert_eq!(found2.len(), 1);
let data = found2[0].data().expect("updated link node data");
assert_eq!(data.inventory_base().uuid(), item_id);
}