add hero runner and clean improve runner lib
This commit is contained in:
234
tests/runner_hero.rs
Normal file
234
tests/runner_hero.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
//! Integration tests for Hero Runner
|
||||
//!
|
||||
//! Tests the hero runner by spawning the binary and dispatching jobs to it.
|
||||
//!
|
||||
//! **IMPORTANT**: Run with `--test-threads=1` to ensure tests run sequentially:
|
||||
//! ```
|
||||
//! cargo test --test runner_hero -- --test-threads=1
|
||||
//! ```
|
||||
|
||||
use hero_job::{Job, JobBuilder, JobStatus};
|
||||
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-hero-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 hero 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);
|
||||
}).ok();
|
||||
});
|
||||
|
||||
// Use escargot to build and get the binary path
|
||||
let binary = escargot::CargoBuild::new()
|
||||
.bin("herorunner")
|
||||
.package("runner-hero")
|
||||
.run()
|
||||
.expect("Failed to build hero runner binary");
|
||||
|
||||
// Start the runner binary
|
||||
let child = binary
|
||||
.command()
|
||||
.args(&[
|
||||
RUNNER_ID,
|
||||
"--redis-url",
|
||||
REDIS_URL,
|
||||
])
|
||||
.spawn()
|
||||
.expect("Failed to start hero runner");
|
||||
|
||||
*RUNNER_PROCESS.lock().unwrap() = Some(child);
|
||||
|
||||
// Wait for runner to be ready with TCP check
|
||||
use std::time::Duration;
|
||||
std::thread::sleep(Duration::from_secs(2));
|
||||
|
||||
println!("✅ Hero runner ready");
|
||||
});
|
||||
}
|
||||
|
||||
/// Cleanup runner process
|
||||
fn cleanup_runner() {
|
||||
if let Ok(mut guard) = RUNNER_PROCESS.lock() {
|
||||
if let Some(mut child) = guard.take() {
|
||||
println!("🧹 Cleaning up hero runner process...");
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to create a test client
|
||||
async fn create_client() -> Client {
|
||||
// Ensure runner is running
|
||||
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")
|
||||
.context_id("test-context")
|
||||
.payload(payload)
|
||||
.runner(RUNNER_ID)
|
||||
.timeout(30)
|
||||
.build()
|
||||
.expect("Failed to build job")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_01_ping_job() {
|
||||
println!("\n🧪 Test: Ping Job");
|
||||
|
||||
let client = create_client().await;
|
||||
|
||||
// Create ping job
|
||||
let job = create_test_job("ping");
|
||||
let job_id = job.id.clone();
|
||||
|
||||
// Save job to Redis
|
||||
client.store_job_in_redis(&job).await.expect("Failed to save job");
|
||||
|
||||
// Queue job to runner
|
||||
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(2)).await;
|
||||
|
||||
// Check job status
|
||||
let status = client.get_status(&job_id).await.expect("Failed to get job status");
|
||||
assert_eq!(status, JobStatus::Finished, "Ping job should be finished");
|
||||
|
||||
// Check result
|
||||
let result = client.get_result(&job_id).await.expect("Failed to get result");
|
||||
assert_eq!(result, Some("pong".to_string()), "Ping should return pong");
|
||||
|
||||
println!("✅ Ping job completed successfully");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_02_simple_heroscript() {
|
||||
println!("\n🧪 Test: Simple Heroscript");
|
||||
|
||||
let client = create_client().await;
|
||||
|
||||
// Create job with simple heroscript
|
||||
let job = create_test_job("print('Hello from hero runner')");
|
||||
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
|
||||
if let Some(result) = client.get_result(&job_id).await.expect("Failed to get result") {
|
||||
println!("Job result: {}", result);
|
||||
}
|
||||
if let Some(error) = client.get_error(&job_id).await.expect("Failed to get error") {
|
||||
println!("Job error: {}", error);
|
||||
}
|
||||
|
||||
println!("✅ Heroscript job completed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_03_job_with_env_vars() {
|
||||
println!("\n🧪 Test: Job with Environment Variables");
|
||||
|
||||
let client = create_client().await;
|
||||
|
||||
// Create job with env vars
|
||||
let mut job = create_test_job("echo $TEST_VAR");
|
||||
job.env_vars.insert("TEST_VAR".to_string(), "test_value".to_string());
|
||||
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
|
||||
if let Some(result) = client.get_result(&job_id).await.expect("Failed to get result") {
|
||||
println!("Job result: {}", result);
|
||||
}
|
||||
|
||||
println!("✅ Job with env vars completed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_04_job_timeout() {
|
||||
println!("\n🧪 Test: Job Timeout");
|
||||
|
||||
let client = create_client().await;
|
||||
|
||||
// Create job with short timeout
|
||||
let mut job = create_test_job("sleep 10");
|
||||
job.timeout = 2; // 2 second timeout
|
||||
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 timeout
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||
|
||||
// Check job status - should be error due to timeout
|
||||
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 timeout): {}", error);
|
||||
assert!(error.contains("timeout") || error.contains("timed out"), "Error should mention timeout");
|
||||
}
|
||||
|
||||
println!("✅ Job timeout handled correctly");
|
||||
}
|
||||
|
||||
/// 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");
|
||||
}
|
||||
Reference in New Issue
Block a user