use proptest::prelude::*;
pub(super) use super::arbitrary::op_non_control_sequence_strategy;
use super::*;
use crate::{Decorator, ONE, mast::MastForest};
#[test]
fn batch_ops_1() {
let ops = vec![Operation::Add];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let mut batch_groups = [ZERO; BATCH_SIZE];
batch_groups[0] = build_group(&ops);
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_2() {
let ops = vec![Operation::Add, Operation::Mul];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let mut batch_groups = [ZERO; BATCH_SIZE];
batch_groups[0] = build_group(&ops);
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_3() {
let ops = vec![Operation::Add, Operation::Push(Felt::new(12345678))];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let mut batch_groups = [ZERO; BATCH_SIZE];
batch_groups[0] = build_group(&ops);
batch_groups[1] = Felt::new(12345678);
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_4() {
let ops = vec![
Operation::Push(ONE),
Operation::Push(Felt::new(2)),
Operation::Push(Felt::new(3)),
Operation::Push(Felt::new(4)),
Operation::Push(Felt::new(5)),
Operation::Push(Felt::new(6)),
Operation::Push(Felt::new(7)),
Operation::Add,
];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let batch_groups = [
build_group(&ops),
ONE,
Felt::new(2),
Felt::new(3),
Felt::new(4),
Felt::new(5),
Felt::new(6),
Felt::new(7),
];
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_5() {
let ops = vec![
Operation::Add,
Operation::Mul,
Operation::Push(ONE),
Operation::Push(Felt::new(2)),
Operation::Push(Felt::new(3)),
Operation::Push(Felt::new(4)),
Operation::Push(Felt::new(5)),
Operation::Push(Felt::new(6)),
Operation::Add,
Operation::Push(Felt::new(7)),
];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let batch0_groups = [
build_group(&ops[..9]),
ONE,
Felt::new(2),
Felt::new(3),
Felt::new(4),
Felt::new(5),
Felt::new(6),
ZERO,
];
let mut batch1_groups = [ZERO; BATCH_SIZE];
batch1_groups[0] = build_group(&[ops[9]]);
batch1_groups[1] = Felt::new(7);
let all_groups = [batch0_groups, batch1_groups].concat();
assert_eq!(hasher::hash_elements(&all_groups), hash);
}
#[test]
fn batch_ops_6() {
let ops = vec![
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Push(Felt::new(7)),
Operation::Add,
Operation::Add,
Operation::Push(Felt::new(11)),
Operation::Mul,
Operation::Mul,
Operation::Add,
];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let batch_groups = [
build_group(&ops[..9]),
Felt::new(7),
Felt::new(11),
build_group(&ops[9..]),
ZERO,
ZERO,
ZERO,
ZERO,
];
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_7() {
let ops = vec![
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Add,
Operation::Add,
Operation::Mul,
Operation::Mul,
Operation::Add,
Operation::Push(Felt::new(11)),
];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let batch_groups = [
build_group(&ops[..8]),
build_group(&[ops[8]]),
Felt::new(11),
ZERO,
ZERO,
ZERO,
ZERO,
ZERO,
];
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_8() {
let ops = vec![
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Add,
Operation::Add,
Operation::Mul,
Operation::Mul,
Operation::Push(ONE),
Operation::Push(Felt::new(2)),
];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let batch_groups = [
build_group(&ops[..8]),
ONE,
build_group(&[ops[8]]),
Felt::new(2),
ZERO,
ZERO,
ZERO,
ZERO,
];
assert_eq!(hasher::hash_elements(&batch_groups), hash);
}
#[test]
fn batch_ops_9() {
let ops = vec![
Operation::Add,
Operation::Mul,
Operation::Push(ONE),
Operation::Push(Felt::new(2)),
Operation::Push(Felt::new(3)),
Operation::Push(Felt::new(4)),
Operation::Push(Felt::new(5)),
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Mul,
Operation::Add,
Operation::Mul,
Operation::Push(Felt::new(6)),
Operation::Pad,
];
let (batches, hash) = super::batch_and_hash_ops(ops.clone());
insta::assert_debug_snapshot!(batches);
insta::assert_debug_snapshot!(build_group_chunks(&batches).collect::<Vec<_>>());
let batch0_groups = [
build_group(&ops[..9]),
ONE,
Felt::new(2),
Felt::new(3),
Felt::new(4),
Felt::new(5),
build_group(&ops[9..17]),
ZERO,
];
let batch1_groups = [build_group(&ops[17..]), Felt::new(6), ZERO, ZERO, ZERO, ZERO, ZERO, ZERO];
let all_groups = [batch0_groups, batch1_groups].concat();
assert_eq!(hasher::hash_elements(&all_groups), hash);
}
#[test]
fn operation_or_decorator_iterator() {
let mut mast_forest = MastForest::new();
let operations = vec![Operation::Add, Operation::Mul, Operation::MovDn2, Operation::MovDn3];
let decorators = vec![
(0, Decorator::Trace(0)), (0, Decorator::Trace(1)), (3, Decorator::Trace(2)), (4, Decorator::Trace(3)), (4, Decorator::Trace(4)), ];
let node =
BasicBlockNode::new_with_raw_decorators(operations, decorators, &mut mast_forest).unwrap();
let mut iterator = node.iter();
assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(0))));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(1))));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::Add)));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::Mul)));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::MovDn2)));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(2))));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Operation(&Operation::MovDn3)));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(3))));
assert_eq!(iterator.next(), Some(OperationOrDecorator::Decorator(&DecoratorId(4))));
assert_eq!(iterator.next(), None);
}
fn build_group(ops: &[Operation]) -> Felt {
let mut group = 0u64;
for (i, op) in ops.iter().enumerate() {
group |= (op.op_code() as u64) << (Operation::OP_BITS * i);
}
Felt::new(group)
}
fn build_group_chunks(batches: &[OpBatch]) -> impl Iterator<Item = &[Operation]> {
batches.iter().flat_map(|opbatch| opbatch.group_chunks())
}
proptest! {
#[test]
fn test_batch_creation_invariants(ops in op_non_control_sequence_strategy(50)) {
let (batches, _) = super::batch_and_hash_ops(ops.clone());
assert!(!batches.is_empty(), "There should be at least one batch");
for batch in &batches {
assert!(batch.num_groups <= BATCH_SIZE);
assert!(batch.num_groups.is_power_of_two());
}
let total_ops_from_batches: usize = batches.iter().map(|batch| {
batch.ops.len() - batch.padding.iter().filter(|b| **b).count()
}).sum();
assert_eq!(total_ops_from_batches, ops.len(), "Total operations from batches should be == input operations");
for batch in &batches {
for chunk in batch.group_chunks() {
let count = chunk.len();
assert!(chunk.len() <= GROUP_SIZE,
"Group {:?} in batch has {} operations, which exceeds the maximum of {}",
chunk, count, GROUP_SIZE);
}
}
}
#[test]
fn test_immediate_value_placement(ops in op_non_control_sequence_strategy(50)) {
let (batches, _) = super::batch_and_hash_ops(ops.clone());
for batch in batches {
let mut op_idx_in_group = 0;
let mut group_idx = 0;
let mut next_group_idx = 1;
for (op_idx_in_batch, op) in batch.ops().iter().enumerate() {
let has_imm = op.imm_value().is_some();
if has_imm {
assert_eq!(batch.indptr[next_group_idx+1] - batch.indptr[next_group_idx], 0, "invalid immediate op count convention");
next_group_idx += 1;
}
if op_idx_in_batch + 1 == batch.indptr[group_idx + 1] {
if has_imm {
assert!(op_idx_in_group < GROUP_SIZE - 1, "invalid op index");
}
group_idx = next_group_idx;
next_group_idx += 1;
op_idx_in_group = 0;
} else {
op_idx_in_group += 1;
}
}
}
}
}
fn decorator_strategy(
nops: usize,
max_decorators: usize,
) -> impl Strategy<Value = Vec<(usize, DecoratorId)>> {
prop::collection::vec(
(0..=nops, any::<u32>().prop_map(DecoratorId::new_unchecked)),
0..=max_decorators,
)
.prop_map(move |mut decorators| {
decorators.sort_by_key(|(idx, _)| *idx);
decorators
})
}
fn decorator_list_strategy(
ops_num: usize,
) -> impl Strategy<Value = (Vec<Operation>, Vec<(usize, DecoratorId)>)> {
op_non_control_sequence_strategy(ops_num)
.prop_flat_map(|ops| (Just(ops.clone()), decorator_strategy(ops.len(), ops.len())))
}
proptest! {
#[test]
fn test_raw_decorator_iter_preserves_decorators(
(ops, decs) in decorator_list_strategy(20)
) {
let block = BasicBlockNode::new(ops.clone(), decs.clone()).unwrap();
let collected_decorators: Vec<(usize, DecoratorId)> = block.raw_decorator_iter().collect();
prop_assert_eq!(collected_decorators, decs);
}
}
#[test]
fn test_mast_node_error_context_decorators_iterates_all_decorators() {
let mut forest = MastForest::new();
let operations = vec![Operation::Add, Operation::Mul];
let before_enter_deco = Decorator::Trace(1);
let op_deco = Decorator::Trace(2);
let after_exit_deco = Decorator::Trace(3);
let before_enter_id = forest.add_decorator(before_enter_deco.clone()).unwrap();
let op_id = forest.add_decorator(op_deco.clone()).unwrap();
let after_exit_id = forest.add_decorator(after_exit_deco.clone()).unwrap();
let mut block = BasicBlockNode::new(operations, vec![(1, op_id)]).unwrap();
block.append_before_enter(&[before_enter_id]);
block.append_after_exit(&[after_exit_id]);
let all_decorators: Vec<_> = block.decorators().collect();
assert_eq!(all_decorators.len(), 3);
assert_eq!(all_decorators[0], (0, before_enter_id));
assert_eq!(all_decorators[1], (1, op_id));
assert_eq!(all_decorators[2], (2, after_exit_id));
}
#[test]
fn test_indexed_decorator_iter_excludes_before_enter_after_exit() {
let mut forest = MastForest::new();
let operations = vec![Operation::Add, Operation::Mul];
let before_enter_deco = Decorator::Trace(1);
let op_deco1 = Decorator::Trace(2);
let op_deco2 = Decorator::Trace(3);
let after_exit_deco = Decorator::Trace(4);
let before_enter_id = forest.add_decorator(before_enter_deco.clone()).unwrap();
let op_id1 = forest.add_decorator(op_deco1.clone()).unwrap();
let op_id2 = forest.add_decorator(op_deco2.clone()).unwrap();
let after_exit_id = forest.add_decorator(after_exit_deco.clone()).unwrap();
let mut block = BasicBlockNode::new(operations, vec![(0, op_id1), (1, op_id2)]).unwrap();
block.append_before_enter(&[before_enter_id]);
block.append_after_exit(&[after_exit_id]);
let indexed_decorators: Vec<_> = block.indexed_decorator_iter().collect();
assert_eq!(indexed_decorators.len(), 2);
assert!(!indexed_decorators.iter().any(|&(_, id)| id == before_enter_id));
assert!(!indexed_decorators.iter().any(|&(_, id)| id == after_exit_id));
let mut indexed_ids: Vec<_> = indexed_decorators.iter().map(|&(_, id)| id).collect();
indexed_ids.sort();
let mut expected_op_ids = vec![op_id1, op_id2];
expected_op_ids.sort();
assert_eq!(indexed_ids, expected_op_ids);
}
#[test]
fn test_decorator_positions() {
let mut forest = MastForest::new();
let trace_deco = Decorator::Trace(42);
let debug_deco = Decorator::Trace(999);
let trace_id = forest.add_decorator(trace_deco.clone()).unwrap();
let debug_id = forest.add_decorator(debug_deco.clone()).unwrap();
let operations = vec![
Operation::Push(Felt::new(1)),
Operation::Push(Felt::new(2)),
Operation::Add,
Operation::Push(Felt::new(3)),
Operation::Mul,
];
let mut block =
BasicBlockNode::new(operations.clone(), vec![(2, trace_id), (4, debug_id)]).unwrap();
block.append_before_enter(&[trace_id, debug_id]);
block.append_after_exit(&[trace_id]);
let all_decorators: Vec<_> = block.decorators().collect();
assert_eq!(all_decorators.len(), 5);
let mut found_positions = Vec::new();
for (pos, _id) in &all_decorators {
found_positions.push(*pos);
}
let mut expected_positions = vec![0, 0, 2, 4, 5]; expected_positions.sort();
assert_eq!(found_positions, expected_positions);
let indexed_decorators: Vec<_> = block.indexed_decorator_iter().collect();
assert_eq!(indexed_decorators.len(), 2);
let indexed_positions: Vec<_> = indexed_decorators.iter().map(|&(pos, _id)| pos).collect();
let mut expected_indexed_positions = vec![2, 4];
expected_indexed_positions.sort();
assert_eq!(indexed_positions, expected_indexed_positions);
assert!(!indexed_positions.contains(&0)); assert!(!indexed_positions.contains(&5));
block.append_before_enter(&[]);
block.append_after_exit(&[]);
let all_decorators_after_mod: Vec<_> = block.decorators().collect();
assert_eq!(
all_decorators_after_mod.len(),
5,
"Expected 5 decorators, got {:?}. All decorators: {:?}",
all_decorators_after_mod.len(),
all_decorators_after_mod.iter().collect::<Vec<_>>()
);
assert!(all_decorators_after_mod.iter().any(|&(_, id)| id == debug_id));
assert!(all_decorators_after_mod.iter().any(|&(_, id)| id == debug_id));
assert!(all_decorators_after_mod.iter().any(|&(_, id)| id == trace_id));
}
proptest! {
#[test]
fn proptest_raw_to_padded_correctness(
(ops, decorators) in
decorator_list_strategy(72)
) {
let block = BasicBlockNode::new(ops.clone(), decorators).unwrap();
let padded_ops = block.op_batches().iter().flat_map(|batch| batch.ops()).collect::<Vec<_>>();
let raw2pad = RawToPaddedPrefix::new(block.op_batches());
let pad2raw = PaddedToRawPrefix::new(block.op_batches());
for r in 0..ops.len() {
let p = r + raw2pad[r];
prop_assert!(
*padded_ops[p] == ops[r],
"Raw->Padded conversion incorrect for raw index {}",
r
);
}
for p in 0..padded_ops.len() {
let r = p - pad2raw[p];
if *padded_ops[p] != Operation::Noop {
prop_assert!(
ops[r] == *padded_ops[p],
"Padded->Raw conversion incorrect for padded index {}",
p
);
}
}
for r in 0..=ops.len() {
let p = r + raw2pad[r];
prop_assert_eq!(
pad2raw[p],
raw2pad[r],
"Cross-array invariant violated for raw {}, padded {}",
r, p
);
}
}
}
proptest! {
#[test]
fn proptest_decorator_iterator_invertibility(
(ops, decorators) in
decorator_list_strategy(72)
) {
let block = BasicBlockNode::new(ops.clone(), decorators.clone()).unwrap();
let raw_iter = block.raw_decorator_iter();
let collected_decorators: Vec<_> = raw_iter.collect();
for (collected_idx, &(_expected_raw_idx, expected_id)) in decorators.iter().enumerate() {
if collected_idx < collected_decorators.len() {
let (actual_raw_idx, actual_id) = collected_decorators[collected_idx];
prop_assert_eq!(actual_id, expected_id);
prop_assert!(actual_raw_idx <= ops.len()); }
}
}
}