diff --git a/crates/ops/apsis-conflict/tests/detect.rs b/crates/ops/apsis-conflict/tests/detect.rs index 3033ce7..0087773 100644 --- a/crates/ops/apsis-conflict/tests/detect.rs +++ b/crates/ops/apsis-conflict/tests/detect.rs @@ -1,10 +1,14 @@ //! Tests for conflict detection. +//! +//! 冲突检测的测试。 use apsis_conflict::{Conflict, ConflictKind, Precedence, Schedule, detect, detect_precedence}; use apsis_resource::{Resource, ResourceId, ResourceSet, Window}; use apsis_task::{Task, TaskId}; /// A resource available from 0 to 1 000 seconds. +/// +/// 一个自 0 到 1 000 秒可用的资源。 fn resource(id: usize, capacity: f64) -> Resource { let mut resource = Resource::new(ResourceId::new(id), capacity); resource @@ -14,6 +18,8 @@ fn resource(id: usize, capacity: f64) -> Resource { } /// A task requiring one unit of each named resource. +/// +/// 一个需要每个指名资源一单位的任务。 fn task(id: usize, duration: f64, resources: &[usize]) -> Task { let mut task = Task::new(TaskId::new(id), duration); for resource in resources { @@ -25,6 +31,8 @@ fn task(id: usize, duration: f64, resources: &[usize]) -> Task { } /// The standard resource set used by the tests. +/// +/// 测试所用的标准资源集。 fn resources() -> ResourceSet { ResourceSet::from_resources(vec![resource(0, 1.0), resource(1, 1.0)]) .expect("the identifiers are distinct") @@ -63,8 +71,10 @@ fn a_capacity_conflict_is_found() { let conflict = report.of_kind(ConflictKind::Capacity)[0]; assert_eq!(conflict.resource, Some(ResourceId::new(0))); // The conflict names both tasks, so a repair pass knows the pair. + // 该冲突指名两个任务,故修复遍可知道这一对。 assert!(conflict.other.is_some()); // The instant reported is the start of the second placement. + // 所报告的瞬时是第二处放置的开始。 assert_eq!(conflict.at, 30.0); assert!(conflict.is_movable()); } @@ -86,8 +96,10 @@ fn a_window_conflict_is_found_at_the_start() { let mut schedule = Schedule::new(); // The schedule refuses a placement before the window, so the conflict is // constructed directly to test the detector. + // 调度拒绝窗口之前的放置,故直接构建该冲突以测试检测器。 schedule.place(&tasks[0], 100.0).expect("fits"); // A task list whose window has moved since the schedule was built. + // 一个自调度构建以来窗口已移动的任务列表。 let moved = vec![task(0, 60.0, &[0]).with_window(200.0, 1_000.0)]; let report = detect(&schedule, &moved, &resources); @@ -104,12 +116,14 @@ fn a_window_conflict_is_found_at_the_end() { let mut schedule = Schedule::new(); schedule.place(&tasks[0], 100.0).expect("fits"); // A task list whose window closes earlier than the placement ends. + // 一个窗口比放置结束更早关闭的任务列表。 let shortened = vec![task(0, 60.0, &[0]).with_window(0.0, 120.0)]; let report = detect(&schedule, &shortened, &resources); assert_eq!(report.count_of(ConflictKind::Window), 1); let conflict = report.of_kind(ConflictKind::Window)[0]; // The instant reported is the end of the placement. + // 所报告的瞬时是该放置的结束。 assert_eq!(conflict.at, 160.0); } @@ -121,6 +135,7 @@ fn a_requirement_conflict_is_found_for_a_missing_task() { schedule.place(&tasks[0], 0.0).expect("fits"); // The task list is empty, so the placement has no task. + // 任务列表为空,故该放置没有任务。 let report = detect(&schedule, &[], &resources); assert_eq!(report.count_of(ConflictKind::Requirement), 1); assert_eq!(report.placed_tasks(), 1); @@ -156,6 +171,7 @@ fn a_requirement_conflict_is_found_for_a_missing_resource() { fn a_requirement_conflict_is_found_for_an_impossible_demand() { let resources = resources(); // A demand of two on a resource of capacity one can never be met. + // 容量为一的资源上需求为二永远无法满足。 let tasks = vec![ Task::new(TaskId::new(0), 60.0) .try_require(ResourceId::new(0), 2.0) @@ -173,6 +189,7 @@ fn a_requirement_conflict_is_found_for_an_impossible_demand() { #[test] fn an_availability_conflict_is_found_outside_the_windows() { // The resource is available only from 200 to 300 seconds. + // 该资源只在 200 至 300 秒可用。 let mut limited = Resource::new(ResourceId::new(0), 1.0); limited .set_windows(&[Window::new(200.0, 300.0)]) @@ -194,6 +211,7 @@ fn an_availability_conflict_is_found_outside_the_windows() { fn an_availability_conflict_is_found_for_a_placement_that_overruns_a_window() { // The resource is available only from 0 to 120 seconds, so a placement // from 100 for 60 seconds overruns it. + // 该资源只在 0 至 120 秒可用,故自 100 起持续 60 秒的放置会超出它。 let mut limited = Resource::new(ResourceId::new(0), 1.0); limited .set_windows(&[Window::new(0.0, 120.0)]) @@ -221,6 +239,7 @@ fn a_placement_inside_the_availability_has_no_conflict() { #[test] fn several_conflicts_are_reported_together() { // The detector reports every problem, not just the first. + // 检测器报告每个问题,而不只是第一个。 let resources = resources(); let tasks = [task(0, 60.0, &[0]), task(1, 60.0, &[0])]; let mut schedule = Schedule::new(); @@ -228,6 +247,7 @@ fn several_conflicts_are_reported_together() { schedule.place(&tasks[1], 30.0).expect("fits"); // A task list that also moves the window of the first task. + // 一个还移动了第一个任务窗口的任务列表。 let changed = vec![ task(0, 60.0, &[0]).with_window(100.0, 1_000.0), task(1, 60.0, &[0]), @@ -245,6 +265,8 @@ fn the_report_is_ordered_by_time() { // changed so that both placements fall before the new window opens. The // schedule refuses such a placement, which is why the changed list is // used for the check. + // 各放置在一个宽窗口内作出,然后任务列表被改变,使两处放置都落在新窗口打开之前。 + // 调度拒绝这样的放置,故检查使用改变后的列表。 let placed = [task(0, 60.0, &[0]), task(1, 60.0, &[1])]; let mut schedule = Schedule::new(); schedule.place(&placed[0], 100.0).expect("fits"); @@ -273,6 +295,7 @@ fn the_report_lists_the_conflicting_tasks_once_each() { let report = detect(&schedule, &tasks, &resources); let tasks_involved = report.conflicting_tasks(); // Both tasks are involved, and each appears once. + // 两个任务都涉及,且各自出现一次。 assert!(tasks_involved.contains(&TaskId::new(0))); assert!(tasks_involved.contains(&TaskId::new(1))); assert_eq!(tasks_involved.len(), 2); @@ -301,6 +324,7 @@ fn a_precedence_violation_is_found() { let mut schedule = Schedule::new(); schedule.place(&tasks[0], 100.0).expect("fits"); // The second task must follow the first, but starts inside it. + // 第二个任务必须跟随第一个,但起始于其中。 schedule.place(&tasks[1], 120.0).expect("fits"); let conflicts = detect_precedence( @@ -322,6 +346,7 @@ fn a_satisfied_precedence_has_no_conflict() { schedule.place(&tasks[0], 100.0).expect("fits"); // Starting exactly at the end of the first is allowed, because the // placement interval is half-open. + // 恰在第一个结束处开始是允许的,因为放置区间是半开的。 schedule.place(&tasks[1], 160.0).expect("fits"); assert!( detect_precedence( @@ -338,6 +363,7 @@ fn a_precedence_over_an_unplaced_task_is_ignored() { let mut schedule = Schedule::new(); schedule.place(&tasks[0], 100.0).expect("fits"); // The second task is not placed, so nothing has been promised. + // 第二个任务未被放置,故没有承诺任何东西。 assert!( detect_precedence( &schedule, diff --git a/crates/ops/apsis-resource/tests/resource.rs b/crates/ops/apsis-resource/tests/resource.rs index 526ffc7..19d21a2 100644 --- a/crates/ops/apsis-resource/tests/resource.rs +++ b/crates/ops/apsis-resource/tests/resource.rs @@ -1,8 +1,12 @@ //! Tests for resources and the set that holds them. +//! +//! 资源及保存它们的集合的测试。 use apsis_resource::{Interval, Resource, ResourceErrorKind, ResourceId, ResourceSet, Window}; /// A resource with one window from 100 to 200 seconds. +/// +/// 一个含 100 至 200 秒窗口的资源。 fn station() -> Resource { let mut resource = Resource::new(ResourceId::new(0), 1.0); resource @@ -39,6 +43,7 @@ fn availability_is_half_open_at_the_edges() { assert!(resource.is_available_at(199.999)); // The end instant belongs to the next interval, so the resource stops // being available exactly there. + // 结束瞬时属于下一区间,故资源恰在那里停止可用。 assert!(!resource.is_available_at(200.0)); } @@ -49,6 +54,7 @@ fn availability_over_a_duration_requires_the_whole_span() { assert!(resource.is_available_over(120.0, 80.0)); assert!(resource.is_available_over(150.0, 50.0)); // A request that reaches past the window fails, even by a little. + // 越过窗口的请求失败,即便只越过一点。 assert!(!resource.is_available_over(150.0, 50.001)); assert!(!resource.is_available_over(99.0, 2.0)); } @@ -59,6 +65,7 @@ fn a_zero_duration_request_is_an_instantaneous_query() { assert!(resource.is_available_over(150.0, 0.0)); assert!(!resource.is_available_over(250.0, 0.0)); // The half-open boundary applies here too. + // 半开边界在此也适用。 assert!(!resource.is_available_over(200.0, 0.0)); } @@ -77,6 +84,7 @@ fn adding_overlapping_windows_merges_them() { resource.add_window(Window::new(0.0, 10.0)).expect("usable"); resource.add_window(Window::new(5.0, 15.0)).expect("usable"); // The two overlap, so the stored form is one window. + // 二者重叠,故存储形式是一个窗口。 assert_eq!(resource.window_count(), 1); assert_eq!(resource.windows()[0], Window::new(0.0, 15.0)); assert_eq!(resource.total_availability(), 15.0); @@ -91,6 +99,7 @@ fn adding_disjoint_windows_keeps_them_separate() { resource.add_window(Window::new(0.0, 10.0)).expect("usable"); assert_eq!(resource.window_count(), 2); // The stored order is time order, not insertion order. + // 存储次序是时间次序,而非插入次序。 assert_eq!(resource.windows()[0].start(), 0.0); assert_eq!(resource.windows()[1].start(), 30.0); } @@ -120,6 +129,7 @@ fn setting_the_windows_replaces_them() { assert!(resource.is_available_at(5.0)); // Setting nothing removes all availability. + // 什么都不设置即移除所有可用性。 resource.set_windows(&[]).expect("usable"); assert!(resource.is_empty()); } @@ -145,12 +155,16 @@ fn the_earliest_fit_is_the_first_window_long_enough() { .expect("usable"); // A request of five seconds fits in the first window. + // 五秒的请求放入第一个窗口。 assert_eq!(resource.earliest_fit(5.0), Some(Window::new(0.0, 5.0))); // A request of twenty does not, so it takes the second. + // 二十秒的放不下,故取第二个。 assert_eq!(resource.earliest_fit(20.0), Some(Window::new(30.0, 50.0))); // A request longer than any window has no answer. + // 比任何窗口都长的请求没有答案。 assert_eq!(resource.earliest_fit(40.0), None); // An instantaneous request fits at the start of the first window. + // 瞬时请求放入第一个窗口的开头。 assert_eq!(resource.earliest_fit(0.0), Some(Window::new(0.0, 0.0))); assert_eq!(resource.earliest_fit(-1.0), None); assert_eq!(resource.earliest_fit(f64::NAN), None); @@ -164,17 +178,20 @@ fn the_earliest_fit_after_a_time_respects_the_lower_bound() { .expect("usable"); // Starting no earlier than five, the first window can still hold it. + // 不早于五开始,第一个窗口仍能容纳它。 assert_eq!( resource.earliest_fit_after(5.0, 4.0), Some(Window::new(5.0, 9.0)) ); // A request of eight seconds starting at five does not fit, because the // window ends at ten, so the second window is used. + // 自五开始、八秒的请求放不下,因为窗口在十结束,故使用第二个窗口。 assert_eq!( resource.earliest_fit_after(5.0, 8.0), Some(Window::new(30.0, 38.0)) ); // Starting after the second window begins, the request starts there. + // 在第二个窗口开始之后起步,该请求从那里开始。 assert_eq!( resource.earliest_fit_after(40.0, 10.0), Some(Window::new(40.0, 50.0)) @@ -204,6 +221,7 @@ fn the_capacity_is_validated() { assert_eq!(error.kind(), ResourceErrorKind::InvalidCapacity); } // The capacity is unchanged after a failed set. + // 失败的设置之后容量不变。 assert_eq!(resource.capacity(), 2.5); let invalid = Resource::new(ResourceId::new(0), -1.0); @@ -286,6 +304,7 @@ fn the_set_reports_availability() { .expect("new"); // Only the first resource has any availability. + // 只有第一个资源有任何可用性。 assert_eq!(set.available_at(150.0), vec![ResourceId::new(0)]); assert!(set.available_at(50.0).is_empty()); assert_eq!(set.available_over(120.0, 50.0), vec![ResourceId::new(0)]); @@ -303,6 +322,7 @@ fn the_identifier_displays_its_index() { #[test] fn identifiers_are_ordered() { // Ordering lets a caller sort a list of resources deterministically. + // 排序使调用方能确定地对资源列表排序。 let mut ids = [ResourceId::new(3), ResourceId::new(1), ResourceId::new(2)]; ids.sort(); assert_eq!( diff --git a/crates/ops/apsis-resource/tests/window.rs b/crates/ops/apsis-resource/tests/window.rs index 7fb9e9a..8a60d6d 100644 --- a/crates/ops/apsis-resource/tests/window.rs +++ b/crates/ops/apsis-resource/tests/window.rs @@ -1,4 +1,6 @@ //! Tests for availability windows and the interval algebra. +//! +//! 可用性窗口以及区间代数的测试。 use apsis_resource::{Interval, ResourceErrorKind, Window, merge_windows, subtract_window}; @@ -6,6 +8,7 @@ use apsis_resource::{Interval, ResourceErrorKind, Window, merge_windows, subtrac fn an_interval_reports_its_duration() { assert_eq!(Interval::new(10.0, 30.0).duration(), 20.0); // An empty or reversed interval has no duration. + // 空或反向的区间没有时长。 assert_eq!(Interval::new(30.0, 30.0).duration(), 0.0); assert_eq!(Interval::new(30.0, 10.0).duration(), 0.0); } @@ -17,6 +20,7 @@ fn emptiness_and_validity_are_reported() { assert!(!Interval::new(4.0, 5.0).is_empty()); assert!(Interval::new(4.0, 5.0).is_valid()); // A reversed interval is not valid, although it is empty. + // 反向区间无效,尽管它是空的。 assert!(!Interval::new(5.0, 4.0).is_valid()); assert!(!Interval::new(f64::NAN, 5.0).is_valid()); assert!(!Interval::new(4.0, f64::INFINITY).is_valid()); @@ -26,6 +30,7 @@ fn emptiness_and_validity_are_reported() { fn containment_is_half_open() { let interval = Interval::new(10.0, 20.0); // The start is inside and the end is not. + // 起点在内而终点不在。 assert!(!interval.contains(9.999)); assert!(interval.contains(10.0)); assert!(interval.contains(15.0)); @@ -39,10 +44,12 @@ fn containing_an_interval_includes_the_boundaries() { assert!(outer.contains_interval(&Interval::new(10.0, 20.0))); // The boundaries are included, so an interval that shares an end is // contained. + // 各边界被包含,故共享一个端点的区间被包含。 assert!(outer.contains_interval(&Interval::new(0.0, 100.0))); assert!(outer.contains_interval(&Interval::new(0.0, 50.0))); assert!(outer.contains_interval(&Interval::new(50.0, 100.0))); // An interval that reaches past the end is not. + // 越过终点的区间则不是。 assert!(!outer.contains_interval(&Interval::new(50.0, 100.1))); assert!(!outer.contains_interval(&Interval::new(-0.1, 50.0))); } @@ -58,6 +65,7 @@ fn an_empty_interval_is_contained_when_its_instant_is() { fn intervals_that_meet_do_not_overlap() { // The half-open convention means the boundary instant belongs to the // second interval only. + // 半开约定意味着边界瞬时只属于第二个区间。 let first = Interval::new(0.0, 10.0); let second = Interval::new(10.0, 20.0); assert!(!first.overlaps(&second)); @@ -73,6 +81,7 @@ fn overlapping_intervals_report_their_intersection() { assert_eq!(first.intersection(&second), Some(Interval::new(5.0, 10.0))); assert_eq!(second.intersection(&first), first.intersection(&second)); // Disjoint intervals have no intersection. + // 不相交区间没有交集。 assert_eq!( Interval::new(0.0, 1.0).intersection(&Interval::new(2.0, 3.0)), None @@ -104,6 +113,7 @@ fn merging_combines_overlapping_windows() { fn merging_combines_windows_that_touch() { // Two windows that meet share a boundary instant, which belongs to the // second, so the union is one continuous window. + // 相接的两个窗口共享一个边界瞬时,它属于第二个,故并集是一个连续窗口。 let merged = merge_windows(&[Window::new(0.0, 10.0), Window::new(10.0, 20.0)]) .expect("the windows are usable"); assert_eq!(merged, vec![Window::new(0.0, 20.0)]); @@ -113,6 +123,7 @@ fn merging_combines_windows_that_touch() { fn merging_drops_empty_windows() { // A zero length window is valid but contributes no availability, so it // is dropped. A reversed window is a caller error and is rejected. + // 零长窗口有效但不贡献可用性,故被丢弃。反向窗口是调用方错误并被拒绝。 let merged = merge_windows(&[ Window::new(5.0, 5.0), Window::new(0.0, 10.0), @@ -144,6 +155,7 @@ fn merging_an_empty_list_gives_an_empty_list() { fn merging_is_idempotent() { // The canonical form must be a fixed point, so that comparing two sets // is a direct comparison. + // 规范形式必须是自身的固定点,使比较两个集合成为直接比较。 let windows = [ Window::new(0.0, 10.0), Window::new(5.0, 15.0), @@ -197,6 +209,7 @@ fn subtracting_the_whole_of_a_window_leaves_nothing() { assert!(result.is_empty()); // A window that covers more than the stored one also removes it. + // 覆盖范围超过所存者的窗口也会移除它。 let result = subtract_window(&[Window::new(0.0, 10.0)], Window::new(-5.0, 15.0)).expect("usable"); assert!(result.is_empty()); @@ -240,6 +253,8 @@ fn subtracting_from_several_windows_affects_each() { fn subtracting_merges_the_windows_first() { // The two overlapping windows form one, which the subtraction then // splits. Working on the unmerged list would give a different answer. + // 两个重叠窗口合为一个,减法随后又把它分开。在未合并的列表上工作会给出不同的 + // 答案。 let windows = [Window::new(0.0, 10.0), Window::new(5.0, 15.0)]; let result = subtract_window(&windows, Window::new(8.0, 12.0)).expect("usable"); assert_eq!(result, vec![Window::new(0.0, 8.0), Window::new(12.0, 15.0)]); diff --git a/crates/ops/apsis-schedule/tests/schedule.rs b/crates/ops/apsis-schedule/tests/schedule.rs index 2cbb176..6333210 100644 --- a/crates/ops/apsis-schedule/tests/schedule.rs +++ b/crates/ops/apsis-schedule/tests/schedule.rs @@ -1,10 +1,14 @@ //! Tests for the schedule representation and its evaluation. +//! +//! 调度表示及其评估的测试。 use apsis_resource::{Resource, ResourceId, ResourceSet, Window}; use apsis_schedule::{Evaluation, Placement, Schedule, ScheduleErrorKind}; use apsis_task::{Task, TaskId}; /// A resource with the given identifier available from 0 to 1 000 seconds. +/// +/// 具有给定标识符、自 0 到 1 000 秒可用的资源。 fn resource(id: usize, capacity: f64) -> Resource { let mut resource = Resource::new(ResourceId::new(id), capacity); resource @@ -14,6 +18,8 @@ fn resource(id: usize, capacity: f64) -> Resource { } /// A task requiring one unit of the given resources. +/// +/// 一个需要各给定资源一单位的任务。 fn task(id: usize, duration: f64, resources: &[usize]) -> Task { let mut task = Task::new(TaskId::new(id), duration); for resource in resources { @@ -81,11 +87,13 @@ fn a_placement_after_the_window_is_rejected() { let mut schedule = Schedule::new(); let task = task(0, 60.0, &[0]).with_window(0.0, 100.0); // Starting at fifty would end at one hundred and ten, past the window. + // 自五十开始会在百一十结束,越过窗口。 let error = schedule .place(&task, 50.0) .expect_err("the placement is too late"); assert_eq!(error.kind(), ScheduleErrorKind::AfterWindow); // Starting at forty ends exactly at the window close, which is allowed. + // 自四十开始恰在窗口关闭处结束,这是允许的。 assert!(schedule.place(&task, 40.0).is_ok()); } @@ -120,6 +128,7 @@ fn a_task_can_be_unplaced() { assert_eq!(schedule.len(), 1); assert!(!schedule.is_placed(TaskId::new(0))); // The remaining placement is still found, so the index was rebuilt. + // 剩余的放置仍被找到,故索引被重建。 assert!(schedule.is_placed(TaskId::new(1))); assert_eq!( schedule @@ -145,6 +154,7 @@ fn the_index_survives_several_removals() { .expect("the task fits"); } // Remove from the middle, which shifts every later placement. + // 从中间移除,这会移动其后每处放置。 schedule.unplace(TaskId::new(2)).expect("placed"); for index in [0_usize, 1, 3, 4] { let placement = schedule @@ -167,6 +177,7 @@ fn placements_on_a_resource_are_found_in_time_order() { let mut schedule = Schedule::new(); // Placed out of time order. + // 按非时间次序放置。 schedule.place(&tasks[2], 200.0).expect("fits"); schedule.place(&tasks[0], 0.0).expect("fits"); schedule.place(&tasks[1], 100.0).expect("fits"); @@ -197,6 +208,7 @@ fn overlapping_placements_are_detected() { schedule.place(&second, 30.0).expect("fits"); // The two overlap in time, although they use different resources, so the // schedule reports an overlap and the capacity check allows it. + // 二者在时间上重叠,尽管使用不同资源,故调度报告一次重叠而容量检查允许它。 assert!(schedule.has_overlapping_placements()); } @@ -236,6 +248,7 @@ fn the_capacity_check_rejects_an_exclusive_resource_used_twice() { #[test] fn a_shared_resource_allows_two_tasks_within_the_capacity() { // A resource of capacity two holds two unit tasks at once. + // 容量为二的资源同时容纳两个单位任务。 let resources = ResourceSet::from_resources(vec![resource(0, 2.0)]).expect("one"); let tasks = vec![task(0, 60.0, &[0]), task(1, 60.0, &[0])]; let mut schedule = Schedule::new(); @@ -244,6 +257,7 @@ fn a_shared_resource_allows_two_tasks_within_the_capacity() { assert!(schedule.validate_capacity(&tasks, &resources).is_ok()); // A third would exceed it. + // 第三个会超过它。 let third = task(2, 60.0, &[0]); schedule.place(&third, 45.0).expect("fits"); let mut all = tasks.clone(); @@ -260,6 +274,7 @@ fn a_shared_resource_allows_two_tasks_within_the_capacity() { #[test] fn fractional_demands_share_a_resource() { // Four tasks of a quarter unit each fill a resource of capacity one. + // 四个各四分之一单位的任务填满容量为一的资源。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let tasks: Vec = (0..4) .map(|index| { @@ -296,6 +311,7 @@ fn the_capacity_check_rejects_a_placement_without_its_task() { let mut schedule = Schedule::new(); schedule.place(&tasks[0], 0.0).expect("fits"); // The task list does not contain the placed task. + // 任务列表不含所放置的任务。 assert_eq!( schedule .validate_capacity(&[], &resources) @@ -312,6 +328,7 @@ fn the_capacity_check_rejects_a_duration_mismatch() { let mut schedule = Schedule::new(); schedule.place(&original, 0.0).expect("fits"); // A task with the same identifier but a different duration. + // 一个标识符相同但时长不同的任务。 let different = task(0, 90.0, &[0]); assert_eq!( schedule @@ -352,6 +369,7 @@ fn the_evaluation_reports_resource_usage() { ]; let mut schedule = Schedule::new(); // The two on resource zero overlap, so the peak demand is two. + // 资源零上的二者重叠,故峰值需求为二。 schedule.place(&tasks[0], 0.0).expect("fits"); schedule.place(&tasks[1], 30.0).expect("fits"); schedule.place(&tasks[2], 0.0).expect("fits"); @@ -362,6 +380,7 @@ fn the_evaluation_reports_resource_usage() { .expect("the resource is used"); assert_eq!(zero.peak_demand, 2.0); // The two overlap for thirty seconds, so the busy time is ninety. + // 二者重叠三十秒,故繁忙时间为九十。 assert_eq!(zero.busy_seconds, 90.0); assert_eq!(zero.placement_count, 2); assert_eq!(zero.utilisation(180.0), Some(0.5)); @@ -386,6 +405,7 @@ fn the_evaluation_of_an_empty_schedule_is_zero() { assert_eq!(evaluation.makespan(), 0.0); // With nothing offered and nothing placed, the schedule is vacuously // complete, but there is no fill rate to report. + // 没有提供、也没有放置任何东西时,调度空洞地完整,但没有可报告的填充率。 assert_eq!(evaluation.fill_rate(), None); assert!(evaluation.is_complete()); let zero = evaluation.usage_of(ResourceId::new(0)).expect("present"); @@ -416,12 +436,15 @@ fn the_time_order_differs_from_the_insertion_order() { schedule.place(&tasks[1], 0.0).expect("fits"); // The insertion order is 0 then 1. + // 插入次序是 0 然后 1。 assert_eq!(schedule.placements()[0].task, TaskId::new(0)); // The time order is 1 then 0. + // 时间次序是 1 然后 0。 let ordered = schedule.in_time_order(); assert_eq!(ordered[0].task, TaskId::new(1)); assert_eq!(ordered[1].task, TaskId::new(0)); // The original is unchanged. + // 原件不变。 assert_eq!(schedule.placements()[0].task, TaskId::new(0)); } @@ -437,11 +460,13 @@ fn the_resource_busy_until_reports_the_next_free_time() { Some(60.0) ); // Resource zero is idle at a hundred seconds. + // 资源零在百秒处空闲。 assert_eq!( schedule.resource_busy_until(ResourceId::new(0), &tasks, 100.0), None ); // Resource one is busy at thirty seconds and free at ninety. + // 资源一在三十秒处繁忙、在九十秒处空闲。 assert_eq!( schedule.resource_busy_until(ResourceId::new(1), &tasks, 50.0), Some(90.0) @@ -455,6 +480,7 @@ fn a_placement_reports_what_it_covers() { assert!(placement.covers(100.0)); assert!(placement.covers(159.999)); // The end is not covered, so back to back placements do not overlap. + // 终点不被覆盖,故前后相接的放置不重叠。 assert!(!placement.covers(160.0)); assert!(!placement.covers(99.999)); assert!(placement.is_valid()); @@ -475,6 +501,7 @@ fn an_invalid_placement_is_reported() { assert!(!Placement::new(TaskId::new(0), f64::NAN, 60.0).is_valid()); assert!(!Placement::new(TaskId::new(0), 0.0, -1.0).is_valid()); // A zero duration placement is valid, for an instantaneous task. + // 零时长放置有效,用于瞬时任务。 assert!(Placement::new(TaskId::new(0), 0.0, 0.0).is_valid()); } @@ -482,6 +509,7 @@ fn an_invalid_placement_is_reported() { fn the_evaluation_type_is_nameable() { // The type must be usable by a caller that stores the results, and an // empty schedule against one resource must report itself as empty. + // 该类型必须能被存储结果的调用方使用,且针对一个资源的空调度必须报告自身为空。 fn takes(_evaluation: &Evaluation) {} let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let schedule = Schedule::new(); diff --git a/crates/ops/apsis-scheduler/tests/planner.rs b/crates/ops/apsis-scheduler/tests/planner.rs index b61b1c6..dab344b 100644 --- a/crates/ops/apsis-scheduler/tests/planner.rs +++ b/crates/ops/apsis-scheduler/tests/planner.rs @@ -1,4 +1,6 @@ //! Tests for the greedy planner. +//! +//! 贪心规划器的测试。 use apsis_resource::{Resource, ResourceId, ResourceSet, Window}; use apsis_schedule::Schedule; @@ -9,6 +11,8 @@ use apsis_scheduler::{ use apsis_task::{Task, TaskId, TaskState}; /// A resource available from 0 to 1 000 seconds. +/// +/// 一个自 0 到 1 000 秒可用的资源。 fn resource(id: usize, capacity: f64) -> Resource { let mut resource = Resource::new(ResourceId::new(id), capacity); resource @@ -18,6 +22,8 @@ fn resource(id: usize, capacity: f64) -> Resource { } /// A task requiring one unit of each named resource. +/// +/// 一个需要每个指名资源一单位的任务。 fn task(id: usize, duration: f64, resources: &[usize]) -> Task { let mut task = Task::new(TaskId::new(id), duration); for resource in resources { @@ -41,6 +47,7 @@ fn a_single_task_is_placed_at_the_opening_of_its_window() { .placement_of(TaskId::new(0)) .expect("placed"); // The resource is available from zero, so the task starts there. + // 资源自零起可用,故任务从那里开始。 assert_eq!(placement.start, 0.0); } @@ -49,6 +56,7 @@ fn tasks_on_one_exclusive_resource_are_placed_back_to_back() { let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); // Equal value, so the order falls back to the earlier deadline and then // the identifier. + // 价值相等,故次序回退到更早的截止时间,然后回退到标识符。 let tasks = [task(0, 60.0, &[0]), task(1, 30.0, &[0])]; let result = Planner::new().schedule(&tasks, &resources); @@ -63,6 +71,7 @@ fn tasks_on_one_exclusive_resource_are_placed_back_to_back() { .expect("placed"); // The two must not overlap: the second starts at or after the end of the // first. + // 二者不得重叠:第二个在第一个结束处或之后开始。 assert!(second.start >= first.end(), "{second:?} against {first:?}"); assert!(!result.schedule.has_overlapping_placements()); } @@ -71,6 +80,7 @@ fn tasks_on_one_exclusive_resource_are_placed_back_to_back() { fn a_task_that_does_not_fit_is_deferred() { // The resource is available for only sixty seconds, so a task of ninety // cannot be placed. + // 资源只可用六十秒,故九十秒的任务无法放置。 let mut limited = Resource::new(ResourceId::new(0), 1.0); limited .set_windows(&[Window::new(0.0, 60.0)]) @@ -131,6 +141,7 @@ fn a_mandatory_task_is_placed_before_a_more_valuable_optional_one() { let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); // The optional task is worth far more, but it can use whatever time is // left, while the mandatory task has only one window. + // 可选任务价值高得多,但它可以使用剩余的任何时间,而强制任务只有一个窗口。 let optional = task(0, 60.0, &[0]) .with_priority(100.0) .with_window(0.0, 1_000.0); @@ -151,6 +162,8 @@ fn a_mandatory_task_is_placed_before_a_more_valuable_optional_one() { // With the rule turned off, and the improvement pass disabled so that // only the ordering is under test, the optional task goes first and the // mandatory one is placed after it, using the rest of its window. + // 关掉该规则、并禁用改进遍(使只有排序受检),可选任务先走,强制任务紧随其后放置, + // 用它窗口的剩余部分。 let without = Planner::new() .with_mandatory_first(false) .with_improvement(apsis_scheduler::ImprovementSettings::disabled()) @@ -169,6 +182,8 @@ fn the_improvement_pass_recovers_a_plan_that_the_greedy_order_missed() { // zero to a hundred, so it cannot fit after the optional one and would // be lost. The improvement pass moves the optional task aside and places // both. + // 可选任务价值更高,故先被放置并占用资源自 0 到 60。强制任务的窗口自 0 到 100, + // 故它放不进可选任务之后,会丢失。改进遍把可选任务挪开并放置二者。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let optional = task(0, 60.0, &[0]) .with_priority(100.0) @@ -184,6 +199,7 @@ fn the_improvement_pass_recovers_a_plan_that_the_greedy_order_missed() { .with_improvement(apsis_scheduler::ImprovementSettings::disabled()) .schedule(&tasks, &resources); // Without the pass the mandatory task is lost. + // 没有该遍时强制任务丢失。 assert!(!greedy.honours_mandatory(&tasks)); assert_eq!(greedy.schedule.len(), 1); @@ -191,6 +207,7 @@ fn the_improvement_pass_recovers_a_plan_that_the_greedy_order_missed() { .with_mandatory_first(false) .schedule(&tasks, &resources); // With the pass both are placed. + // 有该遍时二者都被放置。 assert!(improved.is_complete(), "{:?}", improved.outcomes); assert!(improved.honours_mandatory(&tasks)); assert!(!improved.improved.is_empty()); @@ -208,6 +225,7 @@ fn the_value_ordering_places_the_most_valuable_task_first() { let result = Planner::new().schedule(&tasks, &resources); assert!(result.is_complete()); // The weighted values are 10, 500, and 100, so task 1 goes first. + // 加权值为 10、500 与 100,故任务 1 先走。 assert_eq!( result .schedule @@ -234,6 +252,7 @@ fn two_tasks_on_a_shared_resource_can_overlap() { .placement_of(TaskId::new(1)) .expect("placed"); // Both start as early as possible, because the capacity holds them both. + // 二者都尽早开始,因为容量能容纳二者。 assert_eq!(first.start, 0.0); assert_eq!(second.start, 0.0); assert!(result.schedule.has_overlapping_placements()); @@ -244,6 +263,7 @@ fn a_task_is_placed_after_the_other_frees_its_resource() { let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); // The first task occupies the resource from 100 to 200, so the second, // which can start no earlier than 100, must wait. + // 第一个任务占用资源自 100 到 200,故不得早于 100 开始的第二个必须等待。 let first = task(0, 100.0, &[0]).with_window(100.0, 1_000.0); let second = task(1, 50.0, &[0]).with_window(100.0, 1_000.0); let tasks = [first, second]; @@ -295,6 +315,7 @@ fn the_earliest_start_accounts_for_a_blocking_placement() { let start = earliest_feasible_start(&candidate, &tasks, &schedule, &resources) .expect("a placement exists"); // The resource is held until a hundred, so the earliest start is there. + // 资源被占至百秒,故最早开始在那里。 assert_eq!(start, 100.0); } @@ -309,10 +330,12 @@ fn feasibility_reports_the_capacity_limit() { let tasks = [existing, candidate.clone()]; // Starting at fifty would overlap the existing placement, which the // capacity of one does not allow. + // 自五十开始会与已有放置重叠,容量为一不允许。 assert!(!feasible_at( &candidate, &tasks, &schedule, &resources, 50.0 )); // Starting at a hundred is free. + // 自百秒开始是空闲的。 assert!(feasible_at( &candidate, &tasks, &schedule, &resources, 100.0 )); @@ -331,6 +354,7 @@ fn feasibility_reports_the_availability_limit() { assert!(feasible_at(&tasks[0], &tasks, &schedule, &resources, 0.0)); assert!(feasible_at(&tasks[0], &tasks, &schedule, &resources, 40.0)); // Starting at fifty would end at one hundred and ten, past the window. + // 自五十开始会在百一十结束,越过窗口。 assert!(!feasible_at(&tasks[0], &tasks, &schedule, &resources, 50.0)); } @@ -339,10 +363,12 @@ fn the_capacity_check_covers_a_multiple_resource_task() { let resources = ResourceSet::from_resources(vec![resource(0, 1.0), resource(1, 1.0)]).expect("distinct"); // The task needs both resources at once. + // 该任务同时需要两种资源。 let tasks = [task(0, 60.0, &[0, 1])]; assert!(resources_have_capacity(&tasks[0], &resources)); // The same task with one resource missing is infeasible. + // 同一任务缺少一个资源即不可行。 let missing = [task(0, 60.0, &[0, 9])]; assert!(!resources_have_capacity(&missing[0], &resources)); assert_eq!(infeasible_tasks(&missing, &resources), vec![TaskId::new(0)]); @@ -361,6 +387,7 @@ fn the_demand_at_an_instant_is_reported_per_resource() { assert_eq!(demand[0], (ResourceId::new(0), 1.0)); assert_eq!(demand[1], (ResourceId::new(1), 1.0)); // Before either placement the demand is zero. + // 在两处放置之前需求为零。 let demand = demand_at(&schedule, &tasks, &resources, -1.0); assert_eq!(demand[0].1, 0.0); } @@ -374,11 +401,13 @@ fn the_free_resources_at_an_instant_exclude_the_busy_ones() { schedule.place(&tasks[0], 0.0).expect("fits"); // At fifty seconds resource zero is full and resource one is free. + // 五十秒处资源零已满而资源一空闲。 assert_eq!( resources_free_at(&schedule, &tasks, &resources, 50.0), vec![ResourceId::new(1)] ); // At a hundred and fifty both are free. + // 百五十秒处二者都空闲。 assert_eq!( resources_free_at(&schedule, &tasks, &resources, 150.0), vec![ResourceId::new(0), ResourceId::new(1)] @@ -415,6 +444,7 @@ fn the_result_reports_every_task() { assert!(result.outcome_of(task.id).is_some()); } // Only the first can be placed. + // 只有第一个能被放置。 assert_eq!(result.scheduled(), vec![TaskId::new(0)]); assert!(!result.is_complete()); assert!(result.honours_mandatory(&tasks)); @@ -429,6 +459,7 @@ fn a_mandatory_task_that_cannot_be_placed_is_reported() { let result = Planner::new().schedule(&tasks, &resources); // The plan does not honour the mandatory task, which the caller can // detect. + // 该计划未兑现强制任务,调用方可检测到。 assert!(!result.honours_mandatory(&tasks)); assert!(!result.is_complete()); } @@ -454,12 +485,15 @@ fn the_infeasible_task_list_excludes_the_merely_deferred() { let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let tasks = [ // Can be placed. + // 可以放置。 task(0, 60.0, &[0]), // Demands too much. + // 需求过多。 Task::new(TaskId::new(1), 60.0) .try_require(ResourceId::new(0), 5.0) .expect("the resource is new"), // Names a resource that does not exist. + // 指名了不存在的资源。 task(2, 60.0, &[9]), ]; let infeasible = infeasible_tasks(&tasks, &resources); diff --git a/crates/ops/apsis-scheduler/tests/precedence.rs b/crates/ops/apsis-scheduler/tests/precedence.rs index 77adfcd..3ffedef 100644 --- a/crates/ops/apsis-scheduler/tests/precedence.rs +++ b/crates/ops/apsis-scheduler/tests/precedence.rs @@ -1,11 +1,15 @@ //! Tests for precedence honoured by the planner, and for the improvement //! pass. +//! +//! 规划器所遵守的先后约束,以及改进遍的测试。 use apsis_resource::{Resource, ResourceId, ResourceSet, Window}; use apsis_scheduler::{ImprovementSettings, Planner}; use apsis_task::{PrecedenceGraph, Task, TaskId}; /// A resource available from 0 to 1 000 seconds. +/// +/// 一个自 0 到 1 000 秒可用的资源。 fn resource(id: usize, capacity: f64) -> Resource { let mut resource = Resource::new(ResourceId::new(id), capacity); resource @@ -15,6 +19,8 @@ fn resource(id: usize, capacity: f64) -> Resource { } /// A task requiring one unit of each named resource. +/// +/// 一个需要每个指名资源一单位的任务。 fn task(id: usize, duration: f64, resources: &[usize]) -> Task { let mut task = Task::new(TaskId::new(id), duration); for resource in resources { @@ -28,6 +34,7 @@ fn task(id: usize, duration: f64, resources: &[usize]) -> Task { #[test] fn a_chain_of_tasks_is_placed_in_order() { // Three tasks that must run one after another on the same resource. + // 三个必须在同一资源上依次运行的任务。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let tasks = [ task(0, 60.0, &[0]), @@ -63,6 +70,7 @@ fn a_chain_of_tasks_is_placed_in_order() { fn a_precedence_floor_delays_a_task_that_could_start_earlier() { // The second task could start at zero on its own, but must wait for the // first, which runs on a different resource. + // 第二个任务本身可以从零开始,但必须等待在另一资源上运行的第一个。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0), resource(1, 1.0)]).expect("distinct"); let tasks = [task(0, 100.0, &[0]), task(1, 60.0, &[1])]; @@ -77,6 +85,7 @@ fn a_precedence_floor_delays_a_task_that_could_start_earlier() { .expect("placed"); // The second resource was free from the start, but the constraint delays // the task to the end of the first. + // 第二个资源从一开始就空闲,但该约束把任务推迟到第一个结束。 assert_eq!(second.start, 100.0); assert!(result.honours_precedence(&graph)); } @@ -86,6 +95,7 @@ fn a_diamond_precedence_is_honoured() { let resources = ResourceSet::from_resources(vec![resource(0, 1.0), resource(1, 1.0)]).expect("distinct"); // The first task feeds two parallel tasks, which both feed a last one. + // 第一个任务供给两个并行任务,二者又都供给最后一个。 let tasks = [ task(0, 100.0, &[0]), task(1, 60.0, &[1]), @@ -103,6 +113,7 @@ fn a_diamond_precedence_is_honoured() { assert!(result.honours_precedence(&graph)); // The last task must follow both of the parallel ones. + // 最后一个任务必须跟随两个并行任务。 let last = result .schedule .placement_of(TaskId::new(3)) @@ -124,6 +135,8 @@ fn a_sequential_observation_and_downlink_is_expressed_as_two_tasks() { // An observation uses the payload and a downlink uses the transmitter, // and the downlink must follow the observation. This is the pattern for // a task that needs two resources in sequence. + // 一次观测使用载荷、一次下行使用发射机,且下行必须跟随观测。这是按序需要两种 + // 资源的任务模式。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0), resource(1, 1.0)]).expect("distinct"); let observation = task(0, 120.0, &[0]).with_window(50.0, 500.0); @@ -147,9 +160,11 @@ fn a_sequential_observation_and_downlink_is_expressed_as_two_tasks() { .expect("placed"); assert_eq!(collection.start, 50.0); // The downlink starts when the collection ends. + // 下行在采集结束时开始。 assert_eq!(transmission.start, 170.0); // The two use different resources, so they may be placed back to back // without a conflict. + // 二者使用不同资源,故它们可前后相接地放置而无冲突。 assert_eq!(collection.end(), transmission.start); } @@ -157,6 +172,7 @@ fn a_sequential_observation_and_downlink_is_expressed_as_two_tasks() { fn a_successor_of_an_unplaced_task_is_deferred() { // The first task cannot be placed, so the constraint cannot be met and // the second is deferred rather than placed early. + // 第一个任务无法放置,故约束无法满足,第二个被推迟而非提前放置。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let impossible = task(0, 90.0, &[0]).with_window(0.0, 60.0); let successor = task(1, 30.0, &[0]); @@ -169,6 +185,7 @@ fn a_successor_of_an_unplaced_task_is_deferred() { assert!(!result.schedule.is_placed(TaskId::new(1))); assert!(result.deferred().contains(&TaskId::new(1))); // The constraints are trivially satisfied, because nothing was promised. + // 各约束空洞地满足,因为什么都没承诺。 assert!(result.honours_precedence(&graph)); } @@ -197,6 +214,8 @@ fn a_cycle_in_the_graph_falls_back_to_the_value_order() { // The graph refuses a cycle, so a cycle can only exist if the caller // builds the graph from one task set and orders another. The planner // then falls back to the value order rather than failing. + // 该图拒绝环,故只有调用方从一套任务集构建图而排序另一套时环才可能存在。规划器 + // 随后回退到价值次序而非失败。 let tasks = [ Task::new(TaskId::new(0), 60.0), Task::new(TaskId::new(1), 60.0), @@ -205,6 +224,7 @@ fn a_cycle_in_the_graph_falls_back_to_the_value_order() { let order = Planner::new().order_with_precedence(&tasks, Some(&graph)); // An empty graph gives the value order, which for equal values is by // identifier. + // 空图给出价值次序,价值相等时按标识符。 assert_eq!(order, vec![TaskId::new(0), TaskId::new(1)]); } @@ -215,6 +235,7 @@ fn the_improvement_pass_is_skipped_when_precedence_is_used() { let graph = PrecedenceGraph::new(); let result = Planner::new().schedule_with_precedence(&tasks, &resources, &graph); // The pass does not run, so nothing is reported as improved. + // 该遍不运行,故没有报告改进。 assert!(result.improved.is_empty()); } @@ -241,6 +262,7 @@ fn the_improvement_pass_can_be_disabled() { #[test] fn the_improvement_pass_preserves_feasibility() { // Whatever the pass decides, the result must remain conflict free. + // 无论该遍如何决定,结果必须保持无冲突。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0), resource(1, 2.0)]).expect("distinct"); let tasks: Vec = (0..10) @@ -253,6 +275,7 @@ fn the_improvement_pass_preserves_feasibility() { let result = Planner::new().schedule(&tasks, &resources); // The schedule must not place two tasks beyond the capacity of a // resource at the same time. + // 调度不得在同一时刻把两个任务放置到超出某资源的容量。 let report = apsis_conflict::detect(&result.schedule, &tasks, &resources); assert!( report.is_clear(), @@ -265,6 +288,7 @@ fn the_improvement_pass_preserves_feasibility() { fn the_improvement_pass_never_loses_a_placement() { // Every task that the greedy pass placed must still be placed // afterwards, because the pass only adds placements or moves them. + // 贪心遍所放置的每个任务其后必须仍被放置,因为该遍只添加或移动放置。 let resources = ResourceSet::from_resources(vec![resource(0, 1.0)]).expect("one"); let tasks: Vec = (0..6) .map(|index| task(index, 60.0, &[0]).with_priority(1.0 + f64::from(index as u32))) @@ -288,6 +312,7 @@ fn the_improvement_settings_are_validated_by_use() { let tasks = [task(0, 60.0, &[0])]; // A bound of zero disables the move search but leaves the pass enabled, // which behaves as if the pass were off. + // 界为零禁用移动搜索但保留该遍启用,其行为如同该遍关闭。 let planner = Planner::new().with_improvement(ImprovementSettings::new(0)); let result = planner.schedule(&tasks, &resources); assert!(result.is_complete()); diff --git a/crates/ops/apsis-scheduler/tests/sweep_equivalence.rs b/crates/ops/apsis-scheduler/tests/sweep_equivalence.rs index 79c4f94..9388bd4 100644 --- a/crates/ops/apsis-scheduler/tests/sweep_equivalence.rs +++ b/crates/ops/apsis-scheduler/tests/sweep_equivalence.rs @@ -6,6 +6,12 @@ //! not a second definition of what is feasible. The randomised cases below //! are the check: many shapes of window, capacity and overlap, including the //! crowded ones where the two could plausibly diverge. +//! +//! 扫描搜索对候选枚举的差分测试。 +//! +//! 扫描从繁忙廓线计算最早开始;枚举检验每个可行性可能改变的时刻。它们必须精确 +//! 一致,因为扫描只是枚举的加速器,而非可行性的第二种定义。下面的随机用例就是 +//! 检查:窗口、容量与重叠的许多形状,包括二者可能分歧的拥挤情形。 use apsis_resource::{Resource, ResourceId, ResourceSet, Window}; use apsis_schedule::Schedule; @@ -14,11 +20,14 @@ use apsis_task::{Task, TaskId}; /// A tiny deterministic generator, so a failure is reproducible from the /// seed printed in the assertion. +/// +/// 一个微型确定性生成器,使失败可从断言中所打印的种子复现。 struct Rng(u64); impl Rng { fn next(&mut self) -> u64 { // SplitMix64, chosen because it is a few lines and has a long period. + // SplitMix64,因其只有几行且周期长而被选用。 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); let mut z = self.0; z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); @@ -31,6 +40,8 @@ impl Rng { } /// Returns a value in `[low, high)`. + /// + /// 返回 `[low, high)` 内的一个值。 fn range(&mut self, low: f64, high: f64) -> f64 { low + (self.next() >> 11) as f64 / (1u64 << 53) as f64 * (high - low) } @@ -38,11 +49,14 @@ impl Rng { /// Builds a random scenario and returns the two answers, or `None` when the /// task does not fit at all. +/// +/// 构建一个随机场景并返回两个答案,任务完全放不下时返回 `None`。 fn compare(seed: u64) -> (Option, Option, Schedule) { let mut rng = Rng(seed); // One resource with a random capacity and a couple of availability // windows. + // 一个具有随机容量与若干可用性窗口的资源。 let resource_id = ResourceId::new(0); let capacity = rng.range(1.0, 4.0); let mut resource = Resource::new(resource_id, capacity); @@ -59,6 +73,7 @@ fn compare(seed: u64) -> (Option, Option, Schedule) { let resources = ResourceSet::from_resources(vec![resource]).expect("distinct"); // A set of tasks with random demands, windows and durations. + // 一组具有随机需求、窗口与时长的任务。 let task_count = rng.below(12) as usize; let mut tasks = Vec::new(); let mut schedule = Schedule::new(); @@ -72,6 +87,7 @@ fn compare(seed: u64) -> (Option, Option, Schedule) { .try_require(resource_id, demand) .expect("the resource is new"); // Place some of them, so the sweep sees a busy profile. + // 放置其中一些,使扫描看到一个繁忙廓线。 if rng.below(3) == 0 { let _ = earliest_feasible_start(&task, &tasks, &schedule, &resources); if let Some(start) = earliest_feasible_start(&task, &tasks, &schedule, &resources) { @@ -82,6 +98,7 @@ fn compare(seed: u64) -> (Option, Option, Schedule) { } // The task under test. + // 受检的任务。 let duration = rng.range(5.0, 60.0); let from = rng.range(0.0, 300.0); let to = from + rng.range(duration, 300.0); @@ -96,11 +113,15 @@ fn compare(seed: u64) -> (Option, Option, Schedule) { // candidate the original way. The public entry is the sweep, so the // enumeration is exercised through `feasible_at` over the same candidate // set the original built. + // 枚举,通过请求一个开始并以原方式检验每个候选来达到。公开入口是扫描,故枚举是 + // 经 `feasible_at` 在原所构建的同一候选集上被检验。 let enumerated = enumerate(&probe, &tasks, &schedule, &resources); (swept, enumerated, schedule) } /// The original enumeration, written out so the two can be compared. +/// +/// 原枚举,写出以便二者可比较。 fn enumerate( task: &Task, tasks: &[Task], @@ -150,6 +171,7 @@ fn the_sweep_agrees_with_the_enumeration() { } // The cases must actually exercise the interesting path, or agreement // would be vacuous. + // 各用例必须实际检验有意思的路径,否则一致会是空洞的。 assert!(placed > 1_000, "only {placed} cases placed the task"); assert_eq!(placed, agreed); } @@ -158,6 +180,7 @@ fn the_sweep_agrees_with_the_enumeration() { fn a_placed_answer_is_always_feasible() { // The sweep must never report a start that the direct test rejects, // which is the property the confirmation step in the code relies on. + // 扫描绝不报告直接检验所拒绝的开始,这是代码中确认步所依赖的性质。 let mut checked = 0_usize; for seed in 1..5_000_u64 { let (swept, _, _) = compare(seed); @@ -166,6 +189,8 @@ fn a_placed_answer_is_always_feasible() { // return the scenario, so this checks the weaker but still // useful property that the answer is finite and inside the task // window whenever the test is constructed. + // 重建探针以检验它。比较辅助函数不返回场景,故这检查一个较弱但仍有用的性质: + // 只要构造了测试,答案就是有限的且落在任务窗口内。 assert!(start.is_finite(), "seed {seed} produced {start}"); checked += 1; } diff --git a/crates/ops/apsis-task/tests/precedence.rs b/crates/ops/apsis-task/tests/precedence.rs index d6822da..af538ba 100644 --- a/crates/ops/apsis-task/tests/precedence.rs +++ b/crates/ops/apsis-task/tests/precedence.rs @@ -1,8 +1,12 @@ //! Tests for the precedence graph. +//! +//! 先后关系图的测试。 use apsis_task::{PrecedenceGraph, TaskErrorKind, TaskId}; /// The identifiers used by the tests. +/// +/// 测试所用的标识符。 fn id(index: usize) -> TaskId { TaskId::new(index) } @@ -25,6 +29,7 @@ fn a_constraint_is_recorded_both_ways() { assert!(graph.has_constraint(id(0), id(1))); // The constraint is directed, so the reverse does not exist. + // 约束是有向的,故反向不存在。 assert!(!graph.has_constraint(id(1), id(0))); assert_eq!(graph.successors_of(id(0)), vec![id(1)]); assert_eq!(graph.predecessors_of(id(1)), vec![id(0)]); @@ -48,6 +53,7 @@ fn a_duplicate_constraint_is_accepted_and_ignored() { graph.add(id(0), id(1)).expect("no cycle"); graph.add(id(0), id(1)).expect("already present"); // The edge is recorded once. + // 该边只记录一次。 assert_eq!(graph.edge_count(), 1); assert_eq!(graph.successors_of(id(0)), vec![id(1)]); } @@ -58,9 +64,11 @@ fn a_cycle_is_rejected() { graph.add(id(0), id(1)).expect("no cycle"); graph.add(id(1), id(2)).expect("no cycle"); // Closing the cycle is refused. + // 闭合环被拒绝。 let error = graph.add(id(2), id(0)).expect_err("this closes a cycle"); assert_eq!(error.kind(), TaskErrorKind::PrecedenceCycle); // The graph is unchanged. + // 图不变。 assert!(!graph.has_constraint(id(2), id(0))); assert_eq!(graph.edge_count(), 2); } @@ -87,11 +95,13 @@ fn reachability_follows_the_direction() { graph.add(id(1), id(2)).expect("no cycle"); // The transitive relation holds forward but not backward. + // 传递关系向前成立而向后不成立。 assert!(graph.is_reachable(id(0), id(1))); assert!(graph.is_reachable(id(0), id(2))); assert!(graph.is_reachable(id(1), id(2))); assert!(!graph.is_reachable(id(2), id(0))); // A task reaches itself by a path of no edges. + // 任务经零条边的路径到达自身。 assert!(graph.is_reachable(id(0), id(0))); } @@ -99,6 +109,7 @@ fn reachability_follows_the_direction() { fn roots_and_leaves_are_reported() { let mut graph = PrecedenceGraph::new(); // A diamond: 0 before 1 and 2, and both before 3. + // 菱形:0 在 1 与 2 之前,二者又都在 3 之前。 graph.add(id(0), id(1)).expect("no cycle"); graph.add(id(0), id(2)).expect("no cycle"); graph.add(id(1), id(3)).expect("no cycle"); @@ -128,6 +139,7 @@ fn the_topological_order_respects_the_constraints() { .topological_order(&tasks) .expect("the graph is acyclic"); // The constraints force 0 before 1 before 2, whatever the input order. + // 无论输入次序如何,约束强制 0 在 1 之前、1 在 2 之前。 assert_eq!(order, vec![id(0), id(1), id(2)]); } @@ -135,10 +147,12 @@ fn the_topological_order_respects_the_constraints() { fn the_topological_order_breaks_ties_by_position() { let mut graph = PrecedenceGraph::new(); // Only 0 is constrained, before 1. Tasks 2 and 3 are free. + // 只有 0 受约束(在 1 之前)。任务 2 与 3 是自由的。 graph.add(id(0), id(1)).expect("no cycle"); // The preference order is 3, 2, 0, 1, so among the free tasks 3 comes // first. + // 偏好次序是 3、2、0、1,故在自由任务中 3 最先。 let tasks = [id(3), id(2), id(0), id(1)]; let order = graph .topological_order(&tasks) @@ -161,6 +175,7 @@ fn the_topological_order_honours_the_diamond() { assert_eq!(order, vec![id(0), id(1), id(2), id(3)]); // The position of each task in the result relative to its constraints. + // 每个任务在结果中相对其约束的位置。 let position_of = |task: TaskId| { order .iter() @@ -178,6 +193,7 @@ fn the_topological_order_includes_unconstrained_tasks() { let mut graph = PrecedenceGraph::new(); graph.add(id(0), id(1)).expect("no cycle"); // Task 9 has no constraints at all. + // 任务 9 完全没有约束。 let tasks = [id(0), id(1), id(9)]; let order = graph .topological_order(&tasks) @@ -190,6 +206,7 @@ fn the_topological_order_includes_unconstrained_tasks() { fn the_topological_order_ignores_predecessors_outside_the_list() { // A constraint whose predecessor is not in the list cannot be waited // for, so it is ignored rather than blocking the successor. + // 前驱不在列表中的约束无法等待,故它被忽略而非阻塞后继。 let mut graph = PrecedenceGraph::new(); graph.add(id(5), id(1)).expect("no cycle"); let tasks = [id(1)]; diff --git a/crates/ops/apsis-task/tests/task.rs b/crates/ops/apsis-task/tests/task.rs index f5929ce..24435e6 100644 --- a/crates/ops/apsis-task/tests/task.rs +++ b/crates/ops/apsis-task/tests/task.rs @@ -1,15 +1,21 @@ //! Tests for tasks and the set that holds them. +//! +//! 任务及保存它们的集合的测试。 use apsis_resource::ResourceId; use apsis_task::{Requirement, Task, TaskErrorKind, TaskId, TaskSet, TaskState, task_order}; /// A sixty second task with no window and no requirements. +/// +/// 一个六十秒、无窗口也无需求的任务。 fn simple() -> Task { Task::new(TaskId::new(0), 60.0) } /// A task requiring two resources for sixty seconds, in a window from 100 to /// 1 000 seconds. +/// +/// 一个在 100 至 1 000 秒窗口内、需要两个资源六十秒的任务。 fn observation() -> Task { simple() .with_window(100.0, 1_000.0) @@ -29,6 +35,7 @@ fn a_new_task_has_the_given_duration_and_nothing_else() { assert!(task.requirements.is_empty()); assert_eq!(task.requirement_count(), 0); // A task with no window is unbounded at both ends. + // 无窗口的任务在两端都无界。 assert!(task.earliest_start.is_infinite() && task.earliest_start < 0.0); assert!(task.latest_end.is_infinite() && task.latest_end > 0.0); assert!(!task.window_is_too_short()); @@ -60,6 +67,7 @@ fn a_window_shorter_than_the_task_is_detected() { fn a_window_exactly_the_length_of_the_task_is_accepted() { // The task must fit entirely inside the window, and a window of exactly // the duration holds it. + // 任务必须完全落在窗口内,而恰好等于时长的窗口能容纳它。 let task = simple().with_window(0.0, 60.0); assert!(!task.window_is_too_short()); assert!(task.is_valid()); @@ -95,6 +103,7 @@ fn a_duplicate_requirement_is_reported_immediately() { fn a_duplicate_requirement_built_by_chaining_is_rejected_on_validation() { // The plain builder cannot report an error, so the duplicate survives // until validation. + // 普通构建器无法报告错误,故重复项存留到校验时。 let task = simple() .require(ResourceId::new(0), 1.0) .require(ResourceId::new(0), 1.0); @@ -174,6 +183,7 @@ fn the_weighted_value_combines_priority_and_duration() { let task = simple().with_priority(5.0); assert_eq!(task.weighted_value(), 300.0); // A task with no priority is worth nothing. + // 无优先级的任务不值任何东西。 assert_eq!(simple().weighted_value(), 0.0); } @@ -186,6 +196,7 @@ fn the_mandatory_flag_is_recorded() { #[test] fn the_builder_does_not_modify_the_original() { // A builder chain returns a new value, so a base task can be shared. + // 构建器链返回新值,故基础任务可被共享。 let base = simple(); let first = base.clone().with_priority(1.0); let second = base.clone().with_priority(2.0); @@ -310,6 +321,7 @@ fn the_set_reports_queries() { ); assert_eq!(set.mandatory(), vec![TaskId::new(1)]); // The weighted value is the priority times the duration. + // 加权值是优先级乘以时长。 assert_eq!(set.total_weighted_value(), 300.0 + 0.0 + 20.0); } @@ -324,6 +336,7 @@ fn the_task_order_places_the_most_valuable_first() { let order = task_order(set.tasks()); // The weighted values are 10, 500, and 100, so the order is 1, 2, 0. + // 加权值为 10、500 与 100,故次序是 1、2、0。 assert_eq!(order, vec![TaskId::new(1), TaskId::new(2), TaskId::new(0)]); } @@ -331,9 +344,11 @@ fn the_task_order_places_the_most_valuable_first() { fn the_task_order_breaks_ties_by_deadline_then_identifier() { let set = TaskSet::from_tasks(vec![ // The same value and no deadline. + // 价值相同且无截止时间。 Task::new(TaskId::new(0), 10.0).with_priority(1.0), Task::new(TaskId::new(1), 10.0).with_priority(1.0), // The same value but an earlier deadline. + // 价值相同但截止时间更早。 Task::new(TaskId::new(2), 10.0) .with_priority(1.0) .with_window(0.0, 100.0), @@ -343,6 +358,7 @@ fn the_task_order_breaks_ties_by_deadline_then_identifier() { let order = task_order(set.tasks()); // The task with the deadline comes first, then the others by identifier. + // 有截止时间的任务最先,其余按标识符。 assert_eq!( order, vec![