#[cfg(all(test, feature = "std"))]
mod tests {
use cu29_runtime::config::read_configuration;
use std::fs::{create_dir_all, write};
use tempfile::tempdir;
#[test]
fn test_modular_config_basic() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let config_dir = base_path.join("config");
create_dir_all(&config_dir).unwrap();
let base_config = r#"(
tasks: [
(
id: "source",
type: "tasks::FlippingSource",
config: {
"rate": 10,
},
),
],
cnx: [],
)"#;
let base_path = config_dir.join("base.ron");
write(&base_path, base_config).unwrap();
let motors_config = r#"(
tasks: [
(
id: "motor_{{id}}",
type: "tasks::RPGpio",
config: {
"pin": {{pin}},
"direction": "{{direction}}",
},
),
],
cnx: [
(src: "source", dst: "motor_{{id}}", msg: "tasks::GPIOPayload"),
],
)"#;
let motors_path = config_dir.join("motors.ron");
write(&motors_path, motors_config).unwrap();
let main_config = r#"(
tasks: [],
cnx: [],
monitors: [(
type: "tasks::ConsoleMon",
)],
logging: (
file: "test.copper",
level: "debug",
),
includes: [
(
path: "base.ron",
params: {},
),
(
path: "motors.ron",
params: {
"id": "left",
"pin": 4,
"direction": "forward",
},
),
(
path: "motors.ron",
params: {
"id": "right",
"pin": 5,
"direction": "reverse",
},
),
],
)"#;
let main_path = config_dir.join("main.ron");
write(&main_path, main_config).unwrap();
let config = read_configuration(main_path.to_str().unwrap()).unwrap();
let graph = config.graphs.get_graph(None).unwrap();
let all_nodes = graph.get_all_nodes();
assert_eq!(all_nodes.len(), 3);
let task_ids: Vec<_> = all_nodes
.iter()
.map(|(_, node)| node.get_id().to_string())
.collect();
assert!(task_ids.contains(&"source".to_string()));
assert!(task_ids.contains(&"motor_left".to_string()));
assert!(task_ids.contains(&"motor_right".to_string()));
let left_motor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "motor_left")
.unwrap()
.1;
assert_eq!(left_motor.get_type(), "tasks::RPGpio");
assert_eq!(
left_motor
.get_param::<i32>("pin")
.expect("pin lookup failed"),
Some(4)
);
let left_direction = left_motor
.get_param::<String>("direction")
.expect("direction lookup failed");
assert_eq!(left_direction.as_deref(), Some("forward"));
let right_motor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "motor_right")
.unwrap()
.1;
assert_eq!(right_motor.get_type(), "tasks::RPGpio");
assert_eq!(
right_motor
.get_param::<i32>("pin")
.expect("pin lookup failed"),
Some(5)
);
let right_direction = right_motor
.get_param::<String>("direction")
.expect("direction lookup failed");
assert_eq!(right_direction.as_deref(), Some("reverse"));
let graph = config.graphs.get_graph(None).unwrap();
let source_idx = graph.get_node_id_by_name("source").unwrap();
let motor_left_idx = graph.get_node_id_by_name("motor_left").unwrap();
let motor_right_idx = graph.get_node_id_by_name("motor_right").unwrap();
let left_connection = graph
.get_connection_msg_type(source_idx, motor_left_idx)
.expect("Connection from source to motor_left_idx not found");
assert_eq!(left_connection, "tasks::GPIOPayload");
let right_connection = graph
.get_connection_msg_type(source_idx, motor_right_idx)
.expect("Connection from source to motor_right_idx not found");
assert_eq!(right_connection, "tasks::GPIOPayload");
assert_eq!(
config.get_monitor_config().unwrap().get_type(),
"tasks::ConsoleMon"
);
}
#[test]
fn test_modular_config_nested() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let config_dir = base_path.join("config");
create_dir_all(&config_dir).unwrap();
let sensor_config = r#"(
tasks: [
(
id: "sensor_{{id}}",
type: "tasks::Sensor{{type}}",
config: {
"rate": {{rate}},
},
),
],
cnx: [],
)"#;
let sensor_path = config_dir.join("sensor.ron");
write(&sensor_path, sensor_config).unwrap();
let processor_config = r#"(
tasks: [
(
id: "processor_{{id}}",
type: "tasks::DataProcessor",
config: {
"threshold": {{threshold}},
},
),
],
cnx: [
(src: "sensor_{{sensor_id}}", dst: "processor_{{id}}", msg: "tasks::SensorData"),
],
)"#;
let processor_path = config_dir.join("processor.ron");
write(&processor_path, processor_config).unwrap();
let subsystem_config = r#"(
tasks: [],
cnx: [],
includes: [
(
path: "sensor.ron",
params: {
"id": "{{subsystem_id}}",
"type": "{{sensor_type}}",
"rate": {{sensor_rate}},
},
),
(
path: "processor.ron",
params: {
"id": "{{subsystem_id}}",
"sensor_id": "{{subsystem_id}}",
"threshold": {{processor_threshold}},
},
),
],
)"#;
let subsystem_path = config_dir.join("subsystem.ron");
write(&subsystem_path, subsystem_config).unwrap();
let main_config = r#"(
tasks: [
(
id: "controller",
type: "tasks::MainController",
),
],
cnx: [
(src: "processor_front", dst: "controller", msg: "tasks::ProcessedData"),
(src: "processor_rear", dst: "controller", msg: "tasks::ProcessedData"),
],
includes: [
(
path: "subsystem.ron",
params: {
"subsystem_id": "front",
"sensor_type": "Infrared",
"sensor_rate": 20,
"processor_threshold": 0.75,
},
),
(
path: "subsystem.ron",
params: {
"subsystem_id": "rear",
"sensor_type": "Ultrasonic",
"sensor_rate": 10,
"processor_threshold": 0.5,
},
),
],
)"#;
let main_path = config_dir.join("main.ron");
write(&main_path, main_config).unwrap();
let config = read_configuration(main_path.to_str().unwrap()).unwrap();
let graph = config.graphs.get_graph(None).unwrap();
let all_nodes = graph.get_all_nodes();
assert_eq!(all_nodes.len(), 5);
let task_ids: Vec<_> = all_nodes
.iter()
.map(|(_, node)| node.get_id().to_string())
.collect();
assert!(task_ids.contains(&"controller".to_string()));
assert!(task_ids.contains(&"sensor_front".to_string()));
assert!(task_ids.contains(&"sensor_rear".to_string()));
assert!(task_ids.contains(&"processor_front".to_string()));
assert!(task_ids.contains(&"processor_rear".to_string()));
let front_sensor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "sensor_front")
.unwrap()
.1;
assert_eq!(front_sensor.get_type(), "tasks::SensorInfrared");
assert_eq!(
front_sensor
.get_param::<i32>("rate")
.expect("rate lookup failed"),
Some(20)
);
let rear_sensor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "sensor_rear")
.unwrap()
.1;
assert_eq!(rear_sensor.get_type(), "tasks::SensorUltrasonic");
assert_eq!(
rear_sensor
.get_param::<i32>("rate")
.expect("rate lookup failed"),
Some(10)
);
let front_processor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "processor_front")
.unwrap()
.1;
assert_eq!(
front_processor
.get_param::<f64>("threshold")
.expect("threshold lookup failed"),
Some(0.75)
);
let rear_processor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "processor_rear")
.unwrap()
.1;
assert_eq!(
rear_processor
.get_param::<f64>("threshold")
.expect("threshold lookup failed"),
Some(0.5)
);
let graph = config.graphs.get_graph(None).unwrap();
let controller_idx = graph.get_node_id_by_name("controller").unwrap();
let sensor_front_idx = graph.get_node_id_by_name("sensor_front").unwrap();
let sensor_rear_idx = graph.get_node_id_by_name("sensor_rear").unwrap();
let processor_front_idx = graph.get_node_id_by_name("processor_front").unwrap();
let processor_rear_idx = graph.get_node_id_by_name("processor_rear").unwrap();
let sensor_front_to_processor =
graph.connection_exists(sensor_front_idx, processor_front_idx);
assert!(
sensor_front_to_processor,
"Connection from sensor_front to processor_front not found"
);
let sensor_rear_to_processor = graph.connection_exists(sensor_rear_idx, processor_rear_idx);
assert!(
sensor_rear_to_processor,
"Connection from sensor_rear to processor_rear not found"
);
let processor_front_to_controller =
graph.connection_exists(processor_front_idx, controller_idx);
assert!(
processor_front_to_controller,
"Connection from processor_front to controller not found"
);
let processor_rear_to_controller =
graph.connection_exists(processor_rear_idx, controller_idx);
assert!(
processor_rear_to_controller,
"Connection from processor_rear to controller not found"
);
}
#[test]
fn test_modular_config_override() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let config_dir = base_path.join("config");
create_dir_all(&config_dir).unwrap();
let base_config = r#"(
tasks: [
(
id: "source",
type: "tasks::DefaultSource",
config: {
"param": "default",
},
),
(
id: "processor",
type: "tasks::DefaultProcessor",
),
],
cnx: [
(src: "source", dst: "processor", msg: "tasks::DefaultData"),
],
monitors: [(
type: "tasks::DefaultMonitor",
)],
)"#;
let base_path = config_dir.join("base.ron");
write(&base_path, base_config).unwrap();
let main_config = r#"(
tasks: [
(
id: "source",
type: "tasks::CustomSource",
config: {
"param": "custom",
"additional": true,
},
),
(
id: "sink",
type: "tasks::DataSink",
),
],
cnx: [
(src: "processor", dst: "sink", msg: "tasks::ProcessedData"),
],
monitors: [(
type: "tasks::CustomMonitor",
config: {
"debug": true,
},
)],
includes: [
(
path: "base.ron",
params: {},
),
],
)"#;
let main_path = config_dir.join("main.ron");
write(&main_path, main_config).unwrap();
let config = read_configuration(main_path.to_str().unwrap()).unwrap();
let graph = config.graphs.get_graph(None).unwrap();
let all_nodes = graph.get_all_nodes();
assert_eq!(all_nodes.len(), 3);
let source = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "source")
.unwrap()
.1;
assert_eq!(source.get_type(), "tasks::CustomSource");
let source_param = source
.get_param::<String>("param")
.expect("param lookup failed");
assert_eq!(source_param.as_deref(), Some("custom"));
assert_eq!(
source
.get_param::<bool>("additional")
.expect("additional lookup failed"),
Some(true)
);
let processor = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "processor")
.unwrap()
.1;
assert_eq!(processor.get_type(), "tasks::DefaultProcessor");
let sink = all_nodes
.iter()
.find(|(_, node)| node.get_id() == "sink")
.unwrap()
.1;
assert_eq!(sink.get_type(), "tasks::DataSink");
let graph = config.graphs.get_graph(None).unwrap();
let source_idx = graph.get_node_id_by_name("source").unwrap();
let processor_idx = graph.get_node_id_by_name("processor").unwrap();
let sink_idx = graph.get_node_id_by_name("sink").unwrap();
let source_to_processor = graph.connection_exists(source_idx, processor_idx);
assert!(
source_to_processor,
"Connection from source to processor not found"
);
let processor_to_sink = graph.connection_exists(processor_idx, sink_idx);
assert!(
processor_to_sink,
"Connection from processor to sink not found"
);
assert_eq!(
config.get_monitor_config().unwrap().get_type(),
"tasks::CustomMonitor"
);
assert!(config.get_monitor_config().is_some());
}
#[test]
fn test_modular_config_error_handling() {
let temp_dir = tempdir().unwrap();
let base_path = temp_dir.path();
let config_dir = base_path.join("config");
create_dir_all(&config_dir).unwrap();
let missing_include_config = r#"(
tasks: [],
cnx: [],
includes: [
(
path: "nonexistent.ron",
params: {},
),
],
)"#;
let missing_path = config_dir.join("missing_include.ron");
write(&missing_path, missing_include_config).unwrap();
let result = read_configuration(missing_path.to_str().unwrap());
assert!(result.is_err());
let template_config = r#"(
tasks: [
(
id: "task_{{id}}",
type: "tasks::Task",
config: {
"value": {{value}},
},
),
],
cnx: [],
)"#;
let template_path = config_dir.join("template.ron");
write(&template_path, template_config).unwrap();
let missing_param_config = r#"(
tasks: [],
cnx: [],
includes: [
(
path: "template.ron",
params: {
"id": "test",
// Missing "value" parameter
},
),
],
)"#;
let missing_param_path = config_dir.join("missing_param.ron");
write(&missing_param_path, missing_param_config).unwrap();
let result = read_configuration(missing_param_path.to_str().unwrap());
assert!(result.is_err());
let invalid_syntax_config = r#"(
tasks: [
(
id: "task"
type: "tasks::Task", // Missing comma after id
config: {},
),
],
cnx: [],
)"#;
let invalid_syntax_path = config_dir.join("invalid_syntax.ron");
write(&invalid_syntax_path, invalid_syntax_config).unwrap();
let result = read_configuration(invalid_syntax_path.to_str().unwrap());
assert!(result.is_err());
}
}