use super::*;
use crate::fs::MemFs;
const MIB: u64 = 1024 * 1024;
fn check(quota_headroom: u64) -> Check {
Check {
quota_headroom,
floor: Floor::MayConsume,
}
}
fn demand<'a>(fs: &'a MemFs, path: &'a str, bytes: u64) -> Demand<'a> {
Demand {
fs,
path: Path::new(path),
bytes,
}
}
#[test]
fn a_folder_written_nothing_does_not_refuse_a_reservation() {
let roomy = MemFs::with_capacity(100 * MIB);
let full = MemFs::with_capacity(0);
let pool = Arc::new(CapacityPool::default());
let keep = Check {
quota_headroom: u64::MAX,
floor: Floor::Keep,
};
assert!(
pool.reserve(
1,
&[
demand(&roomy, "/tables", 10 * MIB),
demand(&full, "/blobs", 0)
],
Some(keep),
)
.is_some(),
"the tables fit their volume; the blob folder takes nothing",
);
}
#[test]
fn a_reservation_dropped_after_its_tree_left_restores_no_account() {
let fs = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
let room = pool.hold(3, &[demand(&fs, "/tables", MIB)]);
pool.forget(3);
assert_eq!(pool.held_on(&fs, Path::new("/tables")), MIB);
drop(room);
assert_eq!(pool.held_on(&fs, Path::new("/tables")), 0);
assert!(!pool.accounts(3));
}
#[test]
fn reservation_within_its_room_is_refused_past_the_quota() {
let fs = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
assert!(
pool.reserve_within(
1,
&[(demand(&fs, "/tables", MIB), u64::MAX)],
check(MIB / 2)
)
.is_none(),
"a MiB does not fit half a MiB of quota",
);
assert!(
pool.reserve_within(
1,
&[(demand(&fs, "/tables", MIB), u64::MAX)],
check(2 * MIB)
)
.is_some(),
"it fits two",
);
}
#[test]
fn second_reservation_sees_what_the_first_holds() {
let fs = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
let first = pool
.reserve(1, &[demand(&fs, "/a", 60 * MIB)], Some(check(u64::MAX)))
.expect("60 of 100 MiB fits");
assert!(
pool.reserve(2, &[demand(&fs, "/b", 60 * MIB)], Some(check(u64::MAX)))
.is_none(),
"the second 60 MiB would take bytes the first holds",
);
assert_eq!(pool.available(&fs, Path::new("/b")), 40 * MIB);
drop(first);
assert_eq!(
pool.held(Pool::Volume(fs.volume_id(Path::new("/a")).expect("id"))),
0
);
assert!(
pool.reserve(2, &[demand(&fs, "/b", 60 * MIB)], Some(check(u64::MAX)))
.is_some()
);
}
#[test]
fn folders_share_a_volume_and_volumes_apart_do_not() {
let shared = MemFs::with_capacity(100 * MIB);
let other = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
let _held = pool
.reserve(1, &[demand(&shared, "/tables", 60 * MIB)], None)
.expect("unchecked");
assert!(
!pool.would_fit(2, &[demand(&shared, "/blobs", 60 * MIB)], check(u64::MAX)),
"another folder of the same volume",
);
assert!(
pool.would_fit(2, &[demand(&other, "/blobs", 60 * MIB)], check(u64::MAX)),
"a volume proven apart",
);
}
#[test]
fn demands_of_one_compaction_on_one_volume_add_up() {
let fs = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
assert!(!pool.would_fit(
1,
&[
demand(&fs, "/tables", 60 * MIB),
demand(&fs, "/blobs", 60 * MIB)
],
check(u64::MAX),
));
assert!(pool.would_fit(
1,
&[
demand(&fs, "/tables", 60 * MIB),
demand(&fs, "/blobs", 30 * MIB)
],
check(u64::MAX),
));
}
#[test]
fn an_unproven_folder_shares_every_volume() {
assert!(Pool::Unproven.shares(Pool::Volume(1)));
assert!(Pool::Volume(1).shares(Pool::Unproven));
assert!(!Pool::Volume(1).shares(Pool::Volume(2)));
assert!(!Pool::Volume(1).shares(Pool::Quota(1)));
assert!(!Pool::Quota(1).shares(Pool::Quota(2)));
let mut held = crate::HashMap::default();
held.insert(Pool::Unproven, 30);
held.insert(Pool::Volume(1), 10);
held.insert(Pool::Volume(2), 20);
assert_eq!(held_against(&held, Pool::Volume(1)), 40);
assert_eq!(held_against(&held, Pool::Unproven), 60);
}
#[test]
fn the_quota_caps_what_a_trees_compactions_hold_together() {
let a = MemFs::with_capacity(100 * MIB);
let b = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
let _first = pool
.reserve(1, &[demand(&a, "/t", 30 * MIB)], Some(check(50 * MIB)))
.expect("30 of a 50 MiB quota");
assert!(
pool.reserve(1, &[demand(&b, "/t", 30 * MIB)], Some(check(50 * MIB)))
.is_none(),
"60 MiB in all past a 50 MiB quota",
);
assert!(
pool.reserve(2, &[demand(&b, "/t", 30 * MIB)], Some(check(50 * MIB)))
.is_some(),
"another tree's quota is its own",
);
assert_eq!(pool.quota_held(1), 30 * MIB);
}
#[test]
fn landed_bytes_leave_the_grant_and_the_rest_goes_back_on_drop() -> crate::Result<()> {
let fs = MemFs::with_capacity(100 * MIB);
let volume = Pool::Volume(fs.volume_id(Path::new("/t")).expect("id"));
let pool = Arc::new(CapacityPool::default());
let room = pool
.reserve(1, &[demand(&fs, "/t", 60 * MIB)], Some(check(u64::MAX)))
.expect("fits");
room.landed(&fs, Path::new("/t"), 25 * MIB)?;
assert_eq!(pool.held(volume), 35 * MIB);
assert_eq!(pool.quota_held(1), 60 * MIB);
drop(room);
assert_eq!(pool.held(volume), 0);
assert_eq!(pool.quota_held(1), 0);
Ok(())
}
#[test]
fn an_output_past_its_grant_grows_it_or_stops_the_compaction() -> crate::Result<()> {
let fs = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
let room = pool
.reserve(1, &[demand(&fs, "/t", 10 * MIB)], Some(check(u64::MAX)))
.expect("fits");
room.landed(&fs, Path::new("/t"), 15 * MIB)?;
assert!(!room.refused());
assert_eq!(pool.quota_held(1), 15 * MIB);
fs.set_capacity(MIB / 2);
let err = room
.landed(&fs, Path::new("/t"), MIB)
.expect_err("no room past the grant");
assert!(matches!(err, crate::Error::StorageFull { .. }), "{err:?}");
assert!(room.refused());
Ok(())
}
#[test]
fn an_unchecked_reservation_is_counted_by_a_checked_one() -> crate::Result<()> {
let fs = MemFs::with_capacity(100 * MIB);
let pool = Arc::new(CapacityPool::default());
let unchecked = pool
.reserve(1, &[demand(&fs, "/t", 150 * MIB)], None)
.expect("an unchecked reservation always holds");
assert!(!pool.would_fit(2, &[demand(&fs, "/t", MIB)], check(u64::MAX)));
fs.set_capacity(0);
unchecked.landed(&fs, Path::new("/t"), 200 * MIB)?;
assert!(!unchecked.refused());
Ok(())
}
#[test]
fn room_taken_on_later_is_checked_and_held_until_the_reservation_goes() {
let fs = MemFs::with_capacity(100 * MIB);
let volume = Pool::Volume(fs.volume_id(Path::new("/t")).expect("id"));
let pool = Arc::new(CapacityPool::default());
let room = pool
.reserve(1, &[demand(&fs, "/t", 10 * MIB)], Some(check(u64::MAX)))
.expect("fits");
let _other = pool
.reserve(2, &[demand(&fs, "/u", 60 * MIB)], Some(check(u64::MAX)))
.expect("fits");
assert!(
!room.try_add(&demand(&fs, "/blobs", 40 * MIB), check(u64::MAX)),
"40 MiB do not fit beside the 70 the two hold of 100",
);
assert_eq!(pool.held(volume), 70 * MIB, "a refusal takes nothing");
assert!(room.try_add(&demand(&fs, "/blobs", 20 * MIB), check(u64::MAX)));
assert_eq!(pool.held(volume), 90 * MIB);
assert_eq!(pool.quota_held(1), 30 * MIB);
drop(room);
assert_eq!(pool.held(volume), 60 * MIB);
assert_eq!(pool.quota_held(1), 0);
}
#[test]
fn room_taken_on_another_volume_is_a_grant_of_its_own() {
let tables = MemFs::with_capacity(100 * MIB);
let blobs = MemFs::with_capacity(100 * MIB);
let blob_volume = Pool::Volume(blobs.volume_id(Path::new("/b")).expect("id"));
let pool = Arc::new(CapacityPool::default());
let room = pool
.reserve(1, &[demand(&tables, "/t", 10 * MIB)], Some(check(u64::MAX)))
.expect("fits");
assert!(room.try_add(&demand(&blobs, "/b", 0), check(u64::MAX)));
assert_eq!(pool.held(blob_volume), 0, "nothing taken on");
assert_eq!(pool.quota_held(1), 10 * MIB);
assert!(room.try_add(&demand(&blobs, "/b", 30 * MIB), check(u64::MAX)));
assert_eq!(pool.held(blob_volume), 30 * MIB);
assert_eq!(pool.quota_held(1), 40 * MIB);
drop(room);
assert_eq!(pool.held(blob_volume), 0);
assert_eq!(pool.quota_held(1), 0);
}
#[test]
fn a_file_landed_on_an_unreserved_volume_is_excess() -> crate::Result<()> {
let tables = MemFs::with_capacity(100 * MIB);
let other = MemFs::with_capacity(100 * MIB);
let other_volume = Pool::Volume(other.volume_id(Path::new("/o")).expect("id"));
let pool = Arc::new(CapacityPool::default());
let room = pool
.reserve(1, &[demand(&tables, "/t", 10 * MIB)], Some(check(u64::MAX)))
.expect("fits");
room.landed(&other, Path::new("/o"), 5 * MIB)?;
assert_eq!(pool.quota_held(1), 15 * MIB);
assert_eq!(pool.held(other_volume), 0);
drop(room);
assert_eq!(pool.quota_held(1), 0);
Ok(())
}
#[test]
fn the_pool_debugs_by_name() {
assert_eq!(
format!("{:?}", CapacityPool::default()),
"CapacityPool { .. }"
);
}
#[test]
fn an_unproven_pool_takes_the_least_free_space_of_its_folders() {
let (volumes, total) = tally(
[
(Pool::Unproven, 10, 100 * MIB),
(Pool::Unproven, 20, 5 * MIB),
(Pool::Volume(1), 30, 50 * MIB),
]
.into_iter(),
);
assert_eq!(total, 60);
assert_eq!(
volumes,
[
(Pool::Unproven, 30, 5 * MIB),
(Pool::Volume(1), 30, 50 * MIB)
]
);
}
#[test]
fn quota_growth_counts_a_compaction_started_and_installed_meanwhile() {
let fs = MemFs::with_capacity(u64::MAX);
let pool = Arc::new(CapacityPool::default());
let first = pool
.reserve(1, &[demand(&fs, "/t", 30 * MIB)], Some(check(100 * MIB)))
.expect("30 of a 100 MiB quota");
let second = pool
.reserve(1, &[demand(&fs, "/t", 50 * MIB)], Some(check(100 * MIB)))
.expect("80 of a 100 MiB quota");
drop(second);
let err = first
.landed(&fs, Path::new("/t"), 60 * MIB)
.expect_err("50 installed and 60 written are past the 100 MiB quota");
assert!(matches!(err, crate::Error::StorageFull { .. }), "{err:?}");
}
#[test]
fn quota_growth_counts_what_an_installed_compaction_held() {
let fs = MemFs::with_capacity(u64::MAX);
let pool = Arc::new(CapacityPool::default());
let first = pool
.reserve(1, &[demand(&fs, "/t", 50 * MIB)], Some(check(100 * MIB)))
.expect("50 of a 100 MiB quota");
let second = pool
.reserve(1, &[demand(&fs, "/t", 30 * MIB)], Some(check(100 * MIB)))
.expect("80 of a 100 MiB quota");
drop(first);
let err = second
.landed(&fs, Path::new("/t"), 60 * MIB)
.expect_err("50 installed and 60 written are past the 100 MiB quota");
assert!(matches!(err, crate::Error::StorageFull { .. }), "{err:?}");
}