use std::collections::HashSet;
use epics_base_rs::server::ioc_builder::IocBuilder;
use epics_base_rs::types::EpicsValue;
const DB: &str = r#"
record(waveform, "SRC") {
field(FTVL, "DOUBLE")
field(NELM, "8")
}
record(aai, "CONST:AAI") {
field(FTVL, "DOUBLE")
field(NELM, "8")
field(INP, "[1, 2, 3]")
}
record(waveform, "CONST:WF") {
field(FTVL, "DOUBLE")
field(NELM, "8")
field(INP, "[4, 5]")
}
record(aai, "UNSET:AAI") {
field(FTVL, "DOUBLE")
field(NELM, "8")
}
record(waveform, "UNSET:WF") {
field(FTVL, "DOUBLE")
field(NELM, "8")
}
record(waveform, "LINKED:WF") {
field(FTVL, "DOUBLE")
field(NELM, "8")
field(INP, "SRC")
}
"#;
async fn build() -> std::sync::Arc<epics_base_rs::server::database::PvDatabase> {
IocBuilder::new()
.db_string(DB, &std::collections::HashMap::new())
.unwrap()
.build()
.await
.unwrap()
.0
}
async fn process(db: &epics_base_rs::server::database::PvDatabase, rec: &str) {
let mut visited = HashSet::new();
db.process_record_with_links(rec, &mut visited, 0)
.await
.unwrap();
}
#[epics_macros_rs::epics_test]
async fn constant_array_inp_is_loaded_at_init() {
let db = build().await;
assert_eq!(
db.get_pv("CONST:AAI").unwrap(),
EpicsValue::DoubleArray(vec![1.0, 2.0, 3.0]),
"aai: field(INP,\"[1,2,3]\") must be loaded at init"
);
assert_eq!(db.get_pv("CONST:AAI.NORD").unwrap().to_f64(), Some(3.0));
assert!(
db.get_record("CONST:AAI").unwrap().read().common.udf == 0,
"a record loaded from a constant link is DEFINED"
);
assert_eq!(
db.get_pv("CONST:WF").unwrap(),
EpicsValue::DoubleArray(vec![4.0, 5.0]),
"waveform: field(INP,\"[4,5]\") must be loaded at init"
);
assert_eq!(db.get_pv("CONST:WF.NORD").unwrap().to_f64(), Some(2.0));
}
#[epics_macros_rs::epics_test]
async fn constant_inp_is_not_re_applied_at_process() {
let db = build().await;
for rec in ["CONST:AAI", "CONST:WF"] {
db.put_pv(rec, EpicsValue::DoubleArray(vec![9.0, 8.0, 7.0, 6.0]))
.await
.unwrap();
process(&db, rec).await;
assert_eq!(
db.get_pv(rec).unwrap(),
EpicsValue::DoubleArray(vec![9.0, 8.0, 7.0, 6.0]),
"{rec}: a constant INP must deliver NOTHING at process (C read_aai/read_wf: \
`if (dbLinkIsConstant(pinp)) return 0;`)"
);
assert_eq!(
db.get_pv(&format!("{rec}.NORD")).unwrap().to_f64(),
Some(4.0)
);
}
}
#[epics_macros_rs::epics_test]
async fn unset_inp_does_not_wipe_client_data() {
let db = build().await;
for rec in ["UNSET:AAI", "UNSET:WF"] {
db.put_pv(rec, EpicsValue::DoubleArray(vec![1.5, 2.5]))
.await
.unwrap();
process(&db, rec).await;
assert_eq!(
db.get_pv(rec).unwrap(),
EpicsValue::DoubleArray(vec![1.5, 2.5]),
"{rec}: an unset INP is a constant link — process must not touch VAL"
);
}
}
#[epics_macros_rs::epics_test]
async fn real_db_inp_still_reads_every_cycle() {
let db = build().await;
db.put_pv("SRC", EpicsValue::DoubleArray(vec![10.0, 20.0]))
.await
.unwrap();
process(&db, "LINKED:WF").await;
assert_eq!(
db.get_pv("LINKED:WF").unwrap(),
EpicsValue::DoubleArray(vec![10.0, 20.0])
);
db.put_pv("LINKED:WF", EpicsValue::DoubleArray(vec![0.0]))
.await
.unwrap();
db.put_pv("SRC", EpicsValue::DoubleArray(vec![30.0, 40.0, 50.0]))
.await
.unwrap();
process(&db, "LINKED:WF").await;
assert_eq!(
db.get_pv("LINKED:WF").unwrap(),
EpicsValue::DoubleArray(vec![30.0, 40.0, 50.0])
);
}
#[test]
fn only_a_bracketed_literal_is_a_constant_array() {
use epics_base_rs::server::record::{ParsedLink, parse_link_v2};
assert!(
matches!(parse_link_v2("[1, 2, 3]"), ParsedLink::Constant(_)),
"a bracketed literal IS a constant array"
);
assert_eq!(
parse_link_v2("[1, 2, 3]").constant_value(),
Some(EpicsValue::DoubleArray(vec![1.0, 2.0, 3.0])),
"C `dbConstLoadArray` -> `dbPutConvertJSON`: three elements"
);
assert_eq!(
parse_link_v2("[]").constant_value(),
Some(EpicsValue::DoubleArray(vec![])),
"an empty literal is zero elements (nRequest = 0)"
);
assert_ne!(
parse_link_v2("1 2 3").constant_value(),
Some(EpicsValue::DoubleArray(vec![1.0, 2.0, 3.0])),
"a whitespace-separated list is NOT the constant array [1,2,3]"
);
}