use sorting_race::models::config::{Distribution, FairnessMode, RunConfiguration};
use sorting_race::models::traits::{FairnessModel, Sorter, StepResult, Telemetry};
use sorting_race::services::fairness::comparison::ComparisonBudget;
use sorting_race::services::generator::ArrayGenerator;
use sorting_race::services::sorters::{
bubble::BubbleSort, heap::HeapSort, insertion::InsertionSort, merge::MergeSort,
quick::QuickSort, selection::SelectionSort,
};
#[derive(Debug, Clone, PartialEq)]
enum ControllerState {
Running,
Paused,
}
struct SortingController {
algorithms: Vec<Box<dyn Sorter>>,
fairness_model: Box<dyn FairnessModel>,
state: ControllerState,
step_count: usize,
pause_snapshots: Vec<Vec<AlgorithmSnapshot>>,
}
#[derive(Debug, Clone)]
struct AlgorithmSnapshot {
name: String,
array_state: Vec<i32>,
telemetry: Telemetry,
is_complete: bool,
}
impl SortingController {
fn new(algorithms: Vec<Box<dyn Sorter>>, fairness_model: Box<dyn FairnessModel>) -> Self {
Self {
algorithms,
fairness_model,
state: ControllerState::Running,
step_count: 0,
pause_snapshots: Vec::new(),
}
}
fn pause(&mut self) {
self.state = ControllerState::Paused;
let snapshot = self.take_snapshot();
self.pause_snapshots.push(snapshot);
}
fn resume(&mut self) {
self.state = ControllerState::Running;
}
fn is_paused(&self) -> bool {
self.state == ControllerState::Paused
}
fn is_running(&self) -> bool {
self.state == ControllerState::Running
}
fn step(&mut self) -> bool {
if self.is_paused() {
return false; }
let active_count = self.algorithms.iter().filter(|alg| !alg.is_complete()).count();
if active_count == 0 {
return false; }
let budgets = self.fairness_model.allocate_budget(&self.algorithms);
for (i, algorithm) in self.algorithms.iter_mut().enumerate() {
if budgets[i] > 0 {
algorithm.step(budgets[i]);
}
}
self.step_count += 1;
true
}
fn take_snapshot(&self) -> Vec<AlgorithmSnapshot> {
self.algorithms
.iter()
.map(|alg| AlgorithmSnapshot {
name: alg.name().to_string(),
array_state: alg.get_array().to_vec(),
telemetry: alg.get_telemetry(),
is_complete: alg.is_complete(),
})
.collect()
}
fn get_step_count(&self) -> usize {
self.step_count
}
fn all_complete(&self) -> bool {
self.algorithms.iter().all(|alg| alg.is_complete())
}
fn get_pause_snapshots(&self) -> &Vec<Vec<AlgorithmSnapshot>> {
&self.pause_snapshots
}
fn reset(&mut self, array: Vec<i32>) {
for algorithm in &mut self.algorithms {
algorithm.reset(array.clone());
}
self.step_count = 0;
self.pause_snapshots.clear();
self.state = ControllerState::Running;
}
}
fn create_test_algorithms() -> Vec<Box<dyn Sorter>> {
vec![
Box::new(BubbleSort::new()),
Box::new(InsertionSort::new()),
Box::new(SelectionSort::new()),
Box::new(QuickSort::new()),
Box::new(HeapSort::new()),
Box::new(MergeSort::new()),
]
}
fn verify_snapshots_identical(snapshot1: &[AlgorithmSnapshot], snapshot2: &[AlgorithmSnapshot]) {
assert_eq!(snapshot1.len(), snapshot2.len());
for (s1, s2) in snapshot1.iter().zip(snapshot2.iter()) {
assert_eq!(s1.name, s2.name);
assert_eq!(s1.array_state, s2.array_state);
assert_eq!(s1.is_complete, s2.is_complete);
assert_eq!(s1.telemetry.total_comparisons, s2.telemetry.total_comparisons);
assert_eq!(s1.telemetry.total_moves, s2.telemetry.total_moves);
}
}
#[cfg(test)]
mod pause_resume_tests {
use super::*;
#[test]
fn test_basic_pause_resume() {
let generator = ArrayGenerator::new(12345);
let array = generator.generate(30, &Distribution::Shuffled);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(16));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
assert!(controller.is_running());
assert!(!controller.is_paused());
for _ in 0..5 {
assert!(controller.step());
}
let steps_before_pause = controller.get_step_count();
assert!(steps_before_pause > 0);
controller.pause();
assert!(controller.is_paused());
assert!(!controller.is_running());
controller.resume();
assert!(controller.is_running());
assert!(!controller.is_paused());
assert!(controller.step());
assert!(controller.get_step_count() > steps_before_pause);
}
#[test]
fn test_no_progress_while_paused() {
let generator = ArrayGenerator::new(54321);
let array = generator.generate(25, &Distribution::Shuffled);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(16));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
for _ in 0..3 {
controller.step();
}
let snapshot_before_pause = controller.take_snapshot();
let steps_before_pause = controller.get_step_count();
controller.pause();
for _ in 0..10 {
let made_progress = controller.step();
assert!(!made_progress, "Should not make progress while paused");
}
let snapshot_after_pause_attempts = controller.take_snapshot();
assert_eq!(controller.get_step_count(), steps_before_pause);
verify_snapshots_identical(&snapshot_before_pause, &snapshot_after_pause_attempts);
controller.resume();
assert!(controller.step());
assert!(controller.get_step_count() > steps_before_pause);
}
#[test]
fn test_state_preserved_during_pause_resume() {
let generator = ArrayGenerator::new(98765);
let array = generator.generate(40, &Distribution::NearlySorted);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(12));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
for _ in 0..7 {
controller.step();
}
let snapshot_before_pause = controller.take_snapshot();
controller.pause();
let snapshot_while_paused = controller.take_snapshot();
verify_snapshots_identical(&snapshot_before_pause, &snapshot_while_paused);
controller.resume();
let snapshot_after_resume = controller.take_snapshot();
verify_snapshots_identical(&snapshot_before_pause, &snapshot_after_resume);
controller.step();
let snapshot_after_progress = controller.take_snapshot();
let mut found_progress = false;
for (before, after) in snapshot_before_pause.iter().zip(snapshot_after_progress.iter()) {
if before.telemetry.total_comparisons != after.telemetry.total_comparisons ||
before.telemetry.total_moves != after.telemetry.total_moves ||
before.array_state != after.array_state {
found_progress = true;
break;
}
}
assert!(found_progress, "Should make progress after resuming");
}
#[test]
fn test_multiple_pause_resume_cycles() {
let generator = ArrayGenerator::new(11111);
let array = generator.generate(35, &Distribution::FewUnique);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(8));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
let mut expected_steps = 0;
let mut snapshots_during_execution = Vec::new();
for cycle in 0..5 {
let steps_this_cycle = 3;
for _ in 0..steps_this_cycle {
if controller.all_complete() {
break;
}
controller.step();
}
expected_steps += steps_this_cycle;
let snapshot_before_pause = controller.take_snapshot();
snapshots_during_execution.push(snapshot_before_pause.clone());
controller.pause();
assert!(controller.is_paused());
for _ in 0..5 {
assert!(!controller.step());
}
let snapshot_during_pause = controller.take_snapshot();
verify_snapshots_identical(&snapshot_before_pause, &snapshot_during_pause);
controller.resume();
assert!(controller.is_running());
let snapshot_after_resume = controller.take_snapshot();
verify_snapshots_identical(&snapshot_before_pause, &snapshot_after_resume);
if controller.all_complete() {
break;
}
}
let pause_snapshots = controller.get_pause_snapshots();
assert!(!pause_snapshots.is_empty(), "Should have recorded pause snapshots");
for (manual, recorded) in snapshots_during_execution.iter().zip(pause_snapshots.iter()) {
verify_snapshots_identical(manual, recorded);
}
}
#[test]
fn test_pause_resume_with_different_algorithms_completing() {
let generator = ArrayGenerator::new(22222);
let array = generator.generate(20, &Distribution::Shuffled); let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(20));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
let mut pause_cycle = 0;
while !controller.all_complete() && pause_cycle < 10 {
for _ in 0..2 {
if !controller.step() {
break;
}
}
let snapshot_before_pause = controller.take_snapshot();
let active_before = snapshot_before_pause.iter().filter(|s| !s.is_complete).count();
controller.pause();
let snapshot_during_pause = controller.take_snapshot();
let active_during = snapshot_during_pause.iter().filter(|s| !s.is_complete).count();
assert_eq!(active_before, active_during);
verify_snapshots_identical(&snapshot_before_pause, &snapshot_during_pause);
controller.resume();
let snapshot_after_resume = controller.take_snapshot();
let active_after = snapshot_after_resume.iter().filter(|s| !s.is_complete).count();
assert_eq!(active_before, active_after);
verify_snapshots_identical(&snapshot_before_pause, &snapshot_after_resume);
pause_cycle += 1;
}
assert!(pause_cycle > 0, "Should have performed at least one pause/resume cycle");
let final_snapshot = controller.take_snapshot();
let completed_count = final_snapshot.iter().filter(|s| s.is_complete).count();
assert!(completed_count > 0, "At least some algorithms should have completed");
}
#[test]
fn test_pause_resume_fairness_consistency() {
let generator = ArrayGenerator::new(33333);
let array = generator.generate(30, &Distribution::Reversed);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(10));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array.clone());
let algorithms_ref = create_test_algorithms();
let fairness_model_ref = Box::new(ComparisonBudget::new(10));
let mut controller_ref = SortingController::new(algorithms_ref, fairness_model_ref);
controller_ref.reset(array);
let max_steps = 50;
let mut steps_executed = 0;
for step in 0..max_steps {
if controller.all_complete() || controller_ref.all_complete() {
break;
}
if step % 3 == 2 {
controller.pause();
assert!(!controller.step());
controller.resume();
}
let test_progressed = controller.step();
let ref_progressed = controller_ref.step();
assert_eq!(test_progressed, ref_progressed);
if test_progressed {
steps_executed += 1;
}
}
let test_snapshot = controller.take_snapshot();
let ref_snapshot = controller_ref.take_snapshot();
assert_eq!(test_snapshot.len(), ref_snapshot.len());
for (test_alg, ref_alg) in test_snapshot.iter().zip(ref_snapshot.iter()) {
assert_eq!(test_alg.name, ref_alg.name);
let test_total_ops = test_alg.telemetry.total_comparisons + test_alg.telemetry.total_moves;
let ref_total_ops = ref_alg.telemetry.total_comparisons + ref_alg.telemetry.total_moves;
let ops_diff = if test_total_ops > ref_total_ops {
test_total_ops - ref_total_ops
} else {
ref_total_ops - test_total_ops
};
assert!(
ops_diff <= 30, "Algorithm {} operations differ too much: test={}, ref={}, diff={}",
test_alg.name, test_total_ops, ref_total_ops, ops_diff
);
}
}
#[test]
fn test_pause_resume_edge_cases() {
let generator = ArrayGenerator::new(44444);
let array = generator.generate(15, &Distribution::Shuffled);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(16));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
controller.pause();
assert!(controller.is_paused());
controller.pause(); assert!(controller.is_paused());
controller.resume();
assert!(controller.is_running());
controller.resume(); assert!(controller.is_running());
controller.reset(generator.generate(15, &Distribution::Shuffled));
let initial_snapshot = controller.take_snapshot();
controller.pause();
let paused_snapshot = controller.take_snapshot();
verify_snapshots_identical(&initial_snapshot, &paused_snapshot);
controller.resume();
let resumed_snapshot = controller.take_snapshot();
verify_snapshots_identical(&initial_snapshot, &resumed_snapshot);
while !controller.all_complete() {
controller.step();
}
let completed_snapshot = controller.take_snapshot();
controller.pause();
let paused_complete_snapshot = controller.take_snapshot();
verify_snapshots_identical(&completed_snapshot, &paused_complete_snapshot);
controller.resume();
let resumed_complete_snapshot = controller.take_snapshot();
verify_snapshots_identical(&completed_snapshot, &resumed_complete_snapshot);
assert!(!controller.step());
}
#[test]
fn test_pause_resume_with_zero_budget() {
let generator = ArrayGenerator::new(55555);
let array = generator.generate(25, &Distribution::Shuffled);
let algorithms = create_test_algorithms();
let fairness_model = Box::new(ComparisonBudget::new(0));
let mut controller = SortingController::new(algorithms, fairness_model);
controller.reset(array);
let initial_snapshot = controller.take_snapshot();
for _ in 0..5 {
assert!(!controller.step());
}
let after_steps_snapshot = controller.take_snapshot();
verify_snapshots_identical(&initial_snapshot, &after_steps_snapshot);
controller.pause();
assert!(controller.is_paused());
controller.resume();
assert!(controller.is_running());
assert!(!controller.step());
let final_snapshot = controller.take_snapshot();
verify_snapshots_identical(&initial_snapshot, &final_snapshot);
}
}