use std::collections::HashMap;
use std::time::Duration;
use chrono::{Local, Timelike};
use epics_base_rs::server::record::EventMask;
use epics_base_rs::types::{DbFieldType, EpicsValue};
use epics_ca_rs::server::CaServerBuilder;
#[tokio::test]
async fn timestamp_rval_is_not_posted_when_the_val_string_is_unchanged() {
let secs_left = 60 - Local::now().second();
if secs_left < 5 {
tokio::time::sleep(Duration::from_secs(u64::from(secs_left) + 1)).await;
}
let db_str = r#"
record(timestamp, "TS:MIN") {
field(TST, "5")
}
"#;
let server = CaServerBuilder::new()
.register_record_type("timestamp", || Box::new(std_rs::TimestampRecord::default()))
.db_string(db_str, &HashMap::new())
.unwrap()
.build()
.await
.unwrap();
let db = server.database().clone();
let (mut rval_rx, mut val_rx) = {
let rec = db.get_record("TS:MIN").unwrap();
let mut inst = rec.write();
let rval = inst
.add_subscriber(
"RVAL",
1,
DbFieldType::ULong,
(EventMask::VALUE | EventMask::LOG | EventMask::ALARM).bits(),
)
.expect("RVAL subscription accepted");
let val = inst
.add_subscriber(
"VAL",
2,
DbFieldType::String,
(EventMask::VALUE | EventMask::LOG | EventMask::ALARM).bits(),
)
.expect("VAL subscription accepted");
(rval, val)
};
proc(&db, "TS:MIN").await;
assert!(
val_rx.try_recv().is_ok(),
"first render changes VAL, so C's strncmp guard posts VAL"
);
let first_rval = rval_rx
.try_recv()
.expect("first render posts RVAL from inside the guard");
let first_secs = ulong_of(&first_rval.snapshot.value);
let first_val = server.get("TS:MIN.VAL").await.unwrap();
tokio::time::sleep(Duration::from_millis(1_050)).await;
proc(&db, "TS:MIN").await;
assert_eq!(
server.get("TS:MIN.VAL").await.unwrap(),
first_val,
"TST=5 renders HH:MM — the second cycle must land in the same minute \
(test setup bug if not)"
);
let now_secs = ulong_of(&server.get("TS:MIN.RVAL").await.unwrap());
assert!(
now_secs > first_secs,
"RVAL is refreshed on every process (C timestampRecord.c:94), so the \
seconds count moved: {first_secs} -> {now_secs}"
);
assert!(
rval_rx.try_recv().is_err(),
"RVAL posts only from inside the VAL-string guard \
(timestampRecord.c:160) — an unchanged HH:MM string posts nothing, \
even though RVAL itself changed"
);
assert!(
val_rx.try_recv().is_err(),
"VAL is unchanged, so C posts no VAL event either"
);
}
#[tokio::test]
async fn timestamp_rval_posts_with_val_when_the_string_changes() {
let db_str = r#"
record(timestamp, "TS:SEC") {
field(TST, "4")
}
"#;
let server = CaServerBuilder::new()
.register_record_type("timestamp", || Box::new(std_rs::TimestampRecord::default()))
.db_string(db_str, &HashMap::new())
.unwrap()
.build()
.await
.unwrap();
let db = server.database().clone();
let (mut rval_rx, mut val_rx) = {
let rec = db.get_record("TS:SEC").unwrap();
let mut inst = rec.write();
let rval = inst
.add_subscriber(
"RVAL",
1,
DbFieldType::ULong,
(EventMask::VALUE | EventMask::LOG | EventMask::ALARM).bits(),
)
.expect("RVAL subscription accepted");
let val = inst
.add_subscriber(
"VAL",
2,
DbFieldType::String,
(EventMask::VALUE | EventMask::LOG | EventMask::ALARM).bits(),
)
.expect("VAL subscription accepted");
(rval, val)
};
proc(&db, "TS:SEC").await;
let first = rval_rx.try_recv().expect("first render posts RVAL");
assert!(val_rx.try_recv().is_ok(), "first render posts VAL");
tokio::time::sleep(Duration::from_millis(1_050)).await;
proc(&db, "TS:SEC").await;
let second = rval_rx
.try_recv()
.expect("the HH:MM:SS string moved, so the guard fires and RVAL posts");
assert!(
val_rx.try_recv().is_ok(),
"the changed string posts VAL alongside RVAL"
);
assert!(
ulong_of(&second.snapshot.value) > ulong_of(&first.snapshot.value),
"the posted RVAL carries the fresh seconds count"
);
assert_eq!(
second.mask,
EventMask::VALUE | EventMask::LOG,
"RVAL carries VAL's monitor_mask, not a mask of its own"
);
}
async fn proc(db: &epics_base_rs::server::database::PvDatabase, pv: &str) {
db.put_record_field_from_ca(pv, "PROC", EpicsValue::Short(1))
.await
.unwrap();
}
fn ulong_of(v: &EpicsValue) -> u32 {
match v {
EpicsValue::ULong(n) => *n,
other => panic!("RVAL is DBF_ULONG, got {other:?}"),
}
}