Improve error handling, logging
This commit is contained in:
4
src-tauri/.gitignore
vendored
4
src-tauri/.gitignore
vendored
@@ -6,5 +6,5 @@
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
|
||||
|
||||
schedule.*
|
||||
# Ignore exported txt/doc files and the log file
|
||||
rota.*
|
||||
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@@ -3222,6 +3222,7 @@ dependencies = [
|
||||
name = "rota"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"docx-rs",
|
||||
"itertools",
|
||||
|
||||
@@ -29,3 +29,4 @@ tauri-plugin-log = "2"
|
||||
log = "0.4.29"
|
||||
rand = "0.9.2"
|
||||
docx-rs = "0.4.18"
|
||||
anyhow = "1.0.100"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Context;
|
||||
use chrono::Month;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -6,6 +7,7 @@ use crate::{
|
||||
slot::Day,
|
||||
};
|
||||
|
||||
const MONTH: u8 = 2;
|
||||
const YEAR: i32 = 2026;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -53,30 +55,6 @@ pub struct UserConfig {
|
||||
}
|
||||
|
||||
impl UserConfig {
|
||||
pub fn new(month: u8) -> Self {
|
||||
let month = Month::try_from(month).unwrap();
|
||||
let total_days = month.num_days(YEAR).unwrap();
|
||||
let total_slots = (1..=total_days)
|
||||
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
||||
.sum();
|
||||
|
||||
let total_holiday_slots = (1..=total_days)
|
||||
.filter(|&d| Day(d).is_weekend(month.number_from_month(), YEAR))
|
||||
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
||||
.sum();
|
||||
|
||||
Self {
|
||||
month,
|
||||
year: YEAR,
|
||||
holidays: vec![],
|
||||
residents: vec![],
|
||||
toxic_pairs: vec![],
|
||||
total_days,
|
||||
total_slots,
|
||||
total_holiday_slots,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_holidays(mut self, holidays: Vec<u8>) -> Self {
|
||||
self.holidays = holidays;
|
||||
self.total_holiday_slots = self.total_holiday_slots();
|
||||
@@ -113,7 +91,7 @@ impl UserConfig {
|
||||
|
||||
impl Default for UserConfig {
|
||||
fn default() -> Self {
|
||||
let month = Month::try_from(2).unwrap();
|
||||
let month = Month::try_from(MONTH).unwrap();
|
||||
|
||||
let total_days = month.num_days(YEAR).unwrap();
|
||||
|
||||
@@ -139,11 +117,13 @@ impl Default for UserConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserConfigDTO> for UserConfig {
|
||||
fn from(value: UserConfigDTO) -> Self {
|
||||
let month = Month::try_from(value.month).unwrap();
|
||||
impl TryFrom<UserConfigDTO> for UserConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
let total_days = month.num_days(YEAR).unwrap();
|
||||
fn try_from(value: UserConfigDTO) -> Result<Self, Self::Error> {
|
||||
let month = Month::try_from(value.month)?;
|
||||
|
||||
let total_days = month.num_days(value.year).context("Failed to parse")?;
|
||||
|
||||
let total_slots = (1..=total_days)
|
||||
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
||||
@@ -157,7 +137,7 @@ impl From<UserConfigDTO> for UserConfig {
|
||||
.map(|d| if Day(d).is_open_shift() { 2 } else { 1 })
|
||||
.sum();
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
month,
|
||||
year: value.year,
|
||||
holidays: value.holidays,
|
||||
@@ -170,6 +150,6 @@ impl From<UserConfigDTO> for UserConfig {
|
||||
total_days,
|
||||
total_slots,
|
||||
total_holiday_slots,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use anyhow::Context;
|
||||
use docx_rs::{Docx, Paragraph, Run, RunFonts, Table, TableCell, TableRow};
|
||||
|
||||
use crate::{
|
||||
@@ -16,45 +17,51 @@ pub enum FileType {
|
||||
}
|
||||
|
||||
pub trait Export {
|
||||
fn export(&self, file_type: FileType, config: &UserConfig, tracker: &WorkloadTracker);
|
||||
fn export(
|
||||
&self,
|
||||
file_type: FileType,
|
||||
config: &UserConfig,
|
||||
tracker: &WorkloadTracker,
|
||||
) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
impl Export for MonthlySchedule {
|
||||
fn export(&self, file_type: FileType, config: &UserConfig, tracker: &WorkloadTracker) {
|
||||
fn export(
|
||||
&self,
|
||||
file_type: FileType,
|
||||
config: &UserConfig,
|
||||
tracker: &WorkloadTracker,
|
||||
) -> anyhow::Result<()> {
|
||||
match file_type {
|
||||
FileType::Txt => self.export_as_txt(config, tracker),
|
||||
FileType::Docx => self.export_as_doc(config),
|
||||
FileType::Txt => self.export_as_txt(config, tracker)?,
|
||||
FileType::Docx => self.export_as_doc(config)?,
|
||||
};
|
||||
|
||||
// TODO: make this env var from a config file? Option to change this in-app
|
||||
let env_path = "rota/me/";
|
||||
println!(
|
||||
"exported type {:?}. Saved at folder path {}",
|
||||
file_type, env_path
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MonthlySchedule {
|
||||
pub fn export_as_txt(&self, config: &UserConfig, tracker: &WorkloadTracker) -> String {
|
||||
let file = File::create("schedule.txt").unwrap();
|
||||
pub fn export_as_txt(
|
||||
&self,
|
||||
config: &UserConfig,
|
||||
tracker: &WorkloadTracker,
|
||||
) -> anyhow::Result<()> {
|
||||
let file = File::create("rota.txt")?;
|
||||
let mut writer = std::io::BufWriter::new(file);
|
||||
|
||||
writer
|
||||
.write_all(self.pretty_print(config).as_bytes())
|
||||
.expect("Failed to write schedule");
|
||||
writer.write_all(self.pretty_print(config).as_bytes())?;
|
||||
|
||||
writer
|
||||
.write_all(self.report(config, tracker).as_bytes())
|
||||
.expect("Failed to write report");
|
||||
writer.write_all(self.report(config, tracker).as_bytes())?;
|
||||
|
||||
writer.flush().expect("Failed to flush buffer");
|
||||
"ok".to_string()
|
||||
writer.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn export_as_doc(&self, config: &UserConfig) -> String {
|
||||
let path = std::path::Path::new("./schedule.docx");
|
||||
let file = std::fs::File::create(path).unwrap();
|
||||
pub fn export_as_doc(&self, config: &UserConfig) -> anyhow::Result<()> {
|
||||
let path = std::path::Path::new("rota.docx");
|
||||
let file = std::fs::File::create(path)?;
|
||||
|
||||
let header = Table::new(vec![
|
||||
TableRow::new(vec![TableCell::new().add_paragraph(
|
||||
@@ -101,7 +108,7 @@ impl MonthlySchedule {
|
||||
.iter()
|
||||
.find(|r| Some(&r.id) == slot_first_res_id)
|
||||
.map(|r| r.name.as_str())
|
||||
.unwrap();
|
||||
.unwrap_or("-");
|
||||
|
||||
let res_name_2 = if day.is_open_shift() {
|
||||
let slot_second = Slot::new(Day(d), ShiftPosition::Second);
|
||||
@@ -153,18 +160,23 @@ impl MonthlySchedule {
|
||||
|
||||
doc = doc.add_table(residents_table);
|
||||
|
||||
doc.build().pack(file).unwrap();
|
||||
doc.build().pack(file)?;
|
||||
|
||||
"just a string".to_string()
|
||||
tauri_plugin_opener::open_path(path, None::<&str>)
|
||||
.context("Created file but failed to open it")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Ok;
|
||||
use rstest::{fixture, rstest};
|
||||
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
export::{Export, FileType},
|
||||
resident::Resident,
|
||||
schedule::MonthlySchedule,
|
||||
scheduler::Scheduler,
|
||||
@@ -203,13 +215,29 @@ mod tests {
|
||||
WorkloadTracker::default()
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
pub fn test_export_as_txt(
|
||||
mut schedule: MonthlySchedule,
|
||||
mut tracker: WorkloadTracker,
|
||||
scheduler: Scheduler,
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
schedule.export(FileType::Txt, &scheduler.config, &tracker)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
pub fn test_export_as_doc(
|
||||
mut schedule: MonthlySchedule,
|
||||
mut tracker: WorkloadTracker,
|
||||
scheduler: Scheduler,
|
||||
) {
|
||||
scheduler.run(&mut schedule, &mut tracker);
|
||||
schedule.export_as_doc(&scheduler.config);
|
||||
) -> anyhow::Result<()> {
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
schedule.export(FileType::Docx, &scheduler.config, &tracker)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::{env::home_dir, sync::Mutex};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::{error, info};
|
||||
|
||||
use crate::{
|
||||
config::{UserConfig, UserConfigDTO},
|
||||
export::{Export, FileType},
|
||||
schedule::MonthlySchedule,
|
||||
scheduler::Scheduler,
|
||||
workload::{WorkloadBounds, WorkloadTracker},
|
||||
workload::WorkloadTracker,
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
@@ -14,6 +16,7 @@ pub mod resident;
|
||||
pub mod schedule;
|
||||
pub mod scheduler;
|
||||
pub mod slot;
|
||||
pub mod timer;
|
||||
pub mod workload;
|
||||
|
||||
struct AppState {
|
||||
@@ -25,40 +28,61 @@ struct AppState {
|
||||
/// information for the residents, the forbidden pairs the holidays
|
||||
/// and the period of the schedule
|
||||
#[tauri::command]
|
||||
fn generate(config: UserConfigDTO, state: tauri::State<'_, AppState>) -> MonthlySchedule {
|
||||
let config = UserConfig::from(config);
|
||||
fn generate(
|
||||
config: UserConfigDTO,
|
||||
state: tauri::State<'_, AppState>,
|
||||
) -> Result<MonthlySchedule, String> {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let bounds = WorkloadBounds::new_with_config(&config);
|
||||
let scheduler = Scheduler::new(config, bounds);
|
||||
let scheduler =
|
||||
Scheduler::new_with_config(UserConfig::try_from(config).map_err(|e| e.to_string())?);
|
||||
|
||||
scheduler.run(&mut schedule, &mut tracker);
|
||||
scheduler
|
||||
.run(&mut schedule, &mut tracker)
|
||||
.inspect_err(|e| error!("{e}"))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
info!(
|
||||
"Scheduler finished successfully in {}ms",
|
||||
scheduler.timer.elapsed_in_ms()
|
||||
);
|
||||
|
||||
let mut internal_schedule = state.schedule.lock().unwrap();
|
||||
*internal_schedule = schedule.clone();
|
||||
|
||||
let mut internal_tracker = state.tracker.lock().unwrap();
|
||||
|
||||
*internal_schedule = schedule.clone();
|
||||
*internal_tracker = tracker.clone();
|
||||
|
||||
schedule
|
||||
Ok(schedule)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn export(config: UserConfigDTO, state: tauri::State<'_, AppState>) {
|
||||
let config = UserConfig::from(config);
|
||||
fn export(config: UserConfigDTO, state: tauri::State<'_, AppState>) -> Result<(), String> {
|
||||
let config = UserConfig::try_from(config)
|
||||
.inspect_err(|e| error!("{e}"))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let schedule = state.schedule.lock().unwrap();
|
||||
let tracker = state.tracker.lock().unwrap();
|
||||
schedule.export(FileType::Docx, &config, &tracker);
|
||||
schedule.export(FileType::Txt, &config, &tracker);
|
||||
|
||||
schedule
|
||||
.export(FileType::Docx, &config, &tracker)
|
||||
.inspect_err(|e| error!("{e}"))
|
||||
.map_err(|e| e.to_string())?;
|
||||
schedule
|
||||
.export(FileType::Txt, &config, &tracker)
|
||||
.inspect_err(|e| error!("{e}"))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let log_dir = std::env::current_dir().unwrap_or(std::path::PathBuf::from("."));
|
||||
info!("Files exported at {}", log_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let log_dir = home_dir().unwrap().join(".rota_logs");
|
||||
|
||||
if let Err(e) = std::fs::create_dir_all(&log_dir) {
|
||||
eprintln!("Cannot create log folder: {}", e);
|
||||
}
|
||||
let log_dir = std::env::current_dir().unwrap_or(std::path::PathBuf::from("."));
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(AppState {
|
||||
@@ -70,7 +94,7 @@ pub fn run() {
|
||||
.targets([tauri_plugin_log::Target::new(
|
||||
tauri_plugin_log::TargetKind::Folder {
|
||||
path: log_dir,
|
||||
file_name: Some("rota".to_string()), // Note: Plugin adds .log automatically
|
||||
file_name: Some("rota".to_string()),
|
||||
},
|
||||
)])
|
||||
.level(log::LevelFilter::Info)
|
||||
@@ -79,7 +103,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![generate, export])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
.expect("Error while running tauri application");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -119,7 +119,7 @@ impl MonthlySchedule {
|
||||
.iter()
|
||||
.find(|r| &r.id == res_id)
|
||||
.map(|r| r.name.as_str())
|
||||
.unwrap();
|
||||
.unwrap_or("");
|
||||
|
||||
output.push_str(&format!(
|
||||
"Ημέρα {:2} - {:9} - {:11}: {},\n",
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::{
|
||||
resident::ResidentId,
|
||||
schedule::MonthlySchedule,
|
||||
slot::Slot,
|
||||
timer::Timer,
|
||||
workload::{WorkloadBounds, WorkloadTracker},
|
||||
};
|
||||
|
||||
@@ -11,25 +12,40 @@ use rand::Rng;
|
||||
pub struct Scheduler {
|
||||
pub config: UserConfig,
|
||||
pub bounds: WorkloadBounds,
|
||||
pub timer: Timer,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
pub fn new(config: UserConfig, bounds: WorkloadBounds) -> Self {
|
||||
Self { config, bounds }
|
||||
Self {
|
||||
config,
|
||||
bounds,
|
||||
timer: Timer::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_config(config: UserConfig) -> Self {
|
||||
let bounds = WorkloadBounds::new_with_config(&config);
|
||||
|
||||
Self { config, bounds }
|
||||
Self {
|
||||
config,
|
||||
bounds,
|
||||
timer: Timer::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self, schedule: &mut MonthlySchedule, tracker: &mut WorkloadTracker) -> bool {
|
||||
pub fn run(
|
||||
&self,
|
||||
schedule: &mut MonthlySchedule,
|
||||
tracker: &mut WorkloadTracker,
|
||||
) -> anyhow::Result<bool> {
|
||||
schedule.prefill(&self.config);
|
||||
for (slot, res_id) in schedule.0.iter() {
|
||||
tracker.insert(*res_id, &self.config, *slot);
|
||||
}
|
||||
|
||||
//TODO: add validation
|
||||
|
||||
self.search(schedule, tracker, Slot::default())
|
||||
}
|
||||
|
||||
@@ -41,15 +57,19 @@ impl Scheduler {
|
||||
schedule: &mut MonthlySchedule,
|
||||
tracker: &mut WorkloadTracker,
|
||||
slot: Slot,
|
||||
) -> bool {
|
||||
) -> anyhow::Result<bool> {
|
||||
if self.timer.limit_exceeded() {
|
||||
anyhow::bail!("Time exceeded. Restrictions too tight");
|
||||
}
|
||||
|
||||
if !slot.is_first()
|
||||
&& schedule.restrictions_violated(&slot.previous(), &self.config, &self.bounds, tracker)
|
||||
{
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if slot.greater_than(self.config.total_days) {
|
||||
return tracker.are_all_thresholds_met(&self.config, &self.bounds);
|
||||
return Ok(tracker.are_all_thresholds_met(&self.config, &self.bounds));
|
||||
}
|
||||
|
||||
if schedule.is_slot_manually_assigned(&slot) {
|
||||
@@ -68,15 +88,15 @@ impl Scheduler {
|
||||
schedule.insert(slot, id);
|
||||
tracker.insert(id, &self.config, slot);
|
||||
|
||||
if self.search(schedule, tracker, slot.next()) {
|
||||
return true;
|
||||
if self.search(schedule, tracker, slot.next())? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
schedule.remove(slot);
|
||||
tracker.remove(id, &self.config, slot);
|
||||
}
|
||||
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Return all valid residents for the current slot
|
||||
@@ -150,7 +170,7 @@ mod tests {
|
||||
mut tracker: WorkloadTracker,
|
||||
scheduler: Scheduler,
|
||||
) {
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker));
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker).is_ok());
|
||||
|
||||
for d in 1..=scheduler.config.total_days {
|
||||
let day = Day(d);
|
||||
|
||||
@@ -139,10 +139,16 @@ impl Day {
|
||||
}
|
||||
|
||||
pub fn is_weekend(&self, month: u32, year: i32) -> bool {
|
||||
let date = NaiveDate::from_ymd_opt(year, month, self.0 as u32).unwrap();
|
||||
let date = NaiveDate::from_ymd_opt(year, month, self.0 as u32);
|
||||
|
||||
match date {
|
||||
Some(date) => {
|
||||
let weekday = date.weekday();
|
||||
weekday == Weekday::Sat || weekday == Weekday::Sun
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn weekday(&self, month: u32, year: i32) -> Weekday {
|
||||
let date = NaiveDate::from_ymd_opt(year, month, self.0 as u32).unwrap();
|
||||
|
||||
27
src-tauri/src/timer.rs
Normal file
27
src-tauri/src/timer.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::time::Instant;
|
||||
|
||||
pub const TIME_LIMIT_IN_MS: u128 = 100000;
|
||||
|
||||
pub struct Timer {
|
||||
instant: Instant,
|
||||
limit: u128,
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
pub fn limit_exceeded(&self) -> bool {
|
||||
self.instant.elapsed().as_millis() > self.limit
|
||||
}
|
||||
|
||||
pub fn elapsed_in_ms(&self) -> u128 {
|
||||
self.instant.elapsed().as_millis()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Timer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
instant: std::time::Instant::now(),
|
||||
limit: TIME_LIMIT_IN_MS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#[cfg(test)]
|
||||
mod integration_tests {
|
||||
use anyhow::Ok;
|
||||
use rota_lib::{
|
||||
config::{ToxicPair, UserConfig},
|
||||
resident::Resident,
|
||||
@@ -12,7 +13,7 @@ mod integration_tests {
|
||||
|
||||
#[fixture]
|
||||
fn minimal_config() -> UserConfig {
|
||||
UserConfig::new(2).with_residents(vec![
|
||||
UserConfig::default().with_residents(vec![
|
||||
Resident::new(1, "R1"),
|
||||
Resident::new(2, "R2"),
|
||||
Resident::new(3, "R3"),
|
||||
@@ -22,7 +23,7 @@ mod integration_tests {
|
||||
|
||||
#[fixture]
|
||||
fn maximal_config() -> UserConfig {
|
||||
UserConfig::new(2)
|
||||
UserConfig::default()
|
||||
.with_holidays(vec![2, 3, 10, 11, 12, 25])
|
||||
.with_residents(vec![
|
||||
Resident::new(1, "R1").with_max_shifts(3),
|
||||
@@ -46,7 +47,7 @@ mod integration_tests {
|
||||
|
||||
#[fixture]
|
||||
fn manual_shifts_heavy_config() -> UserConfig {
|
||||
UserConfig::new(2).with_residents(vec![
|
||||
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),
|
||||
@@ -65,7 +66,7 @@ mod integration_tests {
|
||||
|
||||
#[fixture]
|
||||
fn complex_config() -> UserConfig {
|
||||
UserConfig::new(2)
|
||||
UserConfig::default()
|
||||
.with_holidays(vec![5, 12, 19, 26])
|
||||
.with_residents(vec![
|
||||
Resident::new(1, "R1")
|
||||
@@ -93,43 +94,57 @@ mod integration_tests {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_minimal_config(minimal_config: UserConfig) {
|
||||
fn test_minimal_config(minimal_config: UserConfig) -> anyhow::Result<()> {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(minimal_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker));
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
validate_all_constraints(&schedule, &tracker, &minimal_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_maximal_config(maximal_config: UserConfig) {
|
||||
fn test_maximal_config(maximal_config: UserConfig) -> anyhow::Result<()> {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(maximal_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker));
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
validate_all_constraints(&schedule, &tracker, &maximal_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_manual_shifts_heavy_config(manual_shifts_heavy_config: UserConfig) {
|
||||
fn test_manual_shifts_heavy_config(
|
||||
manual_shifts_heavy_config: UserConfig,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(manual_shifts_heavy_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker));
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
validate_all_constraints(&schedule, &tracker, &manual_shifts_heavy_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_complex_config(complex_config: UserConfig) {
|
||||
fn test_complex_config(complex_config: UserConfig) -> anyhow::Result<()> {
|
||||
let mut schedule = MonthlySchedule::new();
|
||||
let mut tracker = WorkloadTracker::default();
|
||||
let scheduler = Scheduler::new_with_config(complex_config.clone());
|
||||
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker));
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker)?);
|
||||
|
||||
validate_all_constraints(&schedule, &tracker, &complex_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_all_constraints(
|
||||
|
||||
Reference in New Issue
Block a user