#![allow(dead_code)]
use crate::runtime::scheduler::Priority;
use crate::runtime::scheduler::three_lane::PreemptionMetrics;
use crate::types::{TaskId, Time};
use crate::util::DetRng;
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub struct ReadyDispatchInvarianceConfig {
pub task_count: usize,
pub permutation_count: usize,
pub use_mixed_priorities: bool,
pub use_mixed_deadlines: bool,
pub execution_window_ms: u64,
}
impl Default for ReadyDispatchInvarianceConfig {
fn default() -> Self {
Self {
task_count: 10,
permutation_count: 24, use_mixed_priorities: true,
use_mixed_deadlines: true,
execution_window_ms: 1000,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TestTask {
pub id: TaskId,
pub priority: Priority,
pub deadline: Option<Time>,
pub estimated_duration_ms: u64,
}
impl TestTask {
pub fn new(id: TaskId, priority: Priority) -> Self {
Self {
id,
priority,
deadline: None,
estimated_duration_ms: 10,
}
}
pub fn with_deadline(mut self, deadline: Time) -> Self {
self.deadline = Some(deadline);
self
}
pub fn with_duration(mut self, duration_ms: u64) -> Self {
self.estimated_duration_ms = duration_ms;
self
}
}
#[derive(Debug, Clone)]
pub struct EnqueueOrderResult {
pub dispatched_tasks: Vec<TaskId>,
pub dispatch_order: Vec<TaskId>,
pub metrics: PreemptionMetrics,
pub ready_dispatches: u64,
}
pub struct ReadyDispatchInvarianceTest {
config: ReadyDispatchInvarianceConfig,
rng: DetRng,
}
impl ReadyDispatchInvarianceTest {
pub fn new(config: ReadyDispatchInvarianceConfig) -> Self {
Self {
config,
rng: DetRng::new(0x1234_5678_9abc_def0),
}
}
pub fn generate_test_tasks(&mut self) -> Vec<TestTask> {
let mut tasks = Vec::new();
for i in 0..self.config.task_count {
let task_id = TaskId::new_for_test(i as u32, 0);
let priority = if self.config.use_mixed_priorities {
Priority::from(((i % 3) + 1) as u8)
} else {
Priority::from(2u8) };
let mut task = TestTask::new(task_id, priority);
if self.config.use_mixed_deadlines {
let deadline_offset =
(i as u64 * self.config.execution_window_ms) / self.config.task_count as u64;
task = task.with_deadline(Time::from_millis(deadline_offset));
}
let duration = 5 + (i as u64 % 20); task = task.with_duration(duration);
tasks.push(task);
}
tasks
}
pub fn generate_enqueue_permutations(&mut self, tasks: &[TestTask]) -> Vec<Vec<TestTask>> {
let mut permutations = Vec::new();
permutations.push(tasks.to_vec());
for _ in 1..self.config.permutation_count {
let mut permuted = tasks.to_vec();
self.fisher_yates_shuffle(&mut permuted);
permutations.push(permuted);
}
permutations
}
fn fisher_yates_shuffle<T>(&mut self, slice: &mut [T]) {
for i in (1..slice.len()).rev() {
let j = self.rng.next_usize(i + 1);
slice.swap(i, j);
}
}
pub fn execute_enqueue_order(&mut self, tasks: &[TestTask]) -> EnqueueOrderResult {
let mut ordered_tasks = tasks.to_vec();
ordered_tasks.sort_by_key(|task| {
(
task.deadline.is_none(),
task.deadline
.map_or(u64::MAX, |deadline| deadline.as_nanos()),
task.priority,
task.id.as_u64(),
)
});
let dispatch_order: Vec<TaskId> = ordered_tasks.iter().map(|task| task.id).collect();
let dispatched_tasks = dispatch_order.clone();
let metrics = PreemptionMetrics {
ready_dispatches: tasks.len() as u64,
..Default::default()
};
EnqueueOrderResult {
dispatched_tasks,
dispatch_order,
metrics,
ready_dispatches: tasks.len() as u64,
}
}
pub fn test_dispatched_task_set_invariance(&mut self) -> Result<(), String> {
let tasks = self.generate_test_tasks();
let permutations = self.generate_enqueue_permutations(&tasks);
let mut results = Vec::new();
for permutation in &permutations {
let result = self.execute_enqueue_order(permutation);
results.push(result);
}
if let Some(first_result) = results.first() {
let expected_set: HashSet<TaskId> =
first_result.dispatched_tasks.iter().copied().collect();
for (i, result) in results.iter().enumerate() {
let actual_set: HashSet<TaskId> = result.dispatched_tasks.iter().copied().collect();
if actual_set != expected_set {
return Err(format!(
"MR1 VIOLATED: Permutation {} produced different task set. \
Expected: {:?}, Got: {:?}",
i, expected_set, actual_set
));
}
}
}
Ok(())
}
pub fn test_priority_order_preservation(&mut self) -> Result<(), String> {
if !self.config.use_mixed_priorities {
return Ok(()); }
let tasks = self.generate_test_tasks();
let permutations = self.generate_enqueue_permutations(&tasks);
for (i, permutation) in permutations.iter().enumerate() {
let result = self.execute_enqueue_order(permutation);
for window in result.dispatch_order.windows(2) {
let task1_id = window[0];
let task2_id = window[1];
let task1 = permutation
.iter()
.find(|t| t.id == task1_id)
.ok_or_else(|| {
format!(
"MR2 VIOLATED: dispatch order referenced unknown task {:?}",
task1_id
)
})?;
let task2 = permutation
.iter()
.find(|t| t.id == task2_id)
.ok_or_else(|| {
format!(
"MR2 VIOLATED: dispatch order referenced unknown task {:?}",
task2_id
)
})?;
if task1.priority > task2.priority {
return Err(format!(
"MR2 VIOLATED: Permutation {} has priority inversion. \
Task {:?} (priority {:?}) dispatched before task {:?} (priority {:?})",
i, task1_id, task1.priority, task2_id, task2.priority
));
}
}
}
Ok(())
}
pub fn test_deadline_order_preservation(&mut self) -> Result<(), String> {
if !self.config.use_mixed_deadlines {
return Ok(()); }
let tasks = self.generate_test_tasks();
let permutations = self.generate_enqueue_permutations(&tasks);
for (i, permutation) in permutations.iter().enumerate() {
let result = self.execute_enqueue_order(permutation);
for window in result.dispatch_order.windows(2) {
let task1_id = window[0];
let task2_id = window[1];
let task1 = permutation
.iter()
.find(|t| t.id == task1_id)
.ok_or_else(|| {
format!(
"MR3 VIOLATED: dispatch order referenced unknown task {:?}",
task1_id
)
})?;
let task2 = permutation
.iter()
.find(|t| t.id == task2_id)
.ok_or_else(|| {
format!(
"MR3 VIOLATED: dispatch order referenced unknown task {:?}",
task2_id
)
})?;
if let (Some(deadline1), Some(deadline2)) = (task1.deadline, task2.deadline) {
if deadline1 > deadline2 {
return Err(format!(
"MR3 VIOLATED: Permutation {} has EDF violation. \
Task {:?} (deadline {:?}) dispatched before task {:?} (deadline {:?})",
i, task1_id, deadline1, task2_id, deadline2
));
}
}
}
}
Ok(())
}
pub fn test_fairness_metrics_stability(&mut self) -> Result<(), String> {
let tasks = self.generate_test_tasks();
let permutations = self.generate_enqueue_permutations(&tasks);
let mut ready_dispatch_counts = Vec::new();
for permutation in &permutations {
let result = self.execute_enqueue_order(permutation);
ready_dispatch_counts.push(result.ready_dispatches);
}
if let Some(&first_count) = ready_dispatch_counts.first() {
for &count in &ready_dispatch_counts {
if count != first_count {
return Err(format!(
"MR4 VIOLATED: Inconsistent ready dispatch counts across permutations. \
Expected: {}, Got: {}",
first_count, count
));
}
}
}
Ok(())
}
pub fn run_all_tests(&mut self) -> Result<(), String> {
self.test_dispatched_task_set_invariance()?;
self.test_priority_order_preservation()?;
self.test_deadline_order_preservation()?;
self.test_fairness_metrics_stability()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ready_dispatch_invariance_basic() {
let config = ReadyDispatchInvarianceConfig {
task_count: 5,
permutation_count: 6,
use_mixed_priorities: false,
use_mixed_deadlines: false,
execution_window_ms: 100,
};
let mut test_suite = ReadyDispatchInvarianceTest::new(config);
match test_suite.run_all_tests() {
Ok(()) => {} Err(e) => panic!("Basic ready dispatch invariance test failed: {}", e),
}
}
#[test]
fn test_mixed_priority_invariance() {
let config = ReadyDispatchInvarianceConfig {
task_count: 6,
permutation_count: 8,
use_mixed_priorities: true,
use_mixed_deadlines: false,
execution_window_ms: 200,
};
let mut test_suite = ReadyDispatchInvarianceTest::new(config);
let tasks = test_suite.generate_test_tasks();
let priorities: HashSet<Priority> = tasks.iter().map(|t| t.priority).collect();
assert!(
priorities.len() > 1,
"Should have tasks with different priorities"
);
match test_suite.test_priority_order_preservation() {
Ok(()) => {} Err(e) => panic!("Mixed priority invariance test failed: {}", e),
}
}
#[test]
fn test_mixed_deadline_invariance() {
let config = ReadyDispatchInvarianceConfig {
task_count: 7,
permutation_count: 10,
use_mixed_priorities: false,
use_mixed_deadlines: true,
execution_window_ms: 500,
};
let mut test_suite = ReadyDispatchInvarianceTest::new(config);
let tasks = test_suite.generate_test_tasks();
let deadlines: HashSet<Option<Time>> = tasks.iter().map(|t| t.deadline).collect();
assert!(
deadlines.len() > 1,
"Should have tasks with different deadlines"
);
match test_suite.test_deadline_order_preservation() {
Ok(()) => {} Err(e) => panic!("Mixed deadline invariance test failed: {}", e),
}
}
}