use super::*;
use crate::server::cov_notifications_tests::recording_transport;
use crate::server::test_transport::TestTransport;
use bacnet_objects::device::{DeviceConfig, DeviceObject};
use bacnet_objects::log_persistence::{LogCommitCadence, LogPersistence, LogSnapshot};
use bacnet_objects::traits::BACnetObject;
use bacnet_objects::trend::TrendLogObject;
use bacnet_types::constructed::{BACnetLogRecord, LogDatum};
use bacnet_types::primitives::{Date, Time};
use std::sync::{Condvar, Mutex as StdMutex};
#[derive(Default)]
struct Commits {
totals: StdMutex<Vec<u32>>,
landed: Condvar,
}
impl Commits {
fn totals(&self) -> Vec<u32> {
self.totals.lock().unwrap().clone()
}
fn wait_for(&self, count: usize) {
let totals = self.totals.lock().unwrap();
let (totals, timeout) = self
.landed
.wait_timeout_while(totals, Duration::from_secs(10), |totals| {
totals.len() < count
})
.unwrap();
assert!(
!timeout.timed_out(),
"{count} commits expected, got {totals:?}"
);
}
}
impl LogPersistence<BACnetLogRecord> for Commits {
fn load(
&self,
_expected: ObjectIdentifier,
) -> Result<Option<LogSnapshot<BACnetLogRecord>>, Error> {
Ok(None)
}
fn commit(&self, snapshot: &LogSnapshot<BACnetLogRecord>) -> Result<(), Error> {
self.totals
.lock()
.unwrap()
.push(snapshot.total_record_count);
self.landed.notify_all();
Ok(())
}
}
fn record(hour: u8) -> BACnetLogRecord {
BACnetLogRecord {
date: Date {
year: 126,
month: 10,
day: 20,
day_of_week: 2,
},
time: Time {
hour,
minute: 0,
second: 0,
hundredths: 0,
},
log_datum: LogDatum::RealValue(f32::from(hour)),
status_flags: None,
}
}
async fn settle() {
for _ in 0..16 {
tokio::task::yield_now().await;
}
}
#[tokio::test(start_paused = true)]
async fn the_server_commits_a_durable_log_on_its_interval_and_at_stop() {
let storage = Arc::new(Commits::default());
let mut log = TrendLogObject::with_persistence(1, "TL-1", 50, storage.clone()).unwrap();
log.set_commit_cadence(LogCommitCadence {
records: 1000,
interval: Duration::from_secs(60),
});
log.add_record(record(1)).unwrap();
let oid = log.object_identifier();
let identity = bacnet_objects::DeviceIdentity::new(100, 0).unwrap();
let device = DeviceObject::new(DeviceConfig {
identity: identity.clone().with_name("Durable-log-device"),
..DeviceConfig::default()
})
.unwrap();
let mut db = ObjectDatabase::new();
db.add(Box::new(device)).unwrap();
db.add(Box::new(log)).unwrap();
let (transport, _sent) = recording_transport();
let config = ServerConfig {
device: identity,
enable_event_enrollment: false,
..ServerConfig::default()
};
let mut server: BACnetServer<TestTransport> =
BACnetServer::start(config, db, transport).await.unwrap();
settle().await;
tokio::time::advance(Duration::from_secs(30)).await;
settle().await;
assert!(storage.totals().is_empty());
tokio::time::advance(Duration::from_secs(31)).await;
settle().await;
let waiting = storage.clone();
tokio::task::spawn_blocking(move || waiting.wait_for(1))
.await
.unwrap();
assert_eq!(storage.totals(), vec![1]);
server
.database()
.write()
.await
.get_mut(&oid)
.unwrap()
.add_trend_record(record(2))
.unwrap();
server.stop().await.unwrap();
assert_eq!(storage.totals(), vec![1, 2]);
}