#![allow(clippy::unwrap_used, clippy::expect_used)]
use brink_format::{
ClosureEnvEntry, DefinitionId, DefinitionTag, LineFlags, ListValue, MapKey, NameId, OrderedMap,
ProjSegment, ShapeId, Value,
};
use brink_runtime::transcript::{read_transcript, write_transcript};
use brink_runtime::{Fragment, Fragments, OutputPart};
use proptest::prelude::*;
fn arb_tag() -> impl Strategy<Value = DefinitionTag> {
prop_oneof![
Just(DefinitionTag::Address),
Just(DefinitionTag::GlobalVar),
Just(DefinitionTag::ListDef),
Just(DefinitionTag::ListItem),
Just(DefinitionTag::ExternalFn),
]
}
fn arb_def_id() -> impl Strategy<Value = DefinitionId> {
(arb_tag(), any::<u64>()).prop_map(|(tag, hash)| DefinitionId::new(tag, hash))
}
fn arb_name_id() -> impl Strategy<Value = NameId> {
any::<u16>().prop_map(NameId)
}
fn arb_map_key() -> impl Strategy<Value = MapKey> {
prop_oneof![
any::<i32>().prop_map(MapKey::Int),
"[a-z]{0,6}".prop_map(|s: String| MapKey::Str(s.into())),
any::<bool>().prop_map(MapKey::Bool),
]
}
fn arb_proj_segment() -> impl Strategy<Value = ProjSegment> {
prop_oneof![
any::<i32>().prop_map(ProjSegment::Index),
any::<i32>().prop_map(|n| ProjSegment::Key(Value::Int(n))),
"[a-z]{0,6}".prop_map(|s: String| ProjSegment::Key(Value::String(s.into()))),
]
}
fn arb_value_leaf() -> impl Strategy<Value = Value> {
prop_oneof![
any::<i32>().prop_map(Value::Int),
any::<f32>().prop_map(Value::Float),
any::<bool>().prop_map(Value::Bool),
"[a-z]{0,8}".prop_map(|s: String| Value::String(s.into())),
(
prop::collection::vec(arb_def_id(), 0..3),
prop::collection::vec(arb_def_id(), 0..3),
)
.prop_map(|(items, origins)| Value::List(ListValue { items, origins }.into())),
arb_def_id().prop_map(Value::DivertTarget),
arb_def_id().prop_map(Value::VariablePointer),
Just(Value::Null),
any::<u32>().prop_map(Value::FragmentRef),
arb_def_id().prop_map(Value::FnRef),
(arb_name_id(), any::<u64>()).prop_map(|(kind, id)| Value::handle(kind, id)),
]
}
fn arb_value() -> impl Strategy<Value = Value> {
arb_value_leaf().prop_recursive(3, 16, 4, |inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..4).prop_map(Value::array),
prop::collection::vec((arb_map_key(), inner.clone()), 0..4).prop_map(|entries| {
let mut map = OrderedMap::new();
for (key, value) in entries {
map.insert(key, value);
}
Value::map(map)
}),
(any::<u32>(), prop::collection::vec(inner.clone(), 0..4))
.prop_map(|(shape, fields)| Value::record(ShapeId(shape), fields)),
(
arb_def_id(),
prop::collection::vec((arb_name_id(), any::<bool>(), inner.clone()), 0..3),
)
.prop_map(|(target, raw_env)| {
let env = raw_env
.into_iter()
.map(|(name, is_ref, payload)| ClosureEnvEntry {
name,
is_ref,
payload,
})
.collect();
Value::closure(target, env)
}),
(
arb_def_id(),
prop::collection::vec(arb_proj_segment(), 0..3),
)
.prop_map(|(cell, segments)| Value::projection(cell, segments)),
]
})
}
fn arb_output_part() -> impl Strategy<Value = OutputPart> {
prop_oneof![
"[^\\x00]{0,16}".prop_map(OutputPart::Text),
(
any::<u32>(),
any::<u16>(),
any::<u8>(),
prop::collection::vec(arb_value(), 0..3),
)
.prop_map(
|(container_idx, line_idx, flag_bits, slots)| OutputPart::LineRef {
container_idx,
line_idx,
slots,
flags: LineFlags::from_bits_truncate(flag_bits),
}
),
arb_value().prop_map(OutputPart::ValueRef),
Just(OutputPart::Newline),
Just(OutputPart::Spring),
Just(OutputPart::Glue),
"[^\\x00]{0,16}".prop_map(OutputPart::Tag),
]
}
#[expect(dead_code, reason = "compile-time-only exhaustiveness guard, see doc")]
fn assert_output_part_variants_exhaustive(part: &OutputPart) {
match part {
OutputPart::Text(_)
| OutputPart::LineRef { .. }
| OutputPart::ValueRef(_)
| OutputPart::Newline
| OutputPart::Spring
| OutputPart::Glue
| OutputPart::Tag(_)
| OutputPart::Checkpoint
| OutputPart::ElementAttach(..)
| OutputPart::ElementAttachEnd => {}
}
}
fn arb_fragment() -> impl Strategy<Value = Fragment> {
(
prop::collection::vec(arb_output_part(), 0..6),
prop::collection::vec("[a-z]{1,6}", 0..3),
)
.prop_map(|(parts, tags)| Fragment { parts, tags })
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn transcript_round_trips_arbitrary_parts_and_values(
parts in prop::collection::vec(arb_output_part(), 0..12),
fragments in prop::collection::vec(arb_fragment(), 0..4),
source_checksum in any::<u32>(),
) {
let bytes = write_transcript(&parts, source_checksum, &Fragments::from(fragments.clone()));
let data = read_transcript(&bytes).expect("a freshly-written transcript must decode");
prop_assert_eq!(data.source_checksum, source_checksum);
prop_assert_eq!(&data.parts, &parts);
prop_assert_eq!(data.fragments.len(), fragments.len());
for (decoded, original) in data.fragments.iter().zip(fragments.iter()) {
prop_assert_eq!(decoded.parts, original.parts.as_slice());
prop_assert_eq!(decoded.tags, original.tags.as_slice());
}
}
}
#[test]
fn fragment_tags_round_trip_through_transcript_codec() {
let fragment = Fragment {
parts: vec![OutputPart::Text("hello".to_string())],
tags: vec!["a_tag".to_string()],
};
let bytes = write_transcript(&[], 0, &Fragments::from(vec![fragment.clone()]));
let data = read_transcript(&bytes).expect("well-formed transcript must decode");
assert_eq!(data.fragments.len(), 1);
assert_eq!(
data.fragments.parts(0).unwrap(),
fragment.parts,
"parts round-trip"
);
assert_eq!(
data.fragments.tags(0).unwrap(),
fragment.tags,
"tags round-trip (fixed by #953)"
);
}