apsis-rs/crates/ops/apsis-resource/tests/resource.rs

333 lines
12 KiB
Rust

//! Tests for resources and the set that holds them.
//!
//! 资源及保存它们的集合的测试。
use apsis_resource::{Interval, Resource, ResourceErrorKind, ResourceId, ResourceSet, Window};
/// A resource with one window from 100 to 200 seconds.
///
/// 一个含 100 至 200 秒窗口的资源。
fn station() -> Resource {
let mut resource = Resource::new(ResourceId::new(0), 1.0);
resource
.add_window(Window::new(100.0, 200.0))
.expect("the window is usable");
resource
}
#[test]
fn a_new_resource_has_no_availability() {
let resource = Resource::new(ResourceId::new(3), 1.0);
assert!(resource.is_empty());
assert_eq!(resource.window_count(), 0);
assert_eq!(resource.total_availability(), 0.0);
assert!(!resource.is_available_at(0.0));
assert_eq!(resource.id, ResourceId::new(3));
assert!(!resource.is_available_at(f64::NAN));
}
#[test]
fn adding_a_window_makes_the_resource_available() {
let resource = station();
assert_eq!(resource.window_count(), 1);
assert_eq!(resource.total_availability(), 100.0);
assert!(resource.is_available_at(150.0));
assert!(!resource.is_available_at(50.0));
assert!(!resource.is_available_at(250.0));
}
#[test]
fn availability_is_half_open_at_the_edges() {
let resource = station();
assert!(resource.is_available_at(100.0));
assert!(resource.is_available_at(199.999));
// The end instant belongs to the next interval, so the resource stops
// being available exactly there.
// 结束瞬时属于下一区间,故资源恰在那里停止可用。
assert!(!resource.is_available_at(200.0));
}
#[test]
fn availability_over_a_duration_requires_the_whole_span() {
let resource = station();
assert!(resource.is_available_over(100.0, 100.0));
assert!(resource.is_available_over(120.0, 80.0));
assert!(resource.is_available_over(150.0, 50.0));
// A request that reaches past the window fails, even by a little.
// 越过窗口的请求失败,即便只越过一点。
assert!(!resource.is_available_over(150.0, 50.001));
assert!(!resource.is_available_over(99.0, 2.0));
}
#[test]
fn a_zero_duration_request_is_an_instantaneous_query() {
let resource = station();
assert!(resource.is_available_over(150.0, 0.0));
assert!(!resource.is_available_over(250.0, 0.0));
// The half-open boundary applies here too.
// 半开边界在此也适用。
assert!(!resource.is_available_over(200.0, 0.0));
}
#[test]
fn a_request_with_an_invalid_duration_is_refused() {
let resource = station();
assert!(!resource.is_available_over(100.0, -1.0));
assert!(!resource.is_available_over(100.0, f64::NAN));
assert!(!resource.is_available_over(f64::NAN, 10.0));
assert!(!resource.is_available_over(100.0, f64::INFINITY));
}
#[test]
fn adding_overlapping_windows_merges_them() {
let mut resource = Resource::new(ResourceId::new(0), 1.0);
resource.add_window(Window::new(0.0, 10.0)).expect("usable");
resource.add_window(Window::new(5.0, 15.0)).expect("usable");
// The two overlap, so the stored form is one window.
// 二者重叠,故存储形式是一个窗口。
assert_eq!(resource.window_count(), 1);
assert_eq!(resource.windows()[0], Window::new(0.0, 15.0));
assert_eq!(resource.total_availability(), 15.0);
}
#[test]
fn adding_disjoint_windows_keeps_them_separate() {
let mut resource = Resource::new(ResourceId::new(0), 1.0);
resource
.add_window(Window::new(30.0, 40.0))
.expect("usable");
resource.add_window(Window::new(0.0, 10.0)).expect("usable");
assert_eq!(resource.window_count(), 2);
// The stored order is time order, not insertion order.
// 存储次序是时间次序,而非插入次序。
assert_eq!(resource.windows()[0].start(), 0.0);
assert_eq!(resource.windows()[1].start(), 30.0);
}
#[test]
fn removing_a_window_splits_availability() {
let mut resource = station();
resource
.remove_window(Window::new(140.0, 160.0))
.expect("usable");
assert_eq!(resource.window_count(), 2);
assert_eq!(resource.windows()[0], Window::new(100.0, 140.0));
assert_eq!(resource.windows()[1], Window::new(160.0, 200.0));
assert!(!resource.is_available_at(150.0));
assert!(resource.is_available_at(130.0));
assert!(resource.is_available_at(180.0));
}
#[test]
fn setting_the_windows_replaces_them() {
let mut resource = station();
resource
.set_windows(&[Window::new(0.0, 10.0), Window::new(20.0, 30.0)])
.expect("usable");
assert_eq!(resource.window_count(), 2);
assert!(!resource.is_available_at(150.0));
assert!(resource.is_available_at(5.0));
// Setting nothing removes all availability.
// 什么都不设置即移除所有可用性。
resource.set_windows(&[]).expect("usable");
assert!(resource.is_empty());
}
#[test]
fn availability_within_a_query_is_the_intersection() {
let resource = station();
let within = resource.availability_within(Interval::new(150.0, 250.0));
assert_eq!(within, vec![Window::new(150.0, 200.0)]);
let outside = resource.availability_within(Interval::new(300.0, 400.0));
assert!(outside.is_empty());
let covering = resource.availability_within(Interval::new(0.0, 1000.0));
assert_eq!(covering, resource.windows().to_vec());
}
#[test]
fn the_earliest_fit_is_the_first_window_long_enough() {
let mut resource = Resource::new(ResourceId::new(0), 1.0);
resource
.set_windows(&[Window::new(0.0, 10.0), Window::new(30.0, 60.0)])
.expect("usable");
// A request of five seconds fits in the first window.
// 五秒的请求放入第一个窗口。
assert_eq!(resource.earliest_fit(5.0), Some(Window::new(0.0, 5.0)));
// A request of twenty does not, so it takes the second.
// 二十秒的放不下,故取第二个。
assert_eq!(resource.earliest_fit(20.0), Some(Window::new(30.0, 50.0)));
// A request longer than any window has no answer.
// 比任何窗口都长的请求没有答案。
assert_eq!(resource.earliest_fit(40.0), None);
// An instantaneous request fits at the start of the first window.
// 瞬时请求放入第一个窗口的开头。
assert_eq!(resource.earliest_fit(0.0), Some(Window::new(0.0, 0.0)));
assert_eq!(resource.earliest_fit(-1.0), None);
assert_eq!(resource.earliest_fit(f64::NAN), None);
}
#[test]
fn the_earliest_fit_after_a_time_respects_the_lower_bound() {
let mut resource = Resource::new(ResourceId::new(0), 1.0);
resource
.set_windows(&[Window::new(0.0, 10.0), Window::new(30.0, 60.0)])
.expect("usable");
// Starting no earlier than five, the first window can still hold it.
// 不早于五开始,第一个窗口仍能容纳它。
assert_eq!(
resource.earliest_fit_after(5.0, 4.0),
Some(Window::new(5.0, 9.0))
);
// A request of eight seconds starting at five does not fit, because the
// window ends at ten, so the second window is used.
// 自五开始、八秒的请求放不下,因为窗口在十结束,故使用第二个窗口。
assert_eq!(
resource.earliest_fit_after(5.0, 8.0),
Some(Window::new(30.0, 38.0))
);
// Starting after the second window begins, the request starts there.
// 在第二个窗口开始之后起步,该请求从那里开始。
assert_eq!(
resource.earliest_fit_after(40.0, 10.0),
Some(Window::new(40.0, 50.0))
);
assert_eq!(resource.earliest_fit_after(100.0, 1.0), None);
assert_eq!(resource.earliest_fit_after(f64::NAN, 1.0), None);
}
#[test]
fn the_busy_until_time_is_the_end_of_the_containing_window() {
let resource = station();
assert_eq!(resource.busy_until(150.0), Some(200.0));
assert_eq!(resource.busy_until(50.0), None);
}
#[test]
fn the_capacity_is_validated() {
let mut resource = Resource::new(ResourceId::new(0), 1.0);
assert!(resource.has_valid_capacity());
assert!(resource.set_capacity(2.5).is_ok());
assert_eq!(resource.capacity(), 2.5);
for capacity in [0.0, -1.0, f64::NAN, f64::INFINITY] {
let error = resource
.set_capacity(capacity)
.expect_err("the capacity is not usable");
assert_eq!(error.kind(), ResourceErrorKind::InvalidCapacity);
}
// The capacity is unchanged after a failed set.
// 失败的设置之后容量不变。
assert_eq!(resource.capacity(), 2.5);
let invalid = Resource::new(ResourceId::new(0), -1.0);
assert!(!invalid.has_valid_capacity());
}
#[test]
fn a_set_holds_resources_by_identifier() {
let mut set = ResourceSet::new();
assert!(set.is_empty());
set.insert(station()).expect("the identifier is new");
set.insert(Resource::new(ResourceId::new(1), 2.0))
.expect("the identifier is new");
assert_eq!(set.len(), 2);
let found = set.get(ResourceId::new(0)).expect("the resource exists");
assert_eq!(found.capacity(), 1.0);
assert!(set.get(ResourceId::new(9)).is_none());
assert_eq!(set.resources().len(), 2);
}
#[test]
fn a_duplicate_identifier_is_rejected() {
let mut set = ResourceSet::new();
set.insert(station()).expect("the identifier is new");
let error = set
.insert(Resource::new(ResourceId::new(0), 5.0))
.expect_err("the identifier is taken");
assert_eq!(error.kind(), ResourceErrorKind::DuplicateResource);
assert_eq!(set.len(), 1);
}
#[test]
fn a_set_can_be_built_from_a_list() {
let set = ResourceSet::from_resources(vec![station(), Resource::new(ResourceId::new(1), 1.0)])
.expect("the identifiers are distinct");
assert_eq!(set.len(), 2);
let error =
ResourceSet::from_resources(vec![station(), Resource::new(ResourceId::new(0), 1.0)])
.expect_err("the identifiers collide");
assert_eq!(error.kind(), ResourceErrorKind::DuplicateResource);
}
#[test]
fn a_resource_can_be_removed() {
let mut set = ResourceSet::new();
set.insert(station()).expect("new");
let removed = set.remove(ResourceId::new(0)).expect("the resource exists");
assert_eq!(removed.id, ResourceId::new(0));
assert!(set.is_empty());
assert_eq!(
set.remove(ResourceId::new(0)).expect_err("gone").kind(),
ResourceErrorKind::NoSuchResource
);
}
#[test]
fn a_resource_can_be_modified_through_the_set() {
let mut set = ResourceSet::new();
set.insert(station()).expect("new");
let resource = set
.get_mut(ResourceId::new(0))
.expect("the resource exists");
resource
.add_window(Window::new(300.0, 400.0))
.expect("usable");
assert_eq!(
set.get(ResourceId::new(0)).expect("exists").window_count(),
2
);
assert!(set.get_mut(ResourceId::new(9)).is_none());
}
#[test]
fn the_set_reports_availability() {
let mut set = ResourceSet::new();
set.insert(station()).expect("new");
set.insert(Resource::new(ResourceId::new(1), 1.0))
.expect("new");
// Only the first resource has any availability.
// 只有第一个资源有任何可用性。
assert_eq!(set.available_at(150.0), vec![ResourceId::new(0)]);
assert!(set.available_at(50.0).is_empty());
assert_eq!(set.available_over(120.0, 50.0), vec![ResourceId::new(0)]);
assert!(set.available_over(120.0, 200.0).is_empty());
assert_eq!(set.total_availability(), 100.0);
}
#[test]
fn the_identifier_displays_its_index() {
assert_eq!(ResourceId::new(4).to_string(), "resource 4");
assert_eq!(ResourceId::new(4).index(), 4);
assert_eq!(ResourceId::new(4), ResourceId(4));
}
#[test]
fn identifiers_are_ordered() {
// Ordering lets a caller sort a list of resources deterministically.
// 排序使调用方能确定地对资源列表排序。
let mut ids = [ResourceId::new(3), ResourceId::new(1), ResourceId::new(2)];
ids.sort();
assert_eq!(
ids,
[ResourceId::new(1), ResourceId::new(2), ResourceId::new(3)]
);
}