use proptest::prelude::*;
use super::{KeyspaceRecord, ManifestEdit, TableMeta, TableRef};
use crate::Error;
fn table() -> impl Strategy<Value = TableMeta> {
(
any::<u32>(),
0_u8..7,
any::<u64>(),
any::<u64>(),
proptest::collection::vec(any::<u8>(), 0..40),
proptest::collection::vec(any::<u8>(), 0..40),
any::<u64>(),
any::<u64>(),
)
.prop_map(
|(keyspace, level, id, size, smallest, largest, min_sequence, max_sequence)| {
TableMeta {
keyspace,
level,
id,
size,
smallest,
largest,
min_sequence,
max_sequence,
}
},
)
}
fn keyspaces() -> impl Strategy<Value = (Vec<KeyspaceRecord>, Vec<u32>, Option<u32>)> {
(
proptest::collection::vec((any::<u32>(), ".{0,12}"), 0..3),
proptest::collection::vec(any::<u32>(), 0..3),
proptest::option::of(any::<u32>()),
)
.prop_map(|(added, dropped, next)| {
let added = added
.into_iter()
.map(|(id, name)| KeyspaceRecord { id, name })
.collect();
(added, dropped, next)
})
}
fn edit() -> impl Strategy<Value = ManifestEdit> {
(
proptest::option::of(any::<u64>()),
proptest::option::of(any::<u64>()),
proptest::collection::vec(table(), 0..4),
proptest::collection::vec((any::<u32>(), 0_u8..7, any::<u64>()), 0..4),
keyspaces(),
)
.prop_map(
|(flushed_sequence, next_file, added, removed, keyspaces)| ManifestEdit {
flushed_sequence,
next_file,
added_keyspaces: keyspaces.0,
dropped_keyspaces: keyspaces.1,
next_keyspace: keyspaces.2,
added,
removed: removed
.into_iter()
.map(|(keyspace, level, id)| TableRef {
keyspace,
level,
id,
})
.collect(),
},
)
}
proptest! {
#[test]
fn every_edit_round_trips(edit in edit()) {
let mut bytes = Vec::new();
edit.encode(&mut bytes);
prop_assert_eq!(ManifestEdit::decode(&bytes).unwrap(), edit);
}
#[test]
fn every_truncation_is_refused(edit in edit()) {
let mut bytes = Vec::new();
edit.encode(&mut bytes);
for len in 0..bytes.len() {
let Some(prefix) = bytes.get(..len) else { continue };
if let Ok(shorter) = ManifestEdit::decode(prefix) {
prop_assert!(shorter != edit, "a truncated edit decoded as the whole one");
}
}
}
#[test]
fn arbitrary_bytes_never_panic(bytes in proptest::collection::vec(any::<u8>(), 0..200)) {
let _ = ManifestEdit::decode(&bytes);
}
}
#[test]
fn an_unknown_version_is_refused() {
assert!(matches!(
ManifestEdit::decode(&[9]),
Err(Error::UnsupportedVersion { version: 9, .. })
));
}
#[test]
fn an_unknown_field_is_refused() {
assert!(matches!(
ManifestEdit::decode(&[1, 0x77]),
Err(Error::Malformed {
reason: "unknown field tag",
..
})
));
}
#[test]
fn a_repeated_scalar_field_is_refused() {
let mut bytes = Vec::new();
ManifestEdit {
flushed_sequence: Some(5),
..ManifestEdit::default()
}
.encode(&mut bytes);
let repeated = bytes.get(1..).unwrap().to_vec();
bytes.extend_from_slice(&repeated);
assert!(matches!(
ManifestEdit::decode(&bytes),
Err(Error::Malformed {
reason: "a field given twice",
..
})
));
}
#[test]
fn a_key_longer_than_the_record_is_refused_before_allocating() {
let bytes = [1, 3, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F];
assert!(matches!(
ManifestEdit::decode(&bytes),
Err(Error::Truncated {
what: "manifest edit key",
..
})
));
}
#[test]
fn an_overlong_integer_is_malformed_not_truncated() {
let mut bytes = vec![1, 1];
bytes.extend_from_slice(&[0x80; 10]);
bytes.push(0);
assert!(matches!(
ManifestEdit::decode(&bytes),
Err(Error::Malformed {
reason: "longer than its type",
..
})
));
}
#[test]
fn a_keyspace_name_that_is_not_text_is_refused() {
let bytes = [1, 5, 3, 2, 0xFF, 0xFE];
assert!(matches!(
ManifestEdit::decode(&bytes),
Err(Error::Malformed {
reason: "a keyspace name is not UTF-8",
..
})
));
}