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