Complete first release candidate audit (#107)
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
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
This commit is contained in:
@@ -10,6 +10,15 @@ use std::sync::{Arc, Mutex};
|
||||
use std::task::{Context, Poll, Wake, Waker};
|
||||
use std::time::Duration;
|
||||
|
||||
const OPENSIM_LOGIN_OPTIONS: [&str; 6] = [
|
||||
"avatar_picker_url",
|
||||
"classified_fee",
|
||||
"currency",
|
||||
"destination_guide_url",
|
||||
"profile-server-url",
|
||||
"search",
|
||||
];
|
||||
|
||||
/// Runs one test future without choosing an async runtime for the library.
|
||||
pub fn block_on<F: Future>(future: F) -> F::Output {
|
||||
struct ThreadWake(std::thread::Thread);
|
||||
@@ -31,6 +40,121 @@ pub fn block_on<F: Future>(future: F) -> F::Output {
|
||||
}
|
||||
}
|
||||
|
||||
/// Tokio runtime kept alive for the complete lifetime of a live-grid fixture.
|
||||
///
|
||||
/// Network login starts UDP, capability, timer, and cancellation tasks. Keeping
|
||||
/// this multi-thread runtime in the fixture lets those tasks continue running
|
||||
/// while a synchronous compatibility assertion observes grid state.
|
||||
pub struct LiveTestRuntime(tokio::runtime::Runtime);
|
||||
|
||||
impl LiveTestRuntime {
|
||||
/// Creates a live-I/O runtime with all Tokio drivers enabled.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if worker threads or the runtime's I/O driver cannot be created.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("create live-grid Tokio runtime"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Runs one live-grid future while retaining the runtime for background I/O.
|
||||
pub fn block_on<F: Future>(&self, future: F) -> F::Output {
|
||||
self.0.block_on(future)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LiveTestRuntime {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Authenticates a test client against the exact `OpenSim` endpoint in `.env`.
|
||||
///
|
||||
/// The helper adds the `OpenSim` response options used by the dedicated smoke
|
||||
/// program and retries only the server's explicit stale-session response. It
|
||||
/// does not substitute a Second Life endpoint, region, or start location.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if parameters cannot be constructed or the dedicated account cannot
|
||||
/// authenticate within 90 seconds.
|
||||
pub fn login_live_grid(
|
||||
runtime: &LiveTestRuntime,
|
||||
client: &mut libremetaverse::GridClient,
|
||||
channel: &str,
|
||||
version: &str,
|
||||
) -> libremetaverse::NetworkManager {
|
||||
client.settings().timing().login_timeout = 30_000;
|
||||
client.settings().timing().logout_timeout = 5_000;
|
||||
// OpenSim sends inventory bootstrap and initial scene packets as part of
|
||||
// login. These lazily constructed services must install their consumers
|
||||
// before credentials cross the wire or that one-shot state is lost.
|
||||
let _ = client.self_();
|
||||
let _ = client.objects();
|
||||
let _ = client.inventory();
|
||||
let _ = client.assets();
|
||||
let _ = client.appearance();
|
||||
let _ = client.avatars();
|
||||
let _ = client.grid();
|
||||
let _ = client.estate();
|
||||
let network = client.network();
|
||||
let (first, last, password, login_url) = live_grid_credentials();
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(90);
|
||||
loop {
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first.clone(),
|
||||
last.clone(),
|
||||
password.clone(),
|
||||
channel.to_owned(),
|
||||
version.to_owned(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.uri.clone_from(&login_url);
|
||||
for option in OPENSIM_LOGIN_OPTIONS {
|
||||
if !login.options.iter().any(|existing| existing == option) {
|
||||
login.options.push(option.to_owned());
|
||||
}
|
||||
}
|
||||
let logged_in = runtime
|
||||
.block_on(network.login_with_login_params_cancellation_token(login, None))
|
||||
.expect("NetworkManager LoginAsync");
|
||||
if logged_in {
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
let simulator = network
|
||||
.current_sim()
|
||||
.expect("CurrentSim is null after successful OpenSim login");
|
||||
client
|
||||
.self_()
|
||||
.complete_agent_movement(simulator)
|
||||
.expect("complete OpenSim agent movement after login");
|
||||
return network;
|
||||
}
|
||||
let message = network.login_message();
|
||||
let stale_session = message.to_ascii_lowercase().contains("already logged in");
|
||||
assert!(
|
||||
stale_session && std::time::Instant::now() < deadline,
|
||||
"OpenSim login failed: {message}"
|
||||
);
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the protocol logout while the live runtime is still active.
|
||||
pub fn logout_live_grid(runtime: &LiveTestRuntime, client: &mut libremetaverse::GridClient) {
|
||||
let network = client.network();
|
||||
let _ = runtime.block_on(network.logout_with_cancellation_token(None));
|
||||
let _ = client.dispose_with_method();
|
||||
}
|
||||
|
||||
/// Loads the live-grid identity from the process environment or workspace `.env`.
|
||||
///
|
||||
/// # Panics
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Exact Rust translations of LibreMetaverse.Tests/AppearanceLiveTests.cs.
|
||||
// Source SHA-256: 9f4ec256d11290cbad70e209d2f9043d4c6afb18255796c09c4cb26415f971a6
|
||||
|
||||
use libremetaverse::GridClient;
|
||||
use libremetaverse::appearance::CurrentOutfitFolder;
|
||||
use libremetaverse::{GridClient, NetworkManager};
|
||||
use libremetaverse_compat_tests::{block_on, live_grid_credentials};
|
||||
use libremetaverse_compat_tests::{LiveTestRuntime, login_live_grid, logout_live_grid};
|
||||
use libremetaverse_types::{UUID, WearableType};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
@@ -11,47 +11,36 @@ use std::time::{Duration, Instant};
|
||||
struct LiveAppearance {
|
||||
client: Arc<GridClient>,
|
||||
cof: CurrentOutfitFolder,
|
||||
runtime: LiveTestRuntime,
|
||||
}
|
||||
|
||||
impl LiveAppearance {
|
||||
fn login() -> Self {
|
||||
let runtime = LiveTestRuntime::new();
|
||||
let mut client = GridClient::new().expect("GridClient constructor");
|
||||
client.settings().timing().login_timeout = 30_000;
|
||||
let network = client.network();
|
||||
let (first, last, password, login_url) = live_grid_credentials();
|
||||
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
|
||||
.expect("NetworkManager StartLocation");
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first,
|
||||
last,
|
||||
password,
|
||||
"Unit Test Framework".into(),
|
||||
"admin@radegast.life".into(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.start = start;
|
||||
login.uri = login_url;
|
||||
let logged_in = block_on(network.login_with_login_params_cancellation_token(login, None))
|
||||
.expect("NetworkManager LoginAsync");
|
||||
assert!(
|
||||
logged_in,
|
||||
"client failed to login: {}",
|
||||
network.login_message()
|
||||
login_live_grid(
|
||||
&runtime,
|
||||
&mut client,
|
||||
"Unit Test Framework",
|
||||
"admin@radegast.life",
|
||||
);
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
let client = Arc::new(client);
|
||||
let cof = CurrentOutfitFolder::new(Some(Arc::clone(&client)))
|
||||
.expect("CurrentOutfitFolder constructor");
|
||||
Self { client, cof }
|
||||
Self {
|
||||
client,
|
||||
cof,
|
||||
runtime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LiveAppearance {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.cof.dispose();
|
||||
let _ = self.client.network().logout_with_method();
|
||||
let _ = self.client.dispose_with_method();
|
||||
if let Some(client) = Arc::get_mut(&mut self.client) {
|
||||
logout_live_grid(&self.runtime, client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +52,9 @@ impl Drop for LiveAppearance {
|
||||
)]
|
||||
fn cof_is_initialized_after_login() {
|
||||
let live = LiveAppearance::login();
|
||||
block_on(live.cof.get_current_outfit_links(None)).unwrap();
|
||||
live.runtime
|
||||
.block_on(live.cof.get_current_outfit_links(None))
|
||||
.unwrap();
|
||||
let folder = live.cof.cof().expect("COF folder is null");
|
||||
assert_ne!(folder.base.uuid(), UUID::zero());
|
||||
}
|
||||
@@ -76,7 +67,9 @@ fn cof_is_initialized_after_login() {
|
||||
)]
|
||||
fn cof_has_expected_folder_name() {
|
||||
let live = LiveAppearance::login();
|
||||
block_on(live.cof.get_current_outfit_links(None)).unwrap();
|
||||
live.runtime
|
||||
.block_on(live.cof.get_current_outfit_links(None))
|
||||
.unwrap();
|
||||
let folder = live.cof.cof().expect("COF folder is null");
|
||||
assert!(folder.base.name().eq_ignore_ascii_case("Current Outfit"));
|
||||
}
|
||||
@@ -89,7 +82,10 @@ fn cof_has_expected_folder_name() {
|
||||
)]
|
||||
fn get_current_outfit_links_returns_links() {
|
||||
let live = LiveAppearance::login();
|
||||
let links = block_on(live.cof.get_current_outfit_links(None)).unwrap();
|
||||
let links = live
|
||||
.runtime
|
||||
.block_on(live.cof.get_current_outfit_links(None))
|
||||
.unwrap();
|
||||
for link in links {
|
||||
assert!(link.is_link().unwrap(), "returned COF item is not a link");
|
||||
}
|
||||
@@ -103,12 +99,14 @@ fn get_current_outfit_links_returns_links() {
|
||||
)]
|
||||
fn get_current_outfit_links_contains_at_least_one_link() {
|
||||
let live = LiveAppearance::login();
|
||||
let links = block_on(live.cof.get_current_outfit_links(None)).unwrap();
|
||||
if links.is_empty() {
|
||||
eprintln!("COF returned zero links; equip at least a default shape for this assertion");
|
||||
return;
|
||||
}
|
||||
assert!(!links.is_empty());
|
||||
let links = live
|
||||
.runtime
|
||||
.block_on(live.cof.get_current_outfit_links(None))
|
||||
.unwrap();
|
||||
assert!(
|
||||
!links.is_empty(),
|
||||
"COF returned zero links; the dedicated account must wear at least its default shape"
|
||||
);
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/AppearanceLiveTests.cs::AppearanceLiveTests.GetWornAt_Shape_ReturnsAtLeastOne::test abc01e346371910610312ff9352ee05c0d317a7c677c0fa8cb398d3b0522c3bf ignored-live
|
||||
@@ -119,12 +117,14 @@ fn get_current_outfit_links_contains_at_least_one_link() {
|
||||
)]
|
||||
fn get_worn_at_shape_returns_at_least_one() {
|
||||
let live = LiveAppearance::login();
|
||||
let worn = block_on(live.cof.get_worn_at(WearableType::Shape, None)).unwrap();
|
||||
if worn.is_empty() {
|
||||
eprintln!("GetWornAtAsync(Shape) returned no items for this account");
|
||||
return;
|
||||
}
|
||||
assert!(!worn.is_empty());
|
||||
let worn = live
|
||||
.runtime
|
||||
.block_on(live.cof.get_worn_at(WearableType::Shape, None))
|
||||
.unwrap();
|
||||
assert!(
|
||||
!worn.is_empty(),
|
||||
"GetWornAtAsync(Shape) returned no items for the dedicated account"
|
||||
);
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/AppearanceLiveTests.cs::AppearanceLiveTests.LastUpdateReceivedCOFVersion_AdvancesAfterLogin::test 5371df8b02df9da20b413f4155f63967184dad23f57708beb56a2c6559e78520 ignored-live
|
||||
@@ -144,11 +144,10 @@ fn last_update_received_cof_version_advances_after_login() {
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
if version == -1 {
|
||||
eprintln!("the simulator did not send AvatarAppearance for self within 25 seconds");
|
||||
return;
|
||||
}
|
||||
assert!(version > -1);
|
||||
assert!(
|
||||
version > -1,
|
||||
"the OpenSim simulator did not send AvatarAppearance for self within 25 seconds"
|
||||
);
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/AppearanceLiveTests.cs::AppearanceLiveTests.RequestOwnAvatarTextures_DoesNotThrow_AndMaintainsVersionGuard::test ee8420690fc9714411450beb5be5755b82ddf7ed0d69e9da88b3184339bea744 ignored-live
|
||||
|
||||
@@ -18,7 +18,25 @@ use std::any::TypeId;
|
||||
use std::sync::Arc;
|
||||
|
||||
struct RecordingTextureProvider;
|
||||
impl IBakingTextureProvider for RecordingTextureProvider {}
|
||||
impl IBakingTextureProvider for RecordingTextureProvider {
|
||||
fn request_texture(
|
||||
&self,
|
||||
_texture_id: UUID,
|
||||
_cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = Result<
|
||||
Option<libremetaverse::assets::AssetTexture>,
|
||||
libremetaverse::Error,
|
||||
>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(async { Ok(None) })
|
||||
}
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/AppearanceManagerTests.cs::AppearanceManagerTests.WearableTypeToAssetType_BodypartsAndClothing_ReturnsExpected::test 6d0f0d09a6da8121ba828159a75ca1c1ce54329ec48060a6786aa8f807c13ec0 translated
|
||||
#[test]
|
||||
|
||||
@@ -233,6 +233,7 @@ fn collada_triangle_converts_to_native_model_asset() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::float_cmp)] // Parsed fixture values are exact decimal constants.
|
||||
fn collada_applies_units_effects_and_instance_material_bindings() {
|
||||
let root = temporary_directory("collada-material");
|
||||
let file = root.join("material.dae");
|
||||
@@ -274,8 +275,8 @@ fn collada_texture_path_cannot_escape_document_root() {
|
||||
let file = root.join("traversal.dae");
|
||||
fs::write(
|
||||
&file,
|
||||
r##"<COLLADA><library_images><image id="escape"><init_from>../secret.jp2</init_from></image></library_images>
|
||||
<library_materials><material id="mat"><diffuse><texture texture="escape"/></diffuse></material></library_materials></COLLADA>"##,
|
||||
r#"<COLLADA><library_images><image id="escape"><init_from>../secret.jp2</init_from></image></library_images>
|
||||
<library_materials><material id="mat"><diffuse><texture texture="escape"/></diffuse></material></library_materials></COLLADA>"#,
|
||||
)
|
||||
.expect("write traversal fixture");
|
||||
assert!(
|
||||
|
||||
@@ -8,14 +8,6 @@ use libremetaverse::{
|
||||
use libremetaverse_types::compat::{CancellationToken, EventHandler, Subscription};
|
||||
use libremetaverse_types::{AssetType, AttachmentPoint, UUID};
|
||||
|
||||
fn member_id<T>(result: Result<T, libremetaverse::Error>) -> &'static str {
|
||||
result
|
||||
.err()
|
||||
.expect("failure-only shim unexpectedly returned a value")
|
||||
.csharp_member()
|
||||
.expect("failure-only shim error")
|
||||
}
|
||||
|
||||
fn compile_bot_and_animation_calls(
|
||||
agent: &AgentManager,
|
||||
chat_type: ChatType,
|
||||
@@ -95,7 +87,7 @@ fn avatar_facing_flows_have_typed_callable_signatures() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avatar_facing_constructors_preserve_native_defaults_and_catalog_errors() {
|
||||
fn avatar_facing_constructors_preserve_native_defaults() {
|
||||
let download = AssetDownload::new().expect("AssetDownload constructor");
|
||||
assert_eq!(download.asset_id, UUID::zero());
|
||||
assert_eq!(download.next_packet, 0);
|
||||
@@ -111,10 +103,9 @@ fn avatar_facing_constructors_preserve_native_defaults_and_catalog_errors() {
|
||||
assert_eq!(wearable.item_id, UUID::zero());
|
||||
assert!(wearable.asset.is_none());
|
||||
|
||||
assert_eq!(
|
||||
member_id(InventoryException::new_with_constructor()),
|
||||
"M:LibreMetaverse.InventoryException.#ctor"
|
||||
);
|
||||
let inventory_error =
|
||||
InventoryException::new_with_constructor().expect("InventoryException constructor");
|
||||
assert_eq!(inventory_error.to_string(), "inventory operation failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,14 +6,6 @@ use libremetaverse_utilities::ConnectionManager;
|
||||
use libremetaverse_voice_vivox::{TCPPipe, VoiceManager as VivoxVoiceManager};
|
||||
use libremetaverse_voice_webrtc::{LibreMetaverseVoiceLogger, VoiceManager as WebRtcVoiceManager};
|
||||
|
||||
fn member_id<T>(result: Result<T, libremetaverse_types::Error>) -> &'static str {
|
||||
result
|
||||
.err()
|
||||
.expect("failure-only shim unexpectedly returned a value")
|
||||
.csharp_member()
|
||||
.expect("failure-only shim error")
|
||||
}
|
||||
|
||||
async fn compile_rlv_calls(
|
||||
service: &RlvService,
|
||||
restrictions: &RlvRestrictionManager,
|
||||
@@ -65,15 +57,11 @@ fn extension_flows_have_typed_callable_signatures() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extension_constructors_have_expected_native_or_failure_behavior() {
|
||||
fn extension_constructors_have_expected_native_behavior() {
|
||||
assert!(YyParser::new().is_ok());
|
||||
assert!(RlvActionCallbacksDefault::new().is_ok());
|
||||
assert_eq!(
|
||||
member_id(TCPPipe::new()),
|
||||
"M:LibreMetaverse.Voice.Vivox.TCPPipe.#ctor"
|
||||
);
|
||||
assert_eq!(
|
||||
member_id(LibreMetaverseVoiceLogger::new()),
|
||||
"M:LibreMetaverse.Voice.WebRTC.LibreMetaverseVoiceLogger.#ctor"
|
||||
);
|
||||
let pipe = TCPPipe::new().expect("TCPPipe constructor");
|
||||
assert!(!pipe.connected());
|
||||
let logger = LibreMetaverseVoiceLogger::new().expect("voice logger constructor");
|
||||
assert!(logger.records().is_empty());
|
||||
}
|
||||
|
||||
@@ -4,62 +4,122 @@
|
||||
// Source SHA-256: 1d65ebbaf8fd22427371e2a7f1ef76a4f9a70df0bea7fa53f8f5d3fd3044fa53
|
||||
|
||||
use libremetaverse::{
|
||||
GridClient, InventoryFolder, InventoryItem, InventoryObjectClass, InventorySortOrder,
|
||||
NetworkManager,
|
||||
Error, GridClient, InventoryFolder, InventoryItem, InventoryObjectClass, InventorySortOrder,
|
||||
PermissionMask,
|
||||
};
|
||||
use libremetaverse_compat_tests::{block_on, live_grid_credentials};
|
||||
use libremetaverse_types::UUID;
|
||||
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");
|
||||
client.settings().timing().login_timeout = 30_000;
|
||||
let network = client.network();
|
||||
let (first, last, password, login_url) = live_grid_credentials();
|
||||
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
|
||||
.expect("NetworkManager StartLocation");
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first,
|
||||
last,
|
||||
password,
|
||||
"Unit Test Framework".into(),
|
||||
"admin@radegast.life".into(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.start = start;
|
||||
login.uri = login_url;
|
||||
let logged_in = block_on(network.login_with_login_params_cancellation_token(login, None))
|
||||
.expect("NetworkManager LoginAsync");
|
||||
assert!(
|
||||
logged_in,
|
||||
"client failed to login: {}",
|
||||
network.login_message()
|
||||
login_live_grid(
|
||||
&runtime,
|
||||
&mut client,
|
||||
"Unit Test Framework",
|
||||
"admin@radegast.life",
|
||||
);
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
assert!(
|
||||
network.current_sim().is_some(),
|
||||
"CurrentSim is null after successful login"
|
||||
);
|
||||
Self { client }
|
||||
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) {
|
||||
let _ = self.client.network().logout_with_method();
|
||||
let _ = self.client.dispose_with_method();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +140,7 @@ fn folders(entries: &[Box<dyn InventoryObjectClass>]) -> impl Iterator<Item = &I
|
||||
}
|
||||
|
||||
fn items(entries: &[Box<dyn InventoryObjectClass>]) -> impl Iterator<Item = &InventoryItem> {
|
||||
entries
|
||||
.iter()
|
||||
.filter_map(|entry| entry.as_any().downcast_ref::<InventoryItem>())
|
||||
entries.iter().filter_map(|entry| entry.inventory_item())
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/InventoryFetchTests.cs::InventoryFetchTests.InventorySkeletonIsPopulatedAfterLogin::test ea1508e3c676639f00bb8a4c4c72d2ee668603bfbff40ee0b93d7f4beb607f75 ignored-live
|
||||
@@ -123,20 +181,22 @@ fn inventory_root_contains_folders() {
|
||||
.root_folder()
|
||||
.expect("inventory root folder");
|
||||
assert_ne!(root.base.uuid(), UUID::zero());
|
||||
let contents = 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");
|
||||
if contents.is_empty() {
|
||||
eprintln!("inventory root fetch returned no results; transient server issue");
|
||||
return;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -148,49 +208,37 @@ fn inventory_root_contains_folders() {
|
||||
)]
|
||||
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();
|
||||
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 skeleton = store
|
||||
.get_contents_with_inventory_folder(&root)
|
||||
.expect("inventory skeleton");
|
||||
let subfolders: Vec<_> = folders(&skeleton).collect();
|
||||
assert!(!subfolders.is_empty(), "no inventory sub-folders");
|
||||
let token = cancellation_after(Duration::from_secs(30));
|
||||
let mut found = None;
|
||||
for folder in subfolders {
|
||||
if token.is_cancellation_requested() {
|
||||
break;
|
||||
}
|
||||
let Ok(contents) = block_on(live.client.inventory().folder_contents(
|
||||
folder.base.uuid(),
|
||||
// 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(token.clone()),
|
||||
Some(cancellation_after(Duration::from_secs(30))),
|
||||
None,
|
||||
)) else {
|
||||
continue;
|
||||
};
|
||||
let found_items: Vec<_> = items(&contents).collect();
|
||||
if let Some(first) = found_items.first() {
|
||||
found = Some((
|
||||
folder.base.name(),
|
||||
found_items.len(),
|
||||
first.base.name(),
|
||||
first.asset_type(),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
let Some((folder_name, count, first_name, first_type)) = found else {
|
||||
eprintln!("no items found in any inventory sub-folder");
|
||||
return;
|
||||
};
|
||||
assert!(count > 0, "folder {folder_name} has no items");
|
||||
eprintln!("found {count} items in {folder_name}; first: {first_name} ({first_type:?})");
|
||||
))
|
||||
.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
|
||||
@@ -202,46 +250,19 @@ fn inventory_folder_contains_items() {
|
||||
fn inventory_item_can_be_fetched_by_id() {
|
||||
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");
|
||||
let skeleton = store
|
||||
.get_contents_with_inventory_folder(&root)
|
||||
.expect("inventory skeleton");
|
||||
let subfolders: Vec<_> = folders(&skeleton).collect();
|
||||
assert!(!subfolders.is_empty(), "no inventory sub-folders");
|
||||
let token = cancellation_after(Duration::from_secs(30));
|
||||
let mut target = None;
|
||||
for folder in subfolders {
|
||||
if token.is_cancellation_requested() {
|
||||
break;
|
||||
}
|
||||
let Ok(contents) = block_on(live.client.inventory().folder_contents(
|
||||
folder.base.uuid(),
|
||||
agent_id,
|
||||
true,
|
||||
true,
|
||||
InventorySortOrder::BY_NAME,
|
||||
Some(token.clone()),
|
||||
None,
|
||||
)) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(item) = items(&contents).next() {
|
||||
target = Some((item.base.uuid(), item.base.name()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
let Some((target_id, target_name)) = target else {
|
||||
eprintln!("no inventory item found to fetch by UUID");
|
||||
return;
|
||||
};
|
||||
let fetched = block_on(live.client.inventory().fetch_item_http(
|
||||
target_id,
|
||||
agent_id,
|
||||
Some(token),
|
||||
))
|
||||
.expect("FetchItemHttpAsync")
|
||||
.expect("fetched inventory item");
|
||||
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);
|
||||
}
|
||||
@@ -289,20 +310,22 @@ fn library_folder_items_can_be_fetched() {
|
||||
.map(|folder| folder.base.owner_id())
|
||||
.find(|owner| *owner != UUID::zero())
|
||||
.expect("library owner UUID");
|
||||
let contents = 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");
|
||||
if contents.is_empty() {
|
||||
eprintln!("library root fetch returned no results; transient server issue");
|
||||
return;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -327,31 +350,37 @@ fn library_sub_folder_contains_items() {
|
||||
.find(|owner| *owner != UUID::zero())
|
||||
.expect("library owner UUID");
|
||||
let token = cancellation_after(Duration::from_secs(30));
|
||||
let mut found = None;
|
||||
let mut fetched_folders = 0_usize;
|
||||
for folder in subfolders {
|
||||
if token.is_cancellation_requested() {
|
||||
break;
|
||||
}
|
||||
let Ok(contents) = block_on(live.client.inventory().folder_contents(
|
||||
folder.base.uuid(),
|
||||
owner,
|
||||
true,
|
||||
true,
|
||||
InventorySortOrder::BY_NAME,
|
||||
Some(token.clone()),
|
||||
None,
|
||||
)) else {
|
||||
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;
|
||||
};
|
||||
let count = items(&contents).count();
|
||||
if count > 0 {
|
||||
found = Some((folder.base.name(), count));
|
||||
break;
|
||||
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());
|
||||
}
|
||||
}
|
||||
let Some((folder_name, count)) = found else {
|
||||
eprintln!("no items found in any library sub-folder");
|
||||
return;
|
||||
};
|
||||
assert!(count > 0, "folder {folder_name} has no items");
|
||||
assert!(
|
||||
fetched_folders > 0,
|
||||
"no OpenSim library sub-folder could be fetched"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
use libremetaverse::http::DownloadManager;
|
||||
use libremetaverse::packets::PacketType;
|
||||
use libremetaverse::{GridClient, GridLayerType, HttpCapsClient, NetworkManager};
|
||||
use libremetaverse_compat_tests::{block_on, live_grid_credentials};
|
||||
use libremetaverse::{GridClient, GridLayerType, HttpCapsClient};
|
||||
use libremetaverse_compat_tests::{LiveTestRuntime, block_on, login_live_grid, logout_live_grid};
|
||||
use libremetaverse_types::compat::{
|
||||
CancellationToken, CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
|
||||
};
|
||||
@@ -158,10 +158,6 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
fn live_credentials() -> (String, String, String, String) {
|
||||
live_grid_credentials()
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f translated
|
||||
#[test]
|
||||
fn queue_download_single_completes_successfully() {
|
||||
@@ -349,61 +345,41 @@ fn put_response_with_non_http_location_does_not_throw() {
|
||||
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
||||
)]
|
||||
fn get_grid_region_live() {
|
||||
let runtime = LiveTestRuntime::new();
|
||||
let mut client = GridClient::new().expect("GridClient constructor");
|
||||
client.self_().movement.set_fly(true);
|
||||
let network = client.network();
|
||||
let (first, last, password, login_url) = live_credentials();
|
||||
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
|
||||
.expect("NetworkManager StartLocation");
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first,
|
||||
last,
|
||||
password,
|
||||
"Unit Test Framework".into(),
|
||||
"contact@radegast.life".into(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.start = start;
|
||||
login.uri = login_url;
|
||||
let logged_in = block_on(network.login_with_login_params_cancellation_token(login, None))
|
||||
.expect("NetworkManager LoginAsync");
|
||||
assert!(
|
||||
logged_in,
|
||||
"client failed to log in: {}",
|
||||
network.login_message()
|
||||
let network = login_live_grid(
|
||||
&runtime,
|
||||
&mut client,
|
||||
"Unit Test Framework",
|
||||
"contact@radegast.life",
|
||||
);
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
let simulator = network
|
||||
.current_sim()
|
||||
.expect("CurrentSim is null after successful login");
|
||||
if simulator.name.is_empty() {
|
||||
eprintln!("CurrentSim.Name is empty after login, proceeding with tests");
|
||||
} else if !simulator.name.eq_ignore_ascii_case("hooper") {
|
||||
eprintln!(
|
||||
"logged in to region '{}' instead of 'Hooper', proceeding with tests",
|
||||
simulator.name
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
!simulator.name.is_empty(),
|
||||
"OpenSim region handshake returned an empty simulator name"
|
||||
);
|
||||
let current_region = simulator.name.clone();
|
||||
|
||||
let region = block_on(
|
||||
client
|
||||
.grid()
|
||||
.get_grid_region_with_string_grid_layer_type_cancellation_token(
|
||||
"Hippo Hollow".into(),
|
||||
GridLayerType::Terrain,
|
||||
None,
|
||||
),
|
||||
)
|
||||
.expect("GridManager GetGridRegionAsync");
|
||||
if let Some(Some(region)) = region {
|
||||
assert_eq!(region.name.to_lowercase(), "hippo hollow");
|
||||
} else {
|
||||
eprintln!("Grid region 'Hippo Hollow' not found; skipping assertion");
|
||||
}
|
||||
let region = runtime
|
||||
.block_on(
|
||||
client
|
||||
.grid()
|
||||
.get_grid_region_with_string_grid_layer_type_cancellation_token(
|
||||
current_region.clone(),
|
||||
GridLayerType::Terrain,
|
||||
None,
|
||||
),
|
||||
)
|
||||
.expect("GridManager GetGridRegionAsync");
|
||||
let region = region
|
||||
.flatten()
|
||||
.expect("OpenSim grid search did not return the current region");
|
||||
assert!(region.name.eq_ignore_ascii_case(¤t_region));
|
||||
|
||||
network.logout_with_method().expect("NetworkManager Logout");
|
||||
let _ = client.dispose_with_method();
|
||||
logout_live_grid(&runtime, &mut client);
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test 8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87 ignored-live
|
||||
@@ -413,6 +389,7 @@ fn get_grid_region_live() {
|
||||
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
||||
)]
|
||||
fn detect_objects_live() {
|
||||
let runtime = LiveTestRuntime::new();
|
||||
let mut client = GridClient::new().expect("GridClient constructor");
|
||||
client.settings().timing().login_timeout = 30 * 1_000;
|
||||
client.self_().movement.set_fly(true);
|
||||
@@ -425,48 +402,20 @@ fn detect_objects_live() {
|
||||
Arc::new(move |_event| callback_detected.store(true, Ordering::SeqCst)),
|
||||
)
|
||||
.expect("register ObjectUpdate callback");
|
||||
let (first, last, password, login_url) = live_credentials();
|
||||
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
|
||||
.expect("NetworkManager StartLocation");
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first,
|
||||
last,
|
||||
password,
|
||||
"Unit Test Framework".into(),
|
||||
"admin@radegast.life".into(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.start = start;
|
||||
login.uri = login_url;
|
||||
let login_network = Arc::clone(&network);
|
||||
let logged_in = block_on_timeout(
|
||||
async move {
|
||||
login_network
|
||||
.login_with_login_params_cancellation_token(login, None)
|
||||
.await
|
||||
},
|
||||
Duration::from_secs(45),
|
||||
)
|
||||
.expect("NetworkManager LoginAsync");
|
||||
assert!(
|
||||
logged_in,
|
||||
"client failed to log in: {}",
|
||||
network.login_message()
|
||||
login_live_grid(
|
||||
&runtime,
|
||||
&mut client,
|
||||
"Unit Test Framework",
|
||||
"admin@radegast.life",
|
||||
);
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let simulator = network
|
||||
.current_sim()
|
||||
.expect("CurrentSim is null after successful login");
|
||||
if simulator.name.is_empty() {
|
||||
eprintln!("CurrentSim.Name is empty, but proceeding with tests");
|
||||
} else if !simulator.name.eq_ignore_ascii_case("hooper") {
|
||||
eprintln!(
|
||||
"logged in to region '{}' instead of 'Hooper', but proceeding with tests",
|
||||
simulator.name
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
!simulator.name.is_empty(),
|
||||
"OpenSim region handshake returned an empty simulator name"
|
||||
);
|
||||
|
||||
let started = Instant::now();
|
||||
while !detected.load(Ordering::SeqCst) {
|
||||
@@ -481,8 +430,7 @@ fn detect_objects_live() {
|
||||
"successfully detected objects"
|
||||
);
|
||||
|
||||
network.logout_with_method().expect("NetworkManager Logout");
|
||||
let _ = client.dispose_with_method();
|
||||
logout_live_grid(&runtime, &mut client);
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18 ignored-live
|
||||
@@ -492,6 +440,7 @@ fn detect_objects_live() {
|
||||
ignore = "requires GRID_USER, GRID_PASSWORD, and GRID_LOGIN_URL in the environment or workspace .env"
|
||||
)]
|
||||
fn caps_queue_live() {
|
||||
let runtime = LiveTestRuntime::new();
|
||||
let mut client = GridClient::new().expect("GridClient constructor");
|
||||
client.settings().timing().login_timeout = 30 * 1_000;
|
||||
client.self_().movement.set_fly(true);
|
||||
@@ -504,48 +453,20 @@ fn caps_queue_live() {
|
||||
Arc::new(move |_event| callback_detected.store(true, Ordering::SeqCst)),
|
||||
)
|
||||
.expect("register ObjectUpdate callback");
|
||||
let (first, last, password, login_url) = live_credentials();
|
||||
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
|
||||
.expect("NetworkManager StartLocation");
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first,
|
||||
last,
|
||||
password,
|
||||
"Unit Test Framework".into(),
|
||||
"admin@radegast.life".into(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.start = start;
|
||||
login.uri = login_url;
|
||||
let login_network = Arc::clone(&network);
|
||||
let logged_in = block_on_timeout(
|
||||
async move {
|
||||
login_network
|
||||
.login_with_login_params_cancellation_token(login, None)
|
||||
.await
|
||||
},
|
||||
Duration::from_secs(45),
|
||||
)
|
||||
.expect("NetworkManager LoginAsync");
|
||||
assert!(
|
||||
logged_in,
|
||||
"client failed to log in: {}",
|
||||
network.login_message()
|
||||
login_live_grid(
|
||||
&runtime,
|
||||
&mut client,
|
||||
"Unit Test Framework",
|
||||
"admin@radegast.life",
|
||||
);
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let simulator = network
|
||||
.current_sim()
|
||||
.expect("CurrentSim is null after successful login");
|
||||
if simulator.name.is_empty() {
|
||||
eprintln!("CurrentSim.Name is empty, but proceeding with tests");
|
||||
} else if !simulator.name.eq_ignore_ascii_case("hooper") {
|
||||
eprintln!(
|
||||
"logged in to region '{}' instead of 'Hooper', but proceeding with tests",
|
||||
simulator.name
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
!simulator.name.is_empty(),
|
||||
"OpenSim region handshake returned an empty simulator name"
|
||||
);
|
||||
|
||||
let already_running = network
|
||||
.current_sim()
|
||||
@@ -568,6 +489,5 @@ fn caps_queue_live() {
|
||||
"CAPS Event Queue is not running and failed to start"
|
||||
);
|
||||
|
||||
network.logout_with_method().expect("NetworkManager Logout");
|
||||
let _ = client.dispose_with_method();
|
||||
logout_live_grid(&runtime, &mut client);
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ fn get_region_restart_schedule_weekly_parses_days_and_time() {
|
||||
schedule.days(),
|
||||
RegionRestartDays(RegionRestartDays::TUESDAY.0 | RegionRestartDays::THURSDAY.0)
|
||||
);
|
||||
assert_eq!(schedule.time(), Duration::from_secs(2 * 60 * 60));
|
||||
assert_eq!(schedule.time(), Duration::from_hours(2));
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.GetRegionRestartScheduleAsync_DailySchedule_ReturnsAllDays::test cd966b6e3ba470c91ac958bf6c71aeb34f4dab060ad5334cb6060e1954b5c6d0 translated
|
||||
@@ -322,7 +322,7 @@ fn get_region_restart_schedule_daily_returns_all_days() {
|
||||
.expect("restart schedule");
|
||||
assert!(schedule.is_daily());
|
||||
assert_eq!(schedule.days(), RegionRestartDays::ALL);
|
||||
assert_eq!(schedule.time(), Duration::from_secs(60 * 60));
|
||||
assert_eq!(schedule.time(), Duration::from_hours(1));
|
||||
}
|
||||
|
||||
// parity-case: LibreMetaverse.Tests/RegionScheduleTests.cs::RegionScheduleTests.GetRegionRestartScheduleAsync_NoRestartConfigured_ReturnsNull::test 59a4b050a79659ccaf0fff13b750d634d803ed7b9e4940cbad676c1bdc04a5fc translated
|
||||
@@ -377,10 +377,9 @@ fn set_region_restart_schedule_weekly_posts_uppercase_days_and_seconds() {
|
||||
);
|
||||
assert!(
|
||||
block_on(
|
||||
client.estate().set_region_restart_schedule(
|
||||
schedule(false, days, Duration::from_secs(5400)),
|
||||
None
|
||||
)
|
||||
client
|
||||
.estate()
|
||||
.set_region_restart_schedule(schedule(false, days, Duration::from_mins(90)), None)
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
@@ -401,11 +400,7 @@ fn set_region_restart_schedule_daily_omits_days() {
|
||||
);
|
||||
assert!(
|
||||
block_on(client.estate().set_region_restart_schedule(
|
||||
schedule(
|
||||
true,
|
||||
RegionRestartDays::NONE,
|
||||
Duration::from_secs(3 * 60 * 60)
|
||||
),
|
||||
schedule(true, RegionRestartDays::NONE, Duration::from_hours(3)),
|
||||
None
|
||||
))
|
||||
.unwrap()
|
||||
|
||||
@@ -1,58 +1,39 @@
|
||||
// Exact Rust translation of LibreMetaverse.Tests/EstateToolsTests.cs.
|
||||
// Source SHA-256: 2f1867304e21e72d20fbeed98e7705528797947eb3115f84f80aa6adc2f3f88e
|
||||
|
||||
use libremetaverse::{GridClient, NetworkManager};
|
||||
use libremetaverse_compat_tests::{block_on, live_grid_credentials};
|
||||
use libremetaverse::GridClient;
|
||||
use libremetaverse_compat_tests::{LiveTestRuntime, login_live_grid, logout_live_grid};
|
||||
use libremetaverse_types::UUID;
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
struct LiveEstate {
|
||||
client: GridClient,
|
||||
runtime: LiveTestRuntime,
|
||||
}
|
||||
|
||||
impl LiveEstate {
|
||||
fn login() -> Self {
|
||||
let runtime = LiveTestRuntime::new();
|
||||
let mut client = GridClient::new().expect("GridClient constructor");
|
||||
client.settings().timing().login_timeout = 30_000;
|
||||
let network = client.network();
|
||||
let (first, last, password, login_url) = live_grid_credentials();
|
||||
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
|
||||
.expect("NetworkManager StartLocation");
|
||||
let mut login = network
|
||||
.default_login_params(
|
||||
first,
|
||||
last,
|
||||
password,
|
||||
"Unit Test Framework".into(),
|
||||
"admin@radegast.life".into(),
|
||||
)
|
||||
.expect("NetworkManager DefaultLoginParams");
|
||||
login.start = start;
|
||||
login.uri = login_url;
|
||||
let logged_in = block_on(network.login_with_login_params_cancellation_token(login, None))
|
||||
.expect("NetworkManager LoginAsync");
|
||||
assert!(
|
||||
logged_in,
|
||||
"client failed to login: {}",
|
||||
network.login_message()
|
||||
let network = login_live_grid(
|
||||
&runtime,
|
||||
&mut client,
|
||||
"Unit Test Framework",
|
||||
"admin@radegast.life",
|
||||
);
|
||||
assert!(network.connected(), "client is not connected to the grid");
|
||||
let current = network.current_sim().expect("CurrentSim after login");
|
||||
if !current.name.is_empty() && !current.name.eq_ignore_ascii_case("Hooper") {
|
||||
eprintln!(
|
||||
"logged in to {} instead of Hooper; covenant request remains read-only",
|
||||
current.name
|
||||
);
|
||||
}
|
||||
Self { client }
|
||||
assert!(
|
||||
!current.name.is_empty(),
|
||||
"OpenSim region handshake returned an empty simulator name"
|
||||
);
|
||||
Self { client, runtime }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LiveEstate {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.client.network().logout_with_method();
|
||||
let _ = self.client.dispose_with_method();
|
||||
logout_live_grid(&self.runtime, &mut self.client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +53,9 @@ fn request_covenant() {
|
||||
estate
|
||||
.request_covenant()
|
||||
.expect("EstateTools RequestCovenant");
|
||||
let Ok((name, covenant)) = receiver.recv_timeout(Duration::from_secs(10)) else {
|
||||
eprintln!("timeout waiting for estate covenant reply after 10 seconds");
|
||||
return;
|
||||
};
|
||||
let (name, covenant) = receiver
|
||||
.recv_timeout(Duration::from_secs(10))
|
||||
.expect("timeout waiting for estate covenant reply after 10 seconds");
|
||||
assert!(!name.is_empty());
|
||||
assert_ne!(covenant, UUID::zero());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user