Translate network lifecycle parity tests
Some checks failed
Rust API gates / api-gates (push) Has been cancelled

This commit is contained in:
2026-08-08 15:03:57 +02:00
parent 4e5d79e647
commit 069e6366b2
9 changed files with 2473 additions and 2077 deletions

View File

@@ -59,9 +59,9 @@ The current workspace is a structural baseline, not a working client:
members with real fields/constants/enum values and standardized failing
bodies;
- all 1,289 discovered NUnit `[Test]`/`[TestCase]` cases have compiling Rust test
entries; 145 cases are semantically translated, four benchmarks are
reviewed, and the other 1,140 intentionally panic with their source identity
and body hash;
entries; 149 cases are semantically translated, three live-grid cases are
reviewed and ignored by default, four benchmarks are reviewed, and the other
1,133 intentionally panic with their source identity and body hash;
- all nine sample/tool projects have compiling Rust binary targets;
- the 128 TestClient command source files are retained as a command inventory.
@@ -332,8 +332,8 @@ markers in hand-written Rust files, so regeneration writes only unresolved
placeholders and fails on body drift. The checked-in audit reports pending,
translated, ignored-live, benchmark, drifted, missing, duplicate, stale, and
unreviewed cases. The initial handover contained 1,289 unreviewed cases; the
current ledger contains 145 translated, four benchmark-reviewed, and 1,140
pending cases.
current ledger contains 149 translated, three ignored-live, four
benchmark-reviewed, and 1,133 pending cases.
The Rust tests must call the public APIs rather than internal replacements.
Where the C# tests call internal members through friend-assembly access, record

View File

@@ -4,7 +4,9 @@
//! surfaces. Their behavior is implemented only when the owning API slice is
//! ported.
use std::collections::BTreeMap;
use std::marker::PhantomData;
use std::sync::Arc;
pub trait Collection<T> {}
@@ -64,7 +66,23 @@ pub struct XmlTextReader(pub String);
pub struct XmlTextWriter(pub String);
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct CancellationToken;
pub struct CancellationToken {
cancelled: bool,
}
impl CancellationToken {
pub const NONE: Self = Self { cancelled: false };
#[must_use]
pub const fn cancelled() -> Self {
Self { cancelled: true }
}
#[must_use]
pub const fn is_cancellation_requested(self) -> bool {
self.cancelled
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Subscription;
@@ -77,7 +95,28 @@ pub struct DictionaryEntry(pub Object, pub Object);
pub struct RateLimitLease;
pub struct CancellationTokenSource;
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct CancellationTokenSource {
cancelled: bool,
}
impl CancellationTokenSource {
#[must_use]
pub const fn new() -> Self {
Self { cancelled: false }
}
pub const fn cancel(&mut self) {
self.cancelled = true;
}
#[must_use]
pub const fn token(self) -> CancellationToken {
CancellationToken {
cancelled: self.cancelled,
}
}
}
pub trait Close {}
@@ -91,17 +130,53 @@ pub struct TimeProvider;
pub struct MediaTypeHeaderValue(pub String);
pub struct HttpMessageHandler;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HttpRequest {
pub method: String,
pub uri: Uri,
pub headers: BTreeMap<String, String>,
pub content_type: Option<String>,
pub body: Vec<u8>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HttpResponse {
pub status_code: u16,
pub headers: BTreeMap<String, String>,
pub content_type: Option<String>,
pub body: Vec<u8>,
}
impl HttpResponse {
#[must_use]
pub const fn is_success_status_code(&self) -> bool {
self.status_code >= 200 && self.status_code < 300
}
}
type HttpHandler = dyn Fn(HttpRequest) -> HttpResponse + Send + Sync;
#[derive(Clone)]
pub struct HttpMessageHandler(Arc<HttpHandler>);
impl HttpMessageHandler {
pub fn new(handler: impl Fn(HttpRequest) -> HttpResponse + Send + Sync + 'static) -> Self {
Self(Arc::new(handler))
}
#[must_use]
pub fn send(&self, request: HttpRequest) -> HttpResponse {
(self.0)(request)
}
}
pub struct HttpClient;
pub struct HttpResponse;
pub struct HttpMethod(pub String);
pub struct AuthenticationHeaderValue(pub String);
pub trait IProgress<T> {}
pub trait IProgress<T>: Send + Sync {}
pub struct IEnumerator;
@@ -148,3 +223,34 @@ pub struct UnicodeCategory(pub i32);
pub struct TextEncoding;
pub struct SocketException;
#[cfg(test)]
mod tests {
use super::{CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri};
use std::collections::BTreeMap;
#[test]
fn cancellation_and_http_fixture_state_are_observable() {
let mut source = CancellationTokenSource::new();
assert!(!source.token().is_cancellation_requested());
source.cancel();
assert!(source.token().is_cancellation_requested());
let handler = HttpMessageHandler::new(|request| HttpResponse {
status_code: 201,
headers: request.headers,
content_type: request.content_type,
body: request.body,
});
let response = handler.send(HttpRequest {
method: "PUT".into(),
uri: Uri("http://example.test/".into()),
headers: BTreeMap::from([("Location".into(), "slcaps://fixture".into())]),
content_type: Some("application/llsd+xml".into()),
body: b"test".to_vec(),
});
assert!(response.is_success_status_code());
assert_eq!(response.body, b"test");
assert_eq!(response.headers["Location"], "slcaps://fixture");
}
}

View File

@@ -53,6 +53,8 @@ pub enum Error {
Argument,
/// An index or destination buffer boundary was exceeded.
IndexOutOfRange,
/// An operation observed a requested cancellation.
Cancelled,
}
impl Error {
@@ -61,7 +63,7 @@ impl Error {
pub const fn csharp_member(self) -> Option<&'static str> {
match self {
Self::NotImplemented(error) => Some(error.csharp_member()),
Self::ArgumentNull | Self::Argument | Self::IndexOutOfRange => None,
Self::ArgumentNull | Self::Argument | Self::IndexOutOfRange | Self::Cancelled => None,
}
}
}
@@ -79,6 +81,7 @@ impl fmt::Display for Error {
Self::ArgumentNull => formatter.write_str("required argument was null"),
Self::Argument => formatter.write_str("argument was invalid"),
Self::IndexOutOfRange => formatter.write_str("index was out of range"),
Self::Cancelled => formatter.write_str("operation was cancelled"),
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ async fn compile_login_shutdown_flows(
limiter: &CapsRateLimiter,
credential: LoginCredential,
) {
let token = CancellationToken;
let token = CancellationToken::NONE;
let _ = network
.login_with_login_credential_string_string_cancellation_token(
credential,
@@ -48,7 +48,7 @@ async fn compile_request_settings_flows(
request: DownloadRequest,
progress: Box<dyn IProgress<HttpCapsClientProgressReport>>,
) {
let token = CancellationToken;
let token = CancellationToken::NONE;
let _ = downloads
.download_with_uri_i_progress_cancellation_token(Uri(String::new()), progress, token)
.await;
@@ -61,7 +61,7 @@ fn compile_object_safe_texture_request(
provider: &dyn IBakingTextureProvider,
texture_id: libremetaverse_types::UUID,
) {
drop(provider.request_texture(texture_id, Some(CancellationToken)));
drop(provider.request_texture(texture_id, Some(CancellationToken::NONE)));
}
#[test]

View File

@@ -2251,45 +2251,6 @@ fn composite_current_outfit_policy_tests_can_attach_while_modifying_07f7e3a9d59d
);
}
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f pending
#[test]
fn download_manager_tests_queue_download_async_single_download_comp_f94b7ff6b74b() {
pending(
"LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test",
"LibreMetaverse.Tests/DownloadManagerTests.cs",
46,
"DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully",
"[Test]",
"f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f",
);
}
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall::test dc3ffee1f6171833a8364750cfac2c6d3bfa812b404129e0425066ea212f2337 pending
#[test]
fn download_manager_tests_queue_download_async_deduplicates_request_eca2cc0336a9() {
pending(
"LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall::test",
"LibreMetaverse.Tests/DownloadManagerTests.cs",
70,
"DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall",
"[Test]",
"dc3ffee1f6171833a8364750cfac2c6d3bfa812b404129e0425066ea212f2337",
);
}
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask::test 9eb3791b1def609d6f45be0fff8ae67ea04332045c0c419c6c2e7d118aeb6271 pending
#[test]
fn download_manager_tests_queue_download_async_cancellation_propaga_dc9717b7179d() {
pending(
"LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask::test",
"LibreMetaverse.Tests/DownloadManagerTests.cs",
101,
"DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask",
"[Test]",
"9eb3791b1def609d6f45be0fff8ae67ea04332045c0c419c6c2e7d118aeb6271",
);
}
// parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.GetParcelEnvironment_UsesParcelidQueryParameter::test 5c2f2b0fa2a86955975c621b2415a92ff3da283215e669956e14bf5ee2a4c1f4 pending
#[test]
fn environment_manager_tests_get_parcel_environment_uses_parcelid_q_a3db7892e643() {
@@ -2823,32 +2784,6 @@ fn gltf_document_tests_parse_node_explicit_matrix_7111222ea972() {
);
}
// parity-case: LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f pending
#[test]
fn grid_client_tests_get_grid_region_7bdbd012db67() {
pending(
"LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test",
"LibreMetaverse.Tests/GridClientTests.cs",
90,
"GridClientTests.GetGridRegion",
"[Test]",
"b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f",
);
}
// parity-case: LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs::HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow::test 9dbcf4023212af6f2294fa048068fe2f4d9e7e3f976d8eefb1dacbc9f6899aa8 pending
#[test]
fn http_caps_client_non_http_location_tests_put_async_response_with_8306ea5da797() {
pending(
"LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs::HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow::test",
"LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs",
47,
"HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow",
"[Test]",
"9dbcf4023212af6f2294fa048068fe2f4d9e7e3f976d8eefb1dacbc9f6899aa8",
);
}
// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields::test 3ae365ed34f12d036dcd44c94f8568d163cfeec2a58b4975975b77b418770131 pending
#[test]
fn inventory_ais_client_tests_parse_links_from_embedded_object_asse_f1f1434e9b61() {
@@ -4448,32 +4383,6 @@ fn misclassified_link_regression_tests_inventory_store_not_contains_c6334b81470b
);
}
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test 8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87 pending
#[test]
fn network_tests_detect_objects_4781c499b3ef() {
pending(
"LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test",
"LibreMetaverse.Tests/NetworkTests.cs",
121,
"NetworkTests.DetectObjects",
"[Test]",
"8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87",
);
}
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18 pending
#[test]
fn network_tests_caps_queue_bbde71137aba() {
pending(
"LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test",
"LibreMetaverse.Tests/NetworkTests.cs",
188,
"NetworkTests.CapsQueue",
"[Test]",
"cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18",
);
}
// parity-case: LibreMetaverse.Tests/OarFileTerrainTests.cs::OarFileTerrainTests.LoadTerrain_StandardRegion_Loads256x256::test c4e3a994baf06b0106a6614e9c3efbcc27e45ff1725f5470d2db6ded1fff6014 pending
#[test]
fn oar_file_terrain_tests_load_terrain_standard_region_loads256x256_d30f28505408() {

View File

@@ -0,0 +1,378 @@
#![allow(clippy::needless_pass_by_value)]
// Exact fixture provenance at LibreMetaverse 2aa70bb68513b39795da5d13c88f31b86e85a3ba:
// DownloadManagerTests.cs 07f31d4db35335602bc8d0a70ef440d38ce7a59f2c7765a184bc33f03b6c6015
// GridClientTests.cs adfee7ccb929270d5e88dccdbdb354c686bd008a167a3a1028b536664813c6d1
// HttpCapsClientNonHttpLocationTests.cs 92cac055067808883132d7415dc87d17e6ad05a62b85cd454e552879f568016f
// NetworkTests.cs 325586ce3cf9af1dbcb8dd36fc2be5647e524f4baeeb260520e44e66172142dc
use libremetaverse::http::DownloadManager;
use libremetaverse::packets::PacketType;
use libremetaverse::{GridClient, GridLayerType, HttpCapsClient, NetworkManager};
use libremetaverse_compat_tests::block_on;
use libremetaverse_types::compat::{
CancellationToken, CancellationTokenSource, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
};
use std::collections::BTreeMap;
use std::future::Future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, mpsc};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Clone)]
struct RecordingHandler {
requests: Arc<Mutex<Vec<HttpRequest>>>,
}
impl RecordingHandler {
fn fixed(response: HttpResponse, delay: Duration) -> (Self, HttpMessageHandler) {
let requests = Arc::new(Mutex::new(Vec::new()));
let recorded = Arc::clone(&requests);
let handler = HttpMessageHandler::new(move |request| {
recorded.lock().expect("request recording").push(request);
if !delay.is_zero() {
thread::sleep(delay);
}
response.clone()
});
(Self { requests }, handler)
}
fn count(&self) -> usize {
self.requests.lock().expect("request recording").len()
}
fn only_request(&self) -> HttpRequest {
let requests = self.requests.lock().expect("request recording");
assert_eq!(requests.len(), 1);
requests[0].clone()
}
}
fn response(status_code: u16, content_type: Option<&str>, body: &[u8]) -> HttpResponse {
HttpResponse {
status_code,
headers: BTreeMap::from([("Content-Length".into(), body.len().to_string())]),
content_type: content_type.map(str::to_owned),
body: body.to_vec(),
}
}
fn block_on_timeout<F>(future: F, timeout: Duration) -> F::Output
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let (tx, rx) = mpsc::sync_channel(1);
let worker = thread::spawn(move || {
let _ = tx.send(block_on(future));
});
let output = rx.recv_timeout(timeout).expect("operation timed out");
worker.join().expect("operation worker");
output
}
fn download_fixture(bytes: &[u8], delay: Duration) -> (RecordingHandler, HttpMessageHandler) {
RecordingHandler::fixed(response(200, None, bytes), delay)
}
fn assert_cancelled<T>(result: Result<T, libremetaverse::Error>) {
match result {
Err(libremetaverse::Error::Cancelled) => {}
Err(libremetaverse::Error::NotImplemented(error)) => panic!(
"expected Cancelled, got failure-only shim {}",
error.csharp_member()
),
Err(error) => panic!("expected Cancelled, got {error:?}"),
Ok(_) => panic!("expected Cancelled, got success"),
}
}
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f translated
#[test]
fn queue_download_single_completes_successfully() {
let bytes = b"hello world".to_vec();
let (recording, handler) = download_fixture(&bytes, Duration::ZERO);
let fake = HttpCapsClient::new(handler).expect("HttpCapsClient constructor");
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(fake);
let downloads = DownloadManager::new(client).expect("DownloadManager constructor");
let result = block_on_timeout(
async move {
downloads
.queue_download_with_uri_string_i_progress_cancellation_token_int32(
Uri("http://example.test/one".into()),
None,
None,
Some(CancellationToken::NONE),
Some(1),
)
.await
},
Duration::from_secs(5),
)
.expect("QueueDownloadAsync");
let (http_response, data) = result;
assert_eq!(data, bytes);
assert!(http_response.is_success_status_code());
assert_eq!(recording.count(), 1);
let request = recording.only_request();
assert_eq!(request.method, "GET");
assert_eq!(request.uri, Uri("http://example.test/one".into()));
}
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall::test dc3ffee1f6171833a8364750cfac2c6d3bfa812b404129e0425066ea212f2337 translated
#[test]
fn queue_download_deduplicates_same_uri() {
let bytes = b"duplicate payload".to_vec();
let (recording, handler) = download_fixture(&bytes, Duration::from_millis(200));
let fake = HttpCapsClient::new(handler).expect("HttpCapsClient constructor");
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(fake);
let downloads = Arc::new(DownloadManager::new(client).expect("DownloadManager constructor"));
let uri = Uri("http://example.test/dup".into());
let first_downloads = Arc::clone(&downloads);
let first_uri = uri.clone();
let first = thread::spawn(move || {
block_on(
first_downloads.queue_download_with_uri_string_i_progress_cancellation_token_int32(
first_uri,
None,
None,
Some(CancellationToken::NONE),
Some(1),
),
)
});
let second = thread::spawn(move || {
block_on(
downloads.queue_download_with_uri_string_i_progress_cancellation_token_int32(
uri,
None,
None,
Some(CancellationToken::NONE),
Some(1),
),
)
});
let started = Instant::now();
let (_, first_data) = first.join().expect("first download").expect("first result");
let (_, second_data) = second
.join()
.expect("second download")
.expect("second result");
assert!(started.elapsed() < Duration::from_secs(5));
assert_eq!(first_data, bytes);
assert_eq!(second_data, bytes);
assert_eq!(recording.count(), 1);
}
// parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask::test 9eb3791b1def609d6f45be0fff8ae67ea04332045c0c419c6c2e7d118aeb6271 translated
#[test]
fn queue_download_cancellation_propagates() {
let bytes = b"will cancel".to_vec();
let (recording, handler) = download_fixture(&bytes, Duration::ZERO);
let fake = HttpCapsClient::new(handler).expect("HttpCapsClient constructor");
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(fake);
let downloads = DownloadManager::new(client).expect("DownloadManager constructor");
let mut cancellation = CancellationTokenSource::new();
cancellation.cancel();
let result = block_on(
downloads.queue_download_with_uri_string_i_progress_cancellation_token_int32(
Uri("http://example.test/cancel".into()),
None,
None,
Some(cancellation.token()),
Some(1),
),
);
assert_cancelled(result);
assert_eq!(recording.count(), 0);
}
// parity-case: LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs::HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow::test 9dbcf4023212af6f2294fa048068fe2f4d9e7e3f976d8eefb1dacbc9f6899aa8 translated
#[test]
fn put_response_with_non_http_location_does_not_throw() {
const SLCAPS: &str = "slcaps://11111111-1111-1111-1111-111111111111/category/x";
let body = b"<llsd><map/></llsd>";
let mut reply = response(201, Some("application/llsd+xml"), body);
reply.headers.insert("Location".into(), SLCAPS.into());
reply
.headers
.insert("Content-Location".into(), SLCAPS.into());
let (recording, handler) = RecordingHandler::fixed(reply, Duration::ZERO);
let mut client = GridClient::new().expect("GridClient constructor");
let fake = HttpCapsClient::new(handler).expect("HttpCapsClient constructor");
client.set_http_caps_client(fake);
let (http_response, response_body) = block_on(
client
.http_caps_client()
.put_with_uri_string_bytes_cancellation_token_i_progress(
Uri("http://127.0.0.1/".into()),
"application/llsd+xml".into(),
b"test".to_vec(),
CancellationToken::NONE,
None,
),
)
.expect("HttpCapsClient PutAsync");
assert_eq!(http_response.status_code, 201);
assert_eq!(response_body, body);
assert_eq!(
http_response.headers.get("Location").map(String::as_str),
Some(SLCAPS)
);
assert_eq!(
http_response
.headers
.get("Content-Location")
.map(String::as_str),
Some(SLCAPS)
);
let request = recording.only_request();
assert_eq!(request.method, "PUT");
assert_eq!(request.uri, Uri("http://127.0.0.1/".into()));
assert_eq!(
request.content_type.as_deref(),
Some("application/llsd+xml")
);
assert_eq!(request.body, b"test");
}
fn live_credentials() -> (String, String, String) {
let full_name = std::env::var("LMVTestAgentUsername")
.expect("live test requires LMVTestAgentUsername='First Last'");
let password =
std::env::var("LMVTestAgentPassword").expect("live test requires LMVTestAgentPassword");
let mut names = full_name.split(' ');
let first = names.next().expect("live first name").to_owned();
let last = names.next().expect("live last name").to_owned();
(first, last, password)
}
fn login_live_client(version: &str, register_object_update: bool) -> (GridClient, Arc<AtomicBool>) {
let client = GridClient::new().expect("GridClient constructor");
let settings = client.settings();
let mut timing = settings.timing();
timing.login_timeout = 30_000;
std::hint::black_box(&timing);
let mut agent = client.self_();
agent.movement.set_fly(true);
let detected = Arc::new(AtomicBool::new(false));
let callback_detected = Arc::clone(&detected);
let network = client.network();
if register_object_update {
network
.register_callback_with_packet_type_event_handler(
PacketType::ObjectUpdate,
Arc::new(move |_event| {
callback_detected.store(true, Ordering::SeqCst);
}),
)
.expect("register ObjectUpdate callback");
}
let (first, last, password) = live_credentials();
let start = NetworkManager::start_location("Hooper".into(), 179, 18, 32)
.expect("NetworkManager StartLocation");
let logged_in = block_on(
network.login_with_string_string_string_string_string_string_cancellation_token(
first,
last,
password,
"Unit Test Framework".into(),
start,
version.into(),
Some(CancellationToken::NONE),
),
)
.expect("NetworkManager LoginAsync");
assert!(
logged_in,
"client failed to log in: {}",
network.login_message()
);
assert!(network.connected(), "client is not connected to the grid");
thread::sleep(Duration::from_secs(1));
assert!(
network.current_sim().is_some(),
"CurrentSim is null after login"
);
(client, detected)
}
fn logout_live_client(client: &GridClient) {
client
.network()
.logout_with_method()
.expect("NetworkManager Logout");
client.dispose_with_method().expect("GridClient Dispose");
}
// parity-case: LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f ignored-live
#[test]
#[ignore = "requires LMVTestAgentUsername and LMVTestAgentPassword plus an opt-in live grid"]
fn get_grid_region_live() {
let (client, _) = login_live_client("contact@radegast.life", false);
let region = block_on(
client
.grid()
.get_grid_region_with_string_grid_layer_type_cancellation_token(
"Hippo Hollow".into(),
GridLayerType::Terrain,
Some(CancellationToken::NONE),
),
)
.expect("GridManager GetGridRegionAsync");
if let Some(Some(region)) = region {
assert_eq!(region.name.to_lowercase(), "hippo hollow");
}
logout_live_client(&client);
}
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test 8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87 ignored-live
#[test]
#[ignore = "requires LMVTestAgentUsername and LMVTestAgentPassword plus an opt-in live grid"]
fn detect_objects_live() {
let (client, detected) = login_live_client("admin@radegast.life", true);
let deadline = Instant::now() + Duration::from_secs(20);
while !detected.load(Ordering::SeqCst) {
assert!(
Instant::now() <= deadline,
"timeout waiting for ObjectUpdate"
);
thread::sleep(Duration::from_millis(100));
}
assert!(detected.load(Ordering::SeqCst));
logout_live_client(&client);
}
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18 ignored-live
#[test]
#[ignore = "requires LMVTestAgentUsername and LMVTestAgentPassword plus an opt-in live grid"]
fn caps_queue_live() {
let (client, _) = login_live_client("admin@radegast.life", true);
let network = client.network();
let already_running = network
.current_sim()
.and_then(|simulator| simulator.caps)
.is_some_and(|caps| caps.is_event_queue_running());
if !already_running {
let (tx, rx) = mpsc::sync_channel(1);
let _subscription = network.subscribe_event_queue_running(Arc::new(move |_event| {
let _ = tx.send(());
}));
rx.recv_timeout(Duration::from_secs(10))
.expect("timeout waiting for event queue to start");
}
logout_live_client(&client);
}

View File

@@ -364,7 +364,7 @@ fn event_subscription_helper_wait_for_event_and_async() {
Box::new(|_event| true),
Box::new(|_event| Some(Guid([0; 16]))),
5_000,
Some(CancellationToken),
Some(CancellationToken::NONE),
Some(Guid([1; 16])),
),
)
@@ -392,7 +392,7 @@ fn repeat_interval_executes_and_cancels() {
Box::new(move || {
action_count.fetch_add(1, Ordering::SeqCst);
}),
CancellationToken,
CancellationToken::NONE,
Some(true),
),
)

File diff suppressed because it is too large Load Diff