use epics_base_rs::runtime::time::thread_sleep_quantum;
use epics_base_rs::server::record::Record;
use epics_base_rs::server::records::sseq::SseqRecord;
use epics_base_rs::types::EpicsValue;
fn dly(rec: &SseqRecord, n: &str) -> f64 {
rec.get_field(&format!("DLY{n}"))
.and_then(|v| v.to_f64())
.unwrap()
}
fn expect_quantized(seconds: f64) -> f64 {
let q = thread_sleep_quantum();
let ticks = seconds / q;
q * (ticks + 0.5).trunc()
}
#[epics_macros_rs::epics_test]
async fn r9_70_init_rounds_every_dly_to_a_clock_tick() {
let mut rec = SseqRecord::default();
rec.put_field("DLY1", EpicsValue::Double(0.017)).unwrap();
rec.put_field("DLY3", EpicsValue::Double(0.003)).unwrap();
rec.put_field("DLY7", EpicsValue::Double(1.234)).unwrap();
rec.put_field("DLYA", EpicsValue::Double(0.025)).unwrap();
rec.init_record(0).unwrap();
let q = thread_sleep_quantum();
assert!(q > 0.0, "test assumes a positive clock quantum, got {q}");
assert_eq!(
dly(&rec, "3"),
0.0,
"DLY3=0.003 is less than half a 0.01 s tick: C rounds it to 0, so the \
step fires with no delay at all"
);
assert_eq!(
dly(&rec, "1"),
expect_quantized(0.017),
"DLY1=0.017 rounds up to 2 ticks (0.02)"
);
assert_eq!(
dly(&rec, "7"),
expect_quantized(1.234),
"DLY7=1.234 rounds to 123 ticks (1.23)"
);
assert_eq!(
dly(&rec, "A"),
expect_quantized(0.025),
"DLYA=0.025 rounds to 3 ticks — C's NINT is round-half-away-from-zero"
);
for n in ["1", "3", "7", "A"] {
let ticks = dly(&rec, n) / q;
assert!(
(ticks - ticks.round()).abs() < 1e-9,
"DLY{n} must be a whole number of {q} s ticks, got {} ({ticks} ticks)",
dly(&rec, n)
);
}
}
#[epics_macros_rs::epics_test]
async fn r9_70_put_to_dly1_quantizes_dly1() {
let mut rec = SseqRecord::default();
rec.put_field("DLY1", EpicsValue::Double(0.037)).unwrap();
rec.special("DLY1", true).unwrap();
assert_eq!(
dly(&rec, "1"),
expect_quantized(0.037),
"a put to DLY1 rounds it to 4 ticks (0.04)"
);
}
#[epics_macros_rs::epics_test]
async fn r9_70_put_to_dlya_quantizes_dly1_and_leaves_dlya_raw() {
let mut rec = SseqRecord::default();
rec.put_field("DLY1", EpicsValue::Double(0.037)).unwrap();
rec.put_field("DLYA", EpicsValue::Double(0.083)).unwrap();
rec.special("DLYA", true).unwrap();
assert_eq!(
dly(&rec, "1"),
expect_quantized(0.037),
"C `special` never advances `plinkGroup` by `lnkIndex`, so the put to \
DLYA rounds DLY1 (0.037 → 0.04)"
);
assert_eq!(
dly(&rec, "A"),
0.083,
"the field actually written keeps its raw value — C rounds DLY1, not \
DLYA (sseqRecord.c:1150-1155)"
);
}
#[epics_macros_rs::epics_test]
async fn r9_70_default_dly_serves_positive_zero_not_negative_zero() {
let mut rec = SseqRecord::default();
rec.init_record(0).unwrap();
for n in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "A"] {
let v = dly(&rec, n);
assert_eq!(v, 0.0, "default DLY{n} is 0.0");
assert!(
v.is_sign_positive(),
"served DLY{n} must be +0.0 (renders \"0\"), got a negative zero \
(renders \"-0\")"
);
}
}
#[epics_macros_rs::epics_test]
async fn r9_70_huge_put_to_dly1_overflows_to_c_long_min() {
let q = thread_sleep_quantum();
let expect_overflow = q * (i64::MIN as f64);
let mut rec = SseqRecord::default();
rec.put_field("DLY1", EpicsValue::Double(1e300)).unwrap();
rec.special("DLY1", true).unwrap();
let v = dly(&rec, "1");
assert_eq!(
v, expect_overflow,
"a huge DLY1 must serve C's overflow value ~-9.22e16, not inf"
);
assert!(v.is_finite() && v < 0.0, "must be finite negative, got {v}");
let mut rec2 = SseqRecord::default();
rec2.put_field("DLY1", EpicsValue::Double(-0.0)).unwrap();
rec2.special("DLY1", true).unwrap();
let z = dly(&rec2, "1");
assert_eq!(z, 0.0);
assert!(
z.is_sign_positive(),
"a -0.0 put to DLY1 must serve +0.0, not -0.0"
);
}
#[epics_macros_rs::epics_test]
async fn r9_70_framework_put_path_matches_c() {
use epics_base_rs::server::database::PvDatabase;
let db = PvDatabase::new();
let mut rec = SseqRecord::default();
rec.put_field("DLY1", EpicsValue::Double(0.037)).unwrap();
db.add_record("SQ", Box::new(rec)).await.unwrap();
db.put_pv("SQ.DLYA", EpicsValue::Double(0.083))
.await
.unwrap();
let inst = db.get_record("SQ").unwrap();
let g = inst.read();
assert_eq!(
g.record.get_field("DLYA").and_then(|v| v.to_f64()),
Some(0.083),
"a CA put to DLYA leaves DLYA raw"
);
assert_eq!(
g.record.get_field("DLY1").and_then(|v| v.to_f64()),
Some(expect_quantized(0.037)),
"...and rounds DLY1 instead"
);
}