50b42e654c
for testability - Add `ManagedChild` trait abstracting process lifecycle (real `TokioChild` + `MockChild` for tests) - Use Unix process groups (`process_group(0)` + `libc::kill(-pgid, SIGKILL)`) for tree-wide termination - Remove cgroup-related code (`CgroupManager`, `ResourceUsage`, `cgroup_path` field) - Update imports across test files to use `workshop-engine` crate directly
185 lines
5.7 KiB
Rust
185 lines
5.7 KiB
Rust
//! Integration tests for finalize hook execution
|
|
//!
|
|
//! These tests verify that shell hooks and plugin hooks execute correctly
|
|
//! through the finalize stage hook executor.
|
|
|
|
use std::collections::HashMap;
|
|
use std::time::Duration;
|
|
use workshop_baker::ExecutionContext;
|
|
use workshop_baker::finalize::plugin::PluginMap;
|
|
use workshop_baker::finalize::stage::hook::hook;
|
|
use workshop_engine::{ExecutionEvent, CustomCommand};
|
|
|
|
fn create_test_context() -> ExecutionContext {
|
|
ExecutionContext {
|
|
task_id: "test-hook-1".to_string(),
|
|
pipeline_name: "test-pipeline".to_string(),
|
|
username: "testuser".to_string(),
|
|
working_dir: std::env::temp_dir(),
|
|
env_vars: HashMap::new(),
|
|
timeout: Some(Duration::from_secs(30)),
|
|
shell: "bash".to_string(),
|
|
dry_run: false,
|
|
privileged: false,
|
|
standalone: false,
|
|
}
|
|
}
|
|
|
|
fn create_test_event_channel() -> (
|
|
Option<tokio::sync::mpsc::Sender<ExecutionEvent>>,
|
|
tokio::sync::mpsc::Receiver<ExecutionEvent>,
|
|
) {
|
|
let (tx, rx) = tokio::sync::mpsc::channel(64);
|
|
(Some(tx), rx)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_shell_hook_success() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let cmd = CustomCommand {
|
|
name: "echo-test".to_string(),
|
|
command: "echo 'hook-ok'".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
};
|
|
|
|
let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await;
|
|
assert!(result.is_ok(), "Shell hook should succeed: {:?}", result);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_shell_hook_failure_propagates() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let cmd = CustomCommand {
|
|
name: "false-cmd".to_string(),
|
|
command: "false".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
};
|
|
|
|
let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await;
|
|
assert!(result.is_err(), "Shell hook failure should propagate");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_hook_timeout_kills_long_command() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let cmd = CustomCommand {
|
|
name: "sleep-forever".to_string(),
|
|
command: "sleep 10".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 500, // 0.5 seconds
|
|
argument: serde_yaml::Value::Null,
|
|
};
|
|
|
|
let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await;
|
|
assert!(result.is_err(), "Hook should fail due to timeout");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_hook_working_dir_applied() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let cmd = CustomCommand {
|
|
name: "pwd-check".to_string(),
|
|
command: "pwd".to_string(),
|
|
environment: None,
|
|
working_dir: Some("/tmp".to_string()),
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
};
|
|
|
|
// We verify by checking the hook returns Ok — the working_dir is passed to
|
|
// Engine::execute_command_with_delta. Full stdout verification would require
|
|
// mocking the Engine or using a more intrusive test setup.
|
|
let result = hook(Some(&vec![cmd]), &ctx, event_tx, &plugins).await;
|
|
assert!(result.is_ok(), "Hook with working_dir should succeed");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_no_hooks_returns_ok() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let result = hook(None, &ctx, event_tx, &plugins).await;
|
|
assert!(result.is_ok(), "No hooks should return Ok immediately");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_multiple_hooks_execute_sequentially() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let cmds = vec![
|
|
CustomCommand {
|
|
name: "hook-1".to_string(),
|
|
command: "true".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
},
|
|
CustomCommand {
|
|
name: "hook-2".to_string(),
|
|
command: "true".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
},
|
|
];
|
|
|
|
let result = hook(Some(&cmds), &ctx, event_tx, &plugins).await;
|
|
assert!(
|
|
result.is_ok(),
|
|
"Multiple hooks should execute sequentially and succeed"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_first_failure_stops_remaining_hooks() {
|
|
let ctx = create_test_context();
|
|
let (event_tx, _event_rx) = create_test_event_channel();
|
|
let plugins = PluginMap::new();
|
|
|
|
let cmds = vec![
|
|
CustomCommand {
|
|
name: "failing-hook".to_string(),
|
|
command: "false".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
},
|
|
CustomCommand {
|
|
name: "never-reached".to_string(),
|
|
command: "true".to_string(),
|
|
environment: None,
|
|
working_dir: None,
|
|
timeout_ms: 5000,
|
|
argument: serde_yaml::Value::Null,
|
|
},
|
|
];
|
|
|
|
let result = hook(Some(&cmds), &ctx, event_tx, &plugins).await;
|
|
assert!(result.is_err(), "First failing hook should stop execution");
|
|
}
|