#![allow(dead_code)]
use crate::config::OrchestratorConfig;
use crate::hooks::HookRunner;
use crate::parallel::ParallelEvent;
use std::path::Path;
use tokio::sync::mpsc;
#[derive(Debug)]
pub struct ExecutionContext<'a> {
pub change_id: &'a str,
pub workspace_path: Option<&'a Path>,
pub config: &'a OrchestratorConfig,
pub hooks: Option<&'a HookRunner>,
pub event_tx: Option<mpsc::Sender<ParallelEvent>>,
}
impl<'a> ExecutionContext<'a> {
pub fn new(change_id: &'a str, config: &'a OrchestratorConfig) -> Self {
Self {
change_id,
workspace_path: None,
config,
hooks: None,
event_tx: None,
}
}
#[allow(dead_code)]
pub fn with_workspace(mut self, path: &'a Path) -> Self {
self.workspace_path = Some(path);
self
}
#[allow(dead_code)]
pub fn with_hooks(mut self, hooks: &'a HookRunner) -> Self {
self.hooks = Some(hooks);
self
}
#[allow(dead_code)]
pub fn with_event_tx(mut self, tx: mpsc::Sender<ParallelEvent>) -> Self {
self.event_tx = Some(tx);
self
}
pub fn is_parallel(&self) -> bool {
self.workspace_path.is_some()
}
pub fn working_dir(&self) -> Option<&Path> {
self.workspace_path
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ExecutionResult {
Success,
Failed { message: String },
Cancelled { reason: String },
}
impl ExecutionResult {
pub fn success() -> Self {
Self::Success
}
pub fn failed(message: impl Into<String>) -> Self {
Self::Failed {
message: message.into(),
}
}
pub fn cancelled(reason: impl Into<String>) -> Self {
Self::Cancelled {
reason: reason.into(),
}
}
pub fn is_success(&self) -> bool {
matches!(self, Self::Success)
}
pub fn is_failed(&self) -> bool {
matches!(self, Self::Failed { .. })
}
#[allow(dead_code)]
pub fn is_cancelled(&self) -> bool {
matches!(self, Self::Cancelled { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ProgressInfo {
pub completed: u32,
pub total: u32,
}
impl ProgressInfo {
pub fn new(completed: u32, total: u32) -> Self {
Self { completed, total }
}
pub fn percentage(&self) -> u32 {
self.total
.checked_div(self.total)
.map(|_| (self.completed * 100) / self.total)
.unwrap_or(0)
}
pub fn is_complete(&self) -> bool {
self.total > 0 && self.completed >= self.total
}
pub fn is_empty(&self) -> bool {
self.completed == 0
}
pub fn remaining(&self) -> u32 {
self.total.saturating_sub(self.completed)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_context_new() {
let config = OrchestratorConfig::default();
let ctx = ExecutionContext::new("test-change", &config);
assert_eq!(ctx.change_id, "test-change");
assert!(ctx.workspace_path.is_none());
assert!(ctx.hooks.is_none());
assert!(ctx.event_tx.is_none());
}
#[test]
fn test_execution_context_is_parallel() {
let config = OrchestratorConfig::default();
let ctx = ExecutionContext::new("test-change", &config);
assert!(!ctx.is_parallel());
let workspace = std::path::PathBuf::from("/tmp/ws");
let ctx_parallel = ExecutionContext::new("test-change", &config).with_workspace(&workspace);
assert!(ctx_parallel.is_parallel());
}
#[test]
fn test_execution_context_working_dir() {
let config = OrchestratorConfig::default();
let ctx = ExecutionContext::new("test-change", &config);
assert!(ctx.working_dir().is_none());
let workspace = std::path::PathBuf::from("/tmp/ws");
let ctx_with_ws = ExecutionContext::new("test-change", &config).with_workspace(&workspace);
assert_eq!(ctx_with_ws.working_dir(), Some(workspace.as_path()));
}
#[test]
fn test_execution_result_success() {
let result = ExecutionResult::success();
assert!(result.is_success());
assert!(!result.is_failed());
assert!(!result.is_cancelled());
}
#[test]
fn test_execution_result_failed() {
let result = ExecutionResult::failed("some error");
assert!(!result.is_success());
assert!(result.is_failed());
assert!(!result.is_cancelled());
if let ExecutionResult::Failed { message } = result {
assert_eq!(message, "some error");
} else {
panic!("Expected Failed variant");
}
}
#[test]
fn test_execution_result_cancelled() {
let result = ExecutionResult::cancelled("user interrupt");
assert!(!result.is_success());
assert!(!result.is_failed());
assert!(result.is_cancelled());
if let ExecutionResult::Cancelled { reason } = result {
assert_eq!(reason, "user interrupt");
} else {
panic!("Expected Cancelled variant");
}
}
#[test]
fn test_execution_result_equality() {
assert_eq!(ExecutionResult::success(), ExecutionResult::Success);
assert_eq!(
ExecutionResult::failed("err"),
ExecutionResult::Failed {
message: "err".to_string()
}
);
assert_ne!(ExecutionResult::success(), ExecutionResult::failed("err"));
}
#[test]
fn test_progress_info_new() {
let progress = ProgressInfo::new(5, 10);
assert_eq!(progress.completed, 5);
assert_eq!(progress.total, 10);
}
#[test]
fn test_progress_info_default() {
let progress = ProgressInfo::default();
assert_eq!(progress.completed, 0);
assert_eq!(progress.total, 0);
}
#[test]
fn test_progress_info_percentage() {
assert_eq!(ProgressInfo::new(0, 10).percentage(), 0);
assert_eq!(ProgressInfo::new(5, 10).percentage(), 50);
assert_eq!(ProgressInfo::new(10, 10).percentage(), 100);
assert_eq!(ProgressInfo::new(3, 10).percentage(), 30);
assert_eq!(ProgressInfo::new(7, 10).percentage(), 70);
}
#[test]
fn test_progress_info_percentage_zero_total() {
let progress = ProgressInfo::new(0, 0);
assert_eq!(progress.percentage(), 0);
}
#[test]
fn test_progress_info_percentage_edge_cases() {
assert_eq!(ProgressInfo::new(1, 3).percentage(), 33); assert_eq!(ProgressInfo::new(2, 3).percentage(), 66); }
#[test]
fn test_progress_info_is_complete() {
assert!(!ProgressInfo::new(0, 10).is_complete());
assert!(!ProgressInfo::new(5, 10).is_complete());
assert!(ProgressInfo::new(10, 10).is_complete());
assert!(ProgressInfo::new(11, 10).is_complete()); assert!(!ProgressInfo::new(0, 0).is_complete()); }
#[test]
fn test_progress_info_is_empty() {
assert!(ProgressInfo::new(0, 10).is_empty());
assert!(ProgressInfo::new(0, 0).is_empty());
assert!(!ProgressInfo::new(1, 10).is_empty());
assert!(!ProgressInfo::new(10, 10).is_empty());
}
#[test]
fn test_progress_info_remaining() {
assert_eq!(ProgressInfo::new(0, 10).remaining(), 10);
assert_eq!(ProgressInfo::new(5, 10).remaining(), 5);
assert_eq!(ProgressInfo::new(10, 10).remaining(), 0);
assert_eq!(ProgressInfo::new(11, 10).remaining(), 0); }
#[test]
fn test_progress_info_equality() {
assert_eq!(ProgressInfo::new(5, 10), ProgressInfo::new(5, 10));
assert_ne!(ProgressInfo::new(5, 10), ProgressInfo::new(6, 10));
assert_ne!(ProgressInfo::new(5, 10), ProgressInfo::new(5, 11));
}
}