use epics_base_rs::server::ioc_builder::IocBuilder;
use epics_base_rs::types::EpicsValue;
const DB: &str = r#"
record(subArray, "SA:CONST") {
field(FTVL, "DOUBLE")
field(INP, "[1, 2, 3, 4]")
field(MALM, "8")
field(NELM, "3")
field(INDX, "0")
}
record(subArray, "SA:EMPTY") {
field(FTVL, "DOUBLE")
field(MALM, "8")
field(NELM, "3")
field(INDX, "1")
}
"#;
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 field(
db: &epics_base_rs::server::database::PvDatabase,
rec: &str,
f: &str,
) -> Option<EpicsValue> {
db.get_pv(&format!("{rec}.{f}")).ok()
}
#[epics_macros_rs::epics_test]
async fn nelm_put_narrows_the_slice_it_does_not_empty_val() {
let db = build().await;
db.put_record_field_from_ca("SA:CONST", "NELM", EpicsValue::Long(2))
.await
.expect("subArray NELM is pp(TRUE), not SPC_NOMOD — the caput must land");
assert_eq!(
db.get_pv("SA:CONST").unwrap(),
EpicsValue::DoubleArray(vec![1.0, 2.0]),
"NELM=2 keeps the first two elements of the constant, it does not wipe VAL"
);
assert_eq!(
field(&db, "SA:CONST", "NORD").await,
Some(EpicsValue::Long(2))
);
assert_eq!(
field(&db, "SA:CONST", "NELM").await,
Some(EpicsValue::ULong(2))
);
}
#[epics_macros_rs::epics_test]
async fn nelm_put_above_malm_clamps_at_process() {
let db = build().await;
db.put_record_field_from_ca("SA:CONST", "NELM", EpicsValue::Long(9))
.await
.unwrap();
assert_eq!(
field(&db, "SA:CONST", "NELM").await,
Some(EpicsValue::ULong(8))
);
assert_eq!(
field(&db, "SA:CONST", "NORD").await,
Some(EpicsValue::Long(4))
);
assert_eq!(
db.get_pv("SA:CONST").unwrap(),
EpicsValue::DoubleArray(vec![1.0, 2.0, 3.0, 4.0])
);
}
#[epics_macros_rs::epics_test]
async fn indx_put_moves_the_window() {
let db = build().await;
db.put_record_field_from_ca("SA:CONST", "INDX", EpicsValue::Long(1))
.await
.expect("subArray INDX is pp(TRUE) — the caput must land");
assert_eq!(
db.get_pv("SA:CONST").unwrap(),
EpicsValue::DoubleArray(vec![2.0, 3.0, 4.0])
);
assert_eq!(
field(&db, "SA:CONST", "NORD").await,
Some(EpicsValue::Long(3))
);
}
#[epics_macros_rs::epics_test]
async fn indx_put_past_the_data_empties_the_slice_and_alarms() {
let db = build().await;
db.put_record_field_from_ca("SA:CONST", "INDX", EpicsValue::Long(5))
.await
.unwrap();
assert_eq!(
field(&db, "SA:CONST", "NORD").await,
Some(EpicsValue::Long(0))
);
assert!(
db.get_record("SA:CONST").unwrap().read().common.udf != 0,
"an empty subArray slice is UNDEFINED (C `prec->udf = !!status`)"
);
}
#[epics_macros_rs::epics_test]
async fn empty_inp_val_put_processes_and_nelm_put_re_subsets() {
let db = build().await;
db.put_record_field_from_ca(
"SA:EMPTY",
"VAL",
EpicsValue::DoubleArray(vec![10.0, 20.0, 30.0, 40.0, 50.0]),
)
.await
.unwrap();
assert_eq!(
db.get_pv("SA:EMPTY").unwrap(),
EpicsValue::DoubleArray(vec![20.0, 30.0, 40.0]),
"VAL is pp(TRUE): the put itself processes and slices"
);
db.put_record_field_from_ca("SA:EMPTY", "NELM", EpicsValue::Long(2))
.await
.unwrap();
assert_eq!(
db.get_pv("SA:EMPTY").unwrap(),
EpicsValue::DoubleArray(vec![30.0, 40.0])
);
assert_eq!(
field(&db, "SA:EMPTY", "NORD").await,
Some(EpicsValue::Long(2))
);
}