1260 lines
44 KiB
Rust
1260 lines
44 KiB
Rust
use anvil::project::config::{
|
|
ProjectConfig, BoardProfile, CONFIG_FILENAME,
|
|
};
|
|
use std::fs;
|
|
use tempfile::TempDir;
|
|
|
|
// ==========================================================================
|
|
// Pin map data
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_pinmap_exists_for_all_presets() {
|
|
use anvil::board::presets;
|
|
use anvil::board::pinmap;
|
|
|
|
// Every board preset (except nano-old) should have a pin map
|
|
for preset in presets::PRESETS {
|
|
let found = pinmap::find_pinmap_fuzzy(preset.name);
|
|
assert!(
|
|
found.is_some(),
|
|
"Board preset '{}' has no pin map. Add one to pinmap.rs.",
|
|
preset.name
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_uno_basics() {
|
|
use anvil::board::pinmap;
|
|
|
|
let m = pinmap::find_pinmap("uno").unwrap();
|
|
assert_eq!(m.total_digital, 14);
|
|
assert_eq!(m.total_analog, 6);
|
|
// Uno has 20 pins total (D0-D13 + A0-A5 mapped as D14-D19)
|
|
assert_eq!(m.pins.len(), 20);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_mega_basics() {
|
|
use anvil::board::pinmap;
|
|
|
|
let m = pinmap::find_pinmap("mega").unwrap();
|
|
assert_eq!(m.total_digital, 54);
|
|
assert_eq!(m.total_analog, 16);
|
|
// Mega has 70 pins: D0-D53 + A0-A15
|
|
assert_eq!(m.pins.len(), 70);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_resolve_alias_a0() {
|
|
use anvil::board::pinmap;
|
|
|
|
let uno = pinmap::find_pinmap("uno").unwrap();
|
|
assert_eq!(pinmap::resolve_alias(uno, "A0"), Some(14));
|
|
assert_eq!(pinmap::resolve_alias(uno, "a0"), Some(14));
|
|
|
|
let mega = pinmap::find_pinmap("mega").unwrap();
|
|
assert_eq!(pinmap::resolve_alias(mega, "A0"), Some(54));
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_spi_pins_differ_by_board() {
|
|
use anvil::board::pinmap;
|
|
|
|
let uno = pinmap::find_pinmap("uno").unwrap();
|
|
let mega = pinmap::find_pinmap("mega").unwrap();
|
|
|
|
let uno_spi = uno.groups.iter().find(|g| g.name == "spi").unwrap();
|
|
let mega_spi = mega.groups.iter().find(|g| g.name == "spi").unwrap();
|
|
|
|
let uno_sck = uno_spi.fixed_pins.iter().find(|p| p.0 == "sck").unwrap().1;
|
|
let mega_sck = mega_spi.fixed_pins.iter().find(|p| p.0 == "sck").unwrap().1;
|
|
|
|
// Uno SCK=13, Mega SCK=52
|
|
assert_ne!(uno_sck, mega_sck, "SPI SCK should differ between uno and mega");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_capabilities_filter() {
|
|
use anvil::board::pinmap;
|
|
|
|
let m = pinmap::find_pinmap("uno").unwrap();
|
|
let pwm = pinmap::pins_with_capability(m, "pwm");
|
|
// Uno has PWM on 3, 5, 6, 9, 10, 11
|
|
assert_eq!(pwm.len(), 6);
|
|
|
|
let analog = pinmap::pins_with_capability(m, "analog");
|
|
assert_eq!(analog.len(), 6); // A0-A5
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_board_capabilities_list() {
|
|
use anvil::board::pinmap;
|
|
|
|
let m = pinmap::find_pinmap("uno").unwrap();
|
|
let caps = pinmap::board_capabilities(m);
|
|
assert!(caps.contains(&"digital"));
|
|
assert!(caps.contains(&"analog"));
|
|
assert!(caps.contains(&"pwm"));
|
|
assert!(caps.contains(&"spi"));
|
|
assert!(caps.contains(&"i2c"));
|
|
assert!(caps.contains(&"uart"));
|
|
assert!(caps.contains(&"interrupt"));
|
|
assert!(caps.contains(&"led"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_mega_has_four_uarts() {
|
|
use anvil::board::pinmap;
|
|
|
|
let m = pinmap::find_pinmap("mega").unwrap();
|
|
let uart_count = m.groups.iter()
|
|
.filter(|g| g.name.starts_with("uart"))
|
|
.count();
|
|
assert_eq!(uart_count, 4, "Mega should have uart, uart1, uart2, uart3");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_leonardo_i2c_different_from_uno() {
|
|
use anvil::board::pinmap;
|
|
|
|
let uno = pinmap::find_pinmap("uno").unwrap();
|
|
let leo = pinmap::find_pinmap("leonardo").unwrap();
|
|
|
|
let uno_i2c = uno.groups.iter().find(|g| g.name == "i2c").unwrap();
|
|
let leo_i2c = leo.groups.iter().find(|g| g.name == "i2c").unwrap();
|
|
|
|
let uno_sda = uno_i2c.fixed_pins.iter().find(|p| p.0 == "sda").unwrap().1;
|
|
let leo_sda = leo_i2c.fixed_pins.iter().find(|p| p.0 == "sda").unwrap().1;
|
|
|
|
// Uno SDA=18 (A4), Leonardo SDA=2
|
|
assert_ne!(uno_sda, leo_sda, "I2C SDA should differ between uno and leonardo");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_spi_has_user_selectable_cs() {
|
|
use anvil::board::pinmap;
|
|
|
|
// All boards with SPI should require user to select CS
|
|
for board in pinmap::available_boards() {
|
|
let m = pinmap::find_pinmap(board).unwrap();
|
|
if let Some(spi) = m.groups.iter().find(|g| g.name == "spi") {
|
|
assert!(
|
|
spi.user_selectable.contains(&"cs"),
|
|
"Board '{}' SPI should have user-selectable CS pin",
|
|
board
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_pinmap_nano_has_analog_only_pins() {
|
|
use anvil::board::pinmap;
|
|
|
|
let m = pinmap::find_pinmap("nano").unwrap();
|
|
let a6 = pinmap::get_pin(m, 20).unwrap(); // A6
|
|
assert!(a6.capabilities.contains(&"analog"));
|
|
assert!(!a6.capabilities.contains(&"digital"),
|
|
"Nano A6 should be analog-only");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin config in .anvil.toml
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_config_with_pins_roundtrips() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment, BusConfig};
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("pin_test");
|
|
|
|
// Add pin assignments
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("red_led".to_string(), PinAssignment {
|
|
pin: 13,
|
|
mode: "output".to_string(),
|
|
});
|
|
assignments.insert("limit_switch".to_string(), PinAssignment {
|
|
pin: 7,
|
|
mode: "input".to_string(),
|
|
});
|
|
|
|
let mut buses = HashMap::new();
|
|
let mut spi_pins = HashMap::new();
|
|
spi_pins.insert("cs".to_string(), 10u8);
|
|
buses.insert("spi".to_string(), BusConfig { user_pins: spi_pins });
|
|
buses.insert("i2c".to_string(), BusConfig { user_pins: HashMap::new() });
|
|
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments,
|
|
buses,
|
|
});
|
|
|
|
config.save(tmp.path()).unwrap();
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert_eq!(pc.assignments.len(), 2);
|
|
assert_eq!(pc.assignments["red_led"].pin, 13);
|
|
assert_eq!(pc.assignments["red_led"].mode, "output");
|
|
assert_eq!(pc.assignments["limit_switch"].pin, 7);
|
|
assert_eq!(pc.buses.len(), 2);
|
|
assert_eq!(*pc.buses["spi"].user_pins.get("cs").unwrap(), 10u8);
|
|
assert!(pc.buses["i2c"].user_pins.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_without_pins_loads_empty() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("no_pins");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert!(loaded.pins.is_empty(), "Config without pins section should load with empty pins");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_pins_skip_serializing_when_empty() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("empty_pins");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let content = fs::read_to_string(tmp.path().join(CONFIG_FILENAME)).unwrap();
|
|
assert!(
|
|
!content.contains("[pins"),
|
|
"Empty pins should not appear in serialized config"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_pins_per_board_independence() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("multi_board_pins");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: None,
|
|
});
|
|
|
|
// Different pin numbers for same friendly name on different boards
|
|
let mut uno_assigns = HashMap::new();
|
|
uno_assigns.insert("status_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
|
|
let mut mega_assigns = HashMap::new();
|
|
mega_assigns.insert("status_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
mega_assigns.insert("extra_led".to_string(), PinAssignment {
|
|
pin: 22, mode: "output".to_string(),
|
|
});
|
|
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments: uno_assigns, buses: HashMap::new(),
|
|
});
|
|
config.pins.insert("mega".to_string(), BoardPinConfig {
|
|
assignments: mega_assigns, buses: HashMap::new(),
|
|
});
|
|
|
|
config.save(tmp.path()).unwrap();
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
|
|
let uno_pins = loaded.pins.get("uno").unwrap();
|
|
let mega_pins = loaded.pins.get("mega").unwrap();
|
|
|
|
assert_eq!(uno_pins.assignments.len(), 1);
|
|
assert_eq!(mega_pins.assignments.len(), 2);
|
|
assert!(mega_pins.assignments.contains_key("extra_led"));
|
|
assert!(!uno_pins.assignments.contains_key("extra_led"));
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: save_pins preserves existing config
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_save_pins_preserves_existing_config() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("preserve_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Reload, add pins, save
|
|
let mut config = ProjectConfig::load(tmp.path()).unwrap();
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Reload and verify original fields survived
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.project.name, "preserve_test");
|
|
assert_eq!(loaded.build.default, "uno");
|
|
assert_eq!(loaded.monitor.baud, 115200);
|
|
assert!(loaded.build.extra_flags.contains(&"-Werror".to_string()));
|
|
// And pins are there
|
|
assert_eq!(loaded.pins["uno"].assignments["led"].pin, 13);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: generated pins.h content
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_generate_pins_header_content() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment, BusConfig};
|
|
use anvil::commands::pin::generate_pins_header;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("gen_test");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("red_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
assignments.insert("motor".to_string(), PinAssignment {
|
|
pin: 9, mode: "pwm".to_string(),
|
|
});
|
|
|
|
let mut spi_pins = HashMap::new();
|
|
spi_pins.insert("cs".to_string(), 10u8);
|
|
let mut buses = HashMap::new();
|
|
buses.insert("spi".to_string(), BusConfig { user_pins: spi_pins });
|
|
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses,
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
generate_pins_header(None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let header = fs::read_to_string(tmp.path().join("lib/hal/pins.h")).unwrap();
|
|
assert!(header.contains("#pragma once"), "pins.h should have pragma once");
|
|
assert!(header.contains("namespace Pins"), "pins.h should have Pins namespace");
|
|
assert!(header.contains("constexpr uint8_t RED_LED = 13;"), "pins.h should have RED_LED");
|
|
assert!(header.contains("constexpr uint8_t MOTOR = 9;"), "pins.h should have MOTOR");
|
|
assert!(header.contains("SPI_SCK"), "pins.h should have SPI bus pins");
|
|
assert!(header.contains("SPI_MOSI"), "pins.h should have SPI MOSI");
|
|
assert!(header.contains("SPI_CS = 10"), "pins.h should have user-selected CS");
|
|
assert!(header.contains("Auto-generated by Anvil"), "pins.h should have generation comment");
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_pins_header_is_ascii_only() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::generate_pins_header;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("ascii_gen");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("sensor".to_string(), PinAssignment {
|
|
pin: 5, mode: "input".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
generate_pins_header(None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let header = fs::read_to_string(tmp.path().join("lib/hal/pins.h")).unwrap();
|
|
for (line_num, line) in header.lines().enumerate() {
|
|
for (col, ch) in line.chars().enumerate() {
|
|
assert!(
|
|
ch.is_ascii(),
|
|
"Non-ASCII in pins.h at {}:{}: '{}' (U+{:04X})",
|
|
line_num + 1, col + 1, ch, ch as u32
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_pins_header_creates_hal_dir() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::generate_pins_header;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("dir_gen");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Don't pre-create lib/hal -- generate should create it
|
|
assert!(!tmp.path().join("lib/hal").exists());
|
|
generate_pins_header(None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
assert!(tmp.path().join("lib/hal/pins.h").exists());
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: init-from copies assignments between boards
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_init_from_copies_assignments() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment, BusConfig};
|
|
use anvil::commands::pin::init_from;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("init_test");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: None,
|
|
});
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("red_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
assignments.insert("button".to_string(), PinAssignment {
|
|
pin: 7, mode: "input".to_string(),
|
|
});
|
|
|
|
let mut spi_pins = HashMap::new();
|
|
spi_pins.insert("cs".to_string(), 10u8);
|
|
let mut buses = HashMap::new();
|
|
buses.insert("spi".to_string(), BusConfig { user_pins: spi_pins });
|
|
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses,
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
init_from("uno", "mega", Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let mega_pins = loaded.pins.get("mega").unwrap();
|
|
assert_eq!(mega_pins.assignments.len(), 2);
|
|
assert_eq!(mega_pins.assignments["red_led"].pin, 13);
|
|
assert_eq!(mega_pins.assignments["button"].pin, 7);
|
|
assert_eq!(mega_pins.buses.len(), 1);
|
|
assert!(mega_pins.buses.contains_key("spi"));
|
|
|
|
// Source should still be intact
|
|
let uno_pins = loaded.pins.get("uno").unwrap();
|
|
assert_eq!(uno_pins.assignments.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_init_from_fails_without_target_board() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::init_from;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("init_fail");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// mega not in boards map -> should fail
|
|
let result = init_from("uno", "mega", Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "init_from should fail when target board doesn't exist");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: assignment validation
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_pin_validates_pin_exists() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("validate_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Pin 99 doesn't exist on uno
|
|
let result = assign_pin("led", "99", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject pin 99 on uno");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_validates_mode() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("mode_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Pin 4 doesn't support PWM on uno
|
|
let result = assign_pin("motor", "4", Some("pwm"), None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject PWM on non-PWM pin");
|
|
|
|
// Pin 9 does support PWM
|
|
let result = assign_pin("motor", "9", Some("pwm"), None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept PWM on PWM-capable pin");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_validates_name() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("name_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Reserved names
|
|
let result = assign_pin("spi", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject reserved name 'spi'");
|
|
|
|
// Invalid characters
|
|
let result = assign_pin("my-led", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject name with hyphens");
|
|
|
|
// Starting with number
|
|
let result = assign_pin("1led", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject name starting with number");
|
|
|
|
// Valid name
|
|
let result = assign_pin("status_led", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept valid name");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_accepts_aliases() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("alias_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// A0 should resolve to pin 14 on uno
|
|
let result = assign_pin("temp_sensor", "A0", Some("analog"), None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept A0 alias");
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert_eq!(pc.assignments["temp_sensor"].pin, 14);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: bus group assignment
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_bus_spi_with_cs() {
|
|
use anvil::commands::pin::assign_bus;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("bus_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let user_pins = vec![("cs", "10")];
|
|
let result = assign_bus("spi", &user_pins, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept SPI with CS pin");
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert!(pc.buses.contains_key("spi"));
|
|
assert_eq!(*pc.buses["spi"].user_pins.get("cs").unwrap(), 10u8);
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_bus_spi_without_cs_fails() {
|
|
use anvil::commands::pin::assign_bus;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("bus_fail");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// SPI requires CS pin
|
|
let result = assign_bus("spi", &[], None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "SPI should require CS pin");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_bus_i2c_no_user_pins() {
|
|
use anvil::commands::pin::assign_bus;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("i2c_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// I2C has no user-selectable pins
|
|
let result = assign_bus("i2c", &[], None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "I2C should work with no user pins");
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert!(pc.buses.contains_key("i2c"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_bus_unknown_fails() {
|
|
use anvil::commands::pin::assign_bus;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("unknown_bus");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_bus("can", &[], None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject unknown bus name");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: remove assignment
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_remove_pin_assignment() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::remove_assignment;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("remove_test");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("red_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
assignments.insert("green_led".to_string(), PinAssignment {
|
|
pin: 11, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
remove_assignment("red_led", None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert!(!pc.assignments.contains_key("red_led"), "red_led should be removed");
|
|
assert!(pc.assignments.contains_key("green_led"), "green_led should remain");
|
|
}
|
|
|
|
#[test]
|
|
fn test_remove_nonexistent_fails() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::remove_assignment;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("remove_fail");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = remove_assignment("nope", None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should fail when removing nonexistent assignment");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: board-specific assignments via --board flag
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_pin_to_specific_board() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("board_pin");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: None,
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Assign to mega specifically
|
|
assign_pin("led", "13", None, Some("mega"), Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert!(loaded.pins.contains_key("mega"), "Should have mega pin config");
|
|
assert!(!loaded.pins.contains_key("uno"), "Should NOT have uno pin config");
|
|
assert_eq!(loaded.pins["mega"].assignments["led"].pin, 13);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: overwrite existing assignment
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_pin_overwrites_existing() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("overwrite_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Assign red_led to pin 13
|
|
assign_pin("red_led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.pins["uno"].assignments["red_led"].pin, 13);
|
|
|
|
// Reassign red_led to pin 6
|
|
assign_pin("red_led", "6", Some("pwm"), None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.pins["uno"].assignments["red_led"].pin, 6);
|
|
assert_eq!(loaded.pins["uno"].assignments["red_led"].mode, "pwm");
|
|
assert_eq!(loaded.pins["uno"].assignments.len(), 1, "Should still be one assignment");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_multiple_pins_sequentially() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("multi_assign");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
assign_pin("red_led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
assign_pin("green_led", "11", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
assign_pin("button", "7", Some("input"), None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
assign_pin("pot", "A0", Some("analog"), None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert_eq!(pc.assignments.len(), 4);
|
|
assert_eq!(pc.assignments["red_led"].pin, 13);
|
|
assert_eq!(pc.assignments["green_led"].pin, 11);
|
|
assert_eq!(pc.assignments["button"].pin, 7);
|
|
assert_eq!(pc.assignments["button"].mode, "input");
|
|
assert_eq!(pc.assignments["pot"].pin, 14); // A0 = digital 14 on uno
|
|
assert_eq!(pc.assignments["pot"].mode, "analog");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: mode defaults and validation edge cases
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_pin_defaults_to_output() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("default_mode");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// No mode specified -> should default to "output"
|
|
assign_pin("led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.pins["uno"].assignments["led"].mode, "output");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_rejects_analog_on_digital_only() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("analog_reject");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Pin 4 on uno is digital-only, no analog capability
|
|
let result = assign_pin("sensor", "4", Some("analog"), None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject analog mode on digital-only pin");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_rejects_invalid_mode_string() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("bad_mode");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("led", "13", Some("servo"), None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject unknown mode 'servo'");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_input_pullup_mode() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("pullup_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("button", "7", Some("input_pullup"), None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept input_pullup mode");
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.pins["uno"].assignments["button"].mode, "input_pullup");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: alias resolution edge cases
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_pin_led_builtin_alias() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("builtin_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("status", "LED_BUILTIN", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept LED_BUILTIN alias");
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert_eq!(loaded.pins["uno"].assignments["status"].pin, 13);
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_sda_alias() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("sda_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("temp_data", "SDA", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept SDA alias");
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
// Uno SDA = pin 18 (A4)
|
|
assert_eq!(loaded.pins["uno"].assignments["temp_data"].pin, 18);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: name validation edge cases
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_assign_pin_underscore_prefix_ok() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("underscore_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("_internal", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept name starting with underscore");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_all_uppercase_ok() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("upper_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("STATUS_LED", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Should accept all-uppercase name");
|
|
}
|
|
|
|
#[test]
|
|
fn test_assign_pin_rejects_spaces() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("space_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = assign_pin("my led", "13", None, None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should reject name with spaces");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: remove bus assignment
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_remove_bus_assignment() {
|
|
use anvil::project::config::{BoardPinConfig, BusConfig};
|
|
use anvil::commands::pin::remove_assignment;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("remove_bus");
|
|
|
|
let mut buses = HashMap::new();
|
|
let mut spi_pins = HashMap::new();
|
|
spi_pins.insert("cs".to_string(), 10u8);
|
|
buses.insert("spi".to_string(), BusConfig { user_pins: spi_pins });
|
|
buses.insert("i2c".to_string(), BusConfig { user_pins: HashMap::new() });
|
|
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments: HashMap::new(),
|
|
buses,
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
remove_assignment("spi", None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
assert!(!pc.buses.contains_key("spi"), "spi should be removed");
|
|
assert!(pc.buses.contains_key("i2c"), "i2c should remain");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: generate_pins_header edge cases
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_generate_fails_with_no_assignments() {
|
|
use anvil::commands::pin::generate_pins_header;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("empty_gen");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = generate_pins_header(None, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_err(), "Should fail when no pin assignments exist");
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_header_includes_board_info_comment() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::generate_pins_header;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("comment_gen");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
generate_pins_header(None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let header = fs::read_to_string(tmp.path().join("lib/hal/pins.h")).unwrap();
|
|
assert!(header.contains("uno"), "pins.h should mention board name");
|
|
assert!(header.contains("arduino:avr:uno"), "pins.h should mention FQBN");
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_header_names_are_uppercased() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::generate_pins_header;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("upper_gen");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("motor_pwm".to_string(), PinAssignment {
|
|
pin: 9, mode: "pwm".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
generate_pins_header(None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let header = fs::read_to_string(tmp.path().join("lib/hal/pins.h")).unwrap();
|
|
assert!(header.contains("MOTOR_PWM"), "pins.h should uppercase pin names");
|
|
assert!(!header.contains("motor_pwm"), "pins.h should not have lowercase pin names");
|
|
}
|
|
|
|
#[test]
|
|
fn test_generate_header_includes_stdint() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::generate_pins_header;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("stdint_gen");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
generate_pins_header(None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let header = fs::read_to_string(tmp.path().join("lib/hal/pins.h")).unwrap();
|
|
assert!(header.contains("#include <stdint.h>"), "pins.h should include stdint.h for uint8_t");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: audit smoke tests (don't crash)
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_audit_with_no_assignments_does_not_crash() {
|
|
use anvil::commands::pin::audit_pins;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("audit_empty");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Should succeed with helpful "no assignments" message, not crash
|
|
let result = audit_pins(None, false, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Audit with no assignments should not crash");
|
|
}
|
|
|
|
#[test]
|
|
fn test_audit_with_assignments_does_not_crash() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment, BusConfig};
|
|
use anvil::commands::pin::audit_pins;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("audit_full");
|
|
|
|
let mut assignments = HashMap::new();
|
|
assignments.insert("red_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
assignments.insert("button".to_string(), PinAssignment {
|
|
pin: 7, mode: "input".to_string(),
|
|
});
|
|
|
|
let mut spi_pins = HashMap::new();
|
|
spi_pins.insert("cs".to_string(), 10u8);
|
|
let mut buses = HashMap::new();
|
|
buses.insert("spi".to_string(), BusConfig { user_pins: spi_pins });
|
|
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments, buses,
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let result = audit_pins(None, false, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Audit with assignments should not crash");
|
|
|
|
// Brief mode should also work
|
|
let result = audit_pins(None, true, Some(tmp.path().to_str().unwrap()));
|
|
assert!(result.is_ok(), "Audit --brief should not crash");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: show_pin_map and show_capabilities smoke tests
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_show_pin_map_without_project() {
|
|
use anvil::commands::pin::show_pin_map;
|
|
|
|
// Should work with just a board name, no project needed
|
|
let result = show_pin_map(Some("uno"), None, Some("/nonexistent"));
|
|
assert!(result.is_ok(), "show_pin_map should work without a project");
|
|
}
|
|
|
|
#[test]
|
|
fn test_show_pin_map_with_filter() {
|
|
use anvil::commands::pin::show_pin_map;
|
|
|
|
let result = show_pin_map(Some("uno"), Some("pwm"), Some("/nonexistent"));
|
|
assert!(result.is_ok(), "show_pin_map with pwm filter should work");
|
|
}
|
|
|
|
#[test]
|
|
fn test_show_pin_map_invalid_filter() {
|
|
use anvil::commands::pin::show_pin_map;
|
|
|
|
let result = show_pin_map(Some("uno"), Some("dac"), Some("/nonexistent"));
|
|
assert!(result.is_err(), "show_pin_map with invalid capability should fail");
|
|
}
|
|
|
|
#[test]
|
|
fn test_show_capabilities_without_project() {
|
|
use anvil::commands::pin::show_capabilities;
|
|
|
|
let result = show_capabilities(Some("mega"), Some("/nonexistent"));
|
|
assert!(result.is_ok(), "show_capabilities should work without a project");
|
|
}
|
|
|
|
#[test]
|
|
fn test_show_pin_map_unknown_board() {
|
|
use anvil::commands::pin::show_pin_map;
|
|
|
|
let result = show_pin_map(Some("esp32"), None, Some("/nonexistent"));
|
|
assert!(result.is_err(), "show_pin_map should fail for unknown board");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: save_pins TOML writer preserves non-pin sections
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_save_pins_writer_preserves_boards_section() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("writer_test");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: Some(57600),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Assign a pin (this exercises save_pins internally)
|
|
assign_pin("led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
// Verify boards section survived
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
assert!(loaded.boards.contains_key("mega"), "mega board should survive pin save");
|
|
assert_eq!(loaded.boards["mega"].baud, Some(57600));
|
|
assert_eq!(loaded.build.default, "uno");
|
|
}
|
|
|
|
#[test]
|
|
fn test_save_pins_writer_idempotent() {
|
|
use anvil::commands::pin::assign_pin;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("idempotent");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Assign same pin twice -> file should stabilize
|
|
assign_pin("led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
let content1 = fs::read_to_string(tmp.path().join(CONFIG_FILENAME)).unwrap();
|
|
|
|
assign_pin("led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
let content2 = fs::read_to_string(tmp.path().join(CONFIG_FILENAME)).unwrap();
|
|
|
|
assert_eq!(content1, content2, "Saving same pin twice should produce identical output");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: init_from does NOT clobber existing target assignments
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_init_from_overwrites_existing_target_pins() {
|
|
use anvil::project::config::{BoardPinConfig, PinAssignment};
|
|
use anvil::commands::pin::init_from;
|
|
use std::collections::HashMap;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let mut config = ProjectConfig::new("overwrite_init");
|
|
config.boards.insert("mega".to_string(), BoardProfile {
|
|
fqbn: "arduino:avr:mega:cpu=atmega2560".to_string(),
|
|
baud: None,
|
|
});
|
|
|
|
// Source: uno has red_led on 13
|
|
let mut uno_assigns = HashMap::new();
|
|
uno_assigns.insert("red_led".to_string(), PinAssignment {
|
|
pin: 13, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("uno".to_string(), BoardPinConfig {
|
|
assignments: uno_assigns, buses: HashMap::new(),
|
|
});
|
|
|
|
// Target: mega already has something
|
|
let mut mega_assigns = HashMap::new();
|
|
mega_assigns.insert("old_pin".to_string(), PinAssignment {
|
|
pin: 22, mode: "output".to_string(),
|
|
});
|
|
config.pins.insert("mega".to_string(), BoardPinConfig {
|
|
assignments: mega_assigns, buses: HashMap::new(),
|
|
});
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// init_from replaces the entire target pin config
|
|
init_from("uno", "mega", Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let mega_pins = loaded.pins.get("mega").unwrap();
|
|
assert!(mega_pins.assignments.contains_key("red_led"), "Should have copied red_led");
|
|
assert!(!mega_pins.assignments.contains_key("old_pin"), "old_pin should be replaced");
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: assign_bus with user_pins round-trips through TOML
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_bus_user_pins_survive_toml_roundtrip() {
|
|
use anvil::commands::pin::assign_bus;
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("bus_roundtrip");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
let user_pins = vec![("cs", "10")];
|
|
assign_bus("spi", &user_pins, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
// Verify the raw TOML content is parseable
|
|
let content = fs::read_to_string(tmp.path().join(CONFIG_FILENAME)).unwrap();
|
|
assert!(
|
|
content.contains("[pins."),
|
|
"TOML should have pins section after bus assign"
|
|
);
|
|
|
|
// Reload and verify
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
let spi = pc.buses.get("spi").unwrap();
|
|
assert_eq!(*spi.user_pins.get("cs").unwrap(), 10u8);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Pin: mixed pins and buses in same board config
|
|
// ==========================================================================
|
|
|
|
#[test]
|
|
fn test_mixed_pins_and_buses_roundtrip() {
|
|
use anvil::commands::pin::{assign_pin, assign_bus};
|
|
|
|
let tmp = TempDir::new().unwrap();
|
|
let config = ProjectConfig::new("mixed_test");
|
|
config.save(tmp.path()).unwrap();
|
|
|
|
// Assign individual pins
|
|
assign_pin("red_led", "13", None, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
assign_pin("button", "7", Some("input"), None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
// Assign bus groups
|
|
let spi_pins = vec![("cs", "10")];
|
|
assign_bus("spi", &spi_pins, None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
assign_bus("i2c", &[], None, Some(tmp.path().to_str().unwrap())).unwrap();
|
|
|
|
// Everything should survive the round-trip
|
|
let loaded = ProjectConfig::load(tmp.path()).unwrap();
|
|
let pc = loaded.pins.get("uno").unwrap();
|
|
|
|
assert_eq!(pc.assignments.len(), 2);
|
|
assert_eq!(pc.assignments["red_led"].pin, 13);
|
|
assert_eq!(pc.assignments["button"].pin, 7);
|
|
assert_eq!(pc.assignments["button"].mode, "input");
|
|
|
|
assert_eq!(pc.buses.len(), 2);
|
|
assert!(pc.buses.contains_key("spi"));
|
|
assert!(pc.buses.contains_key("i2c"));
|
|
assert_eq!(*pc.buses["spi"].user_pins.get("cs").unwrap(), 10u8);
|
|
} |