use std::path::PathBuf; use std::process::{Child, Command, Stdio}; use std::time::Duration; 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::CircleWsClientBuilder; // 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 = "worker"; const CIRCLE_SERVER_WS_BIN_NAME: &str = "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 { 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> { 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 = CircleWsClientBuilder::new(ws_url_str.clone()).build(); 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(()) }