Remove min by shift type boundaries, improve logging, add more tests, add logs in tests, move fixtures in separate file, move restrictions_violated logic into valid_residents of next slot
This commit is contained in:
@@ -2,94 +2,21 @@
|
||||
mod integration_tests {
|
||||
use rota_lib::{
|
||||
config::{ToxicPair, UserConfig},
|
||||
resident::Resident,
|
||||
schedule::{MonthlySchedule, ShiftType},
|
||||
fixtures::{
|
||||
complex_config, hard_config, manual_shifts_heavy_config, maximal_config, minimal_config,
|
||||
},
|
||||
schedule::MonthlySchedule,
|
||||
scheduler::Scheduler,
|
||||
slot::{Day, ShiftPosition, Slot},
|
||||
workload::{WorkloadBounds, WorkloadTracker},
|
||||
};
|
||||
use rstest::{fixture, rstest};
|
||||
use rstest::rstest;
|
||||
|
||||
#[fixture]
|
||||
fn minimal_config() -> UserConfig {
|
||||
UserConfig::default().with_residents(vec![
|
||||
Resident::new(1, "R1"),
|
||||
Resident::new(2, "R2"),
|
||||
Resident::new(3, "R3"),
|
||||
Resident::new(4, "R4"),
|
||||
])
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn maximal_config() -> UserConfig {
|
||||
UserConfig::default()
|
||||
.with_holidays(vec![2, 3, 10, 11, 12, 25])
|
||||
.with_residents(vec![
|
||||
Resident::new(1, "R1").with_max_shifts(3),
|
||||
Resident::new(2, "R2").with_max_shifts(4),
|
||||
Resident::new(3, "R3").with_reduced_load(),
|
||||
Resident::new(4, "R4").with_allowed_types(vec![ShiftType::Closed]),
|
||||
Resident::new(5, "R5")
|
||||
.with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]),
|
||||
Resident::new(6, "R6").with_negative_shifts(vec![Day(5), Day(15), Day(25)]),
|
||||
Resident::new(7, "R7"),
|
||||
Resident::new(8, "R8"),
|
||||
Resident::new(9, "R9"),
|
||||
Resident::new(10, "R10"),
|
||||
])
|
||||
.with_toxic_pairs(vec![
|
||||
ToxicPair::new(1, 2),
|
||||
ToxicPair::new(3, 4),
|
||||
ToxicPair::new(7, 8),
|
||||
])
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn manual_shifts_heavy_config() -> UserConfig {
|
||||
UserConfig::default().with_residents(vec![
|
||||
Resident::new(1, "R1").with_manual_shifts(vec![
|
||||
Slot::new(Day(1), ShiftPosition::First),
|
||||
Slot::new(Day(3), ShiftPosition::First),
|
||||
Slot::new(Day(5), ShiftPosition::Second),
|
||||
]),
|
||||
Resident::new(2, "R2").with_manual_shifts(vec![
|
||||
Slot::new(Day(2), ShiftPosition::First),
|
||||
Slot::new(Day(4), ShiftPosition::First),
|
||||
]),
|
||||
Resident::new(3, "R3"),
|
||||
Resident::new(4, "R4"),
|
||||
Resident::new(5, "R5"),
|
||||
Resident::new(6, "R6"),
|
||||
])
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn complex_config() -> UserConfig {
|
||||
UserConfig::default()
|
||||
.with_holidays(vec![5, 12, 19, 26])
|
||||
.with_residents(vec![
|
||||
Resident::new(1, "R1")
|
||||
.with_max_shifts(3)
|
||||
.with_negative_shifts(vec![Day(1), Day(2), Day(3)]),
|
||||
Resident::new(2, "R2")
|
||||
.with_max_shifts(3)
|
||||
.with_negative_shifts(vec![Day(4), Day(5), Day(6)]),
|
||||
Resident::new(3, "R3")
|
||||
.with_max_shifts(3)
|
||||
.with_negative_shifts(vec![Day(7), Day(8), Day(9)]),
|
||||
Resident::new(4, "R4").with_allowed_types(vec![ShiftType::Closed]),
|
||||
Resident::new(5, "R5")
|
||||
.with_allowed_types(vec![ShiftType::OpenFirst, ShiftType::OpenSecond]),
|
||||
Resident::new(6, "R6"),
|
||||
Resident::new(7, "R7"),
|
||||
Resident::new(8, "R8"),
|
||||
])
|
||||
.with_toxic_pairs(vec![
|
||||
ToxicPair::new(1, 2),
|
||||
ToxicPair::new(2, 3),
|
||||
ToxicPair::new(5, 6),
|
||||
ToxicPair::new(6, 7),
|
||||
])
|
||||
#[ctor::ctor]
|
||||
fn global_setup() {
|
||||
env_logger::builder()
|
||||
.filter_level(log::LevelFilter::Info)
|
||||
.init();
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -98,8 +25,9 @@ mod integration_tests {
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(minimal_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
let solved = scheduler.run(&mut schedule, &mut tracker)?;
|
||||
println!("{}", schedule.report(&minimal_config, &tracker));
|
||||
assert!(solved);
|
||||
validate_all_constraints(&schedule, &tracker, &minimal_config);
|
||||
|
||||
Ok(())
|
||||
@@ -111,8 +39,9 @@ mod integration_tests {
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(maximal_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
let solved = scheduler.run(&mut schedule, &mut tracker)?;
|
||||
println!("{}", schedule.report(&maximal_config, &tracker));
|
||||
assert!(solved);
|
||||
validate_all_constraints(&schedule, &tracker, &maximal_config);
|
||||
|
||||
Ok(())
|
||||
@@ -126,8 +55,9 @@ mod integration_tests {
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(manual_shifts_heavy_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
let solved = scheduler.run(&mut schedule, &mut tracker)?;
|
||||
println!("{}", schedule.report(&manual_shifts_heavy_config, &tracker));
|
||||
assert!(solved);
|
||||
validate_all_constraints(&schedule, &tracker, &manual_shifts_heavy_config);
|
||||
|
||||
Ok(())
|
||||
@@ -139,13 +69,28 @@ mod integration_tests {
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(complex_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
let solved = scheduler.run(&mut schedule, &mut tracker)?;
|
||||
println!("{}", schedule.report(&complex_config, &tracker));
|
||||
assert!(solved);
|
||||
validate_all_constraints(&schedule, &tracker, &complex_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_hard_config(hard_config: UserConfig) -> anyhow::Result<()> {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(hard_config.clone());
|
||||
|
||||
let solved = scheduler.run(&mut schedule, &mut tracker)?;
|
||||
println!("{}", schedule.report(&hard_config, &tracker));
|
||||
assert!(solved);
|
||||
validate_all_constraints(&schedule, &tracker, &hard_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_all_constraints(
|
||||
schedule: &MonthlySchedule,
|
||||
tracker: &WorkloadTracker,
|
||||
@@ -162,8 +107,8 @@ mod integration_tests {
|
||||
.iter()
|
||||
.filter_map(|&p| schedule.get_resident_id(&Slot::new(Day(d - 1), p)))
|
||||
.collect();
|
||||
for res in current {
|
||||
assert!(!previous.contains(&res));
|
||||
for r in current {
|
||||
assert!(!previous.contains(&r));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,20 +126,20 @@ mod integration_tests {
|
||||
}
|
||||
|
||||
let bounds = WorkloadBounds::new_with_config(config);
|
||||
for (slot, res_id) in &schedule.0 {
|
||||
let res = config
|
||||
for (slot, r_id) in &schedule.0 {
|
||||
let r = config
|
||||
.residents
|
||||
.iter()
|
||||
.find(|r| &r.id == res_id)
|
||||
.find(|r| &r.id == r_id)
|
||||
.expect("Resident not found");
|
||||
assert!(res.allowed_types.contains(&slot.shift_type()));
|
||||
assert!(!res.negative_shifts.contains(&slot.day));
|
||||
assert!(r.allowed_types.contains(&slot.shift_type()));
|
||||
assert!(!r.negative_shifts.contains(&slot.day));
|
||||
}
|
||||
|
||||
for resident in &config.residents {
|
||||
let workload = tracker.current_workload(&resident.id);
|
||||
let max = *bounds.max_workloads.get(&resident.id).unwrap();
|
||||
assert!(workload <= max);
|
||||
assert!(workload <= max, "workload: {}, max: {}", workload, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user