303 lines
8.3 KiB
Rust
303 lines
8.3 KiB
Rust
//! Integration tests for EngineBridge command/event routing
|
|
|
|
use libmarathon::engine::{EngineBridge, EngineCommand, EngineCore, EngineEvent};
|
|
use libmarathon::networking::SessionId;
|
|
use std::time::Duration;
|
|
use tokio::time::timeout;
|
|
|
|
/// Get appropriate timeout for engine operations
|
|
/// - With fast_tests: short timeout (networking is mocked)
|
|
/// - Without fast_tests: long timeout (real networking with DHT discovery)
|
|
fn engine_timeout() -> Duration {
|
|
#[cfg(feature = "fast_tests")]
|
|
{
|
|
Duration::from_millis(200)
|
|
}
|
|
#[cfg(not(feature = "fast_tests"))]
|
|
{
|
|
Duration::from_secs(30)
|
|
}
|
|
}
|
|
|
|
/// Get appropriate wait time for command processing
|
|
fn processing_delay() -> Duration {
|
|
#[cfg(feature = "fast_tests")]
|
|
{
|
|
Duration::from_millis(50)
|
|
}
|
|
#[cfg(not(feature = "fast_tests"))]
|
|
{
|
|
// Real networking needs more time for initialization
|
|
Duration::from_secs(20)
|
|
}
|
|
}
|
|
|
|
/// Test that commands sent from "Bevy side" reach the engine
|
|
#[tokio::test]
|
|
async fn test_command_routing() {
|
|
let (bridge, handle) = EngineBridge::new();
|
|
|
|
// Spawn engine in background
|
|
let engine_handle = tokio::spawn(async move {
|
|
// Run engine for a short time
|
|
let core = EngineCore::new(handle, ":memory:");
|
|
timeout(engine_timeout(), core.run())
|
|
.await
|
|
.ok();
|
|
});
|
|
|
|
// Give engine time to start
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
// Send a command from "Bevy side"
|
|
let session_id = SessionId::new();
|
|
bridge.send_command(EngineCommand::StartNetworking {
|
|
session_id: session_id.clone(),
|
|
});
|
|
|
|
// Give engine time to process
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
// Poll events
|
|
let events = bridge.poll_events();
|
|
|
|
// Verify we got a NetworkingStarted event
|
|
assert!(!events.is_empty(), "Should receive at least one event");
|
|
|
|
let has_networking_started = events.iter().any(|e| {
|
|
matches!(
|
|
e,
|
|
EngineEvent::NetworkingStarted {
|
|
session_id: sid,
|
|
..
|
|
} if sid == &session_id
|
|
)
|
|
});
|
|
|
|
assert!(
|
|
has_networking_started,
|
|
"Should receive NetworkingStarted event"
|
|
);
|
|
|
|
// Cleanup
|
|
drop(bridge);
|
|
let _ = engine_handle.await;
|
|
}
|
|
|
|
/// Test that events from engine reach "Bevy side"
|
|
#[tokio::test]
|
|
async fn test_event_routing() {
|
|
let (bridge, handle) = EngineBridge::new();
|
|
|
|
// Spawn engine
|
|
let engine_handle = tokio::spawn(async move {
|
|
let core = EngineCore::new(handle, ":memory:");
|
|
timeout(engine_timeout(), core.run())
|
|
.await
|
|
.ok();
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
// Send StartNetworking command
|
|
let session_id = SessionId::new();
|
|
bridge.send_command(EngineCommand::StartNetworking {
|
|
session_id: session_id.clone(),
|
|
});
|
|
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
// Poll events multiple times to verify queue works
|
|
let events1 = bridge.poll_events();
|
|
let events2 = bridge.poll_events();
|
|
|
|
assert!(!events1.is_empty(), "First poll should return events");
|
|
assert!(
|
|
events2.is_empty(),
|
|
"Second poll should be empty (events already drained)"
|
|
);
|
|
|
|
// Cleanup
|
|
drop(bridge);
|
|
let _ = engine_handle.await;
|
|
}
|
|
|
|
/// Test full lifecycle: Start → Stop networking
|
|
#[tokio::test]
|
|
async fn test_networking_lifecycle() {
|
|
let (bridge, handle) = EngineBridge::new();
|
|
|
|
let engine_handle = tokio::spawn(async move {
|
|
let core = EngineCore::new(handle, ":memory:");
|
|
timeout(engine_timeout(), core.run())
|
|
.await
|
|
.ok();
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
// Start networking
|
|
let session_id = SessionId::new();
|
|
bridge.send_command(EngineCommand::StartNetworking {
|
|
session_id: session_id.clone(),
|
|
});
|
|
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
let events = bridge.poll_events();
|
|
assert!(
|
|
events
|
|
.iter()
|
|
.any(|e| matches!(e, EngineEvent::NetworkingStarted { .. })),
|
|
"Should receive NetworkingStarted"
|
|
);
|
|
|
|
// Stop networking
|
|
bridge.send_command(EngineCommand::StopNetworking);
|
|
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
let events = bridge.poll_events();
|
|
assert!(
|
|
events
|
|
.iter()
|
|
.any(|e| matches!(e, EngineEvent::NetworkingStopped)),
|
|
"Should receive NetworkingStopped"
|
|
);
|
|
|
|
// Cleanup
|
|
drop(bridge);
|
|
let _ = engine_handle.await;
|
|
}
|
|
|
|
/// Test JoinSession command routing
|
|
#[tokio::test]
|
|
async fn test_join_session_routing() {
|
|
let (bridge, handle) = EngineBridge::new();
|
|
|
|
let engine_handle = tokio::spawn(async move {
|
|
let core = EngineCore::new(handle, ":memory:");
|
|
timeout(engine_timeout(), core.run())
|
|
.await
|
|
.ok();
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
// Join a new session (should start networking)
|
|
let session_id = SessionId::new();
|
|
bridge.send_command(EngineCommand::JoinSession {
|
|
session_id: session_id.clone(),
|
|
});
|
|
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
let events = bridge.poll_events();
|
|
assert!(
|
|
events.iter().any(|e| {
|
|
matches!(
|
|
e,
|
|
EngineEvent::NetworkingStarted {
|
|
session_id: sid,
|
|
..
|
|
} if sid == &session_id
|
|
)
|
|
}),
|
|
"JoinSession should start networking"
|
|
);
|
|
|
|
// Cleanup
|
|
drop(bridge);
|
|
let _ = engine_handle.await;
|
|
}
|
|
|
|
/// Test that multiple commands are processed in order
|
|
#[tokio::test]
|
|
async fn test_command_ordering() {
|
|
let (bridge, handle) = EngineBridge::new();
|
|
|
|
let engine_handle = tokio::spawn(async move {
|
|
let core = EngineCore::new(handle, ":memory:");
|
|
timeout(engine_timeout(), core.run())
|
|
.await
|
|
.ok();
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
// Send first command and wait for it to complete
|
|
let session1 = SessionId::new();
|
|
bridge.send_command(EngineCommand::StartNetworking {
|
|
session_id: session1.clone(),
|
|
});
|
|
|
|
// Wait for first networking to start
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
let events1 = bridge.poll_events();
|
|
assert!(
|
|
events1.iter().any(|e| matches!(e, EngineEvent::NetworkingStarted { .. })),
|
|
"Should receive first NetworkingStarted"
|
|
);
|
|
|
|
// Now send stop and start second session
|
|
let session2 = SessionId::new();
|
|
bridge.send_command(EngineCommand::StopNetworking);
|
|
bridge.send_command(EngineCommand::JoinSession {
|
|
session_id: session2.clone(),
|
|
});
|
|
|
|
// Wait for second networking to start
|
|
tokio::time::sleep(processing_delay()).await;
|
|
|
|
let events2 = bridge.poll_events();
|
|
|
|
// Should see: NetworkingStopped, NetworkingStarted(session2)
|
|
let started_events: Vec<_> = events2
|
|
.iter()
|
|
.filter(|e| matches!(e, EngineEvent::NetworkingStarted { .. }))
|
|
.collect();
|
|
|
|
let stopped_events: Vec<_> = events2
|
|
.iter()
|
|
.filter(|e| matches!(e, EngineEvent::NetworkingStopped))
|
|
.collect();
|
|
|
|
assert_eq!(started_events.len(), 1, "Should have 1 NetworkingStarted event in second batch");
|
|
assert_eq!(stopped_events.len(), 1, "Should have 1 NetworkingStopped event");
|
|
|
|
// Cleanup
|
|
drop(bridge);
|
|
let _ = engine_handle.await;
|
|
}
|
|
|
|
/// Test: Shutdown command causes EngineCore to exit gracefully
|
|
#[tokio::test]
|
|
async fn test_shutdown_command() {
|
|
let (bridge, handle) = EngineBridge::new();
|
|
|
|
let engine_handle = tokio::spawn(async move {
|
|
let core = EngineCore::new(handle, ":memory:");
|
|
core.run().await;
|
|
});
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
// Send Shutdown command
|
|
bridge.send_command(EngineCommand::Shutdown);
|
|
|
|
// Wait for engine to exit (should be quick since it's just processing the command)
|
|
let result = timeout(Duration::from_millis(100), engine_handle).await;
|
|
|
|
assert!(
|
|
result.is_ok(),
|
|
"Engine should exit within 100ms after receiving Shutdown command"
|
|
);
|
|
|
|
// Verify that the engine actually exited (not errored)
|
|
assert!(
|
|
result.unwrap().is_ok(),
|
|
"Engine should exit cleanly without panic"
|
|
);
|
|
}
|