fusible 0.18.0

Electronic linter: a fuse that blows to block firmware code generation
//! Tests for firmware pin bindings generator.

mod fixtures;
mod select;
mod select_hint;

use super::*;
use crate::netlist::Netlist;
use fixtures::{generate, mcu_circuit_fixture};

const TRICKY_PINS: &[(&str, &str, u32)] = &[
    ("GPIO1", "3V3_EN", 1),
    ("GPIO2", "IN", 2),
    ("GPIO48", "LED_R", 48),
    ("GPIO8", "LED_G", 8),
    ("GPIO9", "BATT\nfn evil() {}", 9),
    ("GPIO10", "SELF", 10),
    ("GPIO11", "DUP", 11),
    ("GPIO12", "DUP", 12),
];

#[test]
fn extracts_mcu_pins_and_generates_rust_code() {
    let circuit = mcu_circuit_fixture();
    let netlist = Netlist::from_circuit(&circuit);

    let mcu_info =
        extract_mcu_pins(&circuit, &netlist, Some("esp32-s3")).expect("MCU pins must be extracted");

    assert_eq!(mcu_info.name, "ESP32-S3");
    assert_eq!(mcu_info.pins.len(), 2);

    let cam_pin = mcu_info
        .pins
        .iter()
        .find(|p| p.signal_name == "CAM_D0")
        .expect("CAM_D0 pin must exist");
    assert_eq!(cam_pin.pin_number, 4);

    let modem_pin = mcu_info
        .pins
        .iter()
        .find(|p| p.signal_name == "MODEM_TX")
        .expect("MODEM_TX pin must exist");
    assert_eq!(modem_pin.pin_number, 43);

    let rust_code = generate_pins_rs(&mcu_info, "FieldCam Controller");
    assert!(rust_code.contains("pub mod gpio"));
    assert!(rust_code.contains("pub const CAM_D0: u8 = 4;"));
    assert!(rust_code.contains("pub const MODEM_TX: u8 = 43;"));
    assert!(rust_code.contains("pub struct CameraPins"));
    assert!(rust_code.contains("pub struct ModemPins"));
    assert!(rust_code.contains("pub const BOARD_PINS: BoardPins"));
}

#[test]
fn tricky_net_names_produce_valid_unique_identifiers() {
    let (info, code) = generate("ESP32-S3", Some("esp32-s3"), TRICKY_PINS);

    let names: Vec<_> = info.pins.iter().map(|p| p.signal_name.as_str()).collect();
    assert_eq!(
        names,
        [
            "BATT_FN_EVIL",
            "DUP_11",
            "DUP_12",
            "IN",
            "LED_G",
            "LED_R",
            "PIN_3V3_EN",
            "SELF"
        ]
    );
    assert_tricky_code_is_safe(&code);
}

fn assert_tricky_code_is_safe(code: &str) {
    assert!(code.contains("pub const PIN_3V3_EN: u8 = 1;"));
    assert!(code.contains("pub in_: u8,"));
    assert!(code.contains("pub self_: u8,"));
    assert!(code.contains("pub pin_3v3_en: u8,"));
    assert!(code.contains("pub r: u8,"));
    assert!(code.contains("pub g: u8,"));
    assert!(code.contains("hardware net `BATT\\nfn evil() {}`"));
    assert!(code.contains("//! Board: Evil\\nfn injected() {}"));
    for line in code.lines() {
        assert!(!line.starts_with("fn "), "comment injection: {line}");
    }
}

#[test]
fn stm32_port_pins_use_port_times_sixteen_encoding() {
    let (info, code) = generate(
        "STM32G431",
        Some("stm32g431"),
        &[("PA9", "DEBUG_TX", 30), ("PB3", "SPI_SCK", 39)],
    );

    let by_label = |l: &str| {
        info.pins
            .iter()
            .find(|p| p.pin_label == l)
            .unwrap()
            .pin_number
    };
    assert_eq!(by_label("PA9"), 9);
    assert_eq!(by_label("PB3"), 19);
    assert!(code.contains("/// Pin 19 = `PB3` (port * 16 + pin)"));
}

#[test]
fn pin_numbers_above_u8_widen_the_constant_type() {
    let (_, code) = generate("BGA_SOC", None, &[("BALL_A", "SIG_HIGH", 300)]);
    assert!(code.contains("pub const SIG_HIGH: u16 = 300;"));
}

#[test]
fn known_silicon_skips_package_only_pins() {
    let (info, _) = generate(
        "ESP32-S3",
        Some("esp32-s3"),
        &[("GPIO2", "CAM_SIOD", 4), ("VDD3V3", "VDD3V3", 2)],
    );
    let labels: Vec<_> = info.pins.iter().map(|p| p.pin_label.as_str()).collect();
    assert_eq!(labels, ["GPIO2"]);
}