initial commit
This commit is contained in:
15 files changed
+2310
No files matched your search
@@ -0,0 +1,57 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
simulation::run_multiple_simulations,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_average_effort_calculation() {
|
||||
let num_tasks = 10;
|
||||
let min_days = 5.0;
|
||||
let max_days = 15.0;
|
||||
let expected_avg_days = 10.0;
|
||||
let estimate_confidence = 0.8;
|
||||
let num_simulations = 10000;
|
||||
let num_workers = 1; // Using 1 worker to simplify effort calculation
|
||||
|
||||
// Create tasks
|
||||
let tasks: Vec<Task> = (0..num_tasks)
|
||||
.map(|i| {
|
||||
Task::new(
|
||||
&format!("Task{}", i),
|
||||
vec![],
|
||||
days_to_duration(min_days),
|
||||
days_to_duration(max_days),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let schedule =
|
||||
Schedule::new(tasks, num_workers, estimate_confidence).expect("Failed to create schedule");
|
||||
|
||||
let (_, effort_times) = run_multiple_simulations(&schedule, num_simulations);
|
||||
|
||||
let total_effort: Duration = effort_times.iter().sum();
|
||||
let avg_effort = total_effort / num_simulations as u32;
|
||||
let avg_effort_days = avg_effort.as_secs_f64() / 86400.0;
|
||||
|
||||
println!(
|
||||
"Average effort: {:.2} days (expected: {:.2} days)",
|
||||
avg_effort_days,
|
||||
expected_avg_days * num_tasks as f64
|
||||
);
|
||||
|
||||
// Check if the average effort is within 1% of the expected value
|
||||
let expected_total_days = expected_avg_days * num_tasks as f64;
|
||||
let difference_percentage =
|
||||
(avg_effort_days - expected_total_days).abs() / expected_total_days * 100.0;
|
||||
|
||||
assert!(
|
||||
difference_percentage < 1.0,
|
||||
"Average effort ({:.2} days) deviates more than 1% from expected ({:.2} days)",
|
||||
avg_effort_days,
|
||||
expected_total_days
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
|
||||
fn create_schedule_and_expect_error(tasks: Vec<Task>, expected_error: &str) {
|
||||
let result = Schedule::new(tasks, 1, 0.8);
|
||||
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Expected schedule creation to fail, but it didn't"
|
||||
);
|
||||
if let Err(error_message) = result {
|
||||
assert!(
|
||||
error_message.contains(expected_error),
|
||||
"Expected error message '{}', but got '{}'",
|
||||
expected_error,
|
||||
error_message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cyclic_dependency() {
|
||||
let tasks = vec![
|
||||
Task::new(
|
||||
"A",
|
||||
vec!["B".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
];
|
||||
|
||||
create_schedule_and_expect_error(tasks, "Cyclic dependency detected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_missing_dependency() {
|
||||
let tasks = vec![
|
||||
Task::new(
|
||||
"A",
|
||||
vec!["C".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
];
|
||||
|
||||
create_schedule_and_expect_error(tasks, "Missing dependency");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_duration() {
|
||||
let tasks = vec![Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
days_to_duration(0.0),
|
||||
days_to_duration(1.0),
|
||||
)];
|
||||
|
||||
create_schedule_and_expect_error(tasks, "Invalid task duration");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_min_greater_than_max() {
|
||||
let tasks = vec![Task::new(
|
||||
"A",
|
||||
vec![],
|
||||
days_to_duration(3.0),
|
||||
days_to_duration(2.0),
|
||||
)];
|
||||
|
||||
create_schedule_and_expect_error(tasks, "Minimum duration greater than maximum");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_task_list() {
|
||||
let tasks = vec![];
|
||||
create_schedule_and_expect_error(tasks, "Empty task list");
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
simulation::run_multiple_simulations,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
fn create_independent_tasks(num_tasks: usize) -> Vec<Task> {
|
||||
(0..num_tasks)
|
||||
.map(|i| {
|
||||
Task::new(
|
||||
&format!("Task_{}", i),
|
||||
vec![],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(3.0),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_dependencies_schedule() {
|
||||
let num_tasks = 32;
|
||||
let tasks = create_independent_tasks(num_tasks);
|
||||
let estimate_confidence = 0.8;
|
||||
let num_simulations = 1_000;
|
||||
|
||||
let mut prev_avg_effort = 0.0;
|
||||
|
||||
for num_workers in [1, 2, 4, 8] {
|
||||
let schedule = Schedule::new(tasks.clone(), num_workers, estimate_confidence)
|
||||
.expect("Failed to create schedule");
|
||||
let (project_durations, effort_times) =
|
||||
run_multiple_simulations(&schedule, num_simulations);
|
||||
|
||||
let avg_duration = project_durations.iter().sum::<Duration>() / num_simulations as u32;
|
||||
let avg_effort = effort_times.iter().sum::<Duration>() / num_simulations as u32;
|
||||
|
||||
let avg_duration_days = avg_duration.as_secs_f64() / 86400.0;
|
||||
let avg_effort_days = avg_effort.as_secs_f64() / 86400.0;
|
||||
|
||||
println!(
|
||||
"Workers: {}, Avg Duration: {:.2} days, Avg Effort: {:.2} person-days",
|
||||
num_workers, avg_duration_days, avg_effort_days
|
||||
);
|
||||
|
||||
// Check that effort is consistent across different numbers of workers
|
||||
if num_workers > 1 {
|
||||
let effort_difference = (avg_effort_days - prev_avg_effort).abs();
|
||||
assert!(
|
||||
effort_difference < 2.0,
|
||||
"Effort should be consistent. Previous: {:.2}, Current: {:.2}",
|
||||
prev_avg_effort,
|
||||
avg_effort_days
|
||||
);
|
||||
}
|
||||
prev_avg_effort = avg_effort_days;
|
||||
|
||||
// Check effort/duration ratio
|
||||
let effort_ratio = avg_effort_days / avg_duration_days;
|
||||
let expected_ratio = num_workers as f64;
|
||||
let ratio_error = (effort_ratio - expected_ratio).abs() / expected_ratio;
|
||||
|
||||
println!(
|
||||
" Effort/Duration Ratio: {:.2} (Expected: {:.2}, Error: {:.2})",
|
||||
effort_ratio, expected_ratio, ratio_error
|
||||
);
|
||||
|
||||
assert!(ratio_error < 0.20, "Expected effort/duration ratio to be close to number of workers. Expected: {}, Actual: {}", expected_ratio, effort_ratio);
|
||||
|
||||
// For 1 worker, duration should be very close to effort
|
||||
if num_workers == 1 {
|
||||
let duration_effort_ratio = avg_duration_days / avg_effort_days;
|
||||
assert!(
|
||||
(duration_effort_ratio - 1.0).abs() < 0.05,
|
||||
"For single worker, duration should equal effort. Ratio: {}",
|
||||
duration_effort_ratio
|
||||
);
|
||||
}
|
||||
|
||||
// Check that duration decreases proportionally to the increase in workers
|
||||
if num_workers > 1 {
|
||||
let expected_duration = avg_effort_days / num_workers as f64;
|
||||
let actual_duration = avg_duration_days;
|
||||
let duration_ratio = actual_duration / expected_duration;
|
||||
let duration_error = (duration_ratio - 1.0).abs();
|
||||
|
||||
println!(
|
||||
" Duration Ratio: {:.2} (Expected: {:.2}, Actual: {:.2}, Error: {:.2})",
|
||||
duration_ratio, expected_duration, actual_duration, duration_error
|
||||
);
|
||||
|
||||
assert!(duration_error < 0.20, "Expected duration to decrease proportionally with workers. Expected: {:.2}, Actual: {:.2}", expected_duration, actual_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
use mcps::{
|
||||
schedule::Schedule,
|
||||
simulation::run_multiple_simulations,
|
||||
task::{days_to_duration, Task},
|
||||
};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_simulation_with_different_worker_counts() {
|
||||
let tasks = vec![
|
||||
Task::new("A", vec![], days_to_duration(1.0), days_to_duration(3.0)),
|
||||
Task::new(
|
||||
"B",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(4.0),
|
||||
),
|
||||
Task::new(
|
||||
"C",
|
||||
vec!["A".to_string()],
|
||||
days_to_duration(3.0),
|
||||
days_to_duration(5.0),
|
||||
),
|
||||
Task::new(
|
||||
"D",
|
||||
vec!["B".to_string(), "C".to_string()],
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(6.0),
|
||||
),
|
||||
Task::new(
|
||||
"E",
|
||||
vec!["B".to_string()],
|
||||
days_to_duration(1.0),
|
||||
days_to_duration(2.0),
|
||||
),
|
||||
Task::new(
|
||||
"F",
|
||||
vec!["D".to_string(), "E".to_string()],
|
||||
days_to_duration(2.0),
|
||||
days_to_duration(4.0),
|
||||
),
|
||||
];
|
||||
|
||||
let estimate_confidence = 0.8;
|
||||
let num_simulations = 1000;
|
||||
|
||||
let mut all_efforts = Vec::new();
|
||||
|
||||
for num_workers in [1, 2, 4, 8, 16, 32] {
|
||||
let schedule = Schedule::new(tasks.clone(), num_workers, estimate_confidence)
|
||||
.expect("Failed to create schedule");
|
||||
let (project_durations, effort_times) =
|
||||
run_multiple_simulations(&schedule, num_simulations);
|
||||
|
||||
let avg_duration = project_durations.iter().sum::<Duration>() / num_simulations as u32;
|
||||
let avg_effort = effort_times.iter().sum::<Duration>() / num_simulations as u32;
|
||||
|
||||
println!(
|
||||
"Workers: {}, Avg Duration: {:.2} days, Avg Effort: {:.2} person-days",
|
||||
num_workers,
|
||||
avg_duration.as_secs_f64() / 86400.0,
|
||||
avg_effort.as_secs_f64() / 86400.0
|
||||
);
|
||||
|
||||
all_efforts.push(avg_effort);
|
||||
|
||||
// Basic sanity checks
|
||||
assert!(
|
||||
avg_duration <= avg_effort,
|
||||
"Average duration should not exceed average effort"
|
||||
);
|
||||
if num_workers > 1 {
|
||||
assert!(
|
||||
avg_duration < avg_effort,
|
||||
"With multiple workers, average duration should be less than average effort"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that effort remains relatively constant regardless of worker count
|
||||
let max_effort = all_efforts.iter().max().unwrap();
|
||||
let min_effort = all_efforts.iter().min().unwrap();
|
||||
let effort_difference = (max_effort.as_secs_f64() - min_effort.as_secs_f64()) / 86400.0;
|
||||
|
||||
println!("Maximum effort difference: {:.2} days", effort_difference);
|
||||
assert!(
|
||||
effort_difference < 1.0,
|
||||
"Average effort should not vary significantly with different worker counts"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user