use super::*;
use crate::{
assigner_coretime::{mock_helpers::GenesisConfigBuilder, pezpallet::Error, Schedule},
initializer::SessionChangeNotification,
mock::{
new_test_ext, CoretimeAssigner, OnDemand, Paras, ParasShared, RuntimeOrigin, Scheduler,
System, Test,
},
paras::{ParaGenesisArgs, ParaKind},
scheduler::common::Assignment,
};
use pezframe_support::{assert_noop, assert_ok, pezpallet_prelude::*};
use pezkuwi_primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode};
use pezpallet_broker::TaskId;
fn schedule_blank_para(id: ParaId, parakind: ParaKind) {
let validation_code: ValidationCode = vec![1, 2, 3].into();
assert_ok!(Paras::schedule_para_initialize(
id,
ParaGenesisArgs {
genesis_head: Vec::new().into(),
validation_code: validation_code.clone(),
para_kind: parakind,
}
));
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
}
fn run_to_block(
to: BlockNumber,
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
) {
while System::block_number() < to {
let b = System::block_number();
Scheduler::initializer_finalize();
Paras::initializer_finalize(b);
if let Some(notification) = new_session(b + 1) {
let mut notification_with_session_index = notification;
if notification_with_session_index.session_index == SessionIndex::default() {
notification_with_session_index.session_index = ParasShared::scheduled_session();
}
Paras::initializer_on_new_session(¬ification_with_session_index);
Scheduler::initializer_on_new_session(¬ification_with_session_index);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Paras::initializer_initialize(b + 1);
Scheduler::initializer_initialize(b + 1);
OnDemand::on_initialize(b + 1);
Scheduler::advance_claim_queue(&Default::default());
}
}
fn default_test_assignments() -> Vec<(CoreAssignment, PartsOf57600)> {
vec![(CoreAssignment::Idle, PartsOf57600::FULL)]
}
fn default_test_schedule() -> Schedule<BlockNumberFor<Test>> {
Schedule { assignments: default_test_assignments(), end_hint: None, next_schedule: None }
}
#[test]
fn assign_core_works_with_no_prior_schedule() {
let core_idx = CoreIndex(0);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
default_test_assignments(),
None,
));
assert_eq!(
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(11u32), core_idx)),
Some(default_test_schedule())
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.queue
.as_ref()
.and_then(|q| Some(q.first)),
Some(BlockNumberFor::<Test>::from(11u32))
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)),
Some(BlockNumberFor::<Test>::from(11u32))
);
});
}
#[test]
fn end_hint_is_properly_honored() {
let core_idx = CoreIndex(0);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
vec![(CoreAssignment::Task(1), PartsOf57600::FULL)],
Some(15u32),
));
assert!(
CoretimeAssigner::pop_assignment_for_core(core_idx).is_none(),
"No assignment yet in effect"
);
run_to_block(11, |_| None);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(1.into())),
"Assignment should now be present"
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(1.into())),
"Nothing changed, assignment should still be present"
);
run_to_block(15, |_| None);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
None,
"Assignment should now be gone"
);
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
vec![(CoreAssignment::Task(1), PartsOf57600::FULL)],
Some(15u32),
));
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
None,
"Assignment should now be gone"
);
});
}
#[test]
fn assign_core_works_with_prior_schedule() {
let core_idx = CoreIndex(0);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
let default_with_next_schedule =
Schedule { next_schedule: Some(15u32), ..default_test_schedule() };
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
default_test_assignments(),
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(15u32),
default_test_assignments(),
None,
));
assert_eq!(
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(11u32), core_idx)),
Some(default_with_next_schedule)
);
assert_eq!(
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(15u32), core_idx)),
Some(default_test_schedule())
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.queue
.as_ref()
.and_then(|q| Some(q.first)),
Some(BlockNumberFor::<Test>::from(11u32))
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)),
Some(BlockNumberFor::<Test>::from(15u32))
);
});
}
#[test]
fn assign_core_enforces_higher_or_equal_block_number() {
let core_idx = CoreIndex(0);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(12u32),
default_test_assignments(),
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(15u32),
vec![(CoreAssignment::Idle, PartsOf57600(28800))],
None,
));
assert_noop!(
CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
default_test_assignments(),
None,
),
Error::<Test>::DisallowedInsert
);
assert_noop!(
CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(13u32),
default_test_assignments(),
None,
),
Error::<Test>::DisallowedInsert
);
assert_eq!(
CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(15u32),
vec![(CoreAssignment::Pool, PartsOf57600(28800))],
None,
),
Ok(())
);
});
}
#[test]
fn assign_core_enforces_well_formed_schedule() {
let core_idx = CoreIndex(0);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
let empty_assignments: Vec<(CoreAssignment, PartsOf57600)> = vec![];
assert_noop!(
CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
empty_assignments,
None,
),
Error::<Test>::AssignmentsEmpty
);
});
}
#[test]
fn next_schedule_always_points_to_next_work_plan_item() {
let core_idx = CoreIndex(0);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
let start_1 = 15u32;
let start_2 = 20u32;
let start_3 = 25u32;
let start_4 = 30u32;
let start_5 = 35u32;
let expected_schedule_3 =
Schedule { next_schedule: Some(start_4), ..default_test_schedule() };
let expected_schedule_4 =
Schedule { next_schedule: Some(start_5), ..default_test_schedule() };
let expected_schedule_5 = Schedule {
next_schedule: None,
end_hint: None,
assignments: vec![
(CoreAssignment::Pool, PartsOf57600(28800)),
(CoreAssignment::Idle, PartsOf57600(28800)),
],
};
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(start_1),
default_test_assignments(),
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(start_2),
default_test_assignments(),
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(start_3),
default_test_assignments(),
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(start_4),
default_test_assignments(),
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(start_5),
vec![(CoreAssignment::Pool, PartsOf57600(28800))],
None,
));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(start_5),
vec![(CoreAssignment::Idle, PartsOf57600(28800))],
None,
));
run_to_block(start_1, |n| if n == start_1 { Some(Default::default()) } else { None });
CoretimeAssigner::pop_assignment_for_core(core_idx);
run_to_block(start_2, |n| if n == start_2 { Some(Default::default()) } else { None });
CoretimeAssigner::pop_assignment_for_core(core_idx);
assert_eq!(
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(start_3), core_idx)),
Some(expected_schedule_3)
);
assert_eq!(
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(start_4), core_idx)),
Some(expected_schedule_4)
);
assert_eq!(
CoreSchedules::<Test>::get((BlockNumberFor::<Test>::from(start_5), core_idx)),
Some(expected_schedule_5)
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.queue
.as_ref()
.and_then(|q| Some(q.first)),
Some(start_3)
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx).queue.as_ref().and_then(|q| Some(q.last)),
Some(start_5)
);
});
}
#[test]
fn ensure_workload_works() {
let core_idx = CoreIndex(0);
let test_assignment_state =
AssignmentState { ratio: PartsOf57600::FULL, remaining: PartsOf57600::FULL };
let empty_descriptor: CoreDescriptor<BlockNumberFor<Test>> =
CoreDescriptor { queue: None, current_work: None };
let assignments_queued_descriptor = CoreDescriptor {
queue: Some(QueueDescriptor {
first: BlockNumberFor::<Test>::from(11u32),
last: BlockNumberFor::<Test>::from(11u32),
}),
current_work: None,
};
let assignments_active_descriptor = CoreDescriptor {
queue: None,
current_work: Some(WorkState {
assignments: vec![(CoreAssignment::Pool, test_assignment_state)],
end_hint: Some(BlockNumberFor::<Test>::from(15u32)),
pos: 0,
step: PartsOf57600::FULL,
}),
};
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
let mut core_descriptor: CoreDescriptor<BlockNumberFor<Test>> = empty_descriptor.clone();
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
CoretimeAssigner::ensure_workload(10u32, core_idx, &mut core_descriptor);
assert_eq!(core_descriptor, empty_descriptor);
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
vec![(CoreAssignment::Pool, PartsOf57600::FULL)],
Some(BlockNumberFor::<Test>::from(15u32)),
));
core_descriptor = CoreDescriptors::<Test>::get(core_idx);
CoretimeAssigner::ensure_workload(10u32, core_idx, &mut core_descriptor);
assert_eq!(core_descriptor, assignments_queued_descriptor);
CoretimeAssigner::ensure_workload(11u32, core_idx, &mut core_descriptor);
assert_eq!(core_descriptor, assignments_active_descriptor);
CoretimeAssigner::ensure_workload(15u32, core_idx, &mut core_descriptor);
assert_eq!(core_descriptor, empty_descriptor);
});
}
#[test]
fn pop_assignment_for_core_works() {
let para_id = ParaId::from(1);
let core_idx = CoreIndex(0);
let alice = 1u64;
let amt = 10_000_000u128;
let assignments_pool = vec![(CoreAssignment::Pool, PartsOf57600::FULL)];
let assignments_task = vec![(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL)];
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
schedule_blank_para(para_id, ParaKind::Parathread);
on_demand::Credits::<Test>::insert(&alice, amt);
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
assert_ok!(OnDemand::place_order_with_credits(RuntimeOrigin::signed(alice), amt, para_id));
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
default_test_assignments(), None,
));
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
assert_eq!(CoretimeAssigner::pop_assignment_for_core(core_idx), None);
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(21u32),
assignments_pool,
None,
));
run_to_block(21, |n| if n == 21 { Some(Default::default()) } else { None });
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Pool { para_id, core_index: 0.into() })
);
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(31u32),
assignments_task,
None,
));
run_to_block(31, |n| if n == 31 { Some(Default::default()) } else { None });
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(para_id))
);
});
}
#[test]
fn assignment_proportions_in_core_state_work() {
let core_idx = CoreIndex(0);
let task_1 = TaskId::from(1u32);
let task_2 = TaskId::from(2u32);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
let test_assignments = vec![
(CoreAssignment::Task(task_1), PartsOf57600::FULL / 3 * 2),
(CoreAssignment::Task(task_2), PartsOf57600::FULL / 3),
];
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
test_assignments,
None,
));
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
{
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.current_work
.as_ref()
.and_then(|w| Some(w.pos)),
Some(0u16)
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.current_work
.as_ref()
.and_then(|w| Some(w.assignments[0].1.remaining)),
Some(PartsOf57600::FULL / 3)
);
}
{
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.current_work
.as_ref()
.and_then(|w| Some(w.pos)),
Some(1u16)
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.current_work
.as_ref()
.and_then(|w| Some(w.assignments[0].1.remaining)),
Some(PartsOf57600::FULL / 3 * 2)
);
}
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_2.into()))
);
});
}
#[test]
fn equal_assignments_served_equally() {
let core_idx = CoreIndex(0);
let task_1 = TaskId::from(1u32);
let task_2 = TaskId::from(2u32);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
let test_assignments = vec![
(CoreAssignment::Task(task_1), PartsOf57600::FULL / 2),
(CoreAssignment::Task(task_2), PartsOf57600::FULL / 2),
];
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
test_assignments,
None,
));
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_2.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_2.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_2.into()))
);
});
}
#[test]
fn assignment_proportions_indivisible_by_step_work() {
let core_idx = CoreIndex(0);
let task_1 = TaskId::from(1u32);
let ratio_1 = PartsOf57600::FULL / 5 * 3;
let ratio_2 = PartsOf57600::FULL / 5 * 2;
let task_2 = TaskId::from(2u32);
new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
let test_assignments =
vec![(CoreAssignment::Task(task_1), ratio_1), (CoreAssignment::Task(task_2), ratio_2)];
assert_ok!(CoretimeAssigner::assign_core(
core_idx,
BlockNumberFor::<Test>::from(11u32),
test_assignments,
None,
));
run_to_block(11, |n| if n == 11 { Some(Default::default()) } else { None });
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_2.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_1.into()))
);
assert_eq!(
CoretimeAssigner::pop_assignment_for_core(core_idx),
Some(Assignment::Bulk(task_2.into()))
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.current_work
.as_ref()
.and_then(|w| Some(w.assignments[0].1.remaining)),
Some(ratio_1)
);
assert_eq!(
CoreDescriptors::<Test>::get(core_idx)
.current_work
.as_ref()
.and_then(|w| Some(w.assignments[1].1.remaining)),
Some(ratio_2)
);
});
}
#[cfg(test)]
impl std::ops::Div<u16> for PartsOf57600 {
type Output = Self;
fn div(self, rhs: u16) -> Self::Output {
if rhs == 0 {
panic!("Cannot divide by zero!");
}
Self(self.0 / rhs)
}
}
#[cfg(test)]
impl std::ops::Mul<u16> for PartsOf57600 {
type Output = Self;
fn mul(self, rhs: u16) -> Self {
Self(self.0 * rhs)
}
}
#[test]
fn parts_of_57600_ops() {
assert!(PartsOf57600::new_saturating(57601).is_full());
assert!(PartsOf57600::FULL.saturating_add(PartsOf57600(1)).is_full());
assert_eq!(PartsOf57600::ZERO.saturating_sub(PartsOf57600(1)), PartsOf57600::ZERO);
assert_eq!(PartsOf57600::FULL.checked_add(PartsOf57600(0)), Some(PartsOf57600::FULL));
assert_eq!(PartsOf57600::FULL.checked_add(PartsOf57600(1)), None);
}