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 { // 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> { 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(()) }