Files
horus/tests/runner_osiris.rs
Timur Gordon f66edba1d3 Add coordinator client library, installation scripts, and new test runners
- Add coordinator client library to workspace
- Add installation documentation and heroscript
- Add new test runners for Osiris and Sal
- Update hero runner test to handle invalid heroscript errors
- Update README with installation instructions
2025-11-17 10:56:13 +01:00

216 lines
6.3 KiB
Rust

//! Integration tests for Osiris Runner (OSIS)
//!
//! Tests the osiris runner by spawning the binary and dispatching Rhai jobs to it.
//!
//! **IMPORTANT**: Run with `--test-threads=1` to ensure tests run sequentially:
//! ```
//! cargo test --test runner_osiris -- --test-threads=1
//! ```
use hero_job::{Job, JobBuilder};
use hero_job_client::Client;
use std::sync::{Mutex, Once};
use std::process::Child;
use lazy_static::lazy_static;
/// Test configuration
const RUNNER_ID: &str = "test-osiris-runner";
const REDIS_URL: &str = "redis://localhost:6379";
lazy_static! {
static ref RUNNER_PROCESS: Mutex<Option<Child>> = Mutex::new(None);
}
/// Global initialization flag
static INIT: Once = Once::new();
/// Initialize and start the osiris runner binary
async fn init_runner() {
INIT.call_once(|| {
// Register cleanup handler
let _ = std::panic::catch_unwind(|| {
ctrlc::set_handler(move || {
cleanup_runner();
std::process::exit(0);
}).expect("Error setting Ctrl-C handler");
});
println!("🚀 Starting Osiris runner...");
// Build the runner binary
let build_result = escargot::CargoBuild::new()
.bin("runner_osiris")
.package("runner-osiris")
.current_release()
.run()
.expect("Failed to build runner_osiris");
// Spawn the runner process
let child = build_result
.command()
.arg(RUNNER_ID)
.arg("--redis-url")
.arg(REDIS_URL)
.spawn()
.expect("Failed to spawn osiris runner");
*RUNNER_PROCESS.lock().unwrap() = Some(child);
// Give the runner time to start
std::thread::sleep(std::time::Duration::from_secs(2));
println!("✅ Osiris runner ready");
});
}
/// Cleanup runner process
fn cleanup_runner() {
println!("🧹 Cleaning up osiris runner process...");
if let Some(mut child) = RUNNER_PROCESS.lock().unwrap().take() {
let _ = child.kill();
let _ = child.wait();
}
}
/// Create a test job client
async fn create_client() -> Client {
init_runner().await;
Client::builder()
.redis_url(REDIS_URL)
.build()
.await
.expect("Failed to create job client")
}
/// Helper to create a test job
fn create_test_job(payload: &str) -> Job {
JobBuilder::new()
.caller_id("test-caller")
.context_id("test-context")
.runner(RUNNER_ID)
.payload(payload)
.timeout(30)
.build()
.expect("Failed to build test job")
}
#[tokio::test]
async fn test_01_simple_rhai_script() {
println!("\n🧪 Test: Simple Rhai Script");
let client = create_client().await;
// Create job with simple Rhai script
let job = create_test_job(r#"
let x = 10;
let y = 20;
print("Sum: " + (x + y));
x + y
"#);
// Save and queue job
match client.job_run_wait(&job, RUNNER_ID, 5).await {
Ok(result) => {
println!("✅ Job succeeded with result:\n{}", result);
assert!(result.contains("30") || result.contains("Sum"), "Result should contain calculation");
}
Err(e) => {
println!("❌ Job failed with error: {:?}", e);
panic!("Job execution failed");
}
}
println!("✅ Rhai script job completed");
}
#[tokio::test]
async fn test_02_rhai_with_functions() {
println!("\n🧪 Test: Rhai Script with Functions");
let client = create_client().await;
// Create job with Rhai function
let job = create_test_job(r#"
fn calculate(a, b) {
a * b + 10
}
let result = calculate(5, 3);
print("Result: " + result);
result
"#);
let job_id = job.id.clone();
// Save and queue job
client.store_job_in_redis(&job).await.expect("Failed to save job");
client.job_run(&job_id, RUNNER_ID).await.expect("Failed to queue job");
// Wait for job to complete
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Check job status
let status = client.get_status(&job_id).await.expect("Failed to get job status");
println!("📊 Job status: {:?}", status);
// Get result or error
match (client.get_result(&job_id).await, client.get_error(&job_id).await) {
(Ok(Some(result)), _) => {
println!("✅ Job succeeded with result:\n{}", result);
assert!(result.contains("25") || result.contains("Result"), "Result should contain 25");
}
(_, Ok(Some(error))) => {
println!("❌ Job failed with error:\n{}", error);
panic!("Job should have succeeded");
}
_ => {
println!("⚠️ No result or error available");
panic!("Expected result");
}
}
println!("✅ Rhai function job completed");
}
#[tokio::test]
async fn test_03_invalid_rhai_syntax() {
println!("\n🧪 Test: Invalid Rhai Syntax Error Handling");
let client = create_client().await;
// Create job with invalid Rhai syntax
let job = create_test_job("let x = ; // Invalid syntax");
let job_id = job.id.clone();
// Save and queue job
client.store_job_in_redis(&job).await.expect("Failed to save job");
client.job_run(&job_id, RUNNER_ID).await.expect("Failed to queue job");
// Wait for job to complete
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
// Check job status - should be error
let status = client.get_status(&job_id).await.expect("Failed to get job status");
println!("📊 Job status: {:?}", status);
// Should have error
if let Some(error) = client.get_error(&job_id).await.expect("Failed to get error") {
println!("❌ Job error (expected):\n{}", error);
println!("✅ Invalid Rhai syntax error handled correctly");
} else {
println!("⚠️ Expected error for invalid Rhai syntax but got none");
panic!("Job with invalid syntax should have failed");
}
}
/// Final test that ensures cleanup happens
#[tokio::test]
async fn test_zz_cleanup() {
println!("\n🧹 Running cleanup...");
cleanup_runner();
// Wait a bit to ensure process is killed
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
println!("✅ Cleanup complete");
}