circles/examples/server_e2e_rhai_flow.rs
2025-06-19 10:44:40 +03:00

182 lines
6.4 KiB
Rust

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<PathBuf, String> {
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 = 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(())
}