docs: bilingual comments for the sim tests
This commit is contained in:
parent
2ba298e213
commit
4203bf2789
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the event queue.
|
//! Tests for the event queue.
|
||||||
|
//!
|
||||||
|
//! 事件队列的测试。
|
||||||
|
|
||||||
use apsis_event::{Event, EventId, EventQueue};
|
use apsis_event::{Event, EventId, EventQueue};
|
||||||
|
|
||||||
|
|
@ -37,6 +39,7 @@ fn events_are_delivered_in_time_order() {
|
||||||
#[test]
|
#[test]
|
||||||
fn events_at_the_same_time_are_delivered_in_schedule_order() {
|
fn events_at_the_same_time_are_delivered_in_schedule_order() {
|
||||||
// The tie-break is what makes a run reproducible.
|
// The tie-break is what makes a run reproducible.
|
||||||
|
// 并列打破规则正是使运行可复现的东西。
|
||||||
let mut queue = EventQueue::new();
|
let mut queue = EventQueue::new();
|
||||||
queue.schedule(10.0, 'a');
|
queue.schedule(10.0, 'a');
|
||||||
queue.schedule(10.0, 'b');
|
queue.schedule(10.0, 'b');
|
||||||
|
|
@ -54,6 +57,7 @@ fn events_at_the_same_time_are_delivered_in_schedule_order() {
|
||||||
fn the_tie_break_holds_across_a_heap_rebuild() {
|
fn the_tie_break_holds_across_a_heap_rebuild() {
|
||||||
// Scheduling in a different order must still deliver in schedule order,
|
// Scheduling in a different order must still deliver in schedule order,
|
||||||
// because the sequence number, not the insertion position, decides.
|
// because the sequence number, not the insertion position, decides.
|
||||||
|
// 以不同次序调度仍必须按调度次序投递,因为决定的是序号而非插入位置。
|
||||||
let mut queue = EventQueue::new();
|
let mut queue = EventQueue::new();
|
||||||
let mut ids = Vec::new();
|
let mut ids = Vec::new();
|
||||||
for index in 0..20 {
|
for index in 0..20 {
|
||||||
|
|
@ -67,6 +71,7 @@ fn the_tie_break_holds_across_a_heap_rebuild() {
|
||||||
assert_eq!(delivered, (0..20).collect::<Vec<u64>>());
|
assert_eq!(delivered, (0..20).collect::<Vec<u64>>());
|
||||||
|
|
||||||
// The identifiers are also in schedule order.
|
// The identifiers are also in schedule order.
|
||||||
|
// 标识符也按调度次序排列。
|
||||||
for (position, id) in ids.iter().enumerate() {
|
for (position, id) in ids.iter().enumerate() {
|
||||||
assert_eq!(id.sequence(), position as u64);
|
assert_eq!(id.sequence(), position as u64);
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +97,7 @@ fn scheduling_a_negative_or_non_finite_time_is_refused() {
|
||||||
assert_eq!(queue.schedule(time, "x"), None, "time {time}");
|
assert_eq!(queue.schedule(time, "x"), None, "time {time}");
|
||||||
}
|
}
|
||||||
// The queue is unchanged and the sequence number was not consumed.
|
// The queue is unchanged and the sequence number was not consumed.
|
||||||
|
// 队列不变,且序号未被消耗。
|
||||||
assert!(queue.is_empty());
|
assert!(queue.is_empty());
|
||||||
let id = queue.schedule(0.0, "zero").expect("zero is usable");
|
let id = queue.schedule(0.0, "zero").expect("zero is usable");
|
||||||
assert_eq!(id.sequence(), 0);
|
assert_eq!(id.sequence(), 0);
|
||||||
|
|
@ -115,6 +121,7 @@ fn a_follow_up_with_a_bad_delay_is_refused() {
|
||||||
assert_eq!(queue.schedule_after(0.0, f64::INFINITY, "x"), None);
|
assert_eq!(queue.schedule_after(0.0, f64::INFINITY, "x"), None);
|
||||||
assert!(queue.is_empty());
|
assert!(queue.is_empty());
|
||||||
// A zero delay schedules at the current instant.
|
// A zero delay schedules at the current instant.
|
||||||
|
// 零延时在当前瞬时调度。
|
||||||
let id = queue.schedule_after(5.0, 0.0, "now").expect("usable");
|
let id = queue.schedule_after(5.0, 0.0, "now").expect("usable");
|
||||||
assert_eq!(id.time(), 5.0);
|
assert_eq!(id.time(), 5.0);
|
||||||
}
|
}
|
||||||
|
|
@ -123,6 +130,7 @@ fn a_follow_up_with_a_bad_delay_is_refused() {
|
||||||
fn events_can_be_scheduled_from_a_handler() {
|
fn events_can_be_scheduled_from_a_handler() {
|
||||||
// A handler that schedules its successor, which is the pattern a
|
// A handler that schedules its successor, which is the pattern a
|
||||||
// discrete-event simulation uses for a periodic process.
|
// discrete-event simulation uses for a periodic process.
|
||||||
|
// 一个调度其后继的处理器,这是离散事件仿真用于周期过程的模式。
|
||||||
let mut queue: EventQueue<u32> = EventQueue::new();
|
let mut queue: EventQueue<u32> = EventQueue::new();
|
||||||
queue.schedule(0.0, 0).expect("usable");
|
queue.schedule(0.0, 0).expect("usable");
|
||||||
let mut ticks = 0;
|
let mut ticks = 0;
|
||||||
|
|
@ -151,6 +159,7 @@ fn clearing_discards_the_pending_events() {
|
||||||
assert_eq!(queue.clear(), 4);
|
assert_eq!(queue.clear(), 4);
|
||||||
assert!(queue.is_empty());
|
assert!(queue.is_empty());
|
||||||
// The delivered count records history and is not reset.
|
// The delivered count records history and is not reset.
|
||||||
|
// 已投递计数记录历史且不重置。
|
||||||
assert_eq!(queue.delivered(), 1);
|
assert_eq!(queue.delivered(), 1);
|
||||||
assert_eq!(queue.pop(), None);
|
assert_eq!(queue.pop(), None);
|
||||||
}
|
}
|
||||||
|
|
@ -191,6 +200,7 @@ fn the_queue_state_reports_what_is_pending() {
|
||||||
fn a_payload_slot_is_reused_after_a_pop() {
|
fn a_payload_slot_is_reused_after_a_pop() {
|
||||||
// The queue indexes payloads by sequence, so a popped slot must be
|
// The queue indexes payloads by sequence, so a popped slot must be
|
||||||
// reusable without disturbing a later event.
|
// reusable without disturbing a later event.
|
||||||
|
// 队列按序号索引载荷,故被弹出的槽位必须可复用而不扰动更晚的事件。
|
||||||
let mut queue = EventQueue::new();
|
let mut queue = EventQueue::new();
|
||||||
queue.schedule(1.0, "first").expect("usable");
|
queue.schedule(1.0, "first").expect("usable");
|
||||||
queue.schedule(2.0, "second").expect("usable");
|
queue.schedule(2.0, "second").expect("usable");
|
||||||
|
|
@ -205,6 +215,8 @@ fn a_payload_slot_is_reused_after_a_pop() {
|
||||||
fn the_identifier_orders_by_time_then_sequence() {
|
fn the_identifier_orders_by_time_then_sequence() {
|
||||||
// The derived ordering on the identifier is the delivery order, which is
|
// The derived ordering on the identifier is the delivery order, which is
|
||||||
// what lets a caller sort identifiers to reproduce a run.
|
// what lets a caller sort identifiers to reproduce a run.
|
||||||
|
// 标识符上派生的次序即投递次序,这正是使调用方可对标识符排序以复现一次运行的
|
||||||
|
// 原因。
|
||||||
let earlier = EventId::new(1.0, 5).expect("usable");
|
let earlier = EventId::new(1.0, 5).expect("usable");
|
||||||
let later_time = EventId::new(2.0, 0).expect("usable");
|
let later_time = EventId::new(2.0, 0).expect("usable");
|
||||||
let later_sequence = EventId::new(1.0, 6).expect("usable");
|
let later_sequence = EventId::new(1.0, 6).expect("usable");
|
||||||
|
|
@ -246,6 +258,7 @@ fn an_event_can_be_rescheduled() {
|
||||||
assert_eq!(moved.id.sequence(), 1);
|
assert_eq!(moved.id.sequence(), 1);
|
||||||
assert_eq!(moved.payload, "x");
|
assert_eq!(moved.payload, "x");
|
||||||
// The original is unchanged.
|
// The original is unchanged.
|
||||||
|
// 原件不变。
|
||||||
assert_eq!(event.time, 5.0);
|
assert_eq!(event.time, 5.0);
|
||||||
|
|
||||||
assert!(event.reschedule(-1.0, 2).is_none());
|
assert!(event.reschedule(-1.0, 2).is_none());
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the output trace and recorder.
|
//! Tests for the output trace and recorder.
|
||||||
|
//!
|
||||||
|
//! 输出轨迹与记录器的测试。
|
||||||
|
|
||||||
use apsis_output::{Record, RecordKind, Recorder, Trace};
|
use apsis_output::{Record, RecordKind, Recorder, Trace};
|
||||||
|
|
||||||
|
|
@ -20,6 +22,7 @@ fn an_empty_trace_has_nothing() {
|
||||||
fn a_trace_keeps_insertion_order() {
|
fn a_trace_keeps_insertion_order() {
|
||||||
// The order a model recorded in is part of what it decided, so the trace
|
// The order a model recorded in is part of what it decided, so the trace
|
||||||
// does not sort.
|
// does not sort.
|
||||||
|
// 模型记录所用的次序是其决策的一部分,故轨迹不排序。
|
||||||
let mut trace = Trace::new();
|
let mut trace = Trace::new();
|
||||||
trace.push(Record::observation(10.0, "b", 1.0));
|
trace.push(Record::observation(10.0, "b", 1.0));
|
||||||
trace.push(Record::observation(5.0, "a", 2.0));
|
trace.push(Record::observation(5.0, "a", 2.0));
|
||||||
|
|
@ -41,6 +44,7 @@ fn time_order_is_available_on_request() {
|
||||||
let times: Vec<f64> = ordered.iter().map(|record| record.time).collect();
|
let times: Vec<f64> = ordered.iter().map(|record| record.time).collect();
|
||||||
assert_eq!(times, vec![5.0, 7.0, 10.0]);
|
assert_eq!(times, vec![5.0, 7.0, 10.0]);
|
||||||
// The original is unchanged.
|
// The original is unchanged.
|
||||||
|
// 原件不变。
|
||||||
assert_eq!(trace.records()[0].time, 10.0);
|
assert_eq!(trace.records()[0].time, 10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +169,7 @@ fn a_non_finite_record_is_refused() {
|
||||||
assert!(trace.is_empty());
|
assert!(trace.is_empty());
|
||||||
|
|
||||||
// A finite record is accepted.
|
// A finite record is accepted.
|
||||||
|
// 有限的记录被接受。
|
||||||
assert!(trace.push(Record::observation(1.0, "x", 1.0)));
|
assert!(trace.push(Record::observation(1.0, "x", 1.0)));
|
||||||
assert_eq!(trace.len(), 1);
|
assert_eq!(trace.len(), 1);
|
||||||
assert!(trace.is_stable());
|
assert!(trace.is_stable());
|
||||||
|
|
@ -214,6 +219,7 @@ fn taking_the_trace_leaves_the_recorder_empty() {
|
||||||
assert_eq!(trace.len(), 2);
|
assert_eq!(trace.len(), 2);
|
||||||
assert!(recorder.is_empty());
|
assert!(recorder.is_empty());
|
||||||
// The refused count belongs to the recorder and is not reset.
|
// The refused count belongs to the recorder and is not reset.
|
||||||
|
// 被拒绝计数属于记录器且不重置。
|
||||||
recorder.observe(f64::NAN, "x", 1.0);
|
recorder.observe(f64::NAN, "x", 1.0);
|
||||||
assert_eq!(recorder.refused(), 1);
|
assert_eq!(recorder.refused(), 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for building and reading a scenario.
|
//! Tests for building and reading a scenario.
|
||||||
|
//!
|
||||||
|
//! 构建与读取场景的测试。
|
||||||
|
|
||||||
use apsis_resource::{Resource, ResourceId, Window};
|
use apsis_resource::{Resource, ResourceId, Window};
|
||||||
use apsis_scenario::{Scenario, ScenarioErrorKind};
|
use apsis_scenario::{Scenario, ScenarioErrorKind};
|
||||||
|
|
@ -44,6 +46,8 @@ fn the_header_setters_take_effect() {
|
||||||
fn a_limit_the_run_cannot_use_is_cleared() {
|
fn a_limit_the_run_cannot_use_is_cleared() {
|
||||||
// The setters match the engine, which ignores a limit it cannot use, so
|
// The setters match the engine, which ignores a limit it cannot use, so
|
||||||
// a scenario never carries one that would surprise a run.
|
// a scenario never carries one that would surprise a run.
|
||||||
|
// 各设置器与引擎一致,后者忽略它无法使用的限制,故场景绝不携带会让一次运行意外
|
||||||
|
// 的限制。
|
||||||
let scenario = Scenario::new("demo")
|
let scenario = Scenario::new("demo")
|
||||||
.with_time_limit(-1.0)
|
.with_time_limit(-1.0)
|
||||||
.with_event_limit(0);
|
.with_event_limit(0);
|
||||||
|
|
@ -63,6 +67,7 @@ fn the_checked_setters_report_a_bad_value() {
|
||||||
assert!(scenario.set_time_limit(f64::INFINITY).is_err());
|
assert!(scenario.set_time_limit(f64::INFINITY).is_err());
|
||||||
assert!(scenario.set_time_limit(-5.0).is_err());
|
assert!(scenario.set_time_limit(-5.0).is_err());
|
||||||
// A good value is accepted and the scenario keeps its old one on failure.
|
// A good value is accepted and the scenario keeps its old one on failure.
|
||||||
|
// 好的值被接受,失败时场景保留其旧值。
|
||||||
scenario.set_epoch(10.0).expect("the epoch is usable");
|
scenario.set_epoch(10.0).expect("the epoch is usable");
|
||||||
assert_eq!(scenario.epoch(), 10.0);
|
assert_eq!(scenario.epoch(), 10.0);
|
||||||
scenario.set_time_limit(60.0).expect("the limit is usable");
|
scenario.set_time_limit(60.0).expect("the limit is usable");
|
||||||
|
|
@ -78,6 +83,7 @@ fn a_resource_is_added_and_checked() {
|
||||||
assert_eq!(scenario.resources().len(), 1);
|
assert_eq!(scenario.resources().len(), 1);
|
||||||
|
|
||||||
// A second resource with the same identifier is refused.
|
// A second resource with the same identifier is refused.
|
||||||
|
// 标识符相同的第二个资源被拒绝。
|
||||||
let error = scenario
|
let error = scenario
|
||||||
.add_resource(station(2.0, &[]))
|
.add_resource(station(2.0, &[]))
|
||||||
.expect_err("the identifier is taken");
|
.expect_err("the identifier is taken");
|
||||||
|
|
@ -102,6 +108,7 @@ fn a_task_is_added_only_when_its_resources_exist() {
|
||||||
.expect("the resource is new");
|
.expect("the resource is new");
|
||||||
|
|
||||||
// The resource is not in the scenario yet.
|
// The resource is not in the scenario yet.
|
||||||
|
// 该资源还不在场景中。
|
||||||
let error = scenario
|
let error = scenario
|
||||||
.add_task(task.clone())
|
.add_task(task.clone())
|
||||||
.expect_err("unknown resource");
|
.expect_err("unknown resource");
|
||||||
|
|
@ -114,6 +121,7 @@ fn a_task_is_added_only_when_its_resources_exist() {
|
||||||
assert_eq!(scenario.tasks().len(), 1);
|
assert_eq!(scenario.tasks().len(), 1);
|
||||||
|
|
||||||
// A second task with the same identifier is refused.
|
// A second task with the same identifier is refused.
|
||||||
|
// 标识符相同的第二个任务被拒绝。
|
||||||
let error = scenario
|
let error = scenario
|
||||||
.add_task(Task::new(TaskId::new(0), 30.0))
|
.add_task(Task::new(TaskId::new(0), 30.0))
|
||||||
.expect_err("the identifier is taken");
|
.expect_err("the identifier is taken");
|
||||||
|
|
@ -124,6 +132,7 @@ fn a_task_is_added_only_when_its_resources_exist() {
|
||||||
fn an_unusable_task_is_refused() {
|
fn an_unusable_task_is_refused() {
|
||||||
let mut scenario = Scenario::new("demo");
|
let mut scenario = Scenario::new("demo");
|
||||||
// A task with a zero duration is not usable.
|
// A task with a zero duration is not usable.
|
||||||
|
// 零时长的任务不可用。
|
||||||
let error = scenario
|
let error = scenario
|
||||||
.add_task(Task::new(TaskId::new(0), 0.0))
|
.add_task(Task::new(TaskId::new(0), 0.0))
|
||||||
.expect_err("the task is not usable");
|
.expect_err("the task is not usable");
|
||||||
|
|
@ -284,6 +293,7 @@ fn a_scenario_round_trips_through_text() {
|
||||||
let reloaded = Scenario::parse(&text).expect("the rendered text parses");
|
let reloaded = Scenario::parse(&text).expect("the rendered text parses");
|
||||||
assert_eq!(reloaded, original);
|
assert_eq!(reloaded, original);
|
||||||
// A second round trip is stable, so the text form is a fixed point.
|
// A second round trip is stable, so the text form is a fixed point.
|
||||||
|
// 第二次往返是稳定的,故文本形式是固定点。
|
||||||
assert_eq!(reloaded.to_text(), text);
|
assert_eq!(reloaded.to_text(), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,6 +301,7 @@ fn a_scenario_round_trips_through_text() {
|
||||||
fn a_task_with_no_window_round_trips() {
|
fn a_task_with_no_window_round_trips() {
|
||||||
// A task with no window leaves both ends infinite, which the text form
|
// A task with no window leaves both ends infinite, which the text form
|
||||||
// cannot write; the round trip must still recover an equivalent task.
|
// cannot write; the round trip must still recover an equivalent task.
|
||||||
|
// 无窗口的任务使两端均为无限,文本形式写不出来;往返仍必须恢复一个等价任务。
|
||||||
let mut original = Scenario::new("open");
|
let mut original = Scenario::new("open");
|
||||||
original
|
original
|
||||||
.add_resource(station(1.0, &[(0.0, 600.0)]))
|
.add_resource(station(1.0, &[(0.0, 600.0)]))
|
||||||
|
|
@ -308,6 +319,8 @@ fn a_task_with_no_window_round_trips() {
|
||||||
// The window is written as a finite interval that holds the task, which
|
// The window is written as a finite interval that holds the task, which
|
||||||
// is the best a text form with no infinity can do. The task is still
|
// is the best a text form with no infinity can do. The task is still
|
||||||
// usable.
|
// usable.
|
||||||
|
// 该窗口被写为容纳该任务的有限区间,这是没有无穷的文本形式所能做的最好。任务
|
||||||
|
// 仍可用。
|
||||||
assert!(task.is_valid());
|
assert!(task.is_valid());
|
||||||
assert!(task.fits_window());
|
assert!(task.fits_window());
|
||||||
}
|
}
|
||||||
|
|
@ -373,6 +386,7 @@ fn a_qualifier_before_any_task_is_reported() {
|
||||||
#[test]
|
#[test]
|
||||||
fn precedence_may_name_tasks_in_either_order_in_the_file() {
|
fn precedence_may_name_tasks_in_either_order_in_the_file() {
|
||||||
// The three passes let a constraint name a task defined later.
|
// The three passes let a constraint name a task defined later.
|
||||||
|
// 三遍解析使约束可以指名稍后才定义的任务。
|
||||||
let text = "\
|
let text = "\
|
||||||
scenario demo
|
scenario demo
|
||||||
resource 0 capacity 1
|
resource 0 capacity 1
|
||||||
|
|
@ -430,6 +444,7 @@ precedence 1 0
|
||||||
assert_eq!(error.kind(), ScenarioErrorKind::CyclicPrecedence);
|
assert_eq!(error.kind(), ScenarioErrorKind::CyclicPrecedence);
|
||||||
// The second constraint, on the fifth line, is the one that closes the
|
// The second constraint, on the fifth line, is the one that closes the
|
||||||
// cycle.
|
// cycle.
|
||||||
|
// 第五行上的第二条约束正是闭合该环的那条。
|
||||||
assert_eq!(error.line(), Some(5));
|
assert_eq!(error.line(), Some(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
//! Tests for the simulation engine.
|
//! Tests for the simulation engine.
|
||||||
|
//!
|
||||||
|
//! 仿真引擎的测试。
|
||||||
|
|
||||||
use apsis_sim_engine::{Engine, StopReason};
|
use apsis_sim_engine::{Engine, StopReason};
|
||||||
|
|
||||||
|
|
@ -43,6 +45,7 @@ fn a_run_executes_every_event_in_order() {
|
||||||
fn a_handler_can_schedule_follow_up_events() {
|
fn a_handler_can_schedule_follow_up_events() {
|
||||||
// A periodic process that reschedules itself, which is the pattern a
|
// A periodic process that reschedules itself, which is the pattern a
|
||||||
// discrete-event model uses for a clock driven process.
|
// discrete-event model uses for a clock driven process.
|
||||||
|
// 一个重新调度自身的周期过程,这是离散事件模型用于时钟驱动过程的模式。
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.schedule(0.0, 0_u32);
|
engine.schedule(0.0, 0_u32);
|
||||||
|
|
||||||
|
|
@ -61,6 +64,7 @@ fn a_handler_can_schedule_follow_up_events() {
|
||||||
assert_eq!(outcome.reason, StopReason::QueueEmpty);
|
assert_eq!(outcome.reason, StopReason::QueueEmpty);
|
||||||
// The clock ends at the last event, not at the delay of the last
|
// The clock ends at the last event, not at the delay of the last
|
||||||
// follow-up, which was never scheduled.
|
// follow-up, which was never scheduled.
|
||||||
|
// 时钟结束于最后一个事件,而非最后一个后续事件的延时,后者从未被调度。
|
||||||
assert_eq!(outcome.ended_at, 30.0);
|
assert_eq!(outcome.ended_at, 30.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,6 +84,8 @@ fn events_at_the_same_instant_are_delivered_in_schedule_order() {
|
||||||
fn a_follow_up_scheduled_for_the_same_instant_is_delivered_after_the_pending_ones() {
|
fn a_follow_up_scheduled_for_the_same_instant_is_delivered_after_the_pending_ones() {
|
||||||
// This is what makes a cascade at one instant well defined: events
|
// This is what makes a cascade at one instant well defined: events
|
||||||
// scheduled by a handler go behind the events already queued.
|
// scheduled by a handler go behind the events already queued.
|
||||||
|
// 这正是使某一瞬时的级联有明确含义的东西:处理器所调度的事件排在已排队的事件
|
||||||
|
// 之后。
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
engine.schedule(5.0, "first");
|
engine.schedule(5.0, "first");
|
||||||
engine.schedule(5.0, "second");
|
engine.schedule(5.0, "second");
|
||||||
|
|
@ -100,6 +106,7 @@ fn a_follow_up_scheduled_for_the_same_instant_is_delivered_after_the_pending_one
|
||||||
fn a_run_is_reproducible() {
|
fn a_run_is_reproducible() {
|
||||||
// The same inputs must give the same output, which the tie-break on the
|
// The same inputs must give the same output, which the tie-break on the
|
||||||
// schedule order guarantees.
|
// schedule order guarantees.
|
||||||
|
// 相同输入必须给出相同输出,这由调度次序上的并列打破规则保证。
|
||||||
let build = || {
|
let build = || {
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
for index in 0..20 {
|
for index in 0..20 {
|
||||||
|
|
@ -128,9 +135,11 @@ fn a_time_limit_stops_the_run_and_leaves_the_event_pending() {
|
||||||
assert_eq!(outcome.reason, StopReason::TimeLimit);
|
assert_eq!(outcome.reason, StopReason::TimeLimit);
|
||||||
assert!(!outcome.is_natural());
|
assert!(!outcome.is_natural());
|
||||||
// The event beyond the limit is still queued.
|
// The event beyond the limit is still queued.
|
||||||
|
// 超限的事件仍在队列中。
|
||||||
assert_eq!(outcome.remaining, 2);
|
assert_eq!(outcome.remaining, 2);
|
||||||
assert_eq!(engine.next_time(), Some(20.0));
|
assert_eq!(engine.next_time(), Some(20.0));
|
||||||
// The clock is at the last event executed, not at the limit.
|
// The clock is at the last event executed, not at the limit.
|
||||||
|
// 时钟在最后执行的事件处,而非在限制处。
|
||||||
assert_eq!(outcome.ended_at, 10.0);
|
assert_eq!(outcome.ended_at, 10.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,6 +173,8 @@ fn an_event_limit_stops_the_run_without_discarding_events() {
|
||||||
// The limit is a lifetime cap rather than a per run one, so a second
|
// The limit is a lifetime cap rather than a per run one, so a second
|
||||||
// run at once stops without executing anything. Raising the limit lets
|
// run at once stops without executing anything. Raising the limit lets
|
||||||
// the remaining events run.
|
// the remaining events run.
|
||||||
|
// 该限制是生命期上限而非每次运行的上限,故紧接着的第二次运行不执行任何东西即
|
||||||
|
// 停止。提高限制使剩余事件运行。
|
||||||
let outcome = engine.run(|_, event| seen.push(event.payload));
|
let outcome = engine.run(|_, event| seen.push(event.payload));
|
||||||
assert_eq!(outcome.executed, 0);
|
assert_eq!(outcome.executed, 0);
|
||||||
assert_eq!(outcome.reason, StopReason::EventLimit);
|
assert_eq!(outcome.reason, StopReason::EventLimit);
|
||||||
|
|
@ -179,6 +190,7 @@ fn an_event_limit_stops_the_run_without_discarding_events() {
|
||||||
fn the_event_limit_counts_across_runs() {
|
fn the_event_limit_counts_across_runs() {
|
||||||
// The limit is on the total number of events, so a run that already
|
// The limit is on the total number of events, so a run that already
|
||||||
// reached it does nothing.
|
// reached it does nothing.
|
||||||
|
// 该限制针对事件总数,故已达到它的运行什么也不做。
|
||||||
let mut engine = Engine::new().with_event_limit(1);
|
let mut engine = Engine::new().with_event_limit(1);
|
||||||
engine.schedule(1.0, "a").expect("usable");
|
engine.schedule(1.0, "a").expect("usable");
|
||||||
assert_eq!(engine.run(|_, _| {}).executed, 1);
|
assert_eq!(engine.run(|_, _| {}).executed, 1);
|
||||||
|
|
@ -204,11 +216,13 @@ fn a_stop_request_ends_the_run_after_the_current_event() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// The event that requested the stop is the last one executed.
|
// The event that requested the stop is the last one executed.
|
||||||
|
// 请求停止的事件是最后执行的那个。
|
||||||
assert_eq!(seen, vec![0, 1, 2]);
|
assert_eq!(seen, vec![0, 1, 2]);
|
||||||
assert_eq!(outcome.reason, StopReason::Requested);
|
assert_eq!(outcome.reason, StopReason::Requested);
|
||||||
assert_eq!(outcome.remaining, 2);
|
assert_eq!(outcome.remaining, 2);
|
||||||
|
|
||||||
// The request is cleared by the run, so the next run continues.
|
// The request is cleared by the run, so the next run continues.
|
||||||
|
// 该请求由运行清除,故下次运行继续。
|
||||||
let outcome = engine.run(|_, event| seen.push(event.payload));
|
let outcome = engine.run(|_, event| seen.push(event.payload));
|
||||||
assert_eq!(seen, vec![0, 1, 2, 3, 4]);
|
assert_eq!(seen, vec![0, 1, 2, 3, 4]);
|
||||||
assert_eq!(outcome.reason, StopReason::QueueEmpty);
|
assert_eq!(outcome.reason, StopReason::QueueEmpty);
|
||||||
|
|
@ -221,6 +235,7 @@ fn a_stop_request_can_be_cleared_by_hand() {
|
||||||
engine.request_stop();
|
engine.request_stop();
|
||||||
engine.clear_stop();
|
engine.clear_stop();
|
||||||
// The run clears the flag itself, so a clear beforehand is harmless.
|
// The run clears the flag itself, so a clear beforehand is harmless.
|
||||||
|
// 运行自身会清除该标志,故事先清除是无害的。
|
||||||
assert_eq!(engine.run(|_, _| {}).executed, 1);
|
assert_eq!(engine.run(|_, _| {}).executed, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,6 +255,7 @@ fn the_clock_can_be_set_to_start_at_an_offset() {
|
||||||
engine.set_now(1_000.0);
|
engine.set_now(1_000.0);
|
||||||
assert_eq!(engine.now(), 1_000.0);
|
assert_eq!(engine.now(), 1_000.0);
|
||||||
// A relative schedule is measured from the offset.
|
// A relative schedule is measured from the offset.
|
||||||
|
// 相对调度自该偏移量起度量。
|
||||||
engine.schedule_after(5.0, "x").expect("usable");
|
engine.schedule_after(5.0, "x").expect("usable");
|
||||||
assert_eq!(engine.next_time(), Some(1_005.0));
|
assert_eq!(engine.next_time(), Some(1_005.0));
|
||||||
|
|
||||||
|
|
@ -249,6 +265,7 @@ fn the_clock_can_be_set_to_start_at_an_offset() {
|
||||||
assert_eq!(outcome.duration(), 5.0);
|
assert_eq!(outcome.duration(), 5.0);
|
||||||
|
|
||||||
// An unusable time is ignored, leaving the clock alone.
|
// An unusable time is ignored, leaving the clock alone.
|
||||||
|
// 不可用的时间被忽略,时钟不动。
|
||||||
engine.set_now(-1.0);
|
engine.set_now(-1.0);
|
||||||
assert_eq!(engine.now(), 1_005.0);
|
assert_eq!(engine.now(), 1_005.0);
|
||||||
engine.set_now(f64::NAN);
|
engine.set_now(f64::NAN);
|
||||||
|
|
@ -265,6 +282,7 @@ fn the_outcome_reports_the_event_rate() {
|
||||||
}
|
}
|
||||||
let outcome = engine.run(|_, _| {});
|
let outcome = engine.run(|_, _| {});
|
||||||
// Ten events over ninety seconds, so the rate is a ninth per second.
|
// Ten events over ninety seconds, so the rate is a ninth per second.
|
||||||
|
// 九十秒内十个事件,故速率为每秒九分之一。
|
||||||
assert_eq!(outcome.duration(), 90.0);
|
assert_eq!(outcome.duration(), 90.0);
|
||||||
assert!((outcome.event_rate().expect("a positive duration") - 10.0 / 90.0).abs() < 1e-12);
|
assert!((outcome.event_rate().expect("a positive duration") - 10.0 / 90.0).abs() < 1e-12);
|
||||||
}
|
}
|
||||||
|
|
@ -287,6 +305,7 @@ fn the_queue_is_reachable_from_the_engine() {
|
||||||
assert_eq!(engine.queue().len(), 1);
|
assert_eq!(engine.queue().len(), 1);
|
||||||
assert_eq!(engine.queue().peek(), Some(&"a"));
|
assert_eq!(engine.queue().peek(), Some(&"a"));
|
||||||
// The queue may be modified directly.
|
// The queue may be modified directly.
|
||||||
|
// 队列可被直接修改。
|
||||||
engine.queue_mut().clear();
|
engine.queue_mut().clear();
|
||||||
assert!(engine.is_idle());
|
assert!(engine.is_idle());
|
||||||
}
|
}
|
||||||
|
|
@ -296,6 +315,7 @@ fn a_bad_time_limit_is_ignored() {
|
||||||
let mut engine = Engine::new().with_time_limit(-1.0);
|
let mut engine = Engine::new().with_time_limit(-1.0);
|
||||||
engine.schedule(100.0, "x").expect("usable");
|
engine.schedule(100.0, "x").expect("usable");
|
||||||
// The run reaches the event, because no limit applies.
|
// The run reaches the event, because no limit applies.
|
||||||
|
// 该运行到达该事件,因为没有限制适用。
|
||||||
assert_eq!(engine.run(|_, _| {}).executed, 1);
|
assert_eq!(engine.run(|_, _| {}).executed, 1);
|
||||||
|
|
||||||
let mut engine = Engine::new().with_time_limit(f64::NAN);
|
let mut engine = Engine::new().with_time_limit(f64::NAN);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
//! Tests for the simulation facade.
|
//! Tests for the simulation facade.
|
||||||
|
//!
|
||||||
|
//! 仿真门面的测试。
|
||||||
|
|
||||||
use apsis_sim::{
|
use apsis_sim::{
|
||||||
Activity, RecordKind, RunReport, Scenario, Simulation, SimulationErrorKind, StopReason,
|
Activity, RecordKind, RunReport, Scenario, Simulation, SimulationErrorKind, StopReason,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Builds a scenario with one station and a set of tasks.
|
/// Builds a scenario with one station and a set of tasks.
|
||||||
|
///
|
||||||
|
/// 构建一个含一座站与一组任务的场景。
|
||||||
fn scenario_with(tasks: &[(usize, f64, f64)]) -> Scenario {
|
fn scenario_with(tasks: &[(usize, f64, f64)]) -> Scenario {
|
||||||
let mut text = String::from("scenario test\nresource 0 capacity 1\nwindow 0 0 1000\n");
|
let mut text = String::from("scenario test\nresource 0 capacity 1\nwindow 0 0 1000\n");
|
||||||
for &(id, duration, priority) in tasks {
|
for &(id, duration, priority) in tasks {
|
||||||
|
|
@ -34,6 +38,7 @@ fn the_timeline_has_a_start_and_an_end_for_each_task() {
|
||||||
let simulation = Simulation::prepare(scenario_with(&[(0, 60.0, 1.0)])).expect("usable");
|
let simulation = Simulation::prepare(scenario_with(&[(0, 60.0, 1.0)])).expect("usable");
|
||||||
let timeline = simulation.timeline();
|
let timeline = simulation.timeline();
|
||||||
// In time order: the start precedes the end.
|
// In time order: the start precedes the end.
|
||||||
|
// 按时间次序:开始在结束之前。
|
||||||
assert_eq!(timeline.len(), 2);
|
assert_eq!(timeline.len(), 2);
|
||||||
assert!(timeline[0].1.is_start());
|
assert!(timeline[0].1.is_start());
|
||||||
assert_eq!(timeline[0].1.task(), apsis_task::TaskId::new(0));
|
assert_eq!(timeline[0].1.task(), apsis_task::TaskId::new(0));
|
||||||
|
|
@ -83,6 +88,7 @@ fn the_run_starts_at_the_scenario_epoch() {
|
||||||
assert_eq!(session.now(), 1_000.0);
|
assert_eq!(session.now(), 1_000.0);
|
||||||
let report = session.run(|_, _, _| {});
|
let report = session.run(|_, _, _| {});
|
||||||
// The timeline events are absolute, so the run reaches the end event.
|
// The timeline events are absolute, so the run reaches the end event.
|
||||||
|
// 时间线事件是绝对的,故运行到达结束事件。
|
||||||
assert!(report.outcome.ended_at > 1_000.0);
|
assert!(report.outcome.ended_at > 1_000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,6 +134,7 @@ fn a_session_leaves_the_prepared_simulation_alone() {
|
||||||
let mut first = simulation.start();
|
let mut first = simulation.start();
|
||||||
first.run(|_, _, _| {});
|
first.run(|_, _, _| {});
|
||||||
// A second session starts from the full timeline again.
|
// A second session starts from the full timeline again.
|
||||||
|
// 第二个会话又从完整的时间线开始。
|
||||||
let mut second = simulation.start();
|
let mut second = simulation.start();
|
||||||
assert_eq!(second.run(|_, _, _| {}).executed(), 2);
|
assert_eq!(second.run(|_, _, _| {}).executed(), 2);
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +142,7 @@ fn a_session_leaves_the_prepared_simulation_alone() {
|
||||||
#[test]
|
#[test]
|
||||||
fn a_handler_may_schedule_further_activity() {
|
fn a_handler_may_schedule_further_activity() {
|
||||||
// The engine interleaves events the handler schedules with the timeline.
|
// The engine interleaves events the handler schedules with the timeline.
|
||||||
|
// 引擎把处理器所调度的事件与时间线交织。
|
||||||
let simulation = Simulation::prepare(scenario_with(&[(0, 60.0, 1.0)])).expect("usable");
|
let simulation = Simulation::prepare(scenario_with(&[(0, 60.0, 1.0)])).expect("usable");
|
||||||
let mut session = simulation.start();
|
let mut session = simulation.start();
|
||||||
let mut extra = 0_u32;
|
let mut extra = 0_u32;
|
||||||
|
|
@ -148,12 +156,14 @@ fn a_handler_may_schedule_further_activity() {
|
||||||
});
|
});
|
||||||
assert_eq!(extra, 1);
|
assert_eq!(extra, 1);
|
||||||
// Two timeline events plus the one the handler scheduled.
|
// Two timeline events plus the one the handler scheduled.
|
||||||
|
// 两个时间线事件加上处理器所调度的那个。
|
||||||
assert_eq!(report.executed(), 3);
|
assert_eq!(report.executed(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deferred_tasks_are_reported() {
|
fn deferred_tasks_are_reported() {
|
||||||
// Two tasks of 60 s in a 100 s window: only one fits.
|
// Two tasks of 60 s in a 100 s window: only one fits.
|
||||||
|
// 一百秒窗口中的两个六十秒任务:只有一个放得下。
|
||||||
let scenario = Scenario::parse(
|
let scenario = Scenario::parse(
|
||||||
"scenario tight\n\
|
"scenario tight\n\
|
||||||
resource 0 capacity 1\n\
|
resource 0 capacity 1\n\
|
||||||
|
|
@ -209,6 +219,7 @@ fn the_schedule_evaluates() {
|
||||||
fn an_unusable_scenario_is_refused() {
|
fn an_unusable_scenario_is_refused() {
|
||||||
let mut scenario = Scenario::new("");
|
let mut scenario = Scenario::new("");
|
||||||
// An empty name is refused by validation.
|
// An empty name is refused by validation.
|
||||||
|
// 空名称被校验拒绝。
|
||||||
scenario
|
scenario
|
||||||
.add_resource(apsis_resource::Resource::new(
|
.add_resource(apsis_resource::Resource::new(
|
||||||
apsis_resource::ResourceId::new(0),
|
apsis_resource::ResourceId::new(0),
|
||||||
|
|
@ -223,6 +234,7 @@ fn an_unusable_scenario_is_refused() {
|
||||||
fn a_plan_naming_a_task_outside_the_scenario_is_refused() {
|
fn a_plan_naming_a_task_outside_the_scenario_is_refused() {
|
||||||
// A scheduling result that places a task the scenario does not hold is
|
// A scheduling result that places a task the scenario does not hold is
|
||||||
// inconsistent and must be rejected rather than run.
|
// inconsistent and must be rejected rather than run.
|
||||||
|
// 一个放置了场景所不持有任务的调度结果是不一致的,必须被拒绝而非运行。
|
||||||
let mut scenario = Scenario::new("mismatch");
|
let mut scenario = Scenario::new("mismatch");
|
||||||
scenario
|
scenario
|
||||||
.add_resource(apsis_resource::Resource::new(
|
.add_resource(apsis_resource::Resource::new(
|
||||||
|
|
@ -237,6 +249,7 @@ fn a_plan_naming_a_task_outside_the_scenario_is_refused() {
|
||||||
let mut schedule = apsis_schedule::Schedule::new();
|
let mut schedule = apsis_schedule::Schedule::new();
|
||||||
// Task 99 is not in the scenario. A placement records the duration, so
|
// Task 99 is not in the scenario. A placement records the duration, so
|
||||||
// it is placed directly rather than through a task.
|
// it is placed directly rather than through a task.
|
||||||
|
// 任务 99 不在场景中。放置记录时长,故直接放置它而非经任务。
|
||||||
schedule
|
schedule
|
||||||
.place(
|
.place(
|
||||||
&apsis_task::Task::new(apsis_task::TaskId::new(99), 60.0),
|
&apsis_task::Task::new(apsis_task::TaskId::new(99), 60.0),
|
||||||
|
|
@ -256,6 +269,7 @@ fn a_plan_naming_a_task_outside_the_scenario_is_refused() {
|
||||||
fn an_empty_plan_is_accepted() {
|
fn an_empty_plan_is_accepted() {
|
||||||
// A scenario whose tasks none can be placed still prepares; the
|
// A scenario whose tasks none can be placed still prepares; the
|
||||||
// resulting simulation simply has an empty timeline.
|
// resulting simulation simply has an empty timeline.
|
||||||
|
// 没有一个任务放得下的场景仍可准备;所得仿真只是有一条空时间线。
|
||||||
let scenario = Scenario::new("empty");
|
let scenario = Scenario::new("empty");
|
||||||
let result =
|
let result =
|
||||||
apsis_scheduler::Planner::new().schedule(&[], &scenario.resource_set().expect("ok"));
|
apsis_scheduler::Planner::new().schedule(&[], &scenario.resource_set().expect("ok"));
|
||||||
|
|
@ -285,6 +299,7 @@ fn the_session_can_record_without_running() {
|
||||||
assert!(session.record(2.0, RecordKind::Warning, "manual", 6.0));
|
assert!(session.record(2.0, RecordKind::Warning, "manual", 6.0));
|
||||||
assert_eq!(session.recorder().len(), 2);
|
assert_eq!(session.recorder().len(), 2);
|
||||||
// A run takes the trace, so the manual records come out with it.
|
// A run takes the trace, so the manual records come out with it.
|
||||||
|
// 一次运行取走轨迹,故手工记录随之而出。
|
||||||
let report: RunReport = session.run(|_, _, _| {});
|
let report: RunReport = session.run(|_, _, _| {});
|
||||||
assert_eq!(report.trace.len(), 2);
|
assert_eq!(report.trace.len(), 2);
|
||||||
assert_eq!(report.warning_count(), 1);
|
assert_eq!(report.warning_count(), 1);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue