crate::ix!();
impl TryFrom<&Skeleton> for StringSkeleton {
type Error = StringSkeletonError;
#[instrument(level="trace", skip(num_skel))]
fn try_from(num_skel: &Skeleton) -> Result<Self, Self::Error> {
trace!("Converting numeric Skeleton => StringSkeleton, including capstone fields.");
let mut out_map = HashMap::<String, StringSkeletonNode>::new();
for node in num_skel.nodes() {
let is_root_id = Some(node.id()) == *num_skel.root_id();
let is_original_key_root = node.original_key() == "root";
let map_key = if is_root_id && is_original_key_root {
"root".to_string()
} else if !node.original_key().is_empty() && node.original_key() != "root" {
node.original_key().to_string()
} else {
node.name().to_string()
};
trace!(
"Processing node id={} => map_key='{}' original_key='{}' name='{}'",
node.id(),
map_key,
node.original_key(),
node.name()
);
if out_map.contains_key(&map_key) {
warn!("Name collision in final map => '{}'", map_key);
return Err(StringSkeletonError::NameCollision);
}
let mut child_names = Vec::new();
for &cid in node.child_ids() {
if let Some(ch) = num_skel.nodes().iter().find(|n| n.id() == cid) {
let child_is_root_id = Some(ch.id()) == *num_skel.root_id();
let child_is_original_key_root = ch.original_key() == "root";
let cname = if child_is_root_id && child_is_original_key_root {
"root".to_string()
} else if !ch.original_key().is_empty() && ch.original_key() != "root" {
ch.original_key().to_string()
} else {
ch.name().to_string()
};
child_names.push(cname);
} else {
trace!("Skipping invalid child ID={} for node='{}'", cid, map_key);
}
}
let ordering = node.ordering().cloned();
let sn = match node {
SkeletonNode::Dispatch { .. } => {
let mut child_map = HashMap::new();
let child_spec1 = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
for cname in child_names {
child_map.insert(cname, child_spec1.clone());
}
StringSkeletonNode::Dispatch {
name: node.name().to_string(),
ordering: ordering.clone(),
children: child_map,
}
}
SkeletonNode::Aggregate { .. } => {
let mut child_map = HashMap::new();
let child_spec1 = AggregateChildSpecBuilder::default()
.psome_likelihood(100)
.optional(false)
.build()
.unwrap();
for cname in child_names {
child_map.insert(cname, child_spec1.clone());
}
StringSkeletonNode::Aggregate {
name: node.name().to_string(),
ordering: ordering.clone(),
children: child_map,
}
}
SkeletonNode::LeafHolder { leaf_count, capstone, .. } => {
StringSkeletonNode::LeafHolder {
name: node.name().to_string(),
ordering: ordering.clone(),
n_leaves: *leaf_count as u8,
capstone: *capstone,
}
}
};
out_map.insert(map_key, sn);
}
let result = StringSkeletonBuilder::default()
.target_name(num_skel.target_name())
.map(out_map)
.build()
.map_err(|e| {
error!("Failed building StringSkeleton: {:?}", e);
StringSkeletonError::NameCollision
})?;
info!(
"Numeric Skeleton => StringSkeleton succeeded with {} nodes",
result.map().len()
);
Ok(result)
}
}
impl TryFrom<&StringSkeleton> for Skeleton {
type Error = StringSkeletonError;
#[instrument(level="trace", skip(ss))]
fn try_from(ss: &StringSkeleton) -> Result<Self, Self::Error> {
trace!("Converting StringSkeleton => Skeleton with possible capstone fields.");
let has_explicit_root = ss.map().contains_key("root");
let mut name_to_id = HashMap::<String, u16>::new();
let mut next_id: u16 = 0;
if has_explicit_root {
name_to_id.insert("root".to_string(), 0);
}
for map_key in ss.map().keys() {
if map_key == "root" {
name_to_id.entry("root".to_string()).or_insert(0);
continue;
}
if name_to_id.contains_key(map_key) {
error!("Name collision for key: '{}'", map_key);
return Err(StringSkeletonError::NameCollision);
}
while name_to_id.values().any(|&v| v == next_id) {
next_id = next_id.checked_add(1)
.ok_or(StringSkeletonError::NameCollision)?;
}
name_to_id.insert(map_key.clone(), next_id);
next_id = next_id.checked_add(1)
.ok_or(StringSkeletonError::NameCollision)?;
}
let mut used_as_child = HashSet::new();
let mut final_nodes = Vec::new();
for (map_key, node_data) in ss.map().iter() {
let assigned_id = *name_to_id.get(map_key).unwrap_or(&0);
let (child_keys, n_leaves, cap_flag) = match node_data {
StringSkeletonNode::Dispatch { children, .. } => {
let ckeys = children.keys().cloned().collect::<Vec<_>>();
(ckeys, 0u16, false)
}
StringSkeletonNode::Aggregate { children, .. } => {
let ckeys = children.keys().cloned().collect::<Vec<_>>();
(ckeys, 0u16, false)
}
StringSkeletonNode::LeafHolder { n_leaves, capstone, .. } => {
(Vec::new(), *n_leaves as u16, *capstone)
}
};
let mut child_ids = Vec::new();
match node_data {
StringSkeletonNode::Dispatch { children, .. } => {
for ck in children.keys() {
if let Some(&cid) = name_to_id.get(ck) {
child_ids.push(cid);
used_as_child.insert(cid);
} else {
warn!("Missing child name='{}' for node='{}'", ck, map_key);
return Err(StringSkeletonError::MissingChildName);
}
}
}
StringSkeletonNode::Aggregate { children, .. } => {
for ck in children.keys() {
if let Some(&cid) = name_to_id.get(ck) {
child_ids.push(cid);
used_as_child.insert(cid);
} else {
warn!("Missing child name='{}' for node='{}'", ck, map_key);
return Err(StringSkeletonError::MissingChildName);
}
}
}
StringSkeletonNode::LeafHolder { .. } => {
}
}
let (name_for_debug, ordering_for_debug) = match node_data {
StringSkeletonNode::Dispatch { name, ordering, .. } => (name.clone(), ordering.clone()),
StringSkeletonNode::Aggregate { name, ordering, .. } => (name.clone(), ordering.clone()),
StringSkeletonNode::LeafHolder { name, ordering, .. } => (name.clone(), ordering.clone()),
};
let final_name = if name_for_debug.is_empty() {
format!("(unnamed_{})", map_key)
} else {
name_for_debug
};
let kind = match node_data {
StringSkeletonNode::Dispatch { .. } => NodeKind::Dispatch,
StringSkeletonNode::Aggregate { .. } => NodeKind::Aggregate,
StringSkeletonNode::LeafHolder { .. } => NodeKind::LeafHolder,
};
let mut builder = SkeletonNodeBuilder::default()
.id(assigned_id)
.name(final_name)
.original_key(map_key.clone())
.ordering(ordering_for_debug.clone());
if let StringSkeletonNode::Dispatch { .. } | StringSkeletonNode::Aggregate { .. } = node_data {
builder = builder.child_ids(child_ids);
} else if let StringSkeletonNode::LeafHolder { .. } = node_data {
builder = builder.leaf_count(n_leaves).capstone(cap_flag);
}
let built_node = builder.build(kind).map_err(|_| {
error!("SkeletonNodeBuilder failed for node='{}'", map_key);
StringSkeletonError::NameCollision
})?;
debug!(
"Built numeric node => id={}, variant={:?}, capstone={} from map_key='{}'",
built_node.id(),
built_node,
cap_flag,
map_key
);
final_nodes.push(built_node);
}
let root_id = if has_explicit_root {
Some(0_u16)
} else {
final_nodes
.iter()
.map(|nd| nd.id())
.find(|nid| !used_as_child.contains(nid))
};
let sk = SkeletonBuilder::default()
.target_name(ss.target_name().to_string())
.nodes(final_nodes)
.root_id(root_id)
.build()
.map_err(|_| {
error!("SkeletonBuilder failed; possibly ID collision");
StringSkeletonError::NameCollision
})?;
info!(
"StringSkeleton => Skeleton succeeded. node_count={}, root_id={:?}",
sk.node_count(),
sk.root_id(),
);
Ok(sk)
}
}
#[cfg(test)]
mod conversion_tests {
use super::*;
use std::collections::{HashMap, HashSet};
#[traced_test]
fn test_leafholder_capstone_round_trip() {
trace!("Creating a StringSkeleton with one LeafHolder that has capstone=true.");
let mut map = HashMap::new();
let leaf = StringSkeletonNode::LeafHolder {
name: "CapLeaf".to_string(),
ordering: Some(SubBranchOrdering::None),
n_leaves: 5,
capstone: true,
};
map.insert("SingleLeaf".to_string(), leaf);
let str_skel = StringSkeletonBuilder::default()
.map(map)
.build()
.unwrap();
trace!("Converting StringSkeleton => numeric Skeleton.");
let numeric = Skeleton::try_from(&str_skel).expect("string->numeric ok");
assert_eq!(numeric.node_count(), 1);
let node = &numeric.nodes()[0];
match node {
SkeletonNode::LeafHolder {
leaf_count,
capstone,
..
} => {
info!("Numeric LeafHolder => leaf_count={}, capstone={}", leaf_count, capstone);
assert_eq!(*leaf_count, 5);
assert_eq!(*capstone, true);
}
_ => panic!("Expected LeafHolder variant with capstone=true"),
}
trace!("Converting numeric Skeleton => StringSkeleton again.");
let round_trip_str = StringSkeleton::try_from(&numeric).expect("numeric->string ok");
assert_eq!(round_trip_str.map().len(), 1);
let node_data = round_trip_str.map().get("SingleLeaf").expect("Missing SingleLeaf key");
match node_data {
StringSkeletonNode::LeafHolder {
n_leaves,
capstone,
..
} => {
info!("Round-trip => n_leaves={}, capstone={}", n_leaves, capstone);
assert_eq!(*n_leaves, 5);
assert_eq!(*capstone, true);
}
_ => panic!("Expected LeafHolder variant with capstone=true"),
}
}
fn round_trip_string_skeleton(
skel_str: &StringSkeleton
) -> Result<StringSkeleton, StringSkeletonError> {
let numeric = Skeleton::try_from(skel_str)?;
StringSkeleton::try_from(&numeric)
}
#[traced_test]
fn test_string_to_numeric_empty() {
let empty_str_skel = StringSkeletonBuilder::default()
.map(HashMap::new())
.build()
.unwrap();
let numeric_res = Skeleton::try_from(&empty_str_skel);
assert!(numeric_res.is_ok());
let numeric = numeric_res.unwrap();
assert_eq!(numeric.node_count(), 0);
assert_eq!(numeric.measure_tree_depth(), 0);
}
#[traced_test]
fn test_string_to_numeric_single_leaf() {
let mut map = HashMap::new();
let leaf = StringSkeletonNode::LeafHolder {
name: "SoloLeaf".to_string(),
ordering: Some(SubBranchOrdering::None),
n_leaves: 2,
capstone: false,
};
map.insert("Alpha".to_string(), leaf);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 1);
assert_eq!(numeric.measure_tree_depth(), 1);
}
#[traced_test]
fn test_string_to_numeric_single_dispatch() {
let mut map = HashMap::new();
let dispatch = StringSkeletonNode::Dispatch {
name: "SoloDispatch".to_string(),
ordering: None,
children: HashMap::new(),
};
map.insert("Alpha".to_string(), dispatch);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 1);
assert_eq!(numeric.measure_tree_depth(), 1);
}
#[traced_test]
fn test_string_to_numeric_single_aggregate() {
let mut map = HashMap::new();
let agg = StringSkeletonNode::Aggregate {
name: "SoloAgg".to_string(),
ordering: Some(SubBranchOrdering::Alphabetical),
children: HashMap::new(),
};
map.insert("Alpha".to_string(), agg);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 1);
assert_eq!(numeric.measure_tree_depth(), 1);
}
#[traced_test]
fn test_string_to_numeric_dispatch_with_child() {
let mut map = HashMap::new();
let mut child_map = HashMap::new();
let cspec = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
child_map.insert("B".to_string(), cspec);
let nodeA = StringSkeletonNode::Dispatch {
name: "NodeA".to_string(),
ordering: Some(SubBranchOrdering::DifficultyDescending),
children: child_map,
};
map.insert("A".to_string(), nodeA);
let nodeB = StringSkeletonNode::LeafHolder {
name: "NodeB".to_string(),
ordering: Some(SubBranchOrdering::DifficultyAscending),
n_leaves: 3,
capstone: false,
};
map.insert("B".to_string(), nodeB);
let str_skel = StringSkeletonBuilder::default()
.map(map)
.build()
.unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 2);
let depth = numeric.measure_tree_depth();
assert_eq!(depth, 2);
}
#[traced_test]
fn test_string_to_numeric_aggregate_with_two_children() {
let mut map = HashMap::new();
let cspec = AggregateChildSpecBuilder::default()
.psome_likelihood(100)
.optional(false)
.build()
.unwrap();
let mut root_kids = HashMap::new();
root_kids.insert("X".to_string(), cspec);
root_kids.insert("Y".to_string(), cspec);
let root_agg = StringSkeletonNode::Aggregate {
name: "RootAgg".to_string(),
ordering: Some(SubBranchOrdering::Alphabetical),
children: root_kids,
};
map.insert("root".to_string(), root_agg);
let node_x = StringSkeletonNode::LeafHolder {
name: "Xnode".to_string(),
ordering: None,
n_leaves: 2,
capstone: false,
};
map.insert("X".to_string(), node_x);
let node_y = StringSkeletonNode::Dispatch {
name: "Ynode".to_string(),
ordering: None,
children: HashMap::new(),
};
map.insert("Y".to_string(), node_y);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 3);
assert_eq!(numeric.measure_tree_depth(), 2);
assert_eq!(*numeric.root_id(), Some(0u16));
}
#[traced_test]
fn test_string_to_numeric_explicit_root_label() {
let mut map = HashMap::new();
let mut kids = HashMap::new();
let csp = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
kids.insert("Child".to_string(), csp);
let root_dispatch = StringSkeletonNode::Dispatch {
name: "MyRootDispatch".to_string(),
ordering: Some(SubBranchOrdering::DifficultyAscending),
children: kids,
};
map.insert("root".to_string(), root_dispatch);
let child_leaf = StringSkeletonNode::LeafHolder {
name: "ChildLeaf".to_string(),
ordering: None,
n_leaves: 1,
capstone: false,
};
map.insert("Child".to_string(), child_leaf);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 2);
assert_eq!(*numeric.root_id(), Some(0u16));
let depth = numeric.measure_tree_depth();
assert_eq!(depth, 2);
}
#[traced_test]
fn test_string_to_numeric_missing_child() {
let mut map = HashMap::new();
let mut kids_a = HashMap::new();
let csp = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
kids_a.insert("B".to_string(), csp);
let node_a = StringSkeletonNode::Dispatch {
name: "A".to_string(),
ordering: None,
children: kids_a,
};
map.insert("A".to_string(), node_a);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let res = Skeleton::try_from(&str_skel);
assert!(res.is_err());
matches::assert_matches!(res.err().unwrap(), StringSkeletonError::MissingChildName);
}
#[traced_test]
fn test_string_to_numeric_name_collision() {
let mut map = HashMap::new();
let nd1 = StringSkeletonNode::LeafHolder {
name: "Same".to_string(),
ordering: None,
n_leaves: 1,
capstone: false,
};
map.insert("Dup1".to_string(), nd1);
let nd2 = StringSkeletonNode::LeafHolder {
name: "Same".to_string(),
ordering: None,
n_leaves: 2,
capstone: false,
};
map.insert("Dup2".to_string(), nd2);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel);
if numeric.is_ok() {
let sk = numeric.unwrap();
assert_eq!(sk.node_count(), 2);
} else {
info!("NameCollision triggered, acceptable outcome.");
}
}
#[traced_test]
fn test_string_to_numeric_round_trip() {
let mut map = HashMap::new();
let csp = AggregateChildSpecBuilder::default()
.psome_likelihood(100)
.optional(false)
.build()
.unwrap();
let mut kids = HashMap::new();
kids.insert("Side".to_string(), csp);
let node_main = StringSkeletonNode::Aggregate {
name: "MainAggregate".to_string(),
ordering: Some(SubBranchOrdering::Random),
children: kids,
};
map.insert("Main".to_string(), node_main);
let node_side = StringSkeletonNode::LeafHolder {
name: "SideLeaf".to_string(),
ordering: None,
n_leaves: 2,
capstone: false,
};
map.insert("Side".to_string(), node_side);
let skel_str = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&skel_str).expect("string->numeric ok");
let back_str = StringSkeleton::try_from(&numeric).expect("numeric->string ok");
assert_eq!(back_str.map().len(), 2);
}
#[traced_test]
fn test_string_to_numeric_multi_unreferenced() {
let mut map = HashMap::new();
let a = StringSkeletonNode::Dispatch {
name: "A".to_string(),
ordering: None,
children: HashMap::new(),
};
map.insert("A".to_string(), a);
let b = StringSkeletonNode::LeafHolder {
name: "Bleaf".to_string(),
ordering: None,
n_leaves: 1,
capstone: false,
};
map.insert("B".to_string(), b);
let c = StringSkeletonNode::Aggregate {
name: "Cagg".to_string(),
ordering: None,
children: HashMap::new(),
};
map.insert("C".to_string(), c);
let str_skel = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&str_skel).unwrap();
assert_eq!(numeric.node_count(), 3);
assert_eq!(numeric.measure_tree_depth(), 1);
}
#[traced_test]
fn test_numeric_to_string_empty() {
let numeric = SkeletonBuilder::default().build().unwrap();
let str_skel = StringSkeleton::try_from(&numeric).expect("empty => ok");
assert_eq!(str_skel.map().len(), 0);
}
#[traced_test]
fn test_numeric_to_string_dispatch_only() {
let disp_node = SkeletonNodeBuilder::default()
.id(0)
.name("Dispatch0")
.build(NodeKind::Dispatch)
.unwrap();
let numeric = SkeletonBuilder::default()
.nodes(vec![disp_node])
.root_id(Some(0))
.build()
.unwrap();
let str_skel = StringSkeleton::try_from(&numeric).unwrap();
let map = str_skel.map();
assert_eq!(map.len(), 1);
let node_data = map.get("Dispatch0").expect("expected key=Dispatch0");
match node_data {
StringSkeletonNode::Dispatch { children, .. } => {
assert!(children.is_empty());
}
_ => panic!("expected Dispatch variant"),
}
}
#[traced_test]
fn test_numeric_to_string_aggregate_only() {
let agg_node = SkeletonNodeBuilder::default()
.id(0)
.name("Agg0")
.build(NodeKind::Aggregate)
.unwrap();
let numeric = SkeletonBuilder::default()
.nodes(vec![agg_node])
.root_id(Some(0))
.build()
.unwrap();
let str_skel = StringSkeleton::try_from(&numeric).unwrap();
assert_eq!(str_skel.map().len(), 1);
let node_data = str_skel.map().get("Agg0").unwrap();
match node_data {
StringSkeletonNode::Aggregate { children, .. } => {
assert!(children.is_empty());
}
_ => panic!("expected Aggregate variant"),
}
}
#[traced_test]
fn test_numeric_to_string_leafholder_only() {
let leaf_node = SkeletonNodeBuilder::default()
.id(0)
.name("Leaf0")
.leaf_count(3)
.capstone(true)
.build(NodeKind::LeafHolder)
.unwrap();
let numeric = SkeletonBuilder::default()
.nodes(vec![leaf_node])
.root_id(Some(0))
.build()
.unwrap();
let str_skel = StringSkeleton::try_from(&numeric).unwrap();
let map = str_skel.map();
assert_eq!(map.len(), 1);
let node_data = map.get("Leaf0").expect("key=Leaf0");
match node_data {
StringSkeletonNode::LeafHolder { n_leaves, .. } => {
assert_eq!(*n_leaves, 3);
}
_ => panic!("expected LeafHolder variant"),
}
}
#[traced_test]
fn test_numeric_to_string_children() {
let node0 = SkeletonNodeBuilder::default()
.id(0)
.child_ids(vec![1])
.name("Dispatch0")
.build(NodeKind::Dispatch)
.unwrap();
let node1 = SkeletonNodeBuilder::default()
.id(1)
.child_ids(vec![2])
.name("Agg1")
.build(NodeKind::Aggregate)
.unwrap();
let node2 = SkeletonNodeBuilder::default()
.id(2)
.leaf_count(2)
.name("Leaf2")
.build(NodeKind::LeafHolder)
.unwrap();
let numeric = SkeletonBuilder::default()
.nodes(vec![node0, node1, node2])
.root_id(Some(0))
.build()
.unwrap();
let str_skel = StringSkeleton::try_from(&numeric).unwrap();
let map = str_skel.map();
assert_eq!(map.len(), 3);
let n0_data = map.get("Dispatch0").expect("key=Dispatch0");
match n0_data {
StringSkeletonNode::Dispatch { children, .. } => {
assert_eq!(children.len(), 1);
assert!(children.contains_key("Agg1"));
}
_ => panic!("expected Dispatch variant"),
}
let n1_data = map.get("Agg1").expect("key=Agg1");
match n1_data {
StringSkeletonNode::Aggregate { children, .. } => {
assert_eq!(children.len(), 1);
assert!(children.contains_key("Leaf2"));
}
_ => panic!("expected Aggregate variant"),
}
let n2_data = map.get("Leaf2").expect("key=Leaf2");
match n2_data {
StringSkeletonNode::LeafHolder { n_leaves, .. } => {
assert_eq!(*n_leaves, 2);
}
_ => panic!("expected LeafHolder variant"),
}
}
#[traced_test]
fn test_numeric_to_string_duplicate_node_names() {
let n0 = SkeletonNodeBuilder::default()
.id(0)
.name("Same")
.build(NodeKind::Dispatch)
.unwrap();
let n1 = SkeletonNodeBuilder::default()
.id(1)
.name("Same")
.build(NodeKind::LeafHolder)
.unwrap();
let numeric = SkeletonBuilder::default()
.nodes(vec![n0, n1])
.build()
.unwrap();
let res = StringSkeleton::try_from(&numeric);
assert!(res.is_err());
matches::assert_matches!(res.err().unwrap(), StringSkeletonError::NameCollision);
}
#[traced_test]
fn test_numeric_to_string_round_trip() {
let n0 = SkeletonNodeBuilder::default()
.id(0)
.child_ids(vec![1])
.name("RootDispatch")
.build(NodeKind::Dispatch)
.unwrap();
let n1 = SkeletonNodeBuilder::default()
.id(1)
.leaf_count(2)
.name("Leaf2")
.build(NodeKind::LeafHolder)
.unwrap();
let numeric_skel = SkeletonBuilder::default()
.nodes(vec![n0,n1])
.root_id(Some(0))
.build()
.unwrap();
let str_skel = StringSkeleton::try_from(&numeric_skel).expect("to string ok");
let numeric2 = Skeleton::try_from(&str_skel).expect("back to numeric ok");
assert_eq!(numeric2.node_count(), 2);
assert_eq!(numeric2.measure_tree_depth(), 2);
}
#[traced_test]
fn test_self_reference_dispatch() {
let mut map = HashMap::new();
let mut kids = HashMap::new();
let csp = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
kids.insert("root".to_string(), csp);
let node_root = StringSkeletonNode::Dispatch {
name: "SelfRefRoot".to_string(),
ordering: Some(SubBranchOrdering::None),
children: kids,
};
map.insert("root".to_string(), node_root);
let skel_str = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&skel_str).unwrap();
let depth = numeric.measure_tree_depth();
assert_eq!(depth, 1);
}
#[traced_test]
fn test_repeated_child_reference_in_aggregate() {
let mut map = HashMap::new();
let mut parent_kids = HashMap::new();
let csp1 = AggregateChildSpecBuilder::default()
.psome_likelihood(100)
.optional(false)
.build()
.unwrap();
let csp2 = AggregateChildSpecBuilder::default()
.psome_likelihood(50)
.optional(true)
.build()
.unwrap();
parent_kids.insert("Kid".to_string(), csp1);
parent_kids.insert("Kid".to_string(), csp2);
let node_parent = StringSkeletonNode::Aggregate {
name: "ParentAgg".to_string(),
ordering: None,
children: parent_kids,
};
map.insert("Parent".to_string(), node_parent);
let node_kid = StringSkeletonNode::LeafHolder {
name: "KidLeaf".to_string(),
ordering: None,
n_leaves: 2,
capstone: false,
};
map.insert("Kid".to_string(), node_kid);
let skel_str = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&skel_str).unwrap();
assert_eq!(numeric.node_count(), 2);
let depth = numeric.measure_tree_depth();
assert_eq!(depth, 2);
}
#[traced_test]
fn test_string_skeleton_complex_round_trip() {
trace!("Creating a moderately complex skeleton with root => Dispatch => kids=[A, B], plus other unreferenced node D, etc.");
let mut map = HashMap::new();
let mut r_kids = HashMap::new();
let csp = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
r_kids.insert("A".to_string(), csp.clone());
r_kids.insert("B".to_string(), csp);
let root = StringSkeletonNode::Dispatch {
name: "RootDispatch".to_string(),
ordering: Some(SubBranchOrdering::DifficultyAscending),
children: r_kids,
};
map.insert("root".to_string(), root);
let mut a_kids = HashMap::new();
let csp_agg = AggregateChildSpecBuilder::default()
.psome_likelihood(100)
.optional(false)
.build()
.unwrap();
a_kids.insert("C".to_string(), csp_agg);
let nodeA = StringSkeletonNode::Aggregate {
name: "Aagg".to_string(),
ordering: Some(SubBranchOrdering::AlphabeticalReverse),
children: a_kids, };
map.insert("A".to_string(), nodeA);
let nodeB = StringSkeletonNode::LeafHolder {
name: "Bleaf".to_string(),
ordering: None,
n_leaves: 1,
capstone: false,
};
map.insert("B".to_string(), nodeB);
let nodeC = StringSkeletonNode::LeafHolder {
name: "Cleaf".to_string(),
ordering: None,
n_leaves: 2,
capstone: false,
};
map.insert("C".to_string(), nodeC);
let nodeD = StringSkeletonNode::LeafHolder {
name: "Dleaf".to_string(),
ordering: None,
n_leaves: 1,
capstone: false,
};
map.insert("D".to_string(), nodeD);
let user_skel1 = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric1 = Skeleton::try_from(&user_skel1).expect("string->numeric ok");
assert_eq!(numeric1.node_count(), 5);
let depth1 = numeric1.measure_tree_depth();
assert_eq!(depth1, 3);
let user_skel2 = StringSkeleton::try_from(&numeric1).expect("numeric->string ok");
assert_eq!(user_skel2.map().len(), 5);
let numeric2 = Skeleton::try_from(&user_skel2).expect("final numeric ok");
let depth2 = numeric2.measure_tree_depth();
assert_eq!(depth2, 3);
}
#[traced_test]
fn test_string_to_numeric_all_used_as_child_cycle() {
trace!("Testing scenario: A->B, B->C, C->A cycle => no unreferenced nodes => BFS depth=0, root_id=None.");
let mut map = HashMap::new();
let mut a_kids = HashMap::new();
let dsp = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
a_kids.insert("B".to_string(), dsp);
let node_a = StringSkeletonNode::Dispatch {
name: "A".to_string(),
ordering: None,
children: a_kids,
};
map.insert("A".to_string(), node_a);
let mut b_kids = HashMap::new();
let agg_spec = AggregateChildSpecBuilder::default()
.psome_likelihood(100)
.optional(false)
.build()
.unwrap();
b_kids.insert("C".to_string(), agg_spec);
let node_b = StringSkeletonNode::Aggregate {
name: "B".to_string(),
ordering: None,
children: b_kids,
};
map.insert("B".to_string(), node_b);
let mut c_kids = HashMap::new();
let dsp2 = DispatchChildSpecBuilder::default()
.branch_selection_likelihood(100)
.build()
.unwrap();
c_kids.insert("A".to_string(), dsp2);
let node_c = StringSkeletonNode::Dispatch {
name: "C".to_string(),
ordering: None,
children: c_kids,
};
map.insert("C".to_string(), node_c);
let skel_str = StringSkeletonBuilder::default().map(map).build().unwrap();
let numeric = Skeleton::try_from(&skel_str).unwrap();
assert_eq!(numeric.node_count(), 3);
let depth = numeric.measure_tree_depth();
assert_eq!(depth, 0);
}
}