use alloc::{collections::TryReserveError, vec::Vec};
use core::{convert::Infallible, mem, num::NonZeroU32};
extern crate std;
use self::std::panic::{AssertUnwindSafe, catch_unwind};
use proptest::prelude::*;
use proptest::test_runner::{TestCaseError, TestCaseResult};
use super::{
Capacity, IndexKey, WarpId, WarpTable, WarpTableGetAtOutcome, WarpTableGetOutcome,
WarpTableInsertOutcome, WarpTableInsertWithIdOutcome, WarpTableRemoveOutcome,
WarpTableReplaceOutcome, WarpTableRow, WarpTableRowGetError, WarpTableRowGetOutcome,
WarpTableRowRelocation, WarpTableRowReplaceError, WarpTableRowReplaceOutcome,
WarpTableUpdateOutcome, integer_index_word,
};
crate::value_wrapper! {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ItemId(u64);
}
impl IndexKey for ItemId {
fn index_word(self) -> u64 {
integer_index_word(self.value())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Item {
quantity: u32,
}
struct ItemColumns {
quantities: Vec<u32>,
replacement_available: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct ItemRef<'row> {
id: &'row ItemId,
quantity: &'row u32,
}
struct ItemMut<'row> {
id: &'row ItemId,
quantity: &'row mut u32,
}
impl WarpTableRow for Item {
type Id = ItemId;
type Columns = ItemColumns;
type ColumnsCreationError = TryReserveError;
type Indexes = ();
type IndexesCreationError = Infallible;
type UniqueConflict = Infallible;
type IndexesInsertError = Infallible;
type PreparedIndexesInsert<'indexes> = ();
type IndexesRemoveError = Infallible;
type PreparedIndexesRemove<'indexes> = ();
type IndexesReplaceError = Infallible;
type PreparedIndexesReplace<'indexes> = ();
type Ref<'row> = ItemRef<'row>;
type Mut<'row> = ItemMut<'row>;
type Query<'table, Brand: 'table> = ();
type MutQuery<'table, Brand: 'table> = ();
type DetachedQuery<'indexes> = ();
fn try_new_columns(maximum_rows: usize) -> Result<Self::Columns, Self::ColumnsCreationError> {
let mut quantities = Vec::new();
quantities.try_reserve_exact(maximum_rows)?;
Ok(ItemColumns {
quantities,
replacement_available: true,
})
}
fn try_new_indexes(_: usize) -> Result<Self::Indexes, Self::IndexesCreationError> {
Ok(())
}
fn query<'table, Brand>(_: &'table WarpTable<Self, Brand>) -> Self::Query<'table, Brand>
where
Self: 'table,
Brand: 'table,
{
}
fn query_mut<'table, Brand>(
_: &'table mut WarpTable<Self, Brand>,
) -> Self::MutQuery<'table, Brand>
where
Self: 'table,
Brand: 'table,
{
}
fn detached_query<'indexes>(_: &'indexes Self::Indexes) -> Self::DetachedQuery<'indexes>
where
Self: 'indexes,
{
}
fn unique_insert_conflict(_: &Self::Indexes, _: &Self) -> Option<Self::UniqueConflict> {
None
}
fn unique_replace_conflict<'row>(
_: &Self::Indexes,
_: Self::Ref<'row>,
_: &Self,
) -> Option<Self::UniqueConflict>
where
Self: 'row,
{
None
}
fn try_prepare_indexes_insert<'indexes>(
_: &'indexes mut Self::Indexes,
_: &Self,
) -> Result<Self::PreparedIndexesInsert<'indexes>, Self::IndexesInsertError>
where
Self: 'indexes,
{
Ok(())
}
fn commit_indexes_insert<'indexes>(_: Self::PreparedIndexesInsert<'indexes>)
where
Self: 'indexes,
{
}
fn try_prepare_indexes_remove<'indexes, 'row>(
_: &'indexes mut Self::Indexes,
_: Self::Ref<'row>,
_: usize,
) -> Result<Self::PreparedIndexesRemove<'indexes>, Self::IndexesRemoveError>
where
Self: 'indexes + 'row,
{
Ok(())
}
fn commit_indexes_remove<'indexes>(_: Self::PreparedIndexesRemove<'indexes>)
where
Self: 'indexes,
{
}
fn try_prepare_indexes_replace<'indexes, 'row>(
_: &'indexes mut Self::Indexes,
_: Self::Ref<'row>,
_: &Self,
_: usize,
) -> Result<Self::PreparedIndexesReplace<'indexes>, Self::IndexesReplaceError>
where
Self: 'indexes + 'row,
{
Ok(())
}
fn commit_indexes_replace<'indexes>(_: Self::PreparedIndexesReplace<'indexes>)
where
Self: 'indexes,
{
}
fn append(columns: &mut Self::Columns, row: Self) {
columns.quantities.push(row.quantity);
}
fn swap_remove(columns: &mut Self::Columns, index: usize) -> Self {
Self {
quantity: columns.quantities.swap_remove(index),
}
}
fn get<'row>(
id: &'row Self::Id,
columns: &'row Self::Columns,
index: usize,
) -> Result<WarpTableRowGetOutcome<Self::Ref<'row>>, WarpTableRowGetError>
where
Self: 'row,
{
let Some(quantity) = columns.quantities.get(index) else {
return Err(WarpTableRowGetError::ColumnUnavailable {
column: 0,
row: index,
});
};
Ok(WarpTableRowGetOutcome::Present {
row: ItemRef { id, quantity },
})
}
fn get_mut<'row>(
id: &'row Self::Id,
columns: &'row mut Self::Columns,
_: &'row mut Self::Indexes,
index: usize,
) -> Result<WarpTableRowGetOutcome<Self::Mut<'row>>, WarpTableRowGetError>
where
Self: 'row,
{
let Some(quantity) = columns.quantities.get_mut(index) else {
return Err(WarpTableRowGetError::ColumnUnavailable {
column: 0,
row: index,
});
};
Ok(WarpTableRowGetOutcome::Present {
row: ItemMut { id, quantity },
})
}
fn replace(
columns: &mut Self::Columns,
index: usize,
replacement: Self,
) -> Result<WarpTableRowReplaceOutcome<Self>, WarpTableRowReplaceError<Self>> {
if !columns.replacement_available {
return Err(WarpTableRowReplaceError::ColumnUnavailable {
column: 0,
row: index,
rejected: replacement,
});
}
let Some(quantity) = columns.quantities.get_mut(index) else {
return Err(WarpTableRowReplaceError::ColumnUnavailable {
column: 0,
row: index,
rejected: replacement,
});
};
let previous = Self {
quantity: mem::replace(quantity, replacement.quantity),
};
Ok(WarpTableRowReplaceOutcome::Replaced { previous })
}
}
#[test]
fn row_provider_replacement_refusal_preserves_identity_and_columns() -> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(capacity(1)?).map_err(|_| "table unavailable")?;
let id = ItemId::new(7);
assert!(matches!(
table.insert_with_id(id, item(11)),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
table.columns.replacement_available = false;
let Err(payload) = catch_unwind(AssertUnwindSafe(|| table.replace(id, item(19)))) else {
return Err("provider refusal did not stop replacement");
};
assert_eq!(
payload
.downcast_ref::<alloc::string::String>()
.ok_or("unexpected diagnostic")?,
"WarpTable invariant violated: ColumnUnavailable { column: 0, row: 0 }"
);
assert_eq!(table.ids(), &[id]);
assert_eq!(table.columns.quantities, [11]);
assert_eq!(table.len(), 1);
Ok(())
}
#[test]
fn required_removal_preserves_owned_rows_relocation_and_the_callers_refusal()
-> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(capacity(2)?).map_err(|_| "table unavailable")?;
let first = ItemId::new(7);
let second = ItemId::new(9);
for (id, quantity) in [(first, 11), (second, 13)] {
assert!(matches!(
table.insert_with_id(id, item(quantity)),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
}
let removed = super::remove::require_removal(table.remove(first), "required row absent")?;
assert_eq!(
removed,
(
first,
item(11),
WarpTableRowRelocation::Relocated {
id: second,
from: 1,
to: 0
}
)
);
assert_eq!(
super::remove::require_removal(table.remove(first), "required row absent"),
Err("required row absent")
);
assert_eq!(table.ids(), &[second]);
assert_eq!(table.columns.quantities, [13]);
Ok(())
}
#[test]
fn required_update_preserves_identity_position_output_and_absence() -> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(capacity(2)?).map_err(|_| "table unavailable")?;
for (id, quantity) in [(7, 11), (9, 13)] {
assert!(matches!(
table.insert_with_id(ItemId::new(id), item(quantity)),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
}
let outcome = table.update(ItemId::new(9), |row| mem::replace(row.quantity, 17));
assert_eq!(
super::update::require_update(outcome, "required row absent")?,
(ItemId::new(9), 1, 13)
);
let mut calls = 0;
let absent = table.update(ItemId::new(11), |_| calls += 1);
assert_eq!(
super::update::require_update(absent, "required row absent"),
Err("required row absent")
);
assert_eq!(calls, 0);
assert_eq!(table.columns.quantities, [11, 17]);
Ok(())
}
#[derive(Debug, Eq, super::WarpTableRow, PartialEq)]
struct GeneratedItem {
quantity: u32,
}
fn item(quantity: u32) -> Item {
Item { quantity }
}
fn capacity(value: u32) -> Result<Capacity, &'static str> {
NonZeroU32::new(value)
.map(Capacity::new)
.ok_or("capacity was zero")
}
#[test]
fn warp_table_reserves_ids_and_every_column_at_construction() -> Result<(), &'static str> {
let capacity = capacity(2)?;
let table =
WarpTable::<Item>::try_new(capacity).map_err(|_| "table storage was unavailable")?;
assert_eq!(table.capacity(), capacity);
assert!(table.is_empty());
assert!(table.ids.capacity() >= 2);
assert!(table.columns().quantities.capacity() >= 2);
Ok(())
}
#[test]
fn warp_table_resolves_explicit_ids_and_preserves_failures() -> Result<(), &'static str> {
let mut table =
WarpTable::<Item>::try_new(capacity(2)?).map_err(|_| "table storage was unavailable")?;
let first_id = ItemId::new(7);
let first = item(11);
assert_eq!(
table.get(first_id),
WarpTableGetOutcome::Absent { id: first_id }
);
assert_eq!(
table.insert_with_id(first_id, first),
WarpTableInsertWithIdOutcome::Inserted {
id: first_id,
row: 0,
}
);
assert_eq!(
table.insert_with_id(first_id, first),
WarpTableInsertWithIdOutcome::AlreadyPresent {
id: first_id,
existing_row: 0,
rejected: first,
}
);
let second_id = ItemId::new(15);
let second = item(13);
assert_eq!(
table.insert_with_id(second_id, second),
WarpTableInsertWithIdOutcome::Inserted {
id: second_id,
row: 1,
}
);
assert_eq!(table.ids(), [first_id, second_id]);
assert_eq!(table.columns().quantities, [11, 13]);
assert_eq!(
table.get_at(0),
WarpTableGetAtOutcome::Present {
dense_row: 0,
row: ItemRef {
id: &first_id,
quantity: &11,
},
}
);
assert_eq!(
table.get_at(2),
WarpTableGetAtOutcome::Absent { dense_row: 2 }
);
let third_id = ItemId::new(23);
let third = item(17);
assert_eq!(
table.insert_with_id(third_id, third),
WarpTableInsertWithIdOutcome::Full {
id: third_id,
rejected: third,
}
);
assert_eq!(
table.get(first_id),
WarpTableGetOutcome::Present {
row: ItemRef {
id: &first_id,
quantity: &11,
},
}
);
table.columns.quantities.pop();
assert!(catch_unwind(AssertUnwindSafe(|| table.get(second_id))).is_err());
assert!(catch_unwind(AssertUnwindSafe(|| table.get_at(1))).is_err());
table.ids.pop();
assert!(catch_unwind(AssertUnwindSafe(|| table.get_at(0))).is_err());
assert!(catch_unwind(AssertUnwindSafe(|| table.insert_with_id(third_id, third))).is_err());
Ok(())
}
#[test]
fn generated_ids_skip_restored_ids_and_report_exhaustion() -> Result<(), &'static str> {
let mut table = WarpTable::<GeneratedItem>::try_new(capacity(3)?)
.map_err(|_| "table storage was unavailable")?;
let first_id = WarpId::<GeneratedItem>::MIN;
assert!(matches!(
table.insert_with_id(first_id, GeneratedItem { quantity: 11 }),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
let WarpTableInsertOutcome::Inserted {
id: inserted_id,
row: inserted_row,
} = table.insert(GeneratedItem { quantity: 13 })
else {
return Err("generated insertion failed");
};
assert_eq!(inserted_id.value().get(), 2);
assert_eq!(inserted_row, 1);
let WarpTableGetOutcome::Present { row, .. } = table.get(inserted_id) else {
return Err("generated item was unavailable");
};
assert_eq!(row.quantity, &13);
table.next_id = None;
assert_eq!(
table.insert(GeneratedItem { quantity: 17 }),
WarpTableInsertOutcome::IdentifiersExhausted {
rejected: GeneratedItem { quantity: 17 },
}
);
let mut full = WarpTable::<GeneratedItem>::try_new(capacity(1)?)
.map_err(|_| "full table storage was unavailable")?;
assert!(matches!(
full.insert(GeneratedItem { quantity: 19 }),
WarpTableInsertOutcome::Inserted { .. }
));
let next_id = full.next_id;
assert_eq!(
full.insert(GeneratedItem { quantity: 23 }),
WarpTableInsertOutcome::Full {
rejected: GeneratedItem { quantity: 23 },
}
);
assert_eq!(full.next_id, next_id);
Ok(())
}
#[test]
fn warp_table_removes_rows_and_reports_dense_relocation() -> Result<(), &'static str> {
let mut table =
WarpTable::<Item>::try_new(capacity(3)?).map_err(|_| "table storage was unavailable")?;
let rows = [
(ItemId::new(7), item(11)),
(ItemId::new(15), item(13)),
(ItemId::new(23), item(17)),
];
for (id, row) in rows {
assert!(matches!(
table.insert_with_id(id, row),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
}
assert_eq!(
table.remove(rows[1].0),
WarpTableRemoveOutcome::Removed {
id: rows[1].0,
removed: rows[1].1,
relocation: WarpTableRowRelocation::Relocated {
id: rows[2].0,
from: 2,
to: 1,
},
}
);
assert!(matches!(
table.get(rows[2].0),
WarpTableGetOutcome::Present { .. }
));
assert_eq!(table.ids(), [rows[0].0, rows[2].0]);
assert_eq!(table.columns().quantities, [11, 17]);
assert_eq!(
table.remove(rows[1].0),
WarpTableRemoveOutcome::Absent { id: rows[1].0 }
);
table.columns.quantities.pop();
assert!(catch_unwind(AssertUnwindSafe(|| table.remove(rows[0].0))).is_err());
Ok(())
}
#[test]
fn warp_table_replaces_only_a_present_id() -> Result<(), &'static str> {
let mut table =
WarpTable::<Item>::try_new(capacity(2)?).map_err(|_| "table storage was unavailable")?;
let id = ItemId::new(7);
let original = item(11);
assert!(matches!(
table.insert_with_id(id, original),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
let absent_id = ItemId::new(9);
let absent = item(17);
assert_eq!(
table.replace(absent_id, absent),
WarpTableReplaceOutcome::Absent {
id: absent_id,
rejected: absent,
}
);
let replacement = item(19);
assert_eq!(
table.replace(id, replacement),
WarpTableReplaceOutcome::Replaced {
id,
row: 0,
previous: original,
}
);
assert_eq!(table.ids(), [id]);
assert_eq!(table.columns().quantities, [replacement.quantity]);
table.columns.quantities.pop();
let rejected = item(23);
assert!(catch_unwind(AssertUnwindSafe(|| table.replace(id, rejected))).is_err());
Ok(())
}
#[test]
fn warp_table_updates_a_present_id_through_its_mutable_row() -> Result<(), &'static str> {
let mut table =
WarpTable::<Item>::try_new(capacity(1)?).map_err(|_| "table storage was unavailable")?;
let id = ItemId::new(7);
assert!(matches!(
table.insert_with_id(id, item(11)),
WarpTableInsertWithIdOutcome::Inserted { .. }
));
assert_eq!(
table.update(id, |row| {
assert_eq!(*row.id, id);
let previous = *row.quantity;
*row.quantity = 13;
previous
}),
WarpTableUpdateOutcome::Updated {
id,
row: 0,
output: 11,
}
);
assert_eq!(table.columns().quantities, [13]);
assert_eq!(
table.update(ItemId::new(9), |_| ()),
WarpTableUpdateOutcome::Absent { id: ItemId::new(9) }
);
Ok(())
}
#[derive(Clone, Copy, Debug)]
enum ModelOperation {
Insert { id: ItemId, row: Item },
Replace { id: ItemId, replacement: Item },
Remove(ItemId),
Get(ItemId),
}
fn model_operation_strategy() -> impl Strategy<Value = ModelOperation> {
prop_oneof![
4 => (0_u8..32, any::<u16>()).prop_map(|(id, quantity)| ModelOperation::Insert {
id: ItemId::new(u64::from(id)),
row: item(u32::from(quantity)),
}),
3 => (0_u8..32, any::<u16>()).prop_map(|(id, quantity)| ModelOperation::Replace {
id: ItemId::new(u64::from(id)),
replacement: item(u32::from(quantity)),
}),
2 => (0_u8..32).prop_map(|id| ModelOperation::Remove(ItemId::new(u64::from(id)))),
2 => (0_u8..32).prop_map(|id| ModelOperation::Get(ItemId::new(u64::from(id)))),
]
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn warp_table_matches_a_dense_reference_model(
maximum_rows in 1_u32..=16,
operations in prop::collection::vec(model_operation_strategy(), 1..128),
) {
let Some(maximum_rows) = NonZeroU32::new(maximum_rows) else {
return Err(TestCaseError::fail("generated capacity was zero"));
};
let capacity = Capacity::new(maximum_rows);
let model_capacity = usize::try_from(maximum_rows.get())
.map_err(|_| TestCaseError::fail("capacity was not representable"))?;
let mut table = WarpTable::<Item>::try_new(capacity)
.map_err(|_| TestCaseError::fail("table storage was unavailable"))?;
let mut model = Vec::new();
for operation in operations {
apply_model_operation(&mut table, &mut model, model_capacity, operation)?;
assert_table_matches_model(&table, &model)?;
}
}
}
fn apply_model_operation(
table: &mut WarpTable<Item>,
model: &mut Vec<(ItemId, Item)>,
capacity: usize,
operation: ModelOperation,
) -> TestCaseResult {
match operation {
ModelOperation::Insert { id, row } => {
let expected = match model.iter().position(|stored| stored.0 == id) {
Some(existing_row) => WarpTableInsertWithIdOutcome::AlreadyPresent {
id,
existing_row,
rejected: row,
},
None if model.len() >= capacity => {
WarpTableInsertWithIdOutcome::Full { id, rejected: row }
}
None => {
let dense_row = model.len();
model.push((id, row));
WarpTableInsertWithIdOutcome::Inserted { id, row: dense_row }
}
};
prop_assert_eq!(table.insert_with_id(id, row), expected);
}
ModelOperation::Replace { id, replacement } => {
let expected = match model.iter().position(|stored| stored.0 == id) {
Some(row) => {
let Some(stored) = model.get_mut(row) else {
return Err(TestCaseError::fail("modeled row was unavailable"));
};
let previous = mem::replace(&mut stored.1, replacement);
WarpTableReplaceOutcome::Replaced { id, row, previous }
}
None => WarpTableReplaceOutcome::Absent {
id,
rejected: replacement,
},
};
prop_assert_eq!(table.replace(id, replacement), expected);
}
ModelOperation::Remove(id) => {
let expected = match model.iter().position(|stored| stored.0 == id) {
Some(row) => {
let Some(last_row) = model.len().checked_sub(1) else {
return Err(TestCaseError::fail("located a row in an empty model"));
};
let relocation = if row == last_row {
WarpTableRowRelocation::Stable
} else {
let Some(moved) = model.get(last_row) else {
return Err(TestCaseError::fail("last model row was unavailable"));
};
WarpTableRowRelocation::Relocated {
id: moved.0,
from: last_row,
to: row,
}
};
let removed = model.swap_remove(row);
WarpTableRemoveOutcome::Removed {
id,
removed: removed.1,
relocation,
}
}
None => WarpTableRemoveOutcome::Absent { id },
};
prop_assert_eq!(table.remove(id), expected);
}
ModelOperation::Get(id) => {
let expected = match model.iter().find(|stored| stored.0 == id) {
Some(stored) => WarpTableGetOutcome::Present {
row: ItemRef {
id: &stored.0,
quantity: &stored.1.quantity,
},
},
None => WarpTableGetOutcome::Absent { id },
};
prop_assert_eq!(table.get(id), expected);
}
}
Ok(())
}
fn assert_table_matches_model(table: &WarpTable<Item>, model: &[(ItemId, Item)]) -> TestCaseResult {
prop_assert_eq!(table.len(), model.len());
prop_assert_eq!(table.is_empty(), model.is_empty());
prop_assert_eq!(table.ids().len(), model.len());
prop_assert_eq!(table.columns().quantities.len(), model.len());
for ((id, quantity), expected) in table
.ids()
.iter()
.zip(&table.columns().quantities)
.zip(model)
{
prop_assert_eq!(id, &expected.0);
prop_assert_eq!(quantity, &expected.1.quantity);
match table.get(expected.0) {
WarpTableGetOutcome::Present { row, .. } => {
prop_assert_eq!(row.id, id);
prop_assert_eq!(row.quantity, quantity);
}
WarpTableGetOutcome::Absent { .. } => {
prop_assert!(false, "indexed lookup lost a modeled row");
}
}
}
Ok(())
}
#[test]
fn every_public_access_rejects_mismatched_id_length_before_effects() -> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(capacity(2)?).map_err(|_| "table unavailable")?;
assert_eq!(
table.insert_with_id(ItemId::new(7), item(11)),
WarpTableInsertWithIdOutcome::Inserted {
id: ItemId::new(7),
row: 0
}
);
table.ids.clear();
let calls = core::cell::Cell::new(0);
assert!(catch_unwind(AssertUnwindSafe(|| table.get(ItemId::new(7)))).is_err());
assert!(catch_unwind(AssertUnwindSafe(|| table.get_at(0))).is_err());
assert!(
catch_unwind(AssertUnwindSafe(|| {
let _unexpected_entry = table.entry(ItemId::new(7));
}))
.is_err()
);
assert!(catch_unwind(AssertUnwindSafe(|| table.remove(ItemId::new(7)))).is_err());
assert!(catch_unwind(AssertUnwindSafe(|| table.replace(ItemId::new(7), item(19)))).is_err());
assert!(
catch_unwind(AssertUnwindSafe(|| table.update_with(
ItemId::new(7),
23,
|_, context| {
calls.set(context);
}
)))
.is_err()
);
assert_eq!(calls.get(), 0);
assert_eq!(table.len, 1);
assert!(table.ids.is_empty());
assert_eq!(table.columns.quantities, [11]);
let mut generated = WarpTable::<GeneratedItem>::try_new(capacity(2)?)
.map_err(|_| "generated table unavailable")?;
assert!(matches!(
generated.insert(GeneratedItem { quantity: 13 }),
WarpTableInsertOutcome::Inserted { .. }
));
generated.ids.clear();
assert!(
catch_unwind(AssertUnwindSafe(
|| generated.insert(GeneratedItem { quantity: 29 })
))
.is_err()
);
assert_eq!(generated.len, 1);
assert!(generated.ids.is_empty());
assert_eq!(generated.columns.quantity, [13]);
Ok(())
}
#[test]
fn dangling_primary_rows_and_missing_columns_fail_before_callbacks() -> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(capacity(2)?).map_err(|_| "table unavailable")?;
for (id, quantity, row) in [(7, 11, 0), (13, 17, 1)] {
assert_eq!(
table.insert_with_id(ItemId::new(id), item(quantity)),
WarpTableInsertWithIdOutcome::Inserted {
id: ItemId::new(id),
row
}
);
}
table.ids.truncate(1);
table.len = 1;
let calls = core::cell::Cell::new(0);
assert!(
catch_unwind(AssertUnwindSafe(|| {
let _unexpected_entry = table.entry(ItemId::new(13));
}))
.is_err()
);
assert!(catch_unwind(AssertUnwindSafe(|| table.replace(ItemId::new(13), item(23)))).is_err());
assert!(
catch_unwind(AssertUnwindSafe(
|| table.update(ItemId::new(13), |_| calls.set(1))
))
.is_err()
);
table.ids.push(ItemId::new(13));
table.len = 2;
table.columns.quantities.clear();
assert!(
catch_unwind(AssertUnwindSafe(|| {
let _unexpected_entry = table.entry(ItemId::new(7));
}))
.is_err()
);
assert!(
catch_unwind(AssertUnwindSafe(
|| table.update(ItemId::new(7), |_| calls.set(2))
))
.is_err()
);
assert!(catch_unwind(AssertUnwindSafe(|| table.update_at(7, |_| calls.set(3)))).is_err());
assert_eq!(calls.get(), 0);
assert_eq!(table.ids, [ItemId::new(7), ItemId::new(13)]);
assert_eq!(table.len, 2);
assert!(table.columns.quantities.is_empty());
Ok(())
}
#[derive(Debug, Eq, PartialEq, WarpTableRow)]
struct UniqueItem {
#[index(unique)]
key: u8,
value: u32,
}
#[test]
fn insert_and_upsert_conflicts_return_ownership_and_preserve_the_table() -> Result<(), &'static str>
{
use super::WarpTableUpsertOutcome;
let mut table =
WarpTable::<UniqueItem>::try_new(capacity(2)?).map_err(|_| "unique table unavailable")?;
let first = WarpId::new(core::num::NonZeroU64::MIN);
let second = WarpId::new(core::num::NonZeroU64::new(2).ok_or("second id unavailable")?);
let third = WarpId::new(core::num::NonZeroU64::new(3).ok_or("third id unavailable")?);
assert_eq!(
table.upsert(first, UniqueItem { key: 7, value: 11 }),
WarpTableUpsertOutcome::Inserted { id: first, row: 0 }
);
let WarpTableInsertOutcome::UniqueConflict { conflict, rejected } =
table.insert(UniqueItem { key: 7, value: 13 })
else {
return Err("generated duplicate was accepted");
};
assert_eq!(rejected, UniqueItem { key: 7, value: 13 });
assert_eq!(
table.upsert(second, UniqueItem { key: 7, value: 17 }),
WarpTableUpsertOutcome::UniqueConflict {
id: second,
conflict,
rejected: UniqueItem { key: 7, value: 17 }
}
);
assert_eq!(
table.upsert(second, UniqueItem { key: 19, value: 23 }),
WarpTableUpsertOutcome::Inserted { id: second, row: 1 }
);
assert_eq!(
table.upsert(second, UniqueItem { key: 7, value: 29 }),
WarpTableUpsertOutcome::UniqueConflict {
id: second,
conflict,
rejected: UniqueItem { key: 7, value: 29 }
}
);
assert_eq!(
table.upsert(third, UniqueItem { key: 31, value: 37 }),
WarpTableUpsertOutcome::Full {
id: third,
rejected: UniqueItem { key: 31, value: 37 }
}
);
assert_eq!(
table
.rows()
.map(|row| (*row.id, *row.key, *row.value))
.collect::<Vec<_>>(),
[(first, 7, 11), (second, 19, 23)]
);
assert_eq!(
table.upsert(second, UniqueItem { key: 41, value: 43 }),
WarpTableUpsertOutcome::Replaced {
id: second,
row: 1,
previous: UniqueItem { key: 19, value: 23 }
}
);
assert_eq!(
table
.rows()
.map(|row| (*row.id, *row.key, *row.value))
.collect::<Vec<_>>(),
[(first, 7, 11), (second, 41, 43)]
);
Ok(())
}
#[test]
fn unique_update_binding_preserves_absent_context_and_checks_resolved_rows()
-> Result<(), &'static str> {
use super::update::{bind_unique_update, bind_unique_update_with};
use super::{
UniqueIndexLookupOutcome, WarpTableUniqueUpdateOutcome, WarpTableUniqueUpdateWithOutcome,
};
let mut table = WarpTable::<Item>::try_new(capacity(1)?).map_err(|_| "table unavailable")?;
let calls = core::cell::Cell::new(0);
assert_eq!(
bind_unique_update(
&mut table,
UniqueIndexLookupOutcome::Absent { key: 7_u8 },
|_| calls.set(1)
),
WarpTableUniqueUpdateOutcome::Absent { key: 7 }
);
assert_eq!(
bind_unique_update_with(
&mut table,
UniqueIndexLookupOutcome::Absent { key: 11_u8 },
alloc::string::String::from("retained"),
|_, context| context
),
WarpTableUniqueUpdateWithOutcome::Absent {
key: 11,
context: alloc::string::String::from("retained")
}
);
assert!(
catch_unwind(AssertUnwindSafe(|| bind_unique_update(
&mut table,
UniqueIndexLookupOutcome::Present { key: 13_u8, row: 7 },
|_| calls.set(2)
)))
.is_err()
);
assert_eq!(
table.insert_with_id(ItemId::new(17), item(19)),
WarpTableInsertWithIdOutcome::Inserted {
id: ItemId::new(17),
row: 0
}
);
table.ids.clear();
assert!(
catch_unwind(AssertUnwindSafe(|| bind_unique_update(
&mut table,
UniqueIndexLookupOutcome::Present { key: 23_u8, row: 0 },
|_| calls.set(3)
)))
.is_err()
);
assert_eq!(calls.get(), 0);
assert_eq!(table.columns.quantities, [19]);
table.ids.push(ItemId::new(17));
assert_eq!(
bind_unique_update_with(
&mut table,
UniqueIndexLookupOutcome::Present { key: 29_u8, row: 0 },
31_u32,
|row, context| {
*row.quantity = context;
context
}
),
WarpTableUniqueUpdateWithOutcome::Updated {
key: 29,
id: ItemId::new(17),
row: 0,
output: 31
}
);
assert_eq!(table.columns.quantities, [31]);
Ok(())
}
#[test]
fn row_iteration_reports_exact_remaining_length_and_stays_exhausted() -> Result<(), &'static str> {
let mut table = WarpTable::<Item>::try_new(capacity(3)?).map_err(|_| "table unavailable")?;
for (id, quantity, row) in [(7, 11, 0), (13, 17, 1), (19, 23, 2)] {
assert_eq!(
table.insert_with_id(ItemId::new(id), item(quantity)),
WarpTableInsertWithIdOutcome::Inserted {
id: ItemId::new(id),
row
}
);
}
let mut rows = table.rows();
for (remaining, expected) in [
(3, Some((7, 11))),
(2, Some((13, 17))),
(1, Some((19, 23))),
(0, None),
(0, None),
] {
assert_eq!(rows.size_hint(), (remaining, Some(remaining)));
assert_eq!(rows.len(), remaining);
assert_eq!(
rows.next().map(|row| (row.id.value(), *row.quantity)),
expected
);
}
assert_eq!(
table.ids(),
[ItemId::new(7), ItemId::new(13), ItemId::new(19)]
);
assert_eq!(table.columns.quantities, [11, 17, 23]);
Ok(())
}