Translate HTTP and network tests

This commit is contained in:
2026-08-08 18:30:51 +02:00
parent de6e611ef0
commit 6d4d9adaf4
13 changed files with 1793 additions and 1051 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
# Live-grid compatibility tests run automatically when all three values are set.
GRID_USER=First Last
GRID_PASSWORD=
GRID_LOGIN_URL=https://grid.example.test/login

View File

@@ -334,8 +334,8 @@ markers in hand-written Rust files, so regeneration records unresolved cases
without creating fake executable tests 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.
unreviewed cases; the current ledger contains 149 translated, three reviewed
live-grid cases, 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
@@ -358,8 +358,8 @@ The test-suite gate is complete only when:
- a controlled test run fails at standardized unimplemented member IDs rather
than from missing symbols, compile errors, malformed fixtures, or weakened
assertions;
- live-server/device tests are faithfully translated but `#[ignore]` by default
with documented opt-in configuration;
- live-server/device tests are faithfully translated and are conditionally
ignored only while their documented credentials are absent;
- no production method contains real behavior beyond what is required to make
signatures and constants compile.
@@ -712,8 +712,10 @@ Test translation rules:
- async tests use paused/injected time where possible; no arbitrary sleeps.
- Moq interaction tests prefer a small fake recording calls; use Mockall only
when a trait contract has many independent expectations.
- `RequiresLiveServer` tests become `#[ignore]` and require explicit grid URL,
first/last name, and secret environment variables. They never run in normal CI.
- `RequiresLiveServer` tests use `GRID_USER`, `GRID_PASSWORD`, and
`GRID_LOGIN_URL` from the process environment or the ignored workspace
`.env`; they are ignored when any value is absent and run automatically when
all three are present. `.env.example` documents the non-secret shape.
- benchmark-category methods move to Criterion and are removed from correctness
pass/fail counts.
- malformed wire/asset tests become fuzz seeds after direct parity is passing.

View File

@@ -26249,8 +26249,8 @@ P:LibreMetaverse.GridClient.Marketplace property LibreMetaverse.Marketplace.Mark
P:LibreMetaverse.GridClient.Network property LibreMetaverse.NetworkManager LibreMetaverse.GridClient.Network libremetaverse libremetaverse::GridClient::network pub fn network(&self) -> libremetaverse::NetworkManager ; pub fn set_network(&mut self, value: libremetaverse::NetworkManager) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Objects property LibreMetaverse.ObjectManager LibreMetaverse.GridClient.Objects libremetaverse libremetaverse::GridClient::objects pub fn objects(&self) -> libremetaverse::ObjectManager ; pub fn set_objects(&mut self, value: libremetaverse::ObjectManager) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Parcels property LibreMetaverse.ParcelManager LibreMetaverse.GridClient.Parcels libremetaverse libremetaverse::GridClient::parcels pub fn parcels(&self) -> libremetaverse::ParcelManager ; pub fn set_parcels(&mut self, value: libremetaverse::ParcelManager) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Self property LibreMetaverse.AgentManager LibreMetaverse.GridClient.Self libremetaverse libremetaverse::GridClient::self_ pub fn self_(&self) -> libremetaverse::AgentManager ; pub fn set_self_(&mut self, value: libremetaverse::AgentManager) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Settings property LibreMetaverse.Settings LibreMetaverse.GridClient.Settings libremetaverse libremetaverse::GridClient::settings pub fn settings(&self) -> libremetaverse::Settings ; pub fn set_settings(&mut self, value: libremetaverse::Settings) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Self property LibreMetaverse.AgentManager LibreMetaverse.GridClient.Self libremetaverse libremetaverse::GridClient::self_ pub fn self_(&mut self) -> &mut libremetaverse::AgentManager mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Settings property LibreMetaverse.Settings LibreMetaverse.GridClient.Settings libremetaverse libremetaverse::GridClient::settings pub fn settings(&mut self) -> &mut libremetaverse::Settings mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Sound property LibreMetaverse.SoundManager LibreMetaverse.GridClient.Sound libremetaverse libremetaverse::GridClient::sound pub fn sound(&self) -> libremetaverse::SoundManager ; pub fn set_sound(&mut self, value: libremetaverse::SoundManager) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Stats property LibreMetaverse.Stats.UtilizationStatistics LibreMetaverse.GridClient.Stats libremetaverse libremetaverse::GridClient::stats pub fn stats(&self) -> libremetaverse::stats::UtilizationStatistics ; pub fn set_stats(&mut self, value: libremetaverse::stats::UtilizationStatistics) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.GridClient.Terrain property LibreMetaverse.TerrainManager LibreMetaverse.GridClient.Terrain libremetaverse libremetaverse::GridClient::terrain pub fn terrain(&self) -> libremetaverse::TerrainManager ; pub fn set_terrain(&mut self, value: libremetaverse::TerrainManager) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
@@ -30566,7 +30566,7 @@ P:LibreMetaverse.Settings.Logging property LibreMetaverse.LoggingSettings LibreM
P:LibreMetaverse.Settings.Packets property LibreMetaverse.PacketSettings LibreMetaverse.Settings.Packets libremetaverse libremetaverse::Settings::packets pub fn packets(&self) -> libremetaverse::PacketSettings shared_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.Settings.Parcel property LibreMetaverse.ParcelSettings LibreMetaverse.Settings.Parcel libremetaverse libremetaverse::Settings::parcel pub fn parcel(&self) -> libremetaverse::ParcelSettings shared_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.Settings.TexturePipeline property LibreMetaverse.TexturePipelineSettings LibreMetaverse.Settings.TexturePipeline libremetaverse libremetaverse::Settings::texture_pipeline pub fn texture_pipeline(&self) -> libremetaverse::TexturePipelineSettings shared_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.Settings.Timing property LibreMetaverse.TimingSettings LibreMetaverse.Settings.Timing libremetaverse libremetaverse::Settings::timing pub fn timing(&self) -> libremetaverse::TimingSettings shared_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.Settings.Timing property LibreMetaverse.TimingSettings LibreMetaverse.Settings.Timing libremetaverse libremetaverse::Settings::timing pub fn timing(&mut self) -> &mut libremetaverse::TimingSettings mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.Settings.UploadCost property System.Int32 LibreMetaverse.Settings.UploadCost libremetaverse libremetaverse::Settings::upload_cost pub fn upload_cost(&self) -> i32 ; pub fn set_upload_cost(&mut self, value: i32) shared_or_mutable_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.Settings.World property LibreMetaverse.WorldSettings LibreMetaverse.Settings.World libremetaverse libremetaverse::Settings::world pub fn world(&self) -> libremetaverse::WorldSettings shared_self sync infallible_placeholder direct_snake_case accessor_methods mapped
P:LibreMetaverse.SimChangedEventArgs.PreviousSimulator property LibreMetaverse.Simulator LibreMetaverse.SimChangedEventArgs.PreviousSimulator libremetaverse libremetaverse::SimChangedEventArgs::previous_simulator pub fn previous_simulator(&self) -> Option<libremetaverse::Simulator> shared_self sync infallible_placeholder direct_snake_case accessor_methods mapped
Can't render this file because it is too large.

View File

@@ -4,7 +4,10 @@
//! surfaces. Their behavior is implemented only when the owning API slice is
//! ported.
use std::collections::BTreeMap;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
@@ -91,7 +94,7 @@ impl CancellationToken {
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[derive(Debug, Default, Eq, Hash, PartialEq)]
pub struct Subscription;
pub type EventHandler<T> = Arc<dyn Fn(T) + Send + Sync>;
@@ -143,17 +146,65 @@ 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
}
}
pub type HttpResponseFuture = Pin<Box<dyn Future<Output = HttpResponse> + Send>>;
type HttpHandler = dyn Fn(HttpRequest, CancellationToken) -> HttpResponseFuture + Send + Sync;
#[derive(Clone)]
pub struct HttpMessageHandler(Arc<HttpHandler>);
impl HttpMessageHandler {
pub fn new<F, Fut>(handler: F) -> Self
where
F: Fn(HttpRequest, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = HttpResponse> + Send + 'static,
{
Self(Arc::new(move |request, cancellation_token| {
Box::pin(handler(request, cancellation_token))
}))
}
#[must_use]
pub fn send(
&self,
request: HttpRequest,
cancellation_token: CancellationToken,
) -> HttpResponseFuture {
(self.0)(request, cancellation_token)
}
}
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;
@@ -200,3 +251,55 @@ pub struct UnicodeCategory(pub i32);
pub struct TextEncoding;
pub struct SocketException;
#[cfg(test)]
mod tests {
use super::{CancellationToken, HttpMessageHandler, HttpRequest, HttpResponse, Uri};
use std::collections::BTreeMap;
use std::future::Future;
use std::task::{Context, Poll, Waker};
#[test]
fn http_handler_preserves_request_and_response_data() {
let handler = HttpMessageHandler::new(|request, _cancellation_token| async move {
HttpResponse {
status_code: 201,
headers: request.headers,
content_type: request.content_type,
body: request.body,
}
});
let mut 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(),
},
CancellationToken::default(),
);
let mut context = Context::from_waker(Waker::noop());
let response = match Future::poll(response.as_mut(), &mut context) {
Poll::Ready(response) => response,
Poll::Pending => panic!("immediate handler unexpectedly remained pending"),
};
assert!(response.is_success_status_code());
assert_eq!(response.headers["Location"], "slcaps://fixture");
assert_eq!(
response.content_type.as_deref(),
Some("application/llsd+xml")
);
assert_eq!(response.body, b"test");
assert!(
!HttpResponse {
status_code: 399,
headers: BTreeMap::new(),
content_type: None,
body: Vec::new(),
}
.is_success_status_code()
);
}
}

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"),
}
}
}

View File

@@ -11377,19 +11377,11 @@ impl GridClient {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Parcels")
}
/// C# member: `P:LibreMetaverse.GridClient.Self`.
pub fn self_(&self) -> libremetaverse::AgentManager {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Self")
}
/// Setter for C# member: `P:LibreMetaverse.GridClient.Self`.
pub fn set_self_(&mut self, value: libremetaverse::AgentManager) {
pub fn self_(&mut self) -> &mut libremetaverse::AgentManager {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Self")
}
/// C# member: `P:LibreMetaverse.GridClient.Settings`.
pub fn settings(&self) -> libremetaverse::Settings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Settings")
}
/// Setter for C# member: `P:LibreMetaverse.GridClient.Settings`.
pub fn set_settings(&mut self, value: libremetaverse::Settings) {
pub fn settings(&mut self) -> &mut libremetaverse::Settings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.GridClient.Settings")
}
/// C# member: `P:LibreMetaverse.GridClient.Sound`.
@@ -25747,7 +25739,7 @@ impl Settings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.TexturePipeline")
}
/// C# member: `P:LibreMetaverse.Settings.Timing`.
pub fn timing(&self) -> libremetaverse::TimingSettings {
pub fn timing(&mut self) -> &mut libremetaverse::TimingSettings {
libremetaverse_types::unimplemented_api!("P:LibreMetaverse.Settings.Timing")
}
/// C# member: `P:LibreMetaverse.Settings.UploadCost`.

View File

@@ -5,9 +5,9 @@ Generated from LibreMetaverse `2aa70bb68513b39795da5d13c88f31b86e85a3ba`. Stable
## Status
- Total: **1,289**
- Pending/unreviewed: **1,140**
- Translated/reviewed: **145**
- Ignored live/reviewed: **0** (19 upstream live candidates)
- Pending/unreviewed: **1,133**
- Translated/reviewed: **149**
- Ignored live/reviewed: **3** (19 upstream live candidates)
- Benchmarks/reviewed: **4** (4 upstream benchmark candidates)
- Drifted: **0**
@@ -214,9 +214,9 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case`
| `LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithNullLists_DoesNotThrowFromComposite::test` | `CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithNullLists_DoesNotThrowFromComposite` | `` | `LibreMetaverse.Tests/CurrentOutfitFolderTests.cs:588` | `` | `` | `pending source translation` | `pending` | `c512c54f78c46fb4774d9b5c9c1057627592e1c25267811cddf3d687d3d1ec4d` |
| `LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddRemovePolicy_ConcurrentAccess_DoesNotThrow::test` | `CompositeCurrentOutfitPolicyTests.AddRemovePolicy_ConcurrentAccess_DoesNotThrow` | `` | `LibreMetaverse.Tests/CurrentOutfitFolderTests.cs:601` | `` | `` | `pending source translation` | `pending` | `a73a01065867d90305f491e41d9ea61bc27b80c026672ed920368877bfb0fe14` |
| `LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WhileModifyingPolicies_DoesNotThrow::test` | `CompositeCurrentOutfitPolicyTests.CanAttach_WhileModifyingPolicies_DoesNotThrow` | `` | `LibreMetaverse.Tests/CurrentOutfitFolderTests.cs:627` | `` | `` | `pending source translation` | `pending` | `697999feeb776fae9a47cb9678c0ae0234d3b3831ce40c0e6f50fe622a5cc2b7` |
| `LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test` | `DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully` | `` | `LibreMetaverse.Tests/DownloadManagerTests.cs:46` | `` | `` | `pending source translation` | `pending` | `f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f` |
| `LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall::test` | `DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall` | `` | `LibreMetaverse.Tests/DownloadManagerTests.cs:70` | `` | `` | `pending source translation` | `pending` | `dc3ffee1f6171833a8364750cfac2c6d3bfa812b404129e0425066ea212f2337` |
| `LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask::test` | `DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask` | `` | `LibreMetaverse.Tests/DownloadManagerTests.cs:101` | `` | `` | `pending source translation` | `pending` | `9eb3791b1def609d6f45be0fff8ae67ea04332045c0c419c6c2e7d118aeb6271` |
| `LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test` | `DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully` | `` | `LibreMetaverse.Tests/DownloadManagerTests.cs:46` | `` | `` | `tests/compat/tests/network_semantics.rs:192 (`queue_download_single_completes_successfully`)` | `translated` | `f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f` |
| `LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall::test` | `DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall` | `` | `LibreMetaverse.Tests/DownloadManagerTests.cs:70` | `` | `` | `tests/compat/tests/network_semantics.rs:229 (`queue_download_deduplicates_same_uri`)` | `translated` | `dc3ffee1f6171833a8364750cfac2c6d3bfa812b404129e0425066ea212f2337` |
| `LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask::test` | `DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask` | `` | `LibreMetaverse.Tests/DownloadManagerTests.cs:101` | `` | `` | `tests/compat/tests/network_semantics.rs:275 (`queue_download_cancellation_propagates`)` | `translated` | `9eb3791b1def609d6f45be0fff8ae67ea04332045c0c419c6c2e7d118aeb6271` |
| `LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.GetParcelEnvironment_UsesParcelidQueryParameter::test` | `EnvironmentManagerTests.GetParcelEnvironment_UsesParcelidQueryParameter` | `` | `LibreMetaverse.Tests/EnvironmentManagerTests.cs:105` | `Environment` | `` | `pending source translation` | `pending` | `5c2f2b0fa2a86955975c621b2415a92ff3da283215e669956e14bf5ee2a4c1f4` |
| `LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.SetParcelEnvironment_SendsParcelidInQueryStringNotBody::test` | `EnvironmentManagerTests.SetParcelEnvironment_SendsParcelidInQueryStringNotBody` | `` | `LibreMetaverse.Tests/EnvironmentManagerTests.cs:128` | `Environment` | `` | `pending source translation` | `pending` | `f633e2d78e4c6a0fff42917ccfe8de68e4f6133a11e7eef7f9b757c3d1622f0f` |
| `LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.SetRegionEnvironment_OmitsQueryStringEntirely::test` | `EnvironmentManagerTests.SetRegionEnvironment_OmitsQueryStringEntirely` | `` | `LibreMetaverse.Tests/EnvironmentManagerTests.cs:158` | `Environment` | `` | `pending source translation` | `pending` | `bade6e06765506ad7e500ac3e5c9f168605ce25fef86647819d285a68b39f109` |
@@ -258,7 +258,7 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case`
| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.Load_AutoDetectsGlbVsJson::test` | `GltfDocumentTests.Load_AutoDetectsGlbVsJson` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:252` | `` | `` | `pending source translation` | `pending` | `6d07930be2302e504ef9ece3fef10f47b855d33ae6c8bff152e82df0beb2a474` |
| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_TrsDefaults::test` | `GltfDocumentTests.ParseNode_TrsDefaults` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:268` | `` | `` | `pending source translation` | `pending` | `0f0fbe116d20276c2aee917b940bded9de277ed0b3e3b659844c5b3532755259` |
| `LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_ExplicitMatrix::test` | `GltfDocumentTests.ParseNode_ExplicitMatrix` | `` | `LibreMetaverse.Tests/GltfDocumentTests.cs:280` | `` | `` | `pending source translation` | `pending` | `197ba2ee3a66cbb7d927acea13ce9dc84e62af8a500d939957749039118755c8` |
| `LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test` | `GridClientTests.GetGridRegion` | `` | `LibreMetaverse.Tests/GridClientTests.cs:90` | `GridClient, RequiresLiveServer` | `` | `pending source translation` | `pending` | `b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f` |
| `LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test` | `GridClientTests.GetGridRegion` | `` | `LibreMetaverse.Tests/GridClientTests.cs:90` | `GridClient, RequiresLiveServer` | `` | `tests/compat/tests/network_semantics.rs:372 (`get_grid_region_live`)` | `ignored-live` | `b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f` |
| `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroDecode_ValidInput_DecodesCorrectly::test` | `HelpersZeroCodingTests.ZeroDecode_ValidInput_DecodesCorrectly` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:36` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:79 (`zero_decode_valid_input_decodes_correctly`)` | `translated` | `62353da2b1afb7bfdf5142706d15d22086a5e1e8c55f39af3e64686dda4ea071` |
| `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroDecode_NullSource_ThrowsArgumentNullException::test` | `HelpersZeroCodingTests.ZeroDecode_NullSource_ThrowsArgumentNullException` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:64` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:97 (`zero_decode_null_source_throws_argument_null`)` | `translated` | `50ebbd8ea35b87df18cccdea9a26534d5c142228e5b1698cf191b9fd2e9a9957` |
| `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroDecode_NullDestination_ThrowsArgumentNullException::test` | `HelpersZeroCodingTests.ZeroDecode_NullDestination_ThrowsArgumentNullException` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:73` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:108 (`zero_decode_null_destination_throws_argument_null`)` | `translated` | `97197cffb02908653d831f3a62cdce2c1ddd52818343e9aacebc9b4c5ac1ee3e` |
@@ -273,7 +273,7 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case`
| `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroEncode_DestinationTooSmall_ThrowsIndexOutOfRangeException::test` | `HelpersZeroCodingTests.ZeroEncode_DestinationTooSmall_ThrowsIndexOutOfRangeException` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:218` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:222 (`zero_encode_destination_too_small_throws_index_out_of_range`)` | `translated` | `3870f0680f25e8df8de54f27c153feae9d8398e38531d67d6e0255cc34d50966` |
| `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroEncode_WithAppendedAcks_HandlesCorrectly::test` | `HelpersZeroCodingTests.ZeroEncode_WithAppendedAcks_HandlesCorrectly` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:231` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:235 (`zero_encode_with_appended_acks_preserves_count`)` | `translated` | `9803ec7bad46a1fd0f32c58d3ee08cf6a98b03a40a336ae231d1faf8211b273a` |
| `LibreMetaverse.Tests/HelpersZeroCodingTests.cs::HelpersZeroCodingTests.ZeroEncodeAndDecode_RoundTrip_PreservesData::test` | `HelpersZeroCodingTests.ZeroEncodeAndDecode_RoundTrip_PreservesData` | `` | `LibreMetaverse.Tests/HelpersZeroCodingTests.cs:254` | `Helpers` | `` | `tests/compat/tests/wire_semantics.rs:248 (`zero_encode_decode_round_trip_preserves_patterns`)` | `translated` | `7847520873f87d798ea94fd1b6543db9d3606829a4bc3f4ec873e629fb6687eb` |
| `LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs::HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow::test` | `HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow` | `` | `LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs:47` | `Http` | `` | `pending source translation` | `pending` | `9dbcf4023212af6f2294fa048068fe2f4d9e7e3f976d8eefb1dacbc9f6899aa8` |
| `LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs::HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow::test` | `HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow` | `` | `LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs:47` | `Http` | `` | `tests/compat/tests/network_semantics.rs:306 (`put_response_with_non_http_location_does_not_throw`)` | `translated` | `9dbcf4023212af6f2294fa048068fe2f4d9e7e3f976d8eefb1dacbc9f6899aa8` |
| `LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields::test` | `InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields` | `` | `LibreMetaverse.Tests/InventoryAISClientTests.cs:37` | `` | `` | `pending source translation` | `pending` | `3ae365ed34f12d036dcd44c94f8568d163cfeec2a58b4975975b77b418770131` |
| `LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_ItemsKey_ParsesConcreteItems::test` | `InventoryAISClientTests.ParseItemsFromEmbedded_ItemsKey_ParsesConcreteItems` | `` | `LibreMetaverse.Tests/InventoryAISClientTests.cs:82` | `` | `` | `pending source translation` | `pending` | `d9e7c64106639071e1ec6ada7346bfa050a31183becaac79efefcd4755fe2855` |
| `LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_MeshAsset_TreatedAsAttachment::test` | `InventoryAISClientTests.ParseLinksFromEmbedded_MeshAsset_TreatedAsAttachment` | `` | `LibreMetaverse.Tests/InventoryAISClientTests.cs:122` | `` | `` | `pending source translation` | `pending` | `f807110d577c654128dd8d42e618c610c260c4f3367cf2795776564409f876a2` |
@@ -433,8 +433,8 @@ Reviewed Rust tests live outside `generated_parity.rs` and carry a `parity-case`
| `LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs::MisclassifiedLinkRegressionTests.InventoryStore_Indexer_ReturnsAttachment_WhenTargetIsAttachment::test` | `MisclassifiedLinkRegressionTests.InventoryStore_Indexer_ReturnsAttachment_WhenTargetIsAttachment` | `` | `LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs:168` | `Inventory` | `` | `pending source translation` | `pending` | `f58debff8f66b7b417215eb6879f96935647b66cc619fdb00e6815c9ce5e7c67` |
| `LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs::MisclassifiedLinkRegressionTests.InventoryStore_Indexer_ReturnsObject_WhenTargetIsObject::test` | `MisclassifiedLinkRegressionTests.InventoryStore_Indexer_ReturnsObject_WhenTargetIsObject` | `` | `LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs:192` | `Inventory` | `` | `pending source translation` | `pending` | `b8137c994019103977b0b85d8c148e12ae8f9051334ca487db7c88bcbe707d40` |
| `LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs::MisclassifiedLinkRegressionTests.InventoryStore_NotContains_WhenTargetAbsent::test` | `MisclassifiedLinkRegressionTests.InventoryStore_NotContains_WhenTargetAbsent` | `` | `LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs:216` | `Inventory` | `` | `pending source translation` | `pending` | `6900e220c33ec135d02c55e047c7052a095d5dd9d71da4a857ae70bc151c472b` |
| `LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test` | `NetworkTests.DetectObjects` | `` | `LibreMetaverse.Tests/NetworkTests.cs:121` | `Network, RequiresLiveServer` | `` | `pending source translation` | `pending` | `8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87` |
| `LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test` | `NetworkTests.CapsQueue` | `` | `LibreMetaverse.Tests/NetworkTests.cs:188` | `Network, RequiresLiveServer` | `` | `pending source translation` | `pending` | `cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18` |
| `LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test` | `NetworkTests.DetectObjects` | `` | `LibreMetaverse.Tests/NetworkTests.cs:121` | `Network, RequiresLiveServer` | `` | `tests/compat/tests/network_semantics.rs:436 (`detect_objects_live`)` | `ignored-live` | `8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87` |
| `LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test` | `NetworkTests.CapsQueue` | `` | `LibreMetaverse.Tests/NetworkTests.cs:188` | `Network, RequiresLiveServer` | `` | `tests/compat/tests/network_semantics.rs:515 (`caps_queue_live`)` | `ignored-live` | `cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18` |
| `LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.HelperFunctions::test` | `NotationSDTests.HelperFunctions` | `` | `LibreMetaverse.Tests/NotationLLSDTests.cs:53` | `` | `` | `tests/compat/tests/structured_data.rs:622 (`notation_helper_functions`)` | `translated` | `a521ca5407ed3afd1d59f5b65a283bcebe9d34b4c384d68c5fc674b3a1eeb9df` |
| `LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.DeserializeUndef::test` | `NotationSDTests.DeserializeUndef` | `` | `LibreMetaverse.Tests/NotationLLSDTests.cs:80` | `` | `` | `tests/compat/tests/structured_data.rs:644 (`notation_deserialize_undef`)` | `translated` | `8c802fb14244cb423223c7c25702e4120da261f5dd1b378d2eca597277371406` |
| `LibreMetaverse.Tests/NotationLLSDTests.cs::NotationSDTests.SerializeUndef::test` | `NotationSDTests.SerializeUndef` | `` | `LibreMetaverse.Tests/NotationLLSDTests.cs:88` | `` | `` | `tests/compat/tests/structured_data.rs:652 (`notation_serialize_undef`)` | `translated` | `2ceee97b1826a3ba88a50798dba184b48998196192e31527171a9980f06c3281` |

View File

@@ -13932,13 +13932,11 @@ fn member_02488(value: &mut libremetaverse::GridClient) {
// C# member: `P:LibreMetaverse.GridClient.Self`.
fn member_02489(value: &mut libremetaverse::GridClient) {
let _ = value.self_();
let _ = value.set_self_(argument());
}
// C# member: `P:LibreMetaverse.GridClient.Settings`.
fn member_02490(value: &mut libremetaverse::GridClient) {
let _ = value.settings();
let _ = value.set_settings(argument());
}
// C# member: `P:LibreMetaverse.GridClient.Sound`.

30
tests/compat/build.rs Normal file
View File

@@ -0,0 +1,30 @@
use std::path::Path;
const KEYS: [&str; 3] = ["GRID_USER", "GRID_PASSWORD", "GRID_LOGIN_URL"];
fn dotenv_has(path: &Path, key: &str) -> bool {
std::fs::read_to_string(path).is_ok_and(|contents| {
contents.lines().any(|line| {
let line = line.trim().strip_prefix("export ").unwrap_or(line.trim());
line.split_once('=').is_some_and(|(name, value)| {
name.trim() == key && !value.trim().trim_matches(['\'', '"']).is_empty()
})
})
})
}
fn main() {
let dotenv = Path::new(&std::env::var("CARGO_MANIFEST_DIR").expect("manifest directory"))
.join("../..")
.join(".env");
println!("cargo:rustc-check-cfg=cfg(live_grid_credentials)");
println!("cargo:rerun-if-changed={}", dotenv.display());
for key in KEYS {
println!("cargo:rerun-if-env-changed={key}");
}
if KEYS.iter().all(|key| {
std::env::var(key).is_ok_and(|value| !value.trim().is_empty()) || dotenv_has(&dotenv, key)
}) {
println!("cargo:rustc-cfg=live_grid_credentials");
}
}

View File

@@ -175,9 +175,6 @@
// pending-parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.ReportItemChangeAsync_WithNullLists_DoesNotThrowFromComposite::test c512c54f78c46fb4774d9b5c9c1057627592e1c25267811cddf3d687d3d1ec4d
// pending-parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.AddRemovePolicy_ConcurrentAccess_DoesNotThrow::test a73a01065867d90305f491e41d9ea61bc27b80c026672ed920368877bfb0fe14
// pending-parity-case: LibreMetaverse.Tests/CurrentOutfitFolderTests.cs::CompositeCurrentOutfitPolicyTests.CanAttach_WhileModifyingPolicies_DoesNotThrow::test 697999feeb776fae9a47cb9678c0ae0234d3b3831ce40c0e6f50fe622a5cc2b7
// pending-parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_SingleDownload_CompletesSuccessfully::test f8feb54d38f79624ddf1d368e0599259ff9d474c08ea0249a8f244460a1f533f
// pending-parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_DeduplicatesRequests_SameUri_OneHttpCall::test dc3ffee1f6171833a8364750cfac2c6d3bfa812b404129e0425066ea212f2337
// pending-parity-case: LibreMetaverse.Tests/DownloadManagerTests.cs::DownloadManagerTests.QueueDownloadAsync_Cancellation_PropagatesToTask::test 9eb3791b1def609d6f45be0fff8ae67ea04332045c0c419c6c2e7d118aeb6271
// pending-parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.GetParcelEnvironment_UsesParcelidQueryParameter::test 5c2f2b0fa2a86955975c621b2415a92ff3da283215e669956e14bf5ee2a4c1f4
// pending-parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.SetParcelEnvironment_SendsParcelidInQueryStringNotBody::test f633e2d78e4c6a0fff42917ccfe8de68e4f6133a11e7eef7f9b757c3d1622f0f
// pending-parity-case: LibreMetaverse.Tests/EnvironmentManagerTests.cs::EnvironmentManagerTests.SetRegionEnvironment_OmitsQueryStringEntirely::test bade6e06765506ad7e500ac3e5c9f168605ce25fef86647819d285a68b39f109
@@ -219,8 +216,6 @@
// pending-parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.Load_AutoDetectsGlbVsJson::test 6d07930be2302e504ef9ece3fef10f47b855d33ae6c8bff152e82df0beb2a474
// pending-parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_TrsDefaults::test 0f0fbe116d20276c2aee917b940bded9de277ed0b3e3b659844c5b3532755259
// pending-parity-case: LibreMetaverse.Tests/GltfDocumentTests.cs::GltfDocumentTests.ParseNode_ExplicitMatrix::test 197ba2ee3a66cbb7d927acea13ce9dc84e62af8a500d939957749039118755c8
// pending-parity-case: LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f
// pending-parity-case: LibreMetaverse.Tests/HttpCapsClientNonHttpLocationTests.cs::HttpCapsClientNonHttpLocationTests.PutAsync_ResponseWithNonHttpLocationHeader_DoesNotThrow::test 9dbcf4023212af6f2294fa048068fe2f4d9e7e3f976d8eefb1dacbc9f6899aa8
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields::test 3ae365ed34f12d036dcd44c94f8568d163cfeec2a58b4975975b77b418770131
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_ItemsKey_ParsesConcreteItems::test d9e7c64106639071e1ec6ada7346bfa050a31183becaac79efefcd4755fe2855
// pending-parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_MeshAsset_TreatedAsAttachment::test f807110d577c654128dd8d42e618c610c260c4f3367cf2795776564409f876a2
@@ -344,8 +339,6 @@
// pending-parity-case: LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs::MisclassifiedLinkRegressionTests.InventoryStore_Indexer_ReturnsAttachment_WhenTargetIsAttachment::test f58debff8f66b7b417215eb6879f96935647b66cc619fdb00e6815c9ce5e7c67
// pending-parity-case: LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs::MisclassifiedLinkRegressionTests.InventoryStore_Indexer_ReturnsObject_WhenTargetIsObject::test b8137c994019103977b0b85d8c148e12ae8f9051334ca487db7c88bcbe707d40
// pending-parity-case: LibreMetaverse.Tests/MisclassifiedLinkRegressionTests.cs::MisclassifiedLinkRegressionTests.InventoryStore_NotContains_WhenTargetAbsent::test 6900e220c33ec135d02c55e047c7052a095d5dd9d71da4a857ae70bc151c472b
// pending-parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test 8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87
// pending-parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18
// pending-parity-case: LibreMetaverse.Tests/OarFileTerrainTests.cs::OarFileTerrainTests.LoadTerrain_StandardRegion_Loads256x256::test c4e3a994baf06b0106a6614e9c3efbcc27e45ff1725f5470d2db6ded1fff6014
// pending-parity-case: LibreMetaverse.Tests/OarFileTerrainTests.cs::OarFileTerrainTests.LoadTerrain_Varregion_Loads512x512::test 4820338f10acbb4987e26992d7a7957be58ab6bc548674bf18943ef312962894
// pending-parity-case: LibreMetaverse.Tests/OarFileTerrainTests.cs::OarFileTerrainTests.LoadTerrain_WrongByteCount_FailsGracefully::test ff6fb4feb2773c93397d412d0ecf6aef1f3636e823dabb729a7a6cbe94d1513f

View File

@@ -0,0 +1,600 @@
#![allow(clippy::needless_pass_by_value)]
// Exact fixture provenance at LibreMetaverse 2aa70bb68513b39795da5d13c88f31b86e85a3ba:
// DownloadManagerTests.cs 07f31d4db35335602bc8d0a70ef440d38ce7a59f2c7765a184bc33f03b6c6015
// GridClientTests.cs adfee7ccb929270d5e88dccdb354c686bd008a167a3a1028b536664813c6d1
// 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::task::{Context, Poll, Waker};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Clone)]
struct FakeHandler {
requests: Arc<Mutex<Vec<HttpRequest>>>,
}
struct Delay {
state: Arc<Mutex<(bool, Option<Waker>)>>,
}
impl Delay {
fn new(duration: Duration) -> Self {
let state = Arc::new(Mutex::new((false, None::<Waker>)));
let worker_state = Arc::clone(&state);
thread::spawn(move || {
thread::sleep(duration);
let waker = {
let mut state = worker_state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
state.0 = true;
state.1.take()
};
if let Some(waker) = waker {
waker.wake();
}
});
Self { state }
}
}
impl Future for Delay {
type Output = ();
fn poll(self: std::pin::Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
let mut state = self
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if state.0 {
Poll::Ready(())
} else {
state.1 = Some(context.waker().clone());
Poll::Pending
}
}
}
impl FakeHandler {
fn new(data: &[u8], delay: Duration) -> (Self, HttpMessageHandler) {
let requests = Arc::new(Mutex::new(Vec::new()));
let recorded = Arc::clone(&requests);
let response = HttpResponse {
status_code: 200,
headers: BTreeMap::from([("Content-Length".into(), data.len().to_string())]),
content_type: None,
body: data.to_vec(),
};
let handler = HttpMessageHandler::new(move |request, _cancellation_token| {
let recorded = Arc::clone(&recorded);
let response = response.clone();
async move {
recorded
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(request);
if !delay.is_zero() {
Delay::new(delay).await;
}
response
}
});
(Self { requests }, handler)
}
fn send_count(&self) -> usize {
self.requests
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.len()
}
fn only_request(&self) -> HttpRequest {
let requests = self
.requests
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
assert_eq!(requests.len(), 1, "expected exactly one HTTP request");
requests[0].clone()
}
}
fn block_on_timeout<F>(future: F, timeout: Duration) -> F::Output
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let (sender, receiver) = mpsc::sync_channel(1);
let worker = thread::spawn(move || {
let _ = sender.send(block_on(future));
});
let output = receiver.recv_timeout(timeout).expect("operation timed out");
worker.join().expect("operation worker panicked");
output
}
async fn join<F1, F2>(first: F1, second: F2) -> (F1::Output, F2::Output)
where
F1: Future,
F2: Future,
{
let mut first = std::pin::pin!(first);
let mut second = std::pin::pin!(second);
let mut first_output = None;
let mut second_output = None;
std::future::poll_fn(|context| {
if first_output.is_none()
&& let Poll::Ready(output) = first.as_mut().poll(context)
{
first_output = Some(output);
}
if second_output.is_none()
&& let Poll::Ready(output) = second.as_mut().poll(context)
{
second_output = Some(output);
}
if first_output.is_some() && second_output.is_some() {
Poll::Ready((
first_output.take().expect("first future output"),
second_output.take().expect("second future output"),
))
} else {
Poll::Pending
}
})
.await
}
fn credential(name: &str) -> String {
if let Ok(value) = std::env::var(name)
&& !value.trim().is_empty()
{
return value;
}
let dotenv = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join(".env");
let contents = std::fs::read_to_string(dotenv).expect("read workspace .env");
contents
.lines()
.find_map(|line| {
let line = line.trim().strip_prefix("export ").unwrap_or(line.trim());
let (key, value) = line.split_once('=')?;
(key.trim() == name).then(|| value.trim().trim_matches(['\'', '"']).to_owned())
})
.filter(|value| !value.is_empty())
.unwrap_or_else(|| panic!("live test requires {name}"))
}
fn live_credentials() -> (String, String, String, String) {
let full_name = credential("GRID_USER");
let password = credential("GRID_PASSWORD");
let login_url = credential("GRID_LOGIN_URL");
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, login_url)
}
// 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 (handler, message_handler) = FakeHandler::new(&bytes, Duration::ZERO);
let http_caps = HttpCapsClient::new(message_handler).expect("HttpCapsClient constructor");
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(http_caps);
let downloads = Arc::new(DownloadManager::new(client).expect("DownloadManager constructor"));
let worker_downloads = Arc::clone(&downloads);
let result = block_on_timeout(
async move {
worker_downloads
.queue_download_with_uri_string_i_progress_cancellation_token_int32(
Uri("http://example.test/one".into()),
None,
None,
Some(CancellationToken::default()),
Some(1),
)
.await
},
Duration::from_secs(5),
);
let dispose_result = downloads.dispose();
let (response, data) = result.expect("QueueDownloadAsync");
assert_eq!(data, bytes);
assert!(response.is_success_status_code());
assert_eq!(response.headers["Content-Length"], bytes.len().to_string());
let request = handler.only_request();
assert_eq!(request.method, "GET");
assert_eq!(request.uri, Uri("http://example.test/one".into()));
dispose_result.expect("DownloadManager Dispose");
}
// 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 (handler, message_handler) = FakeHandler::new(&bytes, Duration::from_millis(200));
let http_caps = HttpCapsClient::new(message_handler).expect("HttpCapsClient constructor");
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(http_caps);
let downloads = Arc::new(DownloadManager::new(client).expect("DownloadManager constructor"));
let uri = Uri("http://example.test/dup".into());
let worker_downloads = Arc::clone(&downloads);
let (first_result, second_result) = block_on_timeout(
async move {
let first = worker_downloads
.queue_download_with_uri_string_i_progress_cancellation_token_int32(
uri.clone(),
None,
None,
Some(CancellationToken::default()),
Some(1),
);
let second = worker_downloads
.queue_download_with_uri_string_i_progress_cancellation_token_int32(
uri,
None,
None,
Some(CancellationToken::default()),
Some(1),
);
join(first, second).await
},
Duration::from_secs(5),
);
let dispose_result = downloads.dispose();
let (_, first_data) = first_result.expect("first download");
let (_, second_data) = second_result.expect("second download");
assert_eq!(first_data, bytes);
assert_eq!(second_data, bytes);
assert_eq!(handler.send_count(), 1);
let request = handler.only_request();
assert_eq!(request.method, "GET");
assert_eq!(request.uri, Uri("http://example.test/dup".into()));
dispose_result.expect("DownloadManager Dispose");
}
// 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 (handler, message_handler) = FakeHandler::new(&bytes, Duration::ZERO);
let http_caps = HttpCapsClient::new(message_handler).expect("HttpCapsClient constructor");
let mut client = GridClient::new().expect("GridClient constructor");
client.set_http_caps_client(http_caps);
let downloads = DownloadManager::new(client).expect("DownloadManager constructor");
let 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),
),
);
let dispose_result = downloads.dispose();
assert!(
matches!(&result, Err(libremetaverse::Error::Cancelled)),
"expected Cancelled, got {result:?}"
);
assert_eq!(handler.send_count(), 0);
dispose_result.expect("DownloadManager Dispose");
}
// 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 response = HttpResponse {
status_code: 201,
headers: BTreeMap::from([
("Location".into(), SLCAPS.into()),
("Content-Location".into(), SLCAPS.into()),
("Content-Length".into(), body.len().to_string()),
]),
content_type: Some("application/llsd+xml".into()),
body: body.to_vec(),
};
let requests = Arc::new(Mutex::new(Vec::new()));
let recorded = Arc::clone(&requests);
let message_handler = HttpMessageHandler::new(move |request, _cancellation_token| {
let recorded = Arc::clone(&recorded);
let response = response.clone();
async move {
recorded
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(request);
response
}
});
let mut client = GridClient::new().expect("GridClient constructor");
let http_caps = HttpCapsClient::new(message_handler).expect("HttpCapsClient constructor");
client.set_http_caps_client(http_caps);
let (reply, reply_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::default(),
None,
),
)
.expect("HttpCapsClient PutAsync");
assert_eq!(reply.status_code, 201);
assert_eq!(reply.headers["Location"], SLCAPS);
assert_eq!(reply.headers["Content-Location"], SLCAPS);
assert_eq!(reply.content_type.as_deref(), Some("application/llsd+xml"));
assert_eq!(reply_body, body);
let request = {
let requests = requests
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
assert_eq!(requests.len(), 1, "expected exactly one HTTP request");
requests[0].clone()
};
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");
}
// parity-case: LibreMetaverse.Tests/GridClientTests.cs::GridClientTests.GetGridRegion::test b0b72fc63fe829984167a479b763784c7daae8691399f3f3d2feb824090ad59f 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 get_grid_region_live() {
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()
);
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
);
}
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");
}
network.logout_with_method().expect("NetworkManager Logout");
let _ = client.dispose_with_method();
}
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.DetectObjects::test 8b72f1ca30feda2fad0812faf3a2f83d691d3cca9268054b3cc3bf89ad786e87 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 detect_objects_live() {
let mut client = GridClient::new().expect("GridClient constructor");
client.settings().timing().login_timeout = 30 * 1_000;
client.self_().movement.set_fly(true);
let detected = Arc::new(AtomicBool::new(false));
let callback_detected = Arc::clone(&detected);
let network = Arc::new(client.network());
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, 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()
);
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
);
}
let started = Instant::now();
while !detected.load(Ordering::SeqCst) {
assert!(
started.elapsed() <= Duration::from_secs(20),
"timeout waiting for an ObjectUpdate packet after 20 seconds"
);
thread::sleep(Duration::from_millis(100));
}
assert!(
detected.load(Ordering::SeqCst),
"successfully detected objects"
);
network.logout_with_method().expect("NetworkManager Logout");
let _ = client.dispose_with_method();
}
// parity-case: LibreMetaverse.Tests/NetworkTests.cs::NetworkTests.CapsQueue::test cd5af4e7d69df3e938540711877d167fe28efb9af8af7518a62b8f406e46cc18 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 caps_queue_live() {
let mut client = GridClient::new().expect("GridClient constructor");
client.settings().timing().login_timeout = 30 * 1_000;
client.self_().movement.set_fly(true);
let detected = Arc::new(AtomicBool::new(false));
let callback_detected = Arc::clone(&detected);
let network = Arc::new(client.network());
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, 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()
);
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
);
}
let already_running = network
.current_sim()
.and_then(|simulator| simulator.caps)
.is_some_and(|caps| caps.is_event_queue_running());
let event_queue_running = if already_running {
true
} else {
let (sender, receiver) = mpsc::sync_channel(1);
let _subscription = network.subscribe_event_queue_running(Arc::new(move |_event| {
let _ = sender.send(());
}));
receiver
.recv_timeout(Duration::from_secs(10))
.expect("timeout waiting for event queue to start");
true
};
assert!(
event_queue_running,
"CAPS Event Queue is not running and failed to start"
);
network.logout_with_method().expect("NetworkManager Logout");
let _ = client.dispose_with_method();
}

File diff suppressed because it is too large Load Diff

View File

@@ -188,6 +188,20 @@ MEMBER_SIGNATURE_OVERRIDES = {
"mutable_self,owned,mutable_borrow,mutable_borrow,optional_mutable_borrow",
),
}
PROPERTY_SIGNATURE_OVERRIDES = {
"P:LibreMetaverse.GridClient.Self": (
"pub fn self_(&mut self) -> &mut libremetaverse::AgentManager",
"mutable_self",
),
"P:LibreMetaverse.GridClient.Settings": (
"pub fn settings(&mut self) -> &mut libremetaverse::Settings",
"mutable_self",
),
"P:LibreMetaverse.Settings.Timing": (
"pub fn timing(&mut self) -> &mut libremetaverse::TimingSettings",
"mutable_self",
),
}
TYPE_USAGE_OVERRIDES = {
"LibreMetaverse.DictionaryChangeCallback": (
"std::sync::Arc<dyn Fn(libremetaverse::DictionaryEventAction, "
@@ -673,6 +687,9 @@ def member_signature(mapper: Mapper, owner: dict, item: dict, rust_name: str) ->
signature = f"pub {rust_name}: {value_type}"
return signature, "owned_value", asyncness, "none", "direct"
if kind in {"property", "indexer"}:
if item["doc_id"] in PROPERTY_SIGNATURE_OVERRIDES:
signature, ownership = PROPERTY_SIGNATURE_OVERRIDES[item["doc_id"]]
return signature, ownership, asyncness, error_model, "accessor_methods"
value_type = mapper.type(item["type"], item.get("nullability"), generics)
accessors = {accessor["kind"] for accessor in item.get("accessors", [])}
pieces = []