#[cfg(test)]
mod tests {
use crate::hash;
use crate::ir::{Edge, Graph, Node};
use crate::patch::{Operation, Patch};
use crate::types::*;
use proptest::prelude::*;
use std::collections::BTreeMap;
fn arb_node_kind() -> impl Strategy<Value = NodeKind> {
prop_oneof![
Just(NodeKind::Source),
Just(NodeKind::Processor),
Just(NodeKind::Analyzer),
Just(NodeKind::Sink),
Just(NodeKind::Junction),
]
}
fn arb_node() -> impl Strategy<Value = Node> {
(arb_node_kind(), "[a-z]{3,8}").prop_map(|(kind, name)| {
let ports = match kind {
NodeKind::Source => vec![TypedPort {
name: "out".into(),
direction: PortDirection::Output,
domain: ExecutionDomain::Sample,
data_type: DataType::Audio { channels: 2 },
semantic: PortSemantic::Signal,
polarity: PortPolarity::Bipolar,
}],
NodeKind::Sink => vec![TypedPort {
name: "in".into(),
direction: PortDirection::Input,
domain: ExecutionDomain::Sample,
data_type: DataType::Audio { channels: 2 },
semantic: PortSemantic::Signal,
polarity: PortPolarity::Bipolar,
}],
_ => vec![
TypedPort {
name: "in".into(),
direction: PortDirection::Input,
domain: ExecutionDomain::Sample,
data_type: DataType::Audio { channels: 2 },
semantic: PortSemantic::Signal,
polarity: PortPolarity::Bipolar,
},
TypedPort {
name: "out".into(),
direction: PortDirection::Output,
domain: ExecutionDomain::Sample,
data_type: DataType::Audio { channels: 2 },
semantic: PortSemantic::Signal,
polarity: PortPolarity::Bipolar,
},
],
};
let mut config = BTreeMap::new();
config.insert("name".into(), ConfigValue::String(name));
Node {
id: StableId::new(),
kind,
ports,
config,
metadata: MetadataRef(None),
confidence: ConfidenceScore::Verified,
}
})
}
fn arb_config_value() -> impl Strategy<Value = ConfigValue> {
prop_oneof![
any::<f64>().prop_map(ConfigValue::Float),
any::<i64>().prop_map(ConfigValue::Int),
any::<bool>().prop_map(ConfigValue::Bool),
"[a-z]{1,10}".prop_map(ConfigValue::String),
]
}
proptest! {
#[test]
fn constitution_replayability(
node_count in 1usize..8,
) {
let rt = proptest::test_runner::TestRunner::new(Default::default());
let mut nodes = Vec::new();
for _ in 0..node_count {
nodes.push(Node::new_processor(&format!("node_{}", nodes.len())));
}
let patch = Patch::from_operations(
nodes.iter().map(|n| Operation::AddNode(n.clone())).collect()
);
let mut graph = Graph::new();
graph.apply(&patch).unwrap();
let replayed = Graph::replay(&[patch]).unwrap();
let h1 = hash::hash_graph(&graph);
let h2 = hash::hash_graph(&replayed);
prop_assert_eq!(h1, h2, "Replayability violation: graph hashes differ");
}
}
#[test]
fn constitution_hash_stability() {
let node = Node::new_processor("TestGain");
let ops = vec![Operation::AddNode(node.clone())];
let patch1 = Patch::from_operations(ops.clone());
let recomputed_hash = hash::hash_patch(&patch1);
assert_eq!(
patch1.deterministic_hash, recomputed_hash,
"Hash stability violation"
);
}
proptest! {
#[test]
fn constitution_explainability(
patch_count in 1usize..6,
) {
let mut graph = Graph::new();
let mut all_patches = Vec::new();
for i in 0..patch_count {
let node = Node::new_processor(&format!("node_{}", i));
let patch = Patch::from_operations(vec![Operation::AddNode(node)]);
graph.apply(&patch).unwrap();
all_patches.push(patch);
}
prop_assert_eq!(
graph.applied_patches.len(),
all_patches.len(),
"Explainability violation: patch count mismatch"
);
for (applied_id, patch) in graph.applied_patches.iter().zip(all_patches.iter()) {
prop_assert_eq!(
applied_id, &patch.identity,
"Explainability violation: patch ID mismatch"
);
}
let replayed = Graph::replay(&all_patches).unwrap();
let h1 = hash::hash_graph(&graph);
let h2 = hash::hash_graph(&replayed);
prop_assert_eq!(h1, h2,
"Explainability violation: replay produces different state"
);
}
}
#[test]
fn constitution_modify_replay() {
let node = Node::new_processor("Gain");
let p1 = Patch::from_operations(vec![Operation::AddNode(node.clone())]);
let mut delta = BTreeMap::new();
delta.insert(
"gain_db".into(),
ConfigChange {
old: None,
new: Some(ConfigValue::Float(3.5)),
},
);
let p2 = Patch::from_operations(vec![Operation::ModifyConfig {
node_id: node.id,
delta,
}]);
let mut graph = Graph::new();
graph.apply(&p1).unwrap();
graph.apply(&p2).unwrap();
let replayed = Graph::replay(&[p1, p2]).unwrap();
assert_eq!(
hash::hash_graph(&graph),
hash::hash_graph(&replayed),
"Modify-replay round-trip failed"
);
}
#[test]
fn constitution_edge_replay() {
let src = Node::new_source("Sine");
let gain = Node::new_processor("Gain");
let sink = Node::new_sink("Output");
let edge1 = Edge::new(
PortRef {
node_id: src.id,
port_name: "out".into(),
},
PortRef {
node_id: gain.id,
port_name: "in".into(),
},
);
let edge2 = Edge::new(
PortRef {
node_id: gain.id,
port_name: "out".into(),
},
PortRef {
node_id: sink.id,
port_name: "in".into(),
},
);
let p1 = Patch::from_operations(vec![
Operation::AddNode(src),
Operation::AddNode(gain),
Operation::AddNode(sink),
]);
let p2 = Patch::from_operations(vec![Operation::AddEdge(edge1), Operation::AddEdge(edge2)]);
let mut graph = Graph::new();
graph.apply(&p1).unwrap();
graph.apply(&p2).unwrap();
let replayed = Graph::replay(&[p1, p2]).unwrap();
assert_eq!(
hash::hash_graph(&graph),
hash::hash_graph(&replayed),
"Edge replay round-trip failed"
);
}
}