use crate::cnf::CnfFormula;
use crate::cnf::{Reduced, ShowSet};
use crate::dot::*;
use crate::score::vtree_clause_load_per_node;
use crate::tests::dot_fixture::{fixture, node_line};
use crate::vtree::Vtree;
use crate::vtree::VtreeIdx;
#[test]
fn clause_loads_and_labels_follow_the_hand_computed_lcas() {
let (vtree, formula) = fixture();
assert_eq!(
vtree_clause_load_per_node(&vtree, &formula),
vec![0, 1, 0, 0, 2, 1, 1]
);
let ann = annotate_from_cnf(&vtree, &formula, None);
assert_eq!(ann.label(VtreeIdx(4)), Some("c=2 w=1"));
assert_eq!(ann.label(VtreeIdx(5)), Some("c=1 w=1"));
assert_eq!(ann.label(VtreeIdx(6)), Some("c=1 w=0"));
assert_eq!(
ann.label(VtreeIdx(1)),
None,
"leaves take the colour, not the text"
);
assert_eq!(
ann.heat(VtreeIdx(4)),
Some(1.0),
"the heaviest node is the top of the scale"
);
assert_eq!(ann.heat(VtreeIdx(0)), Some(0.0));
let dot = vtree_to_dot(&vtree, Some(&ann));
assert_eq!(
node_line(&dot, 4),
" v4 [shape=circle, label=\"4\", style=filled, fillcolor=\"#800026\", \
fontcolor=\"white\", xlabel=<<FONT COLOR=\"#888888\" POINT-SIZE=\"8\">c=2 w=1</FONT>>];",
);
assert!(node_line(&dot, 0).contains("shape=box"));
assert!(node_line(&dot, 0).contains("fillcolor=\"#ffffb2\""));
}
#[test]
fn a_show_mask_switches_the_reported_width() {
let (vtree, formula) = fixture();
let mask = ShowSet::<Reduced>::from_zero_based([1, 2, 3]).mask(4);
let ann = annotate_from_cnf(&vtree, &formula, Some(&mask));
assert_eq!(
ann.label(VtreeIdx(4)),
Some("c=2 w=0"),
"the crossing var is hidden"
);
assert_eq!(ann.label(VtreeIdx(5)), Some("c=1 w=1"), "this one is kept");
}
#[test]
fn an_unannotated_render_is_the_bare_structure() {
let (vtree, _) = fixture();
let dot = vtree_to_dot(&vtree, None);
assert!(dot.starts_with("graph vtree {\n"), "{dot}");
assert_eq!(dot.matches("shape=box").count(), 4, "one box per leaf");
assert_eq!(
dot.matches("shape=circle").count(),
3,
"one circle per internal node"
);
assert_eq!(
dot.matches(" -- ").count(),
6,
"two undirected edges per internal node"
);
assert_eq!(
dot.matches('{').count(),
dot.matches('}').count(),
"balanced braces"
);
assert!(!dot.contains("fillcolor"), "no annotations, no colour");
assert!(!dot.contains("xlabel"), "no annotations, no labels");
assert!(dot.contains("label=\"X₁\""), "{dot}");
assert!(dot.contains("label=\"X₄\""), "{dot}");
assert!(!dot.contains("X₀"), "there is no variable 0: {dot}");
}
#[test]
fn any_caller_can_annotate_any_node() {
let (vtree, _) = fixture();
let mut ann = VtreeDotAnnotations::new(vtree.num_nodes());
ann.set_label(VtreeIdx(6), "peak <live> width");
ann.set_heat(VtreeIdx(6), 4.0); assert_eq!(ann.heat(VtreeIdx(6)), Some(1.0));
let dot = vtree_to_dot(&vtree, Some(&ann));
assert!(
node_line(&dot, 6).contains("peak <live> width"),
"labels are escaped: {dot}"
);
assert!(
!node_line(&dot, 5).contains("xlabel"),
"unannotated nodes stay plain"
);
assert!(!node_line(&dot, 5).contains("fillcolor"));
}
#[test]
fn a_formula_with_no_clauses_leaves_the_whole_tree_cold() {
let vtree = Vtree::balanced(4);
let formula = CnfFormula {
num_vars: 4,
clauses: Vec::new(),
};
let ann = annotate_from_cnf(&vtree, &formula, None);
for node in 0..vtree.num_nodes() as u32 {
assert_eq!(
ann.heat(VtreeIdx(node)),
Some(0.0),
"node {node} carries no clause, so it carries no heat",
);
}
assert_eq!(ann.label(VtreeIdx(6)), Some("c=0 w=0"));
let dot = vtree_to_dot(&vtree, Some(&ann));
assert!(
!dot.contains("#800026"),
"an empty formula must not render at the alarm end of the scale:\n{dot}",
);
}
#[test]
fn a_heat_that_is_not_a_number_is_stored_as_zero_rather_than_rejected() {
let mut ann = VtreeDotAnnotations::new(3);
ann.set_heat(VtreeIdx(0), f64::NAN);
assert_eq!(ann.heat(VtreeIdx(0)), Some(0.0));
ann.set_heat(VtreeIdx(1), -2.0);
assert_eq!(
ann.heat(VtreeIdx(1)),
Some(0.0),
"below the scale is the bottom of it",
);
}
#[test]
fn an_annotation_outside_the_set_reads_back_as_absent() {
let mut ann = VtreeDotAnnotations::new(3);
assert_eq!(ann.heat(VtreeIdx(99)), None);
assert_eq!(ann.label(VtreeIdx(99)), None);
ann.set_label(VtreeIdx(2), "only text");
assert_eq!(ann.label(VtreeIdx(2)), Some("only text"));
assert_eq!(
ann.heat(VtreeIdx(2)),
None,
"the two halves of a slot are independent",
);
}
#[test]
fn every_markup_character_in_a_label_is_escaped() {
let (vtree, _) = fixture();
let written = r#"a & b < c > d "e""#;
let mut ann = VtreeDotAnnotations::new(vtree.num_nodes());
ann.set_label(VtreeIdx(6), written);
assert_eq!(
ann.label(VtreeIdx(6)),
Some(written),
"the stored label is unescaped",
);
let dot = vtree_to_dot(&vtree, Some(&ann));
assert!(
node_line(&dot, 6).contains("a & b < c > d "e""),
"all four markup characters must be escaped: {dot}",
);
}
#[test]
fn leaf_labels_carry_subscript_digits_past_nine() {
let dot = vtree_to_dot(&Vtree::balanced(12), None);
for label in ["X₉", "X₁₀", "X₁₂"] {
assert!(
dot.contains(&format!("label=\"{label}\"")),
"{label}: {dot}"
);
}
assert!(
!dot.contains("X1"),
"the digits are subscript throughout, never plain: {dot}",
);
}