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
387 lines
14 KiB
Rust
387 lines
14 KiB
Rust
// Exact Rust translations of LibreMetaverse.Tests/InventoryFetchTests.cs and
|
|
// LibreMetaverse.Tests/LibraryInventoryTests.cs.
|
|
// Source SHA-256: f5876550ab924ad034ade848e562ee70bbb4e004299d928f43c45cadd1a01465
|
|
// Source SHA-256: 1d65ebbaf8fd22427371e2a7f1ef76a4f9a70df0bea7fa53f8f5d3fd3044fa53
|
|
|
|
use libremetaverse::{
|
|
Error, GridClient, InventoryFolder, InventoryItem, InventoryObjectClass, InventorySortOrder,
|
|
PermissionMask,
|
|
};
|
|
use libremetaverse_compat_tests::{LiveTestRuntime, login_live_grid, logout_live_grid};
|
|
use libremetaverse_types::compat::{CancellationToken, CancellationTokenSource};
|
|
use libremetaverse_types::{AssetType, FolderType, InventoryType, UUID};
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
struct LiveInventory {
|
|
client: GridClient,
|
|
runtime: LiveTestRuntime,
|
|
disposable_items: Vec<UUID>,
|
|
}
|
|
|
|
impl LiveInventory {
|
|
fn login() -> Self {
|
|
let runtime = LiveTestRuntime::new();
|
|
let mut client = GridClient::new().expect("GridClient constructor");
|
|
login_live_grid(
|
|
&runtime,
|
|
&mut client,
|
|
"Unit Test Framework",
|
|
"admin@radegast.life",
|
|
);
|
|
Self {
|
|
client,
|
|
runtime,
|
|
disposable_items: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn agent_id(&mut self) -> UUID {
|
|
self.client.self_().agent_id()
|
|
}
|
|
|
|
fn create_disposable_texture(&mut self) -> (UUID, InventoryItem) {
|
|
let manager = self.client.inventory();
|
|
let folder = manager
|
|
.find_folder_for_type_with_asset_type(AssetType::Texture)
|
|
.expect("find OpenSim texture folder");
|
|
assert_ne!(folder, UUID::zero(), "OpenSim has no texture folder");
|
|
let marker = UUID::random().expect("inventory fixture UUID");
|
|
let name = format!("MetaCrate Live Test {marker}");
|
|
let agent_id = self.agent_id();
|
|
for attempt in 1..=3 {
|
|
let result = self.runtime.block_on(
|
|
manager.create_item_with_uuid_string_string_asset_type_uuid_inventory_type_permission_mask_cancellation_token(
|
|
folder,
|
|
name.clone(),
|
|
"Disposable OpenSim compatibility fixture".into(),
|
|
AssetType::Texture,
|
|
UUID::zero(),
|
|
InventoryType::TEXTURE,
|
|
PermissionMask::ALL,
|
|
Some(cancellation_after(Duration::from_secs(30))),
|
|
),
|
|
);
|
|
match result {
|
|
Ok(Some(item)) => {
|
|
self.disposable_items.push(item.base.uuid());
|
|
return (folder, item);
|
|
}
|
|
Ok(None) | Err(Error::Cancelled) => {}
|
|
Err(error) => panic!("create disposable inventory item: {error}"),
|
|
}
|
|
|
|
// A UDP callback can be lost even though OpenSim committed the
|
|
// request. Reconcile against the server before retrying so a lost
|
|
// callback does not create duplicate fixtures.
|
|
if let Ok(contents) = self.runtime.block_on(manager.folder_contents(
|
|
folder,
|
|
agent_id,
|
|
true,
|
|
true,
|
|
InventorySortOrder::BY_NAME,
|
|
Some(cancellation_after(Duration::from_secs(30))),
|
|
None,
|
|
)) && let Some(item) = items(&contents)
|
|
.find(|item| item.base.name() == name)
|
|
.cloned()
|
|
{
|
|
self.disposable_items.push(item.base.uuid());
|
|
return (folder, item);
|
|
}
|
|
assert!(
|
|
attempt < 3,
|
|
"OpenSim did not create or return the disposable item"
|
|
);
|
|
}
|
|
unreachable!("bounded inventory fixture retry loop")
|
|
}
|
|
}
|
|
|
|
impl Drop for LiveInventory {
|
|
fn drop(&mut self) {
|
|
if let Ok(trash) = self
|
|
.client
|
|
.inventory()
|
|
.find_folder_for_type_with_folder_type(FolderType::Trash)
|
|
{
|
|
for item in self.disposable_items.drain(..) {
|
|
self.runtime
|
|
.block_on(
|
|
self.client
|
|
.inventory()
|
|
.move_item_with_uuid_uuid_cancellation_token_1cf693fd(
|
|
item,
|
|
trash,
|
|
Some(CancellationToken::default()),
|
|
),
|
|
)
|
|
.expect("move disposable inventory item to trash");
|
|
}
|
|
}
|
|
logout_live_grid(&self.runtime, &mut self.client);
|
|
}
|
|
}
|
|
|
|
fn cancellation_after(duration: Duration) -> CancellationToken {
|
|
let source = Arc::new(CancellationTokenSource::new());
|
|
let token = source.token();
|
|
std::thread::spawn(move || {
|
|
std::thread::sleep(duration);
|
|
source.cancel();
|
|
});
|
|
token
|
|
}
|
|
|
|
fn folders(entries: &[Box<dyn InventoryObjectClass>]) -> impl Iterator<Item = &InventoryFolder> {
|
|
entries
|
|
.iter()
|
|
.filter_map(|entry| entry.as_any().downcast_ref::<InventoryFolder>())
|
|
}
|
|
|
|
fn items(entries: &[Box<dyn InventoryObjectClass>]) -> impl Iterator<Item = &InventoryItem> {
|
|
entries.iter().filter_map(|entry| entry.inventory_item())
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventorySkeletonIsPopulatedAfterLogin::test ea1508e3c676639f00bb8a4c4c72d2ee668603bfbff40ee0b93d7f4beb607f75 ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn inventory_skeleton_is_populated_after_login() {
|
|
let mut live = LiveInventory::login();
|
|
let agent_id = live.agent_id();
|
|
let store = live.client.inventory().store().expect("inventory store");
|
|
let root = store.root_folder().expect("inventory root folder");
|
|
assert_ne!(root.base.uuid(), UUID::zero());
|
|
let children = store
|
|
.get_contents_with_inventory_folder(&root)
|
|
.expect("inventory root contents");
|
|
assert!(!children.is_empty(), "inventory root has no children");
|
|
assert_eq!(folders(&children).count(), children.len());
|
|
let first = folders(&children).next().expect("inventory sub-folder");
|
|
assert_eq!(first.base.owner_id(), agent_id);
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryRootContainsFolders::test 71f41c718060622e1fd653e6b0fc9fe65f3cd68cab3d128f494cdbc90f9d2726 ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn inventory_root_contains_folders() {
|
|
let mut live = LiveInventory::login();
|
|
let agent_id = live.agent_id();
|
|
let root = live
|
|
.client
|
|
.inventory()
|
|
.store()
|
|
.expect("inventory store")
|
|
.root_folder()
|
|
.expect("inventory root folder");
|
|
assert_ne!(root.base.uuid(), UUID::zero());
|
|
let contents = live
|
|
.runtime
|
|
.block_on(live.client.inventory().folder_contents(
|
|
root.base.uuid(),
|
|
agent_id,
|
|
true,
|
|
true,
|
|
InventorySortOrder::BY_NAME,
|
|
Some(cancellation_after(Duration::from_secs(30))),
|
|
None,
|
|
))
|
|
.expect("FolderContentsAsync");
|
|
assert!(
|
|
!contents.is_empty(),
|
|
"inventory root fetch returned no results"
|
|
);
|
|
assert!(folders(&contents).next().is_some());
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryFolderContainsItems::test f0d77649b82d4d7d7778acebd851dd4eb44b76eb687af07bccdd20a1efe96223 ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn inventory_folder_contains_items() {
|
|
let mut live = LiveInventory::login();
|
|
let (_folder, created) = live.create_disposable_texture();
|
|
let created_id = created.base.uuid();
|
|
let agent_id = live.agent_id();
|
|
// OpenSim's CreateInventoryItem reply does not necessarily populate the
|
|
// viewer-side store. Fetch the server-selected parent explicitly so this
|
|
// case validates the grid's folder-contents service rather than cache
|
|
// timing inherited from the Second Life test account.
|
|
let actual_parent = created.base.parent_uuid();
|
|
assert_ne!(
|
|
actual_parent,
|
|
UUID::zero(),
|
|
"created item has no parent folder"
|
|
);
|
|
let contents = live
|
|
.runtime
|
|
.block_on(live.client.inventory().folder_contents(
|
|
actual_parent,
|
|
agent_id,
|
|
true,
|
|
true,
|
|
InventorySortOrder::BY_NAME,
|
|
Some(cancellation_after(Duration::from_secs(30))),
|
|
None,
|
|
))
|
|
.expect("fetch server-selected inventory folder contents");
|
|
let fetched = items(&contents)
|
|
.find(|item| item.base.uuid() == created_id)
|
|
.expect("server-created item was not cached in its parent folder");
|
|
assert_eq!(fetched.base.parent_uuid(), actual_parent);
|
|
assert_eq!(fetched.asset_type(), AssetType::Texture);
|
|
assert_eq!(fetched.inventory_type(), InventoryType::TEXTURE);
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventoryItemCanBeFetchedById::test 7fe89086754a673ab69814202b48aab9add30dd63ee98a953bdedd54d6788af2 ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn inventory_item_can_be_fetched_by_id() {
|
|
let mut live = LiveInventory::login();
|
|
let agent_id = live.agent_id();
|
|
let token = cancellation_after(Duration::from_secs(30));
|
|
let (_, created) = live.create_disposable_texture();
|
|
let target_id = created.base.uuid();
|
|
let target_name = created.base.name();
|
|
let fetched = live
|
|
.runtime
|
|
.block_on(
|
|
live.client
|
|
.inventory()
|
|
.fetch_item_http(target_id, agent_id, Some(token)),
|
|
)
|
|
.expect("FetchItemHttpAsync")
|
|
.expect("fetched inventory item");
|
|
assert_eq!(fetched.base.uuid(), target_id);
|
|
assert_eq!(fetched.base.name(), target_name);
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibraryFoldersArePopulatedAfterLogin::test dc865c88bb514bb71157a451e938b5ae3e3adde208d267d6a7196706dc9cfda2 ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn library_folders_are_populated_after_login() {
|
|
let live = LiveInventory::login();
|
|
let store = live.client.inventory().store().expect("inventory store");
|
|
let root = store.library_folder().expect("library root folder");
|
|
assert_ne!(root.base.uuid(), UUID::zero());
|
|
let children = store
|
|
.get_contents_with_inventory_folder(&root)
|
|
.expect("library root contents");
|
|
assert!(!children.is_empty(), "library root has no children");
|
|
assert_eq!(folders(&children).count(), children.len());
|
|
assert_ne!(
|
|
folders(&children)
|
|
.next()
|
|
.expect("library sub-folder")
|
|
.base
|
|
.owner_id(),
|
|
UUID::zero()
|
|
);
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibraryFolderItemsCanBeFetched::test 475561d29dd25aba52d94f8374fe9083fea2ed7af44dfb6d25b55ce487b080ab ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn library_folder_items_can_be_fetched() {
|
|
let live = LiveInventory::login();
|
|
let store = live.client.inventory().store().expect("inventory store");
|
|
let root = store.library_folder().expect("library root folder");
|
|
let skeleton = store
|
|
.get_contents_with_inventory_folder(&root)
|
|
.expect("library skeleton");
|
|
let owner = folders(&skeleton)
|
|
.map(|folder| folder.base.owner_id())
|
|
.find(|owner| *owner != UUID::zero())
|
|
.expect("library owner UUID");
|
|
let contents = live
|
|
.runtime
|
|
.block_on(live.client.inventory().folder_contents(
|
|
root.base.uuid(),
|
|
owner,
|
|
true,
|
|
true,
|
|
InventorySortOrder::BY_NAME,
|
|
Some(cancellation_after(Duration::from_secs(30))),
|
|
None,
|
|
))
|
|
.expect("FolderContentsAsync");
|
|
assert!(
|
|
!contents.is_empty(),
|
|
"library root fetch returned no results"
|
|
);
|
|
assert!(folders(&contents).next().is_some());
|
|
}
|
|
|
|
// parity-case: LibreMetaverse.Tests/LibraryInventoryTests.cs::LibraryInventoryTests.LibrarySubFolderContainsItems::test a6d02cbcb7fde8248419f1cf7355c3d37f06e8b365dc483fad935dd9a0ac5909 ignored-live
|
|
#[test]
|
|
#[cfg_attr(
|
|
not(live_grid_credentials),
|
|
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
|
)]
|
|
fn library_sub_folder_contains_items() {
|
|
let live = LiveInventory::login();
|
|
let store = live.client.inventory().store().expect("inventory store");
|
|
let root = store.library_folder().expect("library root folder");
|
|
let skeleton = store
|
|
.get_contents_with_inventory_folder(&root)
|
|
.expect("library skeleton");
|
|
let subfolders: Vec<_> = folders(&skeleton).collect();
|
|
assert!(!subfolders.is_empty(), "no library sub-folders");
|
|
let owner = subfolders
|
|
.iter()
|
|
.map(|folder| folder.base.owner_id())
|
|
.find(|owner| *owner != UUID::zero())
|
|
.expect("library owner UUID");
|
|
let token = cancellation_after(Duration::from_secs(30));
|
|
let mut fetched_folders = 0_usize;
|
|
for folder in subfolders {
|
|
if token.is_cancellation_requested() {
|
|
break;
|
|
}
|
|
let Ok(contents) = live
|
|
.runtime
|
|
.block_on(live.client.inventory().folder_contents(
|
|
folder.base.uuid(),
|
|
owner,
|
|
true,
|
|
true,
|
|
InventorySortOrder::BY_NAME,
|
|
Some(token.clone()),
|
|
None,
|
|
))
|
|
else {
|
|
continue;
|
|
};
|
|
fetched_folders += 1;
|
|
for entry in contents {
|
|
assert_eq!(
|
|
entry.inventory_base().parent_uuid(),
|
|
folder.base.uuid(),
|
|
"OpenSim returned a library child with the wrong parent"
|
|
);
|
|
assert_ne!(entry.inventory_base().uuid(), UUID::zero());
|
|
}
|
|
}
|
|
assert!(
|
|
fetched_folders > 0,
|
|
"no OpenSim library sub-folder could be fetched"
|
|
);
|
|
}
|