Skip to content

Commit 67311aa

Browse files
test: [DSM-108] Clean up a couple of scheduler tests (#9329)
These just happened to be the first couple of significant tests for the first two meaningful behaviors encountered while going through execute_round(). And they weren't particularly easy to follow. So I had a go at them. Not much to see.
1 parent 4486ff5 commit 67311aa

File tree

2 files changed

+107
-248
lines changed

2 files changed

+107
-248
lines changed

rs/execution_environment/src/scheduler/tests.rs

Lines changed: 75 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@ use ic_management_canister_types_private::{
1010
CanisterStatusType, EcdsaKeyId, EmptyBlob, Method, Payload as _, SchnorrKeyId,
1111
};
1212
use ic_registry_subnet_type::SubnetType;
13-
use ic_replicated_state::testing::SystemStateTesting;
1413
use ic_replicated_state::{CanisterStatus, metadata_state::testing::NetworkTopologyTesting};
1514
use ic_state_machine_tests::{PayloadBuilder, StateMachineBuilder};
1615
use ic_test_utilities_metrics::{fetch_counter, fetch_histogram_vec_buckets};
1716
use ic_test_utilities_state::get_running_canister;
1817
use ic_test_utilities_types::messages::RequestBuilder;
18+
use ic_types::Cycles;
1919
use ic_types::messages::{CallbackId, Payload, RejectContext};
2020
use ic_types::time::{UNIX_EPOCH, expiry_time_from_now};
21-
use ic_types::{ComputeAllocation, Cycles};
2221
use ic_types_test_utils::ids::{canister_test_id, message_test_id, subnet_test_id, user_test_id};
2322
use ic00::{CanisterHttpRequestArgs, HttpMethod};
2423
use proptest::prelude::*;
@@ -443,182 +442,101 @@ fn can_timeout_stop_canister_requests() {
443442
}
444443

445444
#[test]
446-
fn test_is_next_method_added_to_task_queue() {
447-
let mut test = SchedulerTestBuilder::new().build();
448-
449-
let canister = test.create_canister_with(
450-
Cycles::new(1_000_000_000_000),
451-
ComputeAllocation::zero(),
452-
MemoryAllocation::default(),
453-
None,
454-
None,
455-
None,
456-
);
457-
let has_heartbeat = false;
458-
let has_active_timer = false;
459-
460-
let mut heartbeat_and_timer_canisters = BTreeSet::new();
461-
assert!(
462-
!test
463-
.canister_state(canister)
464-
.system_state
465-
.queues()
466-
.has_input()
467-
);
445+
fn test_maybe_add_heartbeat_or_global_timer_tasks() {
446+
use ExecutionTask as Task;
447+
use NextScheduledMethod::*;
448+
449+
/// Calls `maybe_add_heartbeat_or_global_timer_tasks` in the given state and
450+
/// returns the tasks that were enqueued and the next scheduled method.
451+
fn call(
452+
has_input: bool,
453+
has_heartbeat: bool,
454+
has_active_timer: bool,
455+
next_scheduled_method: NextScheduledMethod,
456+
) -> (Vec<ExecutionTask>, NextScheduledMethod) {
457+
let mut test = SchedulerTestBuilder::new().build();
458+
let canister_id = test.create_canister();
459+
let canister = test.canister_state_mut(canister_id);
460+
461+
if has_input {
462+
canister.push_ingress(Ingress {
463+
source: user_test_id(77),
464+
receiver: canister_id,
465+
effective_canister_id: None,
466+
method_name: String::from("test"),
467+
method_payload: vec![1_u8],
468+
message_id: message_test_id(555),
469+
expiry_time: expiry_time_from_now(),
470+
});
471+
}
472+
while canister.get_next_scheduled_method() != next_scheduled_method {
473+
canister.inc_next_scheduled_method();
474+
}
468475

469-
for _ in 0..3 {
470-
// The timer did not reach the deadline and the canister does not have
471-
// input, hence no method will be chosen.
472-
assert!(!is_next_method_chosen(
473-
test.canister_state_mut(canister),
476+
let mut heartbeat_and_timer_canisters = BTreeSet::new();
477+
maybe_add_heartbeat_or_global_timer_tasks(
478+
canister,
474479
has_heartbeat,
475480
has_active_timer,
476481
&mut heartbeat_and_timer_canisters,
477-
));
478-
assert_eq!(heartbeat_and_timer_canisters, BTreeSet::new());
479-
test.canister_state_mut(canister)
480-
.inc_next_scheduled_method();
481-
}
482-
483-
// Make canister able to schedule both heartbeat and global timer.
484-
let has_heartbeat = true;
485-
let has_active_timer = true;
486-
487-
// Set input.
488-
test.canister_state_mut(canister)
489-
.system_state
490-
.queues_mut()
491-
.push_ingress(Ingress {
492-
source: user_test_id(77),
493-
receiver: canister,
494-
effective_canister_id: None,
495-
method_name: String::from("test"),
496-
method_payload: vec![1_u8],
497-
message_id: message_test_id(555),
498-
expiry_time: expiry_time_from_now(),
499-
});
500-
501-
assert!(
502-
test.canister_state(canister)
503-
.system_state
504-
.queues()
505-
.has_input()
506-
);
507-
508-
while test.canister_state(canister).get_next_scheduled_method() != NextScheduledMethod::Message
509-
{
510-
test.canister_state_mut(canister)
511-
.inc_next_scheduled_method();
512-
}
482+
);
513483

514-
assert!(is_next_method_chosen(
515-
test.canister_state_mut(canister),
516-
has_heartbeat,
517-
has_active_timer,
518-
&mut heartbeat_and_timer_canisters,
519-
));
520-
521-
// Since NextScheduledMethod is Message it is not expected that Heartbeat
522-
// and GlobalTimer are added to the queue.
523-
assert!(
524-
test.canister_state(canister)
525-
.system_state
526-
.task_queue
527-
.is_empty()
528-
);
484+
let mut tasks = Vec::new();
485+
while let Some(task) = canister.system_state.task_queue.pop_front() {
486+
tasks.push(task);
487+
}
529488

530-
assert_eq!(heartbeat_and_timer_canisters, BTreeSet::new());
489+
// Iff a task was enqueued, the canister was added to the set.
490+
assert!(
491+
tasks.is_empty() || heartbeat_and_timer_canisters.contains(&canister_id),
492+
"tasks: {tasks:?}, heartbeat_and_timer_canisters: {heartbeat_and_timer_canisters:?}"
493+
);
531494

532-
while test.canister_state(canister).get_next_scheduled_method()
533-
!= NextScheduledMethod::Heartbeat
534-
{
535-
test.canister_state_mut(canister)
536-
.inc_next_scheduled_method();
495+
(tasks, canister.get_next_scheduled_method())
537496
}
538497

539-
// Since NextScheduledMethod is Heartbeat it is expected that Heartbeat
540-
// and GlobalTimer are added at the front of the queue.
541-
assert!(is_next_method_chosen(
542-
test.canister_state_mut(canister),
543-
has_heartbeat,
544-
has_active_timer,
545-
&mut heartbeat_and_timer_canisters,
546-
));
498+
fn inc(mut method: NextScheduledMethod) -> NextScheduledMethod {
499+
method.inc();
500+
method
501+
}
547502

548-
assert_eq!(heartbeat_and_timer_canisters, BTreeSet::from([canister]));
503+
// With no input, no heartbeat, no active timer, nothing changes.
504+
assert_eq!(call(false, false, false, Message), (vec![], Message));
505+
assert_eq!(call(false, false, false, Heartbeat), (vec![], Heartbeat));
549506
assert_eq!(
550-
test.canister_state(canister)
551-
.system_state
552-
.task_queue
553-
.front(),
554-
Some(&ExecutionTask::Heartbeat)
507+
call(false, false, false, GlobalTimer),
508+
(vec![], GlobalTimer)
555509
);
556510

557-
test.canister_state_mut(canister)
558-
.system_state
559-
.task_queue
560-
.pop_front();
561-
511+
// With an input but no heartbeat or timer, next method advances past Message.
512+
assert_eq!(call(true, false, false, Message), (vec![], inc(Message)));
513+
assert_eq!(call(true, false, false, Heartbeat), (vec![], inc(Message)));
562514
assert_eq!(
563-
test.canister_state(canister)
564-
.system_state
565-
.task_queue
566-
.front(),
567-
Some(&ExecutionTask::GlobalTimer)
515+
call(true, false, false, GlobalTimer),
516+
(vec![], inc(Message))
568517
);
569518

570-
test.canister_state_mut(canister)
571-
.system_state
572-
.task_queue
573-
.pop_front();
574-
575-
assert_eq!(heartbeat_and_timer_canisters, BTreeSet::from([canister]));
576-
577-
heartbeat_and_timer_canisters = BTreeSet::new();
578-
579-
while test.canister_state(canister).get_next_scheduled_method()
580-
!= NextScheduledMethod::GlobalTimer
581-
{
582-
test.canister_state_mut(canister)
583-
.inc_next_scheduled_method();
584-
}
585-
// Since NextScheduledMethod is GlobalTimer it is expected that GlobalTimer
586-
// and Heartbeat are added at the front of the queue.
587-
assert!(is_next_method_chosen(
588-
test.canister_state_mut(canister),
589-
has_heartbeat,
590-
has_active_timer,
591-
&mut heartbeat_and_timer_canisters,
592-
));
593-
594-
assert_eq!(heartbeat_and_timer_canisters, BTreeSet::from([canister]));
519+
// With an input and a heartbeat or timer, it depends on the next method.
520+
assert_eq!(call(true, true, false, Message), (vec![], inc(Message)));
595521
assert_eq!(
596-
test.canister_state(canister)
597-
.system_state
598-
.task_queue
599-
.front(),
600-
Some(&ExecutionTask::GlobalTimer)
522+
call(true, true, false, Heartbeat),
523+
(vec![Task::Heartbeat], inc(Heartbeat))
601524
);
602-
603-
test.canister_state_mut(canister)
604-
.system_state
605-
.task_queue
606-
.pop_front();
607-
608525
assert_eq!(
609-
test.canister_state(canister)
610-
.system_state
611-
.task_queue
612-
.front(),
613-
Some(&ExecutionTask::Heartbeat)
526+
call(true, false, true, GlobalTimer),
527+
(vec![Task::GlobalTimer], inc(GlobalTimer))
614528
);
615529

616-
test.canister_state_mut(canister)
617-
.system_state
618-
.task_queue
619-
.pop_front();
620-
621-
assert_eq!(heartbeat_and_timer_canisters, BTreeSet::from([canister]));
530+
// With all tree, the next method is always scheduled and advanced past.
531+
assert_eq!(call(true, true, true, Message), (vec![], inc(Message)));
532+
assert_eq!(
533+
call(true, true, true, Heartbeat),
534+
(vec![Task::Heartbeat, Task::GlobalTimer], inc(Heartbeat))
535+
);
536+
assert_eq!(
537+
call(true, true, true, GlobalTimer),
538+
(vec![Task::GlobalTimer, Task::Heartbeat], inc(GlobalTimer))
539+
);
622540
}
623541

624542
#[test]

0 commit comments

Comments
 (0)