circles/server_ws/examples/e2e_rhai_flow.rs
2025-06-04 04:51:51 +03:00

143 lines
6.4 KiB
Rust

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