use crate::bits::{MAX_3BIT, MAX_7BIT};
use crate::result::{diagnostic::Location, CompilerResult};
use crate::test_binary::DeviceAddress;
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::hash::Hash;
use std::iter::FromIterator;
use std::path::PathBuf;
use std::u16;
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub relay_micros: u32,
pub sensor_micros: u32,
pub max_segment_length_micros: u32,
pub safe_state: Vec<String>,
pub drivers_path: Option<PathBuf>,
pub relays: HashMap<String, RelayRecord>,
pub sensors: HashMap<String, SensorRecord>,
#[serde(rename = "virtual")]
pub virtuals: Virtual,
pub global_bounds: Option<HashMap<String, ConfigSensorBound>>,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SensorRecord {
pub device_address: ConfigDeviceAddress,
pub polling_interval_ms: u16,
pub calibration: PathBuf,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RelayRecord {
pub device_address: ConfigDeviceAddress,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Virtual {
pub sensors: HashMap<String, VirtualSensorRecord>,
pub relays: HashMap<String, VirtualRelayRecord>,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VirtualSensorRecord {
pub device_address: ConfigDeviceAddress,
pub driver: String,
pub args: Option<BTreeMap<String, u16>>,
pub polling_interval_ms: u16,
pub calibration: Option<PathBuf>,
}
#[derive(Debug, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VirtualRelayRecord {
pub device_address: ConfigDeviceAddress,
pub driver: String,
pub args: Option<BTreeMap<String, u16>>,
}
#[derive(Debug, PartialEq, Deserialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub struct ConfigDeviceAddress {
pub board_address: u8,
pub channel: u8,
}
#[derive(Debug, PartialEq, Deserialize, Clone, Copy)]
#[serde(deny_unknown_fields)]
pub struct ConfigSensorBound {
pub left: f64,
pub right: f64,
}
impl Config {
pub fn get_sensor_device_address(&self, sensor_id: &str) -> CompilerResult<DeviceAddress> {
let mut res = CompilerResult::new("Get virtual address for a sensor_id");
if let Some(record) = self.sensors.get(sensor_id) {
return res.with_value(DeviceAddress {
value: u16::from(record.device_address),
virtuality: 0,
});
}
if let Some(record) = self.virtuals.sensors.get(sensor_id) {
return res.with_value(DeviceAddress {
value: u16::from(record.device_address),
virtuality: 1,
});
}
res.error(format!(
"No sensor or virtual sensor with sensor_id: {} found in environment",
sensor_id
));
res
}
pub fn get_relay_device_address(&self, relay_id: &str) -> CompilerResult<DeviceAddress> {
let mut res = CompilerResult::new("Get device address for a relay_id");
if let Some(record) = self.relays.get(relay_id) {
res.with_value(DeviceAddress {
value: u16::from(record.device_address),
virtuality: 0,
})
} else {
if let Some(record) = self.virtuals.relays.get(relay_id) {
return res.with_value(DeviceAddress {
value: u16::from(record.device_address),
virtuality: 1,
});
}
res.error(format!(
"No relay or virtual relay with relay_id: {} found in environment",
relay_id
));
res
}
}
pub fn get_safe_state_relay_records(&self) -> Vec<&RelayRecord> {
let mut records: Vec<&RelayRecord> = Vec::new();
for relay_name in &self.safe_state {
match self.relays.get(relay_name) {
Some(record) => records.push(record),
None => (),
}
}
records
}
pub fn get_safe_state_v_relay_records(&self) -> Vec<&VirtualRelayRecord> {
let mut records: Vec<&VirtualRelayRecord> = Vec::new();
for relay_name in &self.safe_state {
match self.virtuals.relays.get(relay_name) {
Some(record) => records.push(record),
None => (),
}
}
records
}
pub fn get_relay_records(&self) -> Vec<&RelayRecord> {
let mut records: Vec<&RelayRecord> = self.relays.values().collect();
records.sort_by_key(|record| u16::from(record.device_address));
records
}
pub fn get_sensor_records(&self) -> Vec<&SensorRecord> {
let mut records: Vec<&SensorRecord> = self.sensors.values().collect();
records.sort_by_key(|record| u16::from(record.device_address));
records
}
pub fn get_v_relay_records(&self) -> Vec<&VirtualRelayRecord> {
let mut records: Vec<&VirtualRelayRecord> = self.virtuals.relays.values().collect();
records.sort_by_key(|record| u16::from(record.device_address));
records
}
pub fn get_v_sensor_records(&self) -> Vec<&VirtualSensorRecord> {
let mut records: Vec<&VirtualSensorRecord> = self.virtuals.sensors.values().collect();
records.sort_by_key(|record| u16::from(record.device_address));
records
}
pub fn validate_device_addresses(&self, file_name: &String) -> CompilerResult<()> {
let mut res =
CompilerResult::status_only("Validating physical device address values in config.toml");
let sensor_device_addresses: Vec<ConfigDeviceAddress> = self
.get_sensor_records()
.iter()
.map(|record| record.device_address)
.collect();
let relay_device_addresses: Vec<ConfigDeviceAddress> = self
.get_relay_records()
.iter()
.map(|record| record.device_address)
.collect();
res.require(Config::validate_device_address_values(
sensor_device_addresses,
file_name,
));
res.require(Config::validate_device_address_values(
relay_device_addresses,
file_name,
));
let relay_device_addresses: Vec<u16> = self
.get_relay_records()
.iter()
.map(|record| u16::from(record.device_address))
.collect();
let sensor_device_addresses: Vec<u16> = self
.get_sensor_records()
.iter()
.map(|record| u16::from(record.device_address))
.collect();
let duplicate_device_addresses = Config::find_duplicate_device_address_values(
sensor_device_addresses,
relay_device_addresses,
);
for device_address in duplicate_device_addresses {
res.error(format!(
"Duplicated device address: {}.\n\t{:?}",
device_address, file_name
));
}
res
}
pub fn validate_v_device_addresses(&self, file_name: &String) -> CompilerResult<()> {
let mut res =
CompilerResult::status_only("Validating virtual device address values in config.toml");
let v_sensor_device_addresses: Vec<ConfigDeviceAddress> = self
.get_v_sensor_records()
.iter()
.map(|record| record.device_address)
.collect();
let v_relay_device_addresses: Vec<ConfigDeviceAddress> = self
.get_v_relay_records()
.iter()
.map(|record| record.device_address)
.collect();
res.require(Config::validate_device_address_values(
v_sensor_device_addresses,
file_name,
));
res.require(Config::validate_device_address_values(
v_relay_device_addresses,
file_name,
));
let v_sensor_device_addresses: Vec<u16> = self
.get_v_sensor_records()
.iter()
.map(|record| u16::from(record.device_address))
.collect();
let v_relay_device_addresses: Vec<u16> = self
.get_v_relay_records()
.iter()
.map(|record| u16::from(record.device_address))
.collect();
let duplicate_device_addresses = Config::find_duplicate_device_address_values(
v_sensor_device_addresses,
v_relay_device_addresses,
);
for device_address in duplicate_device_addresses {
res.error(format!(
"Duplicated virtual device address: {:?}.\n\t{:?}",
ConfigDeviceAddress::from(device_address),
file_name
));
}
res
}
fn validate_device_address_values(
device_addresses: Vec<ConfigDeviceAddress>,
file_name: &String,
) -> CompilerResult<()> {
let mut res = CompilerResult::status_only("Validate channel values");
for addr in device_addresses {
if addr.channel > MAX_3BIT {
res.error(format!("Invalid channel: {:?}\n\t{:?}", addr, file_name));
}
if addr.board_address > MAX_7BIT {
res.error(format!(
"Invalid board address: {:?}\n\t{:?}",
addr, file_name
));
}
}
res
}
fn find_duplicate_device_address_values(
mut device_addresses1: Vec<u16>,
mut device_addresses2: Vec<u16>,
) -> HashSet<u16> {
let mut duplicate_device_addresses: HashSet<u16> = HashSet::new();
device_addresses1.sort();
device_addresses2.sort();
for i in 1..device_addresses1.len() {
if device_addresses1[i] == device_addresses1[i - 1]
&& !(duplicate_device_addresses.contains(&device_addresses1[i]))
{
duplicate_device_addresses.insert(device_addresses1[i]);
}
}
for i in 1..device_addresses2.len() {
if device_addresses2[i] == device_addresses2[i - 1]
&& !(duplicate_device_addresses.contains(&device_addresses2[i]))
{
duplicate_device_addresses.insert(device_addresses2[i]);
}
}
device_addresses1.dedup();
for device_address in device_addresses1 {
if device_addresses2.contains(&device_address) {
duplicate_device_addresses.insert(device_address);
}
}
duplicate_device_addresses
}
pub fn validate_device_ids(&self, file_name: &String) -> CompilerResult<()> {
let mut res = CompilerResult::status_only("Validate device IDs");
let mut device_ids: HashSet<String> = HashSet::new();
let mut duplicate_ids: HashSet<String> = HashSet::new();
let mut all_device_ids = Vec::new();
all_device_ids.extend(self.relays.keys());
all_device_ids.extend(self.sensors.keys());
all_device_ids.extend(self.virtuals.relays.keys());
all_device_ids.extend(self.virtuals.sensors.keys());
for id in all_device_ids {
if duplicate_ids.contains(id) {
continue;
}
else if device_ids.contains(id) {
duplicate_ids.insert(id.clone());
}
else {
device_ids.insert(id.clone());
}
}
if !duplicate_ids.is_empty() {
res.error(format!(
"The following device IDs are duplicated: {:?}.\n\t{}",
duplicate_ids.into_iter().collect::<Vec<String>>(),
file_name
));
}
res
}
pub fn validate_safe_state(&mut self, file_name: &String) -> CompilerResult<()> {
let mut res = CompilerResult::status_only("Validating safe state relay IDs");
let mut relay_ids: Vec<&String> = Vec::new();
relay_ids.extend(self.relays.keys());
relay_ids.extend(self.virtuals.relays.keys());
let missing = Self::find_missing(self.safe_state.iter().map(|x| x).collect(), relay_ids);
if !missing.is_empty() {
res.error(format!(
"The following relay names used in the safe state are not defined: {:?}.\n\t{}",
missing.into_iter().collect::<Vec<&String>>(),
file_name
))
}
res
}
pub fn validate_global_bounds(&mut self, file_name: &String) -> CompilerResult<()> {
let mut res = CompilerResult::status_only("Validating global bounds sensor IDs");
if let Some(global_bounds) = &self.global_bounds {
let mut sensor_ids: Vec<&String> = Vec::new();
sensor_ids.extend(self.sensors.keys());
sensor_ids.extend(self.virtuals.sensors.keys());
let global_bound_ids: Vec<&String> = global_bounds.keys().collect();
let missing = Self::find_missing(global_bound_ids, sensor_ids);
if !missing.is_empty() {
res.error(format!(
"The following sensor IDs used in the global bounds are not defined: {:?}.\n\t{}",
missing.into_iter().collect::<Vec<&String>>(), file_name
))
}
}
res
}
fn find_missing<T: Clone + Hash + Eq>(to_check: Vec<T>, given_values: Vec<T>) -> HashSet<T> {
let mut missing: HashSet<T> = HashSet::new();
let given: HashSet<T> = HashSet::from_iter(given_values.iter().cloned());
for val in to_check {
if !given.contains(&val) {
missing.insert(val.clone());
}
}
missing
}
}
pub fn parse(file_id: usize, file_name: String, contents: &str) -> CompilerResult<Config> {
let mut res = CompilerResult::new("Parsing a Config file");
let taplo_parse = taplo::parser::parse(contents);
let taplo_dom = taplo_parse.clone().into_dom();
if !taplo_parse.errors.is_empty() {
for error in taplo_parse.errors {
let location = Location::from_raw(
file_id,
error.range.start().into(),
error.range.end().into(),
);
res.error((location, error.to_string()));
}
}
if !taplo_dom.errors().is_empty() {
for error in taplo_dom.errors() {
res.error(format!("{}\n\t{}", error.to_string(), file_name));
}
}
let mut config: Config = check!(res, toml::from_str(contents));
res.require(config.validate_device_ids(&file_name));
res.require(config.validate_device_addresses(&file_name));
res.require(config.validate_v_device_addresses(&file_name));
res.require(config.validate_safe_state(&file_name));
res.require(config.validate_global_bounds(&file_name));
res.with_value(config)
}
impl From<u16> for ConfigDeviceAddress {
fn from(device_address: u16) -> Self {
ConfigDeviceAddress {
board_address: (device_address / 8) as u8,
channel: (device_address % 8) as u8,
}
}
}
impl From<ConfigDeviceAddress> for u16 {
fn from(device_address: ConfigDeviceAddress) -> Self {
return (device_address.board_address as u16) * 8 + device_address.channel as u16;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::result::Status;
static INPUT: &str = r#"
relay_micros = 100
sensor_micros = 100
max_segment_length_micros = 1000
safe_state = []
drivers_path = "./empty_drivers.toml"
[relays]
a = { device_address = {board_address = 0, channel = 1} }
b = { device_address = {board_address = 0, channel = 2} }
[sensors]
d = { device_address = {board_address = 0, channel = 3}, polling_interval_ms = 5, calibration = "./d.ncf" }
e = { device_address = {board_address = 0, channel = 4}, polling_interval_ms = 5, calibration = "./e.ncf" }
f = { device_address = {board_address = 0, channel = 5}, polling_interval_ms = 5, calibration = "./f.ncf" }
g = { device_address = {board_address = 0, channel = 6}, polling_interval_ms = 5, calibration = "./g.ncf" }
h = { device_address = {board_address = 0, channel = 7}, polling_interval_ms = 5, calibration = "./h.ncf" }
i = { device_address = {board_address = 1, channel = 0}, polling_interval_ms = 5, calibration = "./i.ncf" }
j = { device_address = {board_address = 1, channel = 1}, polling_interval_ms = 5, calibration = "./j.ncf" }
[virtual.relays]
prepare_confirm_button = { device_address = {board_address = 0, channel = 0}, driver = "MissionControl/UDP_Command", args = { id = 14 } }
[virtual.sensors]
confirm_button = { device_address = {board_address = 0, channel = 1}, driver = "MissionControl/UDP_Request", polling_interval_ms = 5, args = { stand_id = 23 } }
[global_bounds]
d = {left=1.0, right=2.0}
"#;
#[test]
fn toml_test() {
let relays: Vec<(String, RelayRecord)> = vec![
(
"a".into(),
RelayRecord {
device_address: 1.into(),
},
),
(
"b".into(),
RelayRecord {
device_address: 2.into(),
},
),
];
let sensors: Vec<(String, SensorRecord)> = vec![
(
"d".into(),
SensorRecord {
device_address: 3.into(),
polling_interval_ms: 5,
calibration: "./d.ncf".into(),
},
),
(
"e".into(),
SensorRecord {
device_address: 4.into(),
polling_interval_ms: 5,
calibration: "./e.ncf".into(),
},
),
(
"f".into(),
SensorRecord {
device_address: 5.into(),
polling_interval_ms: 5,
calibration: "./f.ncf".into(),
},
),
(
"g".into(),
SensorRecord {
device_address: 6.into(),
polling_interval_ms: 5,
calibration: "./g.ncf".into(),
},
),
(
"h".into(),
SensorRecord {
device_address: 7.into(),
polling_interval_ms: 5,
calibration: "./h.ncf".into(),
},
),
(
"i".into(),
SensorRecord {
device_address: 8.into(),
polling_interval_ms: 5,
calibration: "./i.ncf".into(),
},
),
(
"j".into(),
SensorRecord {
device_address: 9.into(),
polling_interval_ms: 5,
calibration: "./j.ncf".into(),
},
),
];
let mut virtual_relay_args1 = BTreeMap::new();
virtual_relay_args1.insert("id".into(), 14);
let virtual_relays: Vec<(String, VirtualRelayRecord)> = vec![(
"prepare_confirm_button".into(),
VirtualRelayRecord {
device_address: 0.into(),
driver: "MissionControl/UDP_Command".into(),
args: Some(virtual_relay_args1),
},
)];
let mut virtual_sensor_args1 = BTreeMap::new();
virtual_sensor_args1.insert("stand_id".into(), 23);
let virtual_sensors: Vec<(String, VirtualSensorRecord)> = vec![(
"confirm_button".into(),
VirtualSensorRecord {
device_address: 1.into(),
driver: "MissionControl/UDP_Request".into(),
args: Some(virtual_sensor_args1),
polling_interval_ms: 5,
calibration: None,
},
)];
let virtuals = Virtual {
sensors: virtual_sensors.into_iter().collect(),
relays: virtual_relays.into_iter().collect(),
};
let mut global_bounds: HashMap<String, ConfigSensorBound> = HashMap::new();
global_bounds.insert(
"d".into(),
ConfigSensorBound {
left: 1.0,
right: 2.0,
},
);
let expected = Config {
relay_micros: 100,
sensor_micros: 100,
max_segment_length_micros: 1000,
safe_state: vec![],
drivers_path: Some(PathBuf::from("./empty_drivers.toml")),
relays: relays.into_iter().collect(),
sensors: sensors.into_iter().collect(),
virtuals: virtuals,
global_bounds: Some(global_bounds),
};
let actual: Config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
assert_eq!(expected.relays, actual.relays);
assert_eq!(expected.sensors, actual.sensors);
assert_eq!(
actual
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
assert_eq!(
actual
.validate_v_device_addresses(&String::from(&"notafile".to_string()))
.get_status(),
Status::Unresolved,
);
assert_eq!(expected, actual);
}
#[test]
fn find_duplicate_device_address_values() {
let device_addresses1 = vec![0, 1, 2, 3];
let device_addresses2 = vec![10, 11, 12, 14, 15, 16, 17];
assert_eq!(
Config::find_duplicate_device_address_values(
device_addresses1.clone(),
device_addresses2.clone()
),
HashSet::new(),
);
let device_addresses1 = vec![0, 1, 2, 3];
let device_addresses3 = vec![1, 2, 3, 4, 5, 6, 7];
let three_dupes: HashSet<u16> = [1, 2, 3].iter().cloned().collect();
assert_eq!(
Config::find_duplicate_device_address_values(
device_addresses1.clone(),
device_addresses3.clone()
),
three_dupes,
);
let device_addresses3 = vec![1, 2, 3, 4, 5, 6, 7];
let device_addresses4 = Vec::new();
assert_eq!(
Config::find_duplicate_device_address_values(
device_addresses3.clone(),
device_addresses4.clone()
),
HashSet::new(),
);
let mut device_addresses2 = vec![10, 11, 12, 14, 15, 16, 17];
let device_addresses5 = vec![0, 1, 1, 3, 3, 3, 2];
let dupes_within: HashSet<u16> = [1, 3].iter().cloned().collect();
assert_eq!(
Config::find_duplicate_device_address_values(
device_addresses2.clone(),
device_addresses5.clone()
),
dupes_within,
);
let mut device_addresses1 = vec![0, 1, 2, 3];
device_addresses2.append(&mut device_addresses1);
let dupes_append: HashSet<u16> = device_addresses1.iter().cloned().collect();
assert_eq!(
Config::find_duplicate_device_address_values(
device_addresses1.clone(),
device_addresses2.clone()
),
dupes_append,
);
}
#[test]
fn parsed_toml_invalid_board_address_values() {
let mut config: Config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config.relays.get_mut("a").unwrap().device_address = ConfigDeviceAddress {
board_address: 0b10000001,
channel: 2,
};
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
config
.virtuals
.relays
.get_mut("prepare_confirm_button")
.unwrap()
.device_address = ConfigDeviceAddress {
board_address: 1 << 7,
channel: 5,
};
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config.relays.get_mut("a").unwrap().device_address = ConfigDeviceAddress {
board_address: 1 << 7,
channel: 3,
};
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
}
#[test]
fn parsed_toml_invalid_channel_values() {
let mut config: Config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config.relays.get_mut("a").unwrap().device_address = ConfigDeviceAddress {
board_address: 0,
channel: 8,
};
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
config
.virtuals
.relays
.get_mut("prepare_confirm_button")
.unwrap()
.device_address = ConfigDeviceAddress {
board_address: 5,
channel: 9,
};
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config.relays.get_mut("a").unwrap().device_address = ConfigDeviceAddress {
board_address: 0,
channel: 8,
};
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
}
#[test]
fn parsed_toml_duplicate_device_addresses() {
let mut config: Config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
config.relays.get_mut("a").unwrap().device_address = 3.into();
config
.virtuals
.relays
.get_mut("prepare_confirm_button")
.unwrap()
.device_address = 1.into();
assert_eq!(
config
.validate_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
assert_eq!(
config
.validate_v_device_addresses(&"notafile".to_string())
.get_status(),
Status::Failed,
);
}
#[test]
fn test_get_safe_state_relay_records() {
let mut config: Config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
assert_eq!(
config
.get_safe_state_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
Vec::<u16>::new()
);
assert_eq!(
config
.get_safe_state_v_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
Vec::<u16>::new()
);
config.safe_state = vec!["a".into()];
assert_eq!(
config
.get_safe_state_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
vec![1]
);
assert_eq!(
config
.get_safe_state_v_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
Vec::<u16>::new()
);
config.safe_state = vec!["a".into(), "b".into()];
assert_eq!(
config
.get_safe_state_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
vec![1, 2]
);
assert_eq!(
config
.get_safe_state_v_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
Vec::<u16>::new()
);
config.safe_state = vec!["prepare_confirm_button".into(), "b".into()];
assert_eq!(
config
.get_safe_state_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
vec![2]
);
assert_eq!(
config
.get_safe_state_v_relay_records()
.into_iter()
.map(|r| u16::from(r.device_address))
.collect::<Vec<u16>>(),
vec![0]
);
}
#[test]
fn safe_state_undefined_relay_name() {
let mut config: Config = parse(0, "empty".to_string(), INPUT).to_option().unwrap();
config.safe_state = vec!["a".into(), "prepare_confirm_button".into()];
assert_eq!(
config
.validate_safe_state(&"notafile".to_string())
.get_status(),
Status::Unresolved,
);
config.safe_state.push("imnotarelay".into());
assert_eq!(
config
.validate_safe_state(&"notafile".to_string())
.get_status(),
Status::Failed,
);
}
}