use crate::tests::test_composition;
use crate::{location_key, Composition, MemoryApplier, MutableState};
use cranpose_macros::composable;
#[composable]
fn recursive_node(depth: usize, index: usize) {
if depth > 1 {
for child_idx in 0..2 {
let child_key = (depth - 1, index * 2 + child_idx);
crate::with_key(&child_key, || {
recursive_node(depth - 1, index * 2 + child_idx);
});
}
}
}
#[composable]
fn recursive_root(depth_state: MutableState<usize>) {
let depth = depth_state.get();
recursive_node(depth, 0);
}
fn count_groups(composition: &Composition<MemoryApplier>) -> usize {
composition.debug_dump_slot_table_groups().len()
}
fn count_gap_groups(composition: &Composition<MemoryApplier>) -> usize {
composition
.debug_dump_all_slots()
.iter()
.filter(|(_idx, desc)| desc.contains("Gap") && desc.contains("was_group_key"))
.count()
}
fn get_gap_keys(composition: &Composition<MemoryApplier>) -> Vec<u64> {
composition
.debug_dump_all_slots()
.iter()
.filter_map(|(_idx, desc)| {
if desc.contains("Gap(was_group_key=") {
let key_start = desc.find("was_group_key=")? + "was_group_key=".len();
let key_end = desc[key_start..].find(',')?;
let key_str = &desc[key_start..key_start + key_end];
key_str.parse::<u64>().ok()
} else {
None
}
})
.collect()
}
#[test]
fn recursive_decrease_increase_preserves_structure() {
let mut composition = test_composition();
let runtime = composition.runtime_handle();
let depth_state = MutableState::with_runtime(3usize, runtime.clone());
let key = location_key(file!(), line!(), column!());
eprintln!("\n=== Initial render at depth 3 ===");
composition
.render(key, &mut || {
recursive_root(depth_state);
})
.expect("initial render");
let initial_groups = count_groups(&composition);
let initial_gaps = count_gap_groups(&composition);
eprintln!(
"Groups: {}, Gaps with keys: {}",
initial_groups, initial_gaps
);
eprintln!(
"Group keys: {:?}",
composition
.debug_dump_slot_table_groups()
.iter()
.map(|(idx, key, _, _)| (idx, key))
.collect::<Vec<_>>()
);
assert!(initial_groups > 0, "Should have groups at depth 3");
eprintln!("\n=== Decrease to depth 2 ===");
depth_state.set(2);
let mut recomp_count = 0;
while composition
.process_invalid_scopes()
.expect("recompose after decrease")
{
recomp_count += 1;
}
eprintln!("Recomposed {} times", recomp_count);
let decreased_groups = count_groups(&composition);
let decreased_gaps = count_gap_groups(&composition);
eprintln!(
"Groups: {}, Gaps with keys: {}",
decreased_groups, decreased_gaps
);
eprintln!(
"Group keys: {:?}",
composition
.debug_dump_slot_table_groups()
.iter()
.map(|(idx, key, _, _)| (idx, key))
.collect::<Vec<_>>()
);
eprintln!("All slots:");
for (idx, desc) in composition.debug_dump_all_slots() {
eprintln!(" [{}] {}", idx, desc);
}
assert!(
decreased_groups < initial_groups,
"Decreasing depth should reduce group count"
);
eprintln!("\n=== Increase back to depth 3 ===");
depth_state.set(3);
while composition
.process_invalid_scopes()
.expect("recompose after increase")
{}
let restored_groups = count_groups(&composition);
let restored_gaps = count_gap_groups(&composition);
eprintln!(
"Groups: {}, Gaps with keys: {}",
restored_groups, restored_gaps
);
eprintln!(
"Group keys: {:?}",
composition
.debug_dump_slot_table_groups()
.iter()
.map(|(idx, key, _, _)| (idx, key))
.collect::<Vec<_>>()
);
eprintln!("All slots (first 30):");
for (idx, desc) in composition.debug_dump_all_slots().iter().take(30) {
eprintln!(" [{}] {}", idx, desc);
}
eprintln!("\nComparison:");
eprintln!(" Initial groups: {}", initial_groups);
eprintln!(" Restored groups: {}", restored_groups);
assert_eq!(
restored_groups,
initial_groups,
"After decrease-increase cycle, should restore exact same number of groups. Initial: {}, Restored: {}",
initial_groups,
restored_groups
);
}
#[test]
fn recursive_decrease_increase_multiple_cycles() {
let mut composition = test_composition();
let runtime = composition.runtime_handle();
let depth_state = MutableState::with_runtime(3usize, runtime.clone());
let key = location_key(file!(), line!(), column!());
composition
.render(key, &mut || {
recursive_root(depth_state);
})
.expect("initial render");
let initial_groups = count_groups(&composition);
let initial_keys: Vec<_> = composition
.debug_dump_slot_table_groups()
.iter()
.map(|(_idx, key, _, _)| *key)
.collect();
eprintln!("Initial keys: {:?}", initial_keys);
for cycle in 0..3 {
eprintln!("\n=== Cycle {} ===", cycle);
depth_state.set(2);
while composition.process_invalid_scopes().expect("recompose") {}
let gaps_after_decrease = count_gap_groups(&composition);
let gap_keys = get_gap_keys(&composition);
eprintln!(
"After decrease: {} gaps with keys: {:?}",
gaps_after_decrease, gap_keys
);
depth_state.set(3);
while composition.process_invalid_scopes().expect("recompose") {}
let groups = count_groups(&composition);
let current_keys: Vec<_> = composition
.debug_dump_slot_table_groups()
.iter()
.map(|(_idx, key, _, _)| *key)
.collect();
let gaps_after_increase = count_gap_groups(&composition);
eprintln!(
"After cycle {}: {} groups (initial: {}), {} gaps remaining",
cycle, groups, initial_groups, gaps_after_increase
);
eprintln!("Current keys: {:?}", current_keys);
let mut key_counts: crate::collections::map::HashMap<u64, i32> =
crate::collections::map::HashMap::default();
for k in ¤t_keys {
*key_counts.entry(*k).or_insert(0) += 1;
}
for (k, count) in key_counts.iter() {
if *count > 1 {
eprintln!("DUPLICATE KEY FOUND: {:?} appears {} times", k, count);
}
}
for k in &initial_keys {
if !current_keys.contains(k) {
eprintln!("MISSING KEY: {:?}", k);
}
}
assert_eq!(
groups, initial_groups,
"After cycle {}: groups should be exactly preserved. Initial: {}, Current: {}",
cycle, initial_groups, groups
);
}
}