Translate network lifecycle parity tests
Some checks failed
Rust API gates / api-gates (push) Has been cancelled
Some checks failed
Rust API gates / api-gates (push) Has been cancelled
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1940
tests/PARITY.md
1940
tests/PARITY.md
File diff suppressed because it is too large
Load Diff
@@ -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]
|
||||
|
||||
@@ -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() {
|
||||
|
||||
378
tests/compat/tests/network_semantics.rs
Normal file
378
tests/compat/tests/network_semantics.rs
Normal 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);
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user