fusible 0.20.0

Electronic linter: a fuse that blows to block firmware code generation
use super::harness::link_violations;
use crate::config::FusibleConfig;
use crate::parser::{Circuit, CircuitElement, Component, Net, Port};

fn board(refdes: &str, pins: &[(u32, Option<&str>)]) -> Circuit {
    let mut elements = vec![CircuitElement::Component(Component {
        component_id: Some(format!("c_{refdes}")),
        name: Some(refdes.into()),
        ..Default::default()
    })];
    for (number, net) in pins {
        let net_id = net.map(|n| format!("net_{n}"));
        if let Some(name) = net {
            elements.push(CircuitElement::Net(Net {
                net_id: net_id.clone(),
                name: Some((*name).into()),
            }));
        }
        elements.push(CircuitElement::Port(Port {
            port_id: Some(format!("{refdes}.{number}")),
            source_component_id: Some(format!("c_{refdes}")),
            pin_number: Some(*number),
            net_id,
            ..Default::default()
        }));
    }
    Circuit {
        elements,
        raw_elements: Vec::new(),
    }
}

fn violations(yaml_wires: &str, from: &Circuit, to: &Circuit) -> Vec<String> {
    let manifest = format!(
        "boards: {{main: {{}}, sensor: {{}}}}\nlinks:\n  uart:\n    from: main.J2\n    to: sensor.J1\n\
         \x20   wires: {yaml_wires}"
    );
    let cfg = FusibleConfig::from_str(&manifest).expect("manifest");
    link_violations("uart", &cfg.links["uart"], from, to)
        .into_iter()
        .map(|d| d.message)
        .collect()
}

#[test]
fn matching_nets_pass() {
    let a = board("J2", &[(1, Some("GND")), (2, Some("TX"))]);
    let b = board("J1", &[(1, Some("GND")), (3, Some("RX"))]);
    let wires = "[{a: 1, b: 1}, {a: 2, b: 3, nets: [TX, RX]}]";
    assert!(violations(wires, &a, &b).is_empty());
}

#[test]
fn different_net_names_blow_the_fuse() {
    let a = board("J2", &[(1, Some("GND"))]);
    let b = board("J1", &[(1, Some("3V3"))]);
    let found = violations("[{a: 1, b: 1}]", &a, &b);
    assert_eq!(found.len(), 1);
    assert!(found[0].contains("carries GND") && found[0].contains("carries 3V3"));
}

#[test]
fn a_declared_net_must_match_each_end() {
    let a = board("J2", &[(2, Some("TX"))]);
    let b = board("J1", &[(3, Some("TX"))]);
    assert_eq!(
        violations("[{a: 2, b: 3, nets: [TX, RX]}]", &a, &b).len(),
        1
    );
}

#[test]
fn missing_part_missing_pin_and_floating_pin_fail_closed() {
    let a = board("J2", &[(1, Some("GND")), (4, None)]);
    let b = board("J1", &[(1, Some("GND"))]);
    assert!(violations("[{a: 9, b: 1}]", &a, &b)[0].contains("does not exist"));
    assert!(violations("[{a: 4, b: 1}]", &a, &b)[0].contains("not connected"));
    let other = board("J9", &[(1, Some("GND"))]);
    assert!(violations("[{a: 1, b: 1}]", &a, &other)[0].contains("has no part J1"));
}