mcps 0.3.0

A command-line tool for running Monte Carlo simulations on project schedules.
Documentation
use std::time::Duration;

#[derive(Debug, Clone)]
pub struct Task {
    pub id: String,
    pub dependencies: Vec<String>,
    pub min_time: Duration,
    pub likely_time: Duration,
    pub max_time: Duration,
}

impl Task {
    pub fn new(
        id: &str,
        dependencies: Vec<String>,
        min_time: Duration,
        likely_time: Duration,
        max_time: Duration,
    ) -> Self {
        Task {
            id: id.to_string(),
            dependencies,
            min_time,
            likely_time,
            max_time,
        }
    }
}

pub fn days_to_duration(days: f64) -> Duration {
    Duration::from_secs_f64(days * 24.0 * 60.0 * 60.0)
}

#[cfg(test)]
mod tests {
    use super::*;

    const EPSILON: f64 = 1e-6;

    fn assert_duration_eq(actual: Duration, expected: Duration) {
        let actual_secs = actual.as_secs_f64();
        let expected_secs = expected.as_secs_f64();
        assert!(
            (actual_secs - expected_secs).abs() < EPSILON,
            "Durations are not equal: actual = {:?}, expected = {:?}",
            actual,
            expected
        );
    }

    #[test]
    fn test_task_creation() {
        let task = Task::new(
            "Task1",
            vec!["Dep1".to_string(), "Dep2".to_string()],
            Duration::from_secs(1),
            Duration::from_secs(3),
            Duration::from_secs(5),
        );

        assert_eq!(task.id, "Task1");
        assert_eq!(task.dependencies, vec!["Dep1", "Dep2"]);
        assert_eq!(task.min_time, Duration::from_secs(1));
        assert_eq!(task.max_time, Duration::from_secs(5));
    }

    #[test]
    fn test_task_creation_empty_dependencies() {
        let task = Task::new(
            "Task2",
            vec![],
            Duration::from_secs(2),
            Duration::from_secs(7),
            Duration::from_secs(10),
        );

        assert_eq!(task.id, "Task2");
        assert!(task.dependencies.is_empty());
        assert_eq!(task.min_time, Duration::from_secs(2));
        assert_eq!(task.max_time, Duration::from_secs(10));
    }

    #[test]
    fn test_task_creation_same_min_max_time() {
        let task = Task::new(
            "Task3",
            vec!["Dep3".to_string()],
            Duration::from_secs(5),
            Duration::from_secs(5),
            Duration::from_secs(5),
        );

        assert_eq!(task.id, "Task3");
        assert_eq!(task.dependencies, vec!["Dep3"]);
        assert_eq!(task.min_time, task.max_time);
    }

    #[test]
    fn test_task_clone() {
        let task1 = Task::new(
            "Task4",
            vec!["Dep4".to_string()],
            Duration::from_secs(3),
            Duration::from_secs(5),
            Duration::from_secs(7),
        );
        let task2 = task1.clone();

        assert_eq!(task1.id, task2.id);
        assert_eq!(task1.dependencies, task2.dependencies);
        assert_eq!(task1.min_time, task2.min_time);
        assert_eq!(task1.max_time, task2.max_time);
    }

    #[test]
    fn test_days_to_duration() {
        assert_duration_eq(days_to_duration(1.0), Duration::from_secs(24 * 60 * 60));
        assert_duration_eq(days_to_duration(7.0), Duration::from_secs(7 * 24 * 60 * 60));
        assert_duration_eq(days_to_duration(0.0), Duration::from_secs(0));
        assert_duration_eq(
            days_to_duration(365.0),
            Duration::from_secs(365 * 24 * 60 * 60),
        );
    }

    #[test]
    fn test_days_to_duration_fractional() {
        assert_duration_eq(days_to_duration(0.5), Duration::from_secs(12 * 60 * 60));
        assert_duration_eq(days_to_duration(1.5), Duration::from_secs(36 * 60 * 60));
        assert_duration_eq(days_to_duration(0.25), Duration::from_secs(6 * 60 * 60));
        assert_duration_eq(
            days_to_duration(0.1),
            Duration::from_secs_f64(0.1 * 24.0 * 60.0 * 60.0),
        );
    }

    #[test]
    fn test_days_to_duration_large_number() {
        let large_days = 1000.0;
        let expected_seconds = large_days * 24.0 * 60.0 * 60.0;
        assert_duration_eq(
            days_to_duration(large_days),
            Duration::from_secs_f64(expected_seconds),
        );
    }

    #[test]
    fn test_days_to_duration_very_small() {
        let small_days = 1e-6;
        let expected_seconds = small_days * 24.0 * 60.0 * 60.0;
        assert_duration_eq(
            days_to_duration(small_days),
            Duration::from_secs_f64(expected_seconds),
        );
    }
}