use alloc::sync::Arc;
use core::sync::atomic::Ordering;
use super::super::{Memory, MemoryAccount, MemoryError};
use super::{TableAllocator, scoped};
use crate::{Encoding, Limits, Parser};
type TableVec = allocator_api2_hashbrown::vec::Vec<u8, TableAllocator>;
fn table(values: &TableVec) -> &TableAllocator {
values.allocator()
}
fn memory(storage: usize, work: usize) -> Memory {
Memory::new(Arc::new(MemoryAccount::new(storage, work)))
}
#[test]
fn table_and_vector_interfaces_share_live_capacity_and_growth_peak() {
let memory = memory(24, usize::MAX);
let mut current = memory.vector::<u8>();
current.try_extend_from_slice(&[7; 8]).unwrap();
let mut table_interface = TableVec::new_in(TableAllocator::new(memory.clone()));
scoped(&mut table_interface, table, &memory, |values| {
values.try_reserve_exact(8)
})
.unwrap();
table_interface.push(42);
assert_eq!(memory.used(), 16);
let attempts = table_interface.allocator().attempts.load(Ordering::Relaxed);
assert!(
scoped(&mut table_interface, table, &memory, |values| {
values.try_reserve_exact(8)
})
.is_err()
);
assert_eq!(
table_interface.allocator().failure(),
MemoryError::LimitExceeded
);
assert_eq!(
table_interface.allocator().attempts.load(Ordering::Relaxed),
attempts
);
assert_eq!(&*table_interface, &[42]);
assert_eq!(table_interface.capacity(), 8);
assert_eq!(memory.used(), 16);
drop(current);
assert_eq!(memory.used(), 8);
scoped(&mut table_interface, table, &memory, |values| {
values.try_reserve_exact(8)
})
.unwrap();
assert_eq!(&*table_interface, &[42]);
assert_eq!(memory.used(), table_interface.capacity());
drop(table_interface);
assert_eq!(memory.used(), 0);
}
#[test]
fn table_allocation_without_an_installed_scope_is_refused_before_the_backend() {
let memory = memory(64, 1000);
let mut values = TableVec::new_in(TableAllocator::new(memory.clone()));
assert!(values.try_reserve_exact(8).is_err());
assert_eq!(values.allocator().failure(), MemoryError::WorkExceeded);
assert_eq!(values.allocator().attempts.load(Ordering::Relaxed), 0);
assert_eq!(values.capacity(), 0);
assert_eq!(memory.used(), 0);
assert_eq!(memory.work_used(), 0);
}
#[test]
fn table_scope_is_cleared_after_success_and_after_refusal() {
let memory = memory(8, 1000);
let mut values = TableVec::new_in(TableAllocator::new(memory.clone()));
scoped(&mut values, table, &memory, |values| {
assert!(values.allocator().scope_installed());
values.try_reserve_exact(8)
})
.unwrap();
assert!(!values.allocator().scope_installed());
assert!(
scoped(&mut values, table, &memory, |values| {
values.try_reserve_exact(64)
})
.is_err()
);
assert!(!values.allocator().scope_installed());
}
#[test]
fn table_grow_admits_new_then_old_and_keeps_refused_work_spent() {
for (work, grown) in [(8 + 17 + 7, false), (8 + 17 + 8, true)] {
let memory = memory(64, work);
let mut values = TableVec::new_in(TableAllocator::new(memory.clone()));
scoped(&mut values, table, &memory, |values| {
values.try_reserve_exact(8)
})
.unwrap();
values.push(42);
let attempts = values.allocator().attempts.load(Ordering::Relaxed);
let result = scoped(&mut values, table, &memory, |values| {
values.try_reserve_exact(16)
});
assert_eq!(result.is_ok(), grown);
assert_eq!(&*values, &[42]);
if grown {
assert_eq!(values.capacity(), 17);
assert_eq!(memory.used(), 17);
assert_eq!(memory.work_used(), 8 + 17 + 8);
} else {
assert_eq!(values.allocator().failure(), MemoryError::WorkExceeded);
assert_eq!(
values.allocator().attempts.load(Ordering::Relaxed),
attempts
);
assert_eq!(values.capacity(), 8);
assert_eq!(memory.used(), 8);
assert_eq!(memory.work_used(), 8 + 17);
}
}
}
#[test]
fn table_scope_charges_the_local_parser_ceiling_and_the_aggregate() {
for (local, aggregate) in [(7, 1000), (1000, 7), (8, 8)] {
let memory = memory(64, aggregate);
let parser = Parser::new(
Encoding::Der,
Limits {
max_work: local,
..Limits::default()
},
memory.clone(),
);
let reader = parser.read(&[], 0).unwrap();
let execution = reader.execution();
let mut values = TableVec::new_in(TableAllocator::new(memory.clone()));
let result = scoped(&mut values, table, &execution, |values| {
values.try_reserve_exact(8)
});
if local == 8 {
assert!(result.is_ok());
assert_eq!(memory.work_used(), 8);
assert_eq!(memory.used(), 8);
} else {
assert!(result.is_err());
assert_eq!(values.allocator().failure(), MemoryError::WorkExceeded);
assert_eq!(values.allocator().attempts.load(Ordering::Relaxed), 0);
assert_eq!(memory.used(), 0);
}
}
}
#[test]
fn quota_rejection_never_reaches_the_backend() {
let memory = memory(0, usize::MAX);
let mut bytes = memory.vector::<u8>();
assert_eq!(bytes.try_reserve_exact(1), Err(MemoryError::LimitExceeded));
assert_eq!(bytes.values.allocator().attempts.load(Ordering::Relaxed), 0);
assert_eq!(memory.used(), 0);
}
#[test]
fn backend_failure_rolls_back_a_pending_reservation() {
let memory = memory(16, usize::MAX);
let mut bytes = memory.vector::<u8>();
bytes
.values
.allocator()
.fail_backend
.store(true, Ordering::Relaxed);
assert_eq!(bytes.try_reserve_exact(8), Err(MemoryError::OutOfMemory));
assert_eq!(bytes.capacity(), 0);
assert_eq!(memory.used(), 0);
}
#[test]
fn failed_growth_keeps_old_bytes_and_old_charge() {
let memory = memory(32, usize::MAX);
let mut bytes = memory.vector::<u8>();
bytes.try_reserve_exact(8).unwrap();
bytes.try_push(42).unwrap();
bytes
.values
.allocator()
.fail_backend
.store(true, Ordering::Relaxed);
assert_eq!(bytes.try_reserve_exact(8), Err(MemoryError::OutOfMemory));
assert_eq!(&*bytes, &[42]);
assert_eq!(bytes.capacity(), 8);
assert_eq!(memory.used(), 8);
}