use super::acquisition_tests::{set_clock, set_time};
use super::multiple_tests::{av, clocked_database, member, multiple, records};
use super::*;
use bacnet_types::calendar::SpecificDate;
const MINUTE: i64 = 6_000;
const HOUR: i64 = 360_000;
const DAY: i64 = 24 * HOUR;
fn device_clock(local: i64) -> ClockFrame {
let mut day = SpecificDate::new(2024, 2, 29).unwrap();
for _ in 0..local.div_euclid(DAY) {
let (year, month, next) = (day.year(), day.month(), day.day() + 1);
day = SpecificDate::new(year, month, next)
.or_else(|| SpecificDate::new(year, month + 1, 1))
.unwrap_or_else(|| SpecificDate::new(year + 1, 1, 1).unwrap());
}
let tod = local.rem_euclid(DAY);
ClockFrame {
local_date: day.to_date(),
local_time: Time {
hour: (tod / HOUR) as u8,
minute: (tod / MINUTE % 60) as u8,
second: (tod / 100 % 60) as u8,
hundredths: (tod % 100) as u8,
},
..frame()
}
}
struct Rig {
db: ObjectDatabase,
oid: ObjectIdentifier,
time: Arc<Mutex<Duration>>,
clock: Arc<WallClock>,
monotonic: Duration,
local: i64,
}
impl Rig {
fn new(interval: u32, offset: u32, local: i64) -> Self {
let (mut db, time, clock) = clocked_database();
let mut log = multiple(
interval,
8,
vec![member(av(), P::PRESENT_VALUE, None, None)],
);
log.set_align_intervals(true);
log.set_interval_offset(offset);
let oid = log.object_identifier();
db.add(Box::new(log)).unwrap();
let mut rig = Self {
db,
oid,
time,
clock,
monotonic: Duration::ZERO,
local,
};
rig.set_clocks();
rig
}
fn set_clocks(&mut self) {
set_time(&self.time, self.monotonic);
set_clock(&self.clock, Some(device_clock(self.local)));
}
fn pass(&mut self, hundredths: i64) {
self.monotonic += Duration::from_millis(hundredths as u64 * 10);
self.local += hundredths;
self.set_clocks();
self.db.poll_trend_logs();
}
fn set_device_clock(&mut self, local: i64, elapsed: i64) {
self.monotonic += Duration::from_millis(elapsed as u64 * 10);
self.local = local;
self.set_clocks();
self.db.poll_trend_logs();
}
fn wait(&self) -> Duration {
self.db.trend_poll.0[&self.oid].remaining(self.monotonic)
}
fn wake(&mut self) {
let wait = self.wait();
self.pass((wait.as_millis() / 10) as i64);
}
fn taken(&self) -> Vec<i64> {
records(&self.db, self.oid)
.iter()
.map(|record| {
let day = SpecificDate::from_date(&record.date)
.unwrap()
.days_since_1970()
- SpecificDate::new(2024, 2, 29).unwrap().days_since_1970();
let t = record.time;
day * DAY
+ i64::from(t.hour) * HOUR
+ i64::from(t.minute) * MINUTE
+ i64::from(t.second) * 100
+ i64::from(t.hundredths)
})
.collect()
}
}
#[test]
fn aligned_polls_wait_for_each_boundary_and_never_take_one_twice() {
let mut rig = Rig::new(6_000, 0, 12 * HOUR + 37);
rig.pass(0);
assert!(rig.taken().is_empty());
assert_eq!(rig.wait(), Duration::from_millis(59_630));
rig.pass(5_962);
assert!(rig.taken().is_empty());
rig.wake();
assert_eq!(rig.taken(), [12 * HOUR + MINUTE]);
assert_eq!(rig.wait(), Duration::from_secs(60));
rig.set_device_clock(12 * HOUR + 2 * MINUTE - 1, 6_000);
assert_eq!(rig.taken().len(), 1);
assert_eq!(rig.wait(), Duration::from_millis(10));
rig.wake();
rig.pass(1);
assert_eq!(rig.taken(), [12 * HOUR + MINUTE, 12 * HOUR + 2 * MINUTE]);
rig.pass(MINUTE + 2);
assert_eq!(rig.taken()[2], 12 * HOUR + 3 * MINUTE + 3);
assert_eq!(rig.wait(), Duration::from_millis(59_970));
}
#[test]
fn interval_offset_shifts_each_boundary_modulo_the_interval() {
let mut rig = Rig::new(6_000, 6_150, 12 * HOUR + 37);
rig.pass(0);
assert_eq!(rig.wait(), Duration::from_millis(1_130));
rig.wake();
assert_eq!(rig.taken(), [12 * HOUR + 150]);
let mut rig = Rig::new(6_000, 0, 12 * HOUR + 37);
set_clock(&rig.clock, None);
rig.db.poll_trend_logs();
assert_eq!(rig.wait(), RECONCILE);
rig.pass(10);
assert_eq!(rig.wait(), Duration::from_millis(59_530));
}
#[test]
fn alignment_needs_an_interval_that_divides_a_day() {
let mut rig = Rig::new(7_000, 50, 12 * HOUR + 37);
rig.pass(0);
assert_eq!(rig.taken().len(), 1);
let (mut db, _, _) = clocked_database();
let mut log = multiple(6_000, 8, vec![member(av(), P::PRESENT_VALUE, None, None)]);
log.set_interval_offset(50);
let oid = log.object_identifier();
db.add(Box::new(log)).unwrap();
db.poll_trend_logs();
assert_eq!(records(&db, oid).len(), 1);
let mut rig = Rig::new(720_000, 0, 12 * HOUR + 37);
rig.pass(0);
assert_eq!(rig.wait(), Duration::from_millis(7_199_630));
}
#[test]
fn a_clock_set_forward_moves_the_plan_without_an_off_boundary_record() {
let mut rig = Rig::new(360_000, 0, 13 * HOUR - 10 * MINUTE);
rig.pass(0);
rig.wake();
assert_eq!(rig.taken(), [13 * HOUR]);
rig.pass(5 * MINUTE);
rig.set_device_clock(13 * HOUR + 45 * MINUTE, 0);
assert_eq!(rig.wait(), Duration::from_secs(15 * 60));
rig.wake();
rig.wake();
assert_eq!(rig.taken(), [13 * HOUR, 14 * HOUR, 15 * HOUR]);
rig.pass(50 * MINUTE);
rig.set_device_clock(16 * HOUR + 30 * MINUTE, 0);
rig.wake();
assert_eq!(rig.taken()[3..], [17 * HOUR]);
}
#[test]
fn a_clock_set_back_logs_each_boundary_as_the_clock_reaches_it() {
let mut rig = Rig::new(720_000, 0, -10 * MINUTE);
rig.pass(0);
rig.wake();
assert_eq!(rig.taken(), [0]);
rig.pass(2 * HOUR - MINUTE);
rig.set_device_clock(HOUR, MINUTE);
assert_eq!(rig.taken(), [0]);
assert_eq!(rig.wait(), Duration::from_secs(3_600));
rig.wake();
assert_eq!(rig.taken(), [0, 2 * HOUR]);
rig.pass(10 * MINUTE);
rig.set_device_clock(HOUR + 50 * MINUTE, 0);
rig.wake();
assert_eq!(rig.taken(), [0, 2 * HOUR, 2 * HOUR]);
}
#[test]
fn boundaries_run_on_across_midnight() {
let mut rig = Rig::new(6_000, 0, 24 * HOUR - 90 * 100);
rig.pass(0);
for _ in 0..3 {
rig.wake();
}
assert_eq!(
rig.taken(),
[24 * HOUR - MINUTE, 24 * HOUR, 24 * HOUR + MINUTE]
);
let dates: Vec<_> = records(&rig.db, rig.oid).iter().map(|r| r.date).collect();
assert_eq!(dates[0], frame().local_date);
assert_eq!(
dates[1..],
[SpecificDate::new(2024, 3, 1).unwrap().to_date(); 2]
);
}
#[test]
fn a_log_interval_change_mid_wait_plans_from_the_new_interval() {
let mut rig = Rig::new(360_000, 0, 13 * HOUR - 10 * MINUTE);
rig.pass(0);
rig.wake();
assert_eq!(rig.taken(), [13 * HOUR]);
rig.pass(10 * MINUTE);
write(
&mut rig.db,
rig.oid,
P::LOG_INTERVAL,
PropertyValue::Unsigned(90_000),
);
rig.pass(0);
assert_eq!(rig.wait(), Duration::from_secs(5 * 60));
rig.wake();
rig.wake();
assert_eq!(
rig.taken(),
[13 * HOUR, 13 * HOUR + 15 * MINUTE, 13 * HOUR + 30 * MINUTE]
);
}