#![allow(clippy::expect_used)]
use proptest::prelude::*;
use crate::payload::blob::{BlobKind, BlobPayload};
use crate::payload::block::{BlockKind, BlockPayload};
use crate::payload::common::MerkleRoot;
use crate::payload::refs::{RefKind, RefStatePayload, RefUpdatePayload};
use crate::{CanonicalEncode, ObjectId};
fn object_id_strategy() -> impl Strategy<Value = ObjectId> {
proptest::array::uniform32(any::<u8>()).prop_map(ObjectId::from_bytes)
}
fn sorted_unique_object_ids(max_len: usize) -> impl Strategy<Value = Vec<ObjectId>> {
proptest::collection::vec(object_id_strategy(), 0..max_len).prop_map(|mut ids| {
ids.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
ids.dedup();
ids
})
}
fn block_kind_strategy() -> impl Strategy<Value = BlockKind> {
prop_oneof![
Just(BlockKind::Root),
Just(BlockKind::Normal),
Just(BlockKind::Merge),
Just(BlockKind::Repair),
Just(BlockKind::Import),
]
}
fn block_payload_strategy() -> impl Strategy<Value = BlockPayload> {
(
sorted_unique_object_ids(3),
block_kind_strategy(),
proptest::collection::vec(object_id_strategy(), 0..3),
proptest::array::uniform32(any::<u8>()),
proptest::option::of(object_id_strategy()),
)
.prop_map(
|(parent_block_ids, kind, patch_ids, root_bytes, snapshot_blob_ref)| BlockPayload {
parent_block_ids,
kind,
patch_ids,
state_merkle_root: MerkleRoot(root_bytes),
snapshot_blob_ref,
mainline_parent_id: None,
merge_baseline_block_id: None,
},
)
}
fn ref_kind_strategy() -> impl Strategy<Value = RefKind> {
prop_oneof![Just(RefKind::Branch), Just(RefKind::Tag)]
}
fn ref_name_strategy() -> impl Strategy<Value = String> {
"(heads|tags)/[a-z0-9]{1,8}"
}
fn ref_state_payload_strategy() -> impl Strategy<Value = RefStatePayload> {
(
ref_name_strategy(),
ref_kind_strategy(),
object_id_strategy(),
any::<u64>(),
proptest::option::of(object_id_strategy()),
sorted_unique_object_ids(3),
any::<bool>(),
)
.prop_map(
|(
ref_name,
kind,
target_object_id,
update_seq,
previous_ref_state_id,
required_attestation_ids,
closed,
)| RefStatePayload {
ref_name,
kind,
target_object_id,
update_seq,
previous_ref_state_id,
required_attestation_ids,
closed,
},
)
}
fn ref_update_payload_strategy() -> impl Strategy<Value = RefUpdatePayload> {
(
ref_name_strategy(),
proptest::option::of(object_id_strategy()),
object_id_strategy(),
object_id_strategy(),
any::<u64>(),
any::<u64>(),
"[a-zA-Z0-9_-]{1,8}",
)
.prop_map(
|(
ref_name,
old_ref_state_id,
new_ref_state_id,
new_target_object_id,
update_seq,
created_at,
author_key_id,
)| RefUpdatePayload {
ref_name,
old_ref_state_id,
new_ref_state_id,
new_target_object_id,
update_seq,
created_at,
author_key_id,
},
)
}
fn blob_kind_strategy() -> impl Strategy<Value = BlobKind> {
prop_oneof![
Just(BlobKind::Text),
Just(BlobKind::Binary),
Just(BlobKind::Snapshot),
]
}
fn blob_payload_strategy() -> impl Strategy<Value = BlobPayload> {
(
blob_kind_strategy(),
proptest::collection::vec(any::<u8>(), 0..256),
)
.prop_map(|(blob_kind, content)| BlobPayload::new(blob_kind, content))
}
proptest! {
#[test]
fn block_payload_round_trips(payload in block_payload_strategy()) {
let bytes = payload.to_canonical_bytes()
.expect("generation invariants keep BlockPayload structurally valid");
let decoded = BlockPayload::decode_canonical(&bytes)
.expect("bytes produced by the encoder must always decode");
prop_assert_eq!(decoded, payload);
}
#[test]
fn block_payload_decode_never_panics_on_arbitrary_bytes(
bytes in proptest::collection::vec(any::<u8>(), 0..512)
) {
let _ = BlockPayload::decode_canonical(&bytes);
}
#[test]
fn ref_state_payload_round_trips(payload in ref_state_payload_strategy()) {
let bytes = payload.to_canonical_bytes()
.expect("generation invariants keep RefStatePayload structurally valid");
let schema_version = if payload.closed {
crate::payload::refs::REF_STATE_CLOSED_SCHEMA
} else {
1
};
let decoded = RefStatePayload::decode_canonical(&bytes, schema_version)
.expect("bytes produced by the encoder must always decode at the matching schema");
prop_assert_eq!(decoded, payload);
}
#[test]
fn ref_state_payload_decode_never_panics_on_arbitrary_bytes(
bytes in proptest::collection::vec(any::<u8>(), 0..512),
schema_version in any::<u32>(),
) {
let _ = RefStatePayload::decode_canonical(&bytes, schema_version);
}
#[test]
fn ref_update_payload_round_trips(payload in ref_update_payload_strategy()) {
let bytes = payload.to_canonical_bytes()
.expect("generation invariants keep RefUpdatePayload structurally valid");
let decoded = RefUpdatePayload::decode_canonical(&bytes)
.expect("bytes produced by the encoder must always decode");
prop_assert_eq!(decoded, payload);
}
#[test]
fn ref_update_payload_decode_never_panics_on_arbitrary_bytes(
bytes in proptest::collection::vec(any::<u8>(), 0..512)
) {
let _ = RefUpdatePayload::decode_canonical(&bytes);
}
#[test]
fn blob_payload_round_trips(payload in blob_payload_strategy()) {
let bytes = payload.to_canonical_bytes()
.expect("generation invariants keep BlobPayload structurally valid");
let decoded = BlobPayload::decode_canonical(&bytes)
.expect("bytes produced by the encoder must always decode");
prop_assert_eq!(decoded, payload);
}
#[test]
fn blob_payload_decode_never_panics_on_arbitrary_bytes(
bytes in proptest::collection::vec(any::<u8>(), 0..512)
) {
let _ = BlobPayload::decode_canonical(&bytes);
}
}