Complete first release candidate audit (#107)
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled

This commit is contained in:
2026-08-12 14:44:28 +00:00
parent dceb394378
commit c9a1170a27
140 changed files with 82175 additions and 27179 deletions

View File

@@ -9,139 +9,58 @@
///
/// Native Rust mapping of C# `LibreMetaverse.Utilities.ConnectionManager` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub struct ConnectionManager;
impl ConnectionManager {
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.#ctor(LibreMetaverse.GridClient,System.Int32)`.
///
/// C# signature: `LibreMetaverse.Utilities.ConnectionManager(LibreMetaverse.GridClient client, System.Int32 timerFrequency)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn new(
client: libremetaverse::GridClient,
timer_frequency: i32,
) -> Result<Self, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.ConnectionManager.#ctor(LibreMetaverse.GridClient,System.Int32)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient,System.String,System.String,System.String,System.String,System.String,System.String)`.
///
/// C# signature: `System.Threading.Tasks.Task<System.Boolean> LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient client, System.String firstName, System.String lastName, System.String password, System.String userAgent, System.String start, System.String author)`.
/// Mapping contract: ownership `owned,owned,owned,owned,owned,owned,owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub async fn persistent_login(
client: libremetaverse::GridClient,
first_name: String,
last_name: String,
password: String,
user_agent: String,
start: String,
author: String,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient,System.String,System.String,System.String,System.String,System.String,System.String)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64 handle, LibreMetaverse.Vector3 desiredPosition)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn stay_in_sim(
&self,
handle: u64,
desired_position: libremetaverse_types::Vector3,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64,LibreMetaverse.Vector3)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.Stop`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.ConnectionManager.Stop()`.
/// Mapping contract: ownership `shared_self`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub fn stop(&self) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented("M:LibreMetaverse.Utilities.ConnectionManager.Stop")
}
}
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.#ctor(LibreMetaverse.GridClient,System.Int32)`.
///
/// C# signature: `LibreMetaverse.Utilities.ConnectionManager(LibreMetaverse.GridClient client, System.Int32 timerFrequency)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient,System.String,System.String,System.String,System.String,System.String,System.String)`.
///
/// C# signature: `System.Threading.Tasks.Task<System.Boolean> LibreMetaverse.Utilities.ConnectionManager.PersistentLogin(LibreMetaverse.GridClient client, System.String firstName, System.String lastName, System.String password, System.String userAgent, System.String start, System.String author)`.
/// Mapping contract: ownership `owned,owned,owned,owned,owned,owned,owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.ConnectionManager.StayInSim(System.UInt64 handle, LibreMetaverse.Vector3 desiredPosition)`.
/// Mapping contract: ownership `shared_self,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.ConnectionManager.Stop`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.ConnectionManager.Stop()`.
/// Mapping contract: ownership `shared_self`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub use crate::service::ConnectionManager;
/// C# type: `T:LibreMetaverse.Utilities.Realism`.
///
/// Native Rust mapping of C# `LibreMetaverse.Utilities.Realism` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub struct Realism;
impl Realism {
/// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient client, System.String message)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn chat_with_grid_client_string(
client: libremetaverse::GridClient,
message: String,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient client, System.String message, LibreMetaverse.ChatType type, System.Int32 cps)`.
/// Mapping contract: ownership `owned,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn chat_with_grid_client_string_chat_type_int32(
client: libremetaverse::GridClient,
message: String,
type_: libremetaverse::ChatType,
cps: i32,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32,System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient client, System.String message, LibreMetaverse.ChatType type, System.Int32 cps, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `owned,owned,owned,owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub async fn chat_with_grid_client_string_chat_type_int32_cancellation_token(
client: libremetaverse::GridClient,
message: String,
type_: libremetaverse::ChatType,
cps: i32,
cancellation_token: Option<libremetaverse_types::compat::CancellationToken>,
) -> Result<(), crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32,System.Threading.CancellationToken)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient)`.
///
/// C# signature: `System.Boolean LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient client)`.
/// Mapping contract: ownership `owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn shoot_with_grid_client(
client: libremetaverse::GridClient,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient)",
)
}
/// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Boolean LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient client, LibreMetaverse.Vector3 target)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub fn shoot_with_grid_client_vector3(
client: libremetaverse::GridClient,
target: libremetaverse_types::Vector3,
) -> Result<bool, crate::Error> {
libremetaverse_types::not_implemented(
"M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient,LibreMetaverse.Vector3)",
)
}
}
/// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient client, System.String message)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32)`.
///
/// C# signature: `System.Void LibreMetaverse.Utilities.Realism.Chat(LibreMetaverse.GridClient client, System.String message, LibreMetaverse.ChatType type, System.Int32 cps)`.
/// Mapping contract: ownership `owned,owned,owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient,System.String,LibreMetaverse.ChatType,System.Int32,System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task LibreMetaverse.Utilities.Realism.ChatAsync(LibreMetaverse.GridClient client, System.String message, LibreMetaverse.ChatType type, System.Int32 cps, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `owned,owned,owned,owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient)`.
///
/// C# signature: `System.Boolean LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient client)`.
/// Mapping contract: ownership `owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
/// C# member: `M:LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient,LibreMetaverse.Vector3)`.
///
/// C# signature: `System.Boolean LibreMetaverse.Utilities.Realism.Shoot(LibreMetaverse.GridClient client, LibreMetaverse.Vector3 target)`.
/// Mapping contract: ownership `owned,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `descriptive_overload_name`; kind `method`.
pub use crate::service::Realism;
/// C# type: `T:LibreMetaverse.Utilities.WaterType`.
///

View File

@@ -7,6 +7,8 @@
extern crate self as libremetaverse_utilities;
mod generated;
#[allow(clippy::all, clippy::pedantic)] // Public shapes mirror the pinned C# API.
mod service;
pub use generated::*;
pub use libremetaverse as core;

View File

@@ -0,0 +1,362 @@
//! Native implementations of the upstream connection and realism helpers.
#![allow(clippy::missing_errors_doc)] // Signatures and error semantics mirror the mapped C# API.
use libremetaverse::{ChatType, GridClient};
use libremetaverse_types::compat::{CancellationToken, CancellationTokenSource};
use libremetaverse_types::{Error, UUID, Vector3};
use std::sync::{Arc, Mutex, MutexGuard, mpsc};
use std::thread::JoinHandle;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const TYPE_ANIMATION: &str = "c541c47f-e0c0-058b-ad1a-d6ae3a4584d9";
fn lock<T>(value: &Mutex<T>) -> MutexGuard<'_, T> {
value
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
struct CheckLoop {
stop: mpsc::Sender<()>,
cancellation: Arc<CancellationTokenSource>,
worker: JoinHandle<()>,
}
/// Keeps a client in one simulator and provides the upstream persistent-login policy.
pub struct ConnectionManager {
client: GridClient,
timer_frequency: Duration,
destination: Arc<Mutex<(u64, Vector3)>>,
check_loop: Mutex<Option<CheckLoop>>,
}
impl ConnectionManager {
/// Creates an idle connection manager. Monitoring starts with [`Self::stay_in_sim`].
pub fn new(client: GridClient, timer_frequency: i32) -> Result<Self, Error> {
let milliseconds = u64::try_from(timer_frequency).map_err(|_| Error::Argument)?;
if milliseconds == 0 {
return Err(Error::Argument);
}
Ok(Self {
client,
timer_frequency: Duration::from_millis(milliseconds),
destination: Arc::new(Mutex::new((0, Vector3::zero()))),
check_loop: Mutex::new(None),
})
}
/// Retries transient login errors with the delays defined by `LibreMetaverse`.
pub async fn persistent_login(
client: GridClient,
first_name: String,
last_name: String,
password: String,
user_agent: String,
start: String,
author: String,
) -> Result<bool, Error> {
let mut unknown_logins = 0_u8;
loop {
if client
.network()
.login_with_string_string_string_string_string_string_cancellation_token(
first_name.clone(),
last_name.clone(),
password.clone(),
user_agent.clone(),
start.clone(),
author.clone(),
None,
)
.await?
{
return Ok(true);
}
let wait = match client.network().login_error_key().as_str() {
"god" => Duration::from_mins(10),
"key" | "disabled" => return Ok(false),
"presence" | "timed out" | "no connection" | "bad response" => {
Duration::from_mins(1)
}
_ => {
unknown_logins += 1;
if unknown_logins >= 5 {
return Ok(false);
}
Duration::from_mins(2)
}
};
tokio::time::sleep(wait).await;
}
}
/// Starts (or retargets) the background simulator-presence check.
pub fn stay_in_sim(&self, handle: u64, desired_position: Vector3) -> Result<(), Error> {
*lock(&self.destination) = (handle, desired_position);
let mut active = lock(&self.check_loop);
if active.is_some() {
return Ok(());
}
let mut client = self.client.clone();
let destination = Arc::clone(&self.destination);
let frequency = self.timer_frequency;
let cancellation = Arc::new(CancellationTokenSource::new());
let token = cancellation.token();
let (stop, stopped) = mpsc::channel();
let worker = std::thread::Builder::new()
.name("libremetaverse-stay-in-sim".into())
.spawn(move || {
let Ok(runtime) = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
else {
return;
};
loop {
if token.is_cancellation_requested() {
break;
}
let (target_handle, target_position) = *lock(&destination);
if target_handle != 0
&& let Some(current) = client.network().current_sim()
&& current.handle != 0
&& current.handle != target_handle
{
let _ = runtime.block_on(
client
.self_()
.teleport_with_u_int64_vector3_cancellation_token(
target_handle,
target_position,
Some(token.clone()),
),
);
}
match stopped.recv_timeout(frequency) {
Ok(()) | Err(mpsc::RecvTimeoutError::Disconnected) => break,
Err(mpsc::RecvTimeoutError::Timeout) => {}
}
}
})
.map_err(|_| Error::InvalidOperation)?;
*active = Some(CheckLoop {
stop,
cancellation,
worker,
});
Ok(())
}
/// Cancels and joins the active presence check. Calling it repeatedly is safe.
pub fn stop(&self) -> Result<(), Error> {
let Some(active) = lock(&self.check_loop).take() else {
return Ok(());
};
active.cancellation.cancel();
let _ = active.stop.send(());
active.worker.join().map_err(|_| Error::InvalidOperation)
}
}
impl Drop for ConnectionManager {
fn drop(&mut self) {
let _ = self.stop();
}
}
/// Human-like chat and mouselook helpers.
pub struct Realism;
impl Realism {
/// Sends normal chat at the upstream default of three characters per second.
pub fn chat_with_grid_client_string(client: GridClient, message: String) -> Result<(), Error> {
Self::chat_with_grid_client_string_chat_type_int32(client, message, ChatType::Normal, 3)
}
/// Blocking compatibility wrapper around the asynchronous typing sequence.
pub fn chat_with_grid_client_string_chat_type_int32(
client: GridClient,
message: String,
type_: ChatType,
cps: i32,
) -> Result<(), Error> {
std::thread::Builder::new()
.name("libremetaverse-realism-chat".into())
.spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.map_err(|_| Error::InvalidOperation)?;
runtime.block_on(
Self::chat_with_grid_client_string_chat_type_int32_cancellation_token(
client, message, type_, cps, None,
),
)
})
.map_err(|_| Error::InvalidOperation)?
.join()
.map_err(|_| Error::InvalidOperation)?
}
/// Emits start/stop typing chat and animation traffic while delaying by `cps`.
pub async fn chat_with_grid_client_string_chat_type_int32_cancellation_token(
mut client: GridClient,
message: String,
type_: ChatType,
cps: i32,
cancellation_token: Option<CancellationToken>,
) -> Result<(), Error> {
let cps = usize::try_from(cps)
.ok()
.filter(|value| *value > 0)
.ok_or(Error::Argument)?;
let cancellation = cancellation_token.unwrap_or_default();
cancellation.throw_if_cancellation_requested()?;
let animation = UUID::new_with_string(TYPE_ANIMATION.into())?;
let agent = client.self_();
agent.chat(String::new(), 0, ChatType::StartTyping, None)?;
if let Err(error) = agent.animation_start(animation, false) {
let _ = agent.chat(String::new(), 0, ChatType::StopTyping, None);
return Err(error);
}
let mut characters = 0_usize;
let mut typing = true;
let outcome = async {
while characters < message.chars().count() {
cancellation.throw_if_cancellation_requested()?;
if !typing {
agent.chat(String::new(), 0, ChatType::StartTyping, None)?;
agent.animation_start(animation, false)?;
typing = true;
} else if pseudo_random_tenth() {
agent.chat(String::new(), 0, ChatType::StopTyping, None)?;
agent.animation_stop(animation, false)?;
typing = false;
}
tokio::select! {
() = tokio::time::sleep(Duration::from_secs(1)) => {}
() = cancellation.cancelled() => return Err(Error::Cancelled),
}
characters = characters.saturating_add(cps);
}
agent.chat(message, 0, type_, None)
}
.await;
let stop_chat = agent.chat(String::new(), 0, ChatType::StopTyping, None);
let stop_animation = agent.animation_stop(animation, false);
outcome.and(stop_chat).and(stop_animation)
}
/// Performs the mouselook press/release sequence when agent updates are enabled.
pub fn shoot_with_grid_client(mut client: GridClient) -> Result<bool, Error> {
if !client.settings().agent().send_updates {
return Ok(false);
}
let movement = &mut client.self_().movement;
movement.set_mouselook(true);
movement.set_ml_button_down(true);
let outcome = (|| {
movement.send_update_with_boolean(None)?;
movement.set_ml_button_up(true);
movement.set_ml_button_down(false);
movement.set_finish_anim(true);
movement.send_update_with_boolean(None)?;
movement.set_mouselook(false);
movement.set_ml_button_up(false);
movement.set_finish_anim(false);
movement.send_update_with_boolean(None)
})();
movement.set_mouselook(false);
movement.set_ml_button_down(false);
movement.set_ml_button_up(false);
movement.set_finish_anim(false);
outcome.map(|()| true)
}
/// Turns toward `target` before performing the mouselook press/release sequence.
pub fn shoot_with_grid_client_vector3(
mut client: GridClient,
target: Vector3,
) -> Result<bool, Error> {
if !client.self_().movement.turn_toward(target, None)? {
return Ok(false);
}
Self::shoot_with_grid_client(client)
}
}
fn pseudo_random_tenth() -> bool {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.subsec_nanos())
% 10
== 9
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connection_manager_rejects_non_positive_frequency() {
assert!(matches!(
ConnectionManager::new(GridClient::new().unwrap(), 0),
Err(Error::Argument)
));
assert!(matches!(
ConnectionManager::new(GridClient::new().unwrap(), -1),
Err(Error::Argument)
));
}
#[test]
fn connection_monitor_can_retarget_and_stop_without_a_login() {
let manager = ConnectionManager::new(GridClient::new().unwrap(), 10).unwrap();
manager.stay_in_sim(1, Vector3::unit_x()).unwrap();
manager.stay_in_sim(2, Vector3::unit_y()).unwrap();
manager.stop().unwrap();
manager.stop().unwrap();
}
#[test]
fn shoot_reports_disabled_updates_without_network_traffic() {
let mut client = GridClient::new().unwrap();
client.settings().agent_mut().send_updates = false;
assert!(!Realism::shoot_with_grid_client(client).unwrap());
}
#[tokio::test(flavor = "current_thread")]
async fn chat_rejects_zero_rate_before_network_traffic() {
let error = Realism::chat_with_grid_client_string_chat_type_int32_cancellation_token(
GridClient::new().unwrap(),
"hello".into(),
ChatType::Normal,
0,
None,
)
.await
.unwrap_err();
assert_eq!(error, Error::Argument);
}
#[tokio::test(flavor = "current_thread")]
async fn chat_honors_pre_cancelled_token_before_network_traffic() {
let source = CancellationTokenSource::new();
source.cancel();
let error = Realism::chat_with_grid_client_string_chat_type_int32_cancellation_token(
GridClient::new().unwrap(),
"hello".into(),
ChatType::Normal,
3,
Some(source.token()),
)
.await
.unwrap_err();
assert_eq!(error, Error::Cancelled);
}
}