add ws client and server packages
This commit is contained in:
143
server_ws/examples/e2e_rhai_flow.rs
Normal file
143
server_ws/examples/e2e_rhai_flow.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::process::{Command, Child, Stdio};
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
use tokio::time::sleep;
|
||||
// tokio_tungstenite and direct futures_util for ws stream are no longer needed here
|
||||
// use tokio_tungstenite::{connect_async, tungstenite::protocol::Message as WsMessage};
|
||||
// use futures_util::{StreamExt, SinkExt};
|
||||
// use serde_json::Value; // No longer needed as CircleWsClient::play takes String
|
||||
// Uuid is handled by CircleWsClient internally for requests.
|
||||
// use uuid::Uuid;
|
||||
use circle_client_ws::CircleWsClient;
|
||||
// PlayResultClient and CircleWsClientError will be resolved via the client methods if needed,
|
||||
// or this indicates they were not actually needed in the scope of this file directly.
|
||||
// The compiler warning suggests they are unused from this specific import.
|
||||
|
||||
const TEST_CIRCLE_NAME: &str = "e2e_test_circle";
|
||||
const TEST_SERVER_PORT: u16 = 9876; // Choose a unique port for the test
|
||||
const RHAI_WORKER_BIN_NAME: &str = "rhai_worker";
|
||||
const CIRCLE_SERVER_WS_BIN_NAME: &str = "circle_server_ws";
|
||||
|
||||
// RAII guard for cleaning up child processes
|
||||
struct ChildProcessGuard {
|
||||
child: Child,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl ChildProcessGuard {
|
||||
fn new(child: Child, name: String) -> Self {
|
||||
Self { child, name }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ChildProcessGuard {
|
||||
fn drop(&mut self) {
|
||||
log::info!("Cleaning up {} process (PID: {})...", self.name, self.child.id());
|
||||
match self.child.kill() {
|
||||
Ok(_) => {
|
||||
log::info!("Successfully sent kill signal to {} (PID: {}).", self.name, self.child.id());
|
||||
// Optionally wait for a short period or check status
|
||||
match self.child.wait() {
|
||||
Ok(status) => log::info!("{} (PID: {}) exited with status: {}", self.name, self.child.id(), status),
|
||||
Err(e) => log::warn!("Error waiting for {} (PID: {}): {}", self.name, self.child.id(), e),
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to kill {} (PID: {}): {}", self.name, self.child.id(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_target_dir() -> Result<PathBuf, String> {
|
||||
// Try to find the cargo target directory relative to current exe or manifest
|
||||
let mut current_exe = std::env::current_exe().map_err(|e| format!("Failed to get current exe path: {}", e))?;
|
||||
// current_exe is target/debug/examples/e2e_rhai_flow
|
||||
// want target/debug/
|
||||
if current_exe.ends_with("examples/e2e_rhai_flow") { // Adjust if example name changes
|
||||
current_exe.pop(); // remove e2e_rhai_flow
|
||||
current_exe.pop(); // remove examples
|
||||
Ok(current_exe)
|
||||
} else {
|
||||
// Fallback: Assume 'target/debug' relative to workspace root if CARGO_MANIFEST_DIR is set
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?;
|
||||
let workspace_root = PathBuf::from(manifest_dir).parent().ok_or("Failed to get workspace root")?.to_path_buf();
|
||||
Ok(workspace_root.join("target").join("debug"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let target_dir = find_target_dir().map_err(|e| {
|
||||
log::error!("Could not determine target directory: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let rhai_worker_path = target_dir.join(RHAI_WORKER_BIN_NAME);
|
||||
let circle_server_ws_path = target_dir.join(CIRCLE_SERVER_WS_BIN_NAME);
|
||||
|
||||
if !rhai_worker_path.exists() {
|
||||
return Err(format!("Rhai worker binary not found at {:?}. Ensure it's built (e.g., cargo build --package rhai_worker)", rhai_worker_path).into());
|
||||
}
|
||||
if !circle_server_ws_path.exists() {
|
||||
return Err(format!("Circle server WS binary not found at {:?}. Ensure it's built (e.g., cargo build --package circle_server_ws)", circle_server_ws_path).into());
|
||||
}
|
||||
|
||||
log::info!("Starting {}...", RHAI_WORKER_BIN_NAME);
|
||||
let rhai_worker_process = Command::new(&rhai_worker_path)
|
||||
.args(["--circles", TEST_CIRCLE_NAME])
|
||||
.stdout(Stdio::piped()) // Capture stdout
|
||||
.stderr(Stdio::piped()) // Capture stderr
|
||||
.spawn()?;
|
||||
let _rhai_worker_guard = ChildProcessGuard::new(rhai_worker_process, RHAI_WORKER_BIN_NAME.to_string());
|
||||
log::info!("{} started with PID {}", RHAI_WORKER_BIN_NAME, _rhai_worker_guard.child.id());
|
||||
|
||||
log::info!("Starting {} for circle '{}' on port {}...", CIRCLE_SERVER_WS_BIN_NAME, TEST_CIRCLE_NAME, TEST_SERVER_PORT);
|
||||
let circle_server_process = Command::new(&circle_server_ws_path)
|
||||
.args(["--port", &TEST_SERVER_PORT.to_string(), "--circle-name", TEST_CIRCLE_NAME])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
let _circle_server_guard = ChildProcessGuard::new(circle_server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string());
|
||||
log::info!("{} started with PID {}", CIRCLE_SERVER_WS_BIN_NAME, _circle_server_guard.child.id());
|
||||
|
||||
// Give servers a moment to start
|
||||
sleep(Duration::from_secs(3)).await; // Increased sleep
|
||||
|
||||
let ws_url_str = format!("ws://127.0.0.1:{}/ws", TEST_SERVER_PORT);
|
||||
|
||||
log::info!("Creating CircleWsClient for {}...", ws_url_str);
|
||||
let mut client = CircleWsClient::new(ws_url_str.clone());
|
||||
|
||||
log::info!("Connecting CircleWsClient...");
|
||||
client.connect().await.map_err(|e| {
|
||||
log::error!("CircleWsClient connection failed: {}", e);
|
||||
format!("CircleWsClient connection failed: {}", e)
|
||||
})?;
|
||||
log::info!("CircleWsClient connected successfully.");
|
||||
|
||||
let script_to_run = "let a = 5; let b = 10; print(\"E2E Rhai: \" + (a+b)); a + b";
|
||||
|
||||
log::info!("Sending 'play' request via CircleWsClient for script: '{}'", script_to_run);
|
||||
|
||||
match client.play(script_to_run.to_string()).await {
|
||||
Ok(play_result) => {
|
||||
log::info!("Received play result: {:?}", play_result);
|
||||
assert_eq!(play_result.output, "15");
|
||||
log::info!("E2E Test Passed! Correct output '15' received via CircleWsClient.");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("CircleWsClient play request failed: {}", e);
|
||||
return Err(format!("CircleWsClient play request failed: {}", e).into());
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Disconnecting CircleWsClient...");
|
||||
client.disconnect().await;
|
||||
log::info!("CircleWsClient disconnected.");
|
||||
|
||||
log::info!("E2E Rhai flow example completed successfully.");
|
||||
// Guards will automatically clean up child processes when they go out of scope here
|
||||
Ok(())
|
||||
}
|
153
server_ws/examples/timeout_demonstration.rs
Normal file
153
server_ws/examples/timeout_demonstration.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
// Example: Timeout Demonstration for circle_server_ws
|
||||
//
|
||||
// This example demonstrates how circle_server_ws handles Rhai scripts that exceed
|
||||
// the configured execution timeout (default 30 seconds).
|
||||
//
|
||||
// This example will attempt to start its own instance of circle_server_ws.
|
||||
// Ensure circle_server_ws is compiled (cargo build --bin circle_server_ws).
|
||||
|
||||
use circle_client_ws::CircleWsClient;
|
||||
use tokio::time::{sleep, Duration};
|
||||
use std::process::{Command, Child, Stdio};
|
||||
use std::path::PathBuf;
|
||||
|
||||
const EXAMPLE_SERVER_PORT: u16 = 8089; // Using a specific port for this example
|
||||
const WS_URL: &str = "ws://127.0.0.1:8089/ws";
|
||||
const CIRCLE_NAME_FOR_EXAMPLE: &str = "timeout_example_circle";
|
||||
const CIRCLE_SERVER_WS_BIN_NAME: &str = "circle_server_ws";
|
||||
const SCRIPT_TIMEOUT_SECONDS: u64 = 30; // This is the server-side timeout we expect to hit
|
||||
|
||||
// RAII guard for cleaning up child processes
|
||||
struct ChildProcessGuard {
|
||||
child: Child,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl ChildProcessGuard {
|
||||
fn new(child: Child, name: String) -> Self {
|
||||
log::info!("{} process started with PID: {}", name, child.id());
|
||||
Self { child, name }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ChildProcessGuard {
|
||||
fn drop(&mut self) {
|
||||
log::info!("Cleaning up {} process (PID: {})...", self.name, self.child.id());
|
||||
match self.child.kill() {
|
||||
Ok(_) => {
|
||||
log::info!("Successfully sent kill signal to {} (PID: {}).", self.name, self.child.id());
|
||||
match self.child.wait() {
|
||||
Ok(status) => log::info!("{} (PID: {}) exited with status: {}", self.name, self.child.id(), status),
|
||||
Err(e) => log::warn!("Error waiting for {} (PID: {}): {}", self.name, self.child.id(), e),
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Failed to kill {} (PID: {}): {}", self.name, self.child.id(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_target_bin_path(bin_name: &str) -> Result<PathBuf, String> {
|
||||
let mut current_exe = std::env::current_exe().map_err(|e| format!("Failed to get current exe path: {}", e))?;
|
||||
// current_exe is typically target/debug/examples/timeout_demonstration
|
||||
// We want to find target/debug/[bin_name]
|
||||
current_exe.pop(); // remove executable name
|
||||
current_exe.pop(); // remove examples directory
|
||||
let target_debug_dir = current_exe;
|
||||
let bin_path = target_debug_dir.join(bin_name);
|
||||
if !bin_path.exists() {
|
||||
// Fallback: try CARGO_BIN_EXE_[bin_name] if running via `cargo run --example` which sets these
|
||||
if let Ok(cargo_bin_path_str) = std::env::var(format!("CARGO_BIN_EXE_{}", bin_name.to_uppercase())) {
|
||||
let cargo_bin_path = PathBuf::from(cargo_bin_path_str);
|
||||
if cargo_bin_path.exists() {
|
||||
return Ok(cargo_bin_path);
|
||||
}
|
||||
}
|
||||
// Fallback: try target/debug/[bin_name] relative to CARGO_MANIFEST_DIR (crate root)
|
||||
if let Ok(manifest_dir_str) = std::env::var("CARGO_MANIFEST_DIR") {
|
||||
let bin_path_rel_manifest = PathBuf::from(manifest_dir_str).join("target").join("debug").join(bin_name);
|
||||
if bin_path_rel_manifest.exists() {
|
||||
return Ok(bin_path_rel_manifest);
|
||||
}
|
||||
}
|
||||
return Err(format!("Binary '{}' not found at {:?}. Ensure it's built.", bin_name, bin_path));
|
||||
}
|
||||
Ok(bin_path)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let server_bin_path = find_target_bin_path(CIRCLE_SERVER_WS_BIN_NAME)?;
|
||||
log::info!("Found server binary at: {:?}", server_bin_path);
|
||||
|
||||
log::info!("Starting {} for circle '{}' on port {}...", CIRCLE_SERVER_WS_BIN_NAME, CIRCLE_NAME_FOR_EXAMPLE, EXAMPLE_SERVER_PORT);
|
||||
let server_process = Command::new(&server_bin_path)
|
||||
.args([
|
||||
"--port", &EXAMPLE_SERVER_PORT.to_string(),
|
||||
"--circle-name", CIRCLE_NAME_FOR_EXAMPLE
|
||||
])
|
||||
.stdout(Stdio::piped()) // Pipe stdout to keep terminal clean, or Stdio::inherit() to see server logs
|
||||
.stderr(Stdio::piped()) // Pipe stderr as well
|
||||
.spawn()
|
||||
.map_err(|e| format!("Failed to start {}: {}. Ensure it is built.", CIRCLE_SERVER_WS_BIN_NAME, e))?;
|
||||
|
||||
let _server_guard = ChildProcessGuard::new(server_process, CIRCLE_SERVER_WS_BIN_NAME.to_string());
|
||||
|
||||
log::info!("Giving the server a moment to start up...");
|
||||
sleep(Duration::from_secs(3)).await; // Wait for server to initialize
|
||||
|
||||
log::info!("Attempting to connect to WebSocket server at: {}", WS_URL);
|
||||
let mut client = CircleWsClient::new(WS_URL.to_string());
|
||||
|
||||
log::info!("Connecting client...");
|
||||
if let Err(e) = client.connect().await {
|
||||
log::error!("Failed to connect to WebSocket server: {}", e);
|
||||
log::error!("Please check server logs if it failed to start correctly.");
|
||||
return Err(e.into());
|
||||
}
|
||||
log::info!("Client connected successfully.");
|
||||
|
||||
// This Rhai script is designed to run for much longer than the typical server timeout.
|
||||
let long_running_script = "
|
||||
log(\"Rhai: Starting long-running script...\");
|
||||
let mut x = 0;
|
||||
for i in 0..9999999999 { // Extremely large loop
|
||||
x = x + i;
|
||||
if i % 100000000 == 0 {
|
||||
// log(\"Rhai: Loop iteration \" + i);
|
||||
}
|
||||
}
|
||||
// This part should not be reached if timeout works correctly.
|
||||
log(\"Rhai: Long-running script finished calculation (x = \" + x + \").\");
|
||||
print(x);
|
||||
x
|
||||
".to_string();
|
||||
|
||||
log::info!("Sending long-running script (expected to time out on server after ~{}s)...", SCRIPT_TIMEOUT_SECONDS);
|
||||
|
||||
match client.play(long_running_script).await {
|
||||
Ok(play_result) => {
|
||||
log::warn!("Received unexpected success from play request: {:?}", play_result);
|
||||
log::warn!("This might indicate the script finished faster than expected, or the timeout didn't trigger.");
|
||||
}
|
||||
Err(e) => {
|
||||
log::info!("Received expected error from play request: {}", e);
|
||||
log::info!("This demonstrates the server timing out the script execution.");
|
||||
// You can further inspect the error details if CircleWsClientError provides them.
|
||||
// For example, if e.to_string() contains 'code: -32002' or 'timed out'.
|
||||
if e.to_string().contains("timed out") || e.to_string().contains("-32002") {
|
||||
log::info!("Successfully received timeout error from the server!");
|
||||
} else {
|
||||
log::warn!("Received an error, but it might not be the expected timeout error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Disconnecting client...");
|
||||
client.disconnect().await;
|
||||
log::info!("Client disconnected.");
|
||||
log::info!("Timeout demonstration example finished.");
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user