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