monorepo/emseries/tests/test_io.rs

414 lines
15 KiB
Rust
Raw Permalink Normal View History

/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of the Luminescent Dreams Tools.
Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/
#[macro_use]
extern crate serde_derive;
extern crate chrono;
extern crate chrono_tz;
extern crate dimensioned;
extern crate emseries;
#[cfg(test)]
mod test {
use chrono::prelude::*;
use chrono_tz::Etc::UTC;
use dimensioned::si::{Kilogram, Meter, Second, KG, M, S};
use emseries::*;
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
struct Distance(Meter<f64>);
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
struct Duration(Second<f64>);
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
struct BikeTrip {
datetime: DateTimeTz,
distance: Distance,
duration: Duration,
comments: String,
}
impl Recordable for BikeTrip {
fn timestamp(&self) -> DateTimeTz {
self.datetime.clone()
}
fn tags(&self) -> Vec<String> {
Vec::new()
}
}
fn mk_trips() -> [BikeTrip; 5] {
[
BikeTrip {
datetime: DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0)),
distance: Distance(58741.055 * M),
duration: Duration(11040.0 * S),
comments: String::from("long time ago"),
},
BikeTrip {
datetime: DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
distance: Distance(17702.0 * M),
duration: Duration(2880.0 * S),
comments: String::from("day 2"),
},
BikeTrip {
datetime: DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0)),
distance: Distance(41842.945 * M),
duration: Duration(7020.0 * S),
comments: String::from("Do Some Distance!"),
},
BikeTrip {
datetime: DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)),
distance: Distance(34600.895 * M),
duration: Duration(5580.0 * S),
comments: String::from("I did a lot of distance back then"),
},
BikeTrip {
datetime: DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)),
distance: Distance(6437.376 * M),
duration: Duration(960.0 * S),
comments: String::from("day 5"),
},
]
}
fn run_test<T>(test: T) -> ()
where
T: FnOnce(tempfile::TempPath),
{
let tmp_file = tempfile::NamedTempFile::new().expect("temporary path created");
let tmp_path = tmp_file.into_temp_path();
test(tmp_path);
}
fn run<T>(test: T) -> ()
where
T: FnOnce(Series<BikeTrip>),
{
let tmp_file = tempfile::NamedTempFile::new().expect("temporary path created");
let tmp_path = tmp_file.into_temp_path();
let ts: Series<BikeTrip> = Series::open(&tmp_path.to_string_lossy())
.expect("the time series should open correctly");
test(ts);
}
#[test]
pub fn can_add_and_retrieve_entries() {
run(|mut series| {
let trips = mk_trips();
let uuid = series
.put(trips[0].clone())
.expect("expect a successful put");
let record_res = series.get(&uuid);
for trip in &trips[1..=4] {
series.put(trip.clone()).expect("expect a successful put");
}
match record_res {
None => assert!(false, "There should have been a value here"),
Some(tr) => {
assert_eq!(
tr.timestamp(),
DateTimeTz(UTC.ymd(2011, 10, 29).and_hms(0, 0, 0))
);
assert_eq!(tr.duration, Duration(11040.0 * S));
assert_eq!(tr.comments, String::from("long time ago"));
assert_eq!(tr, trips[0]);
}
}
})
}
#[test]
pub fn can_search_for_an_entry_with_exact_time() {
run_test(|path| {
let trips = mk_trips();
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
for trip in &trips[0..=4] {
ts.put(trip.clone()).expect("expect a successful put");
}
let v: Vec<(&UniqueId, &BikeTrip)> = ts
.search(exact_time(DateTimeTz(
UTC.ymd(2011, 10, 31).and_hms(0, 0, 0),
)))
.collect();
assert_eq!(v.len(), 1);
assert_eq!(*v[0].1, trips[1]);
})
}
#[test]
pub fn can_get_entries_in_time_range() {
run_test(|path| {
let trips = mk_trips();
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
for trip in &trips[0..=4] {
ts.put(trip.clone()).expect("expect a successful put");
}
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
time_range(
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
true,
DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)),
true,
),
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
);
assert_eq!(v.len(), 3);
assert_eq!(*v[0].1, trips[1]);
assert_eq!(*v[1].1, trips[2]);
assert_eq!(*v[2].1, trips[3]);
})
}
#[test]
pub fn persists_and_reads_an_entry() {
run_test(|path| {
let trips = mk_trips();
{
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
for trip in &trips[0..=4] {
ts.put(trip.clone()).expect("expect a successful put");
}
}
{
let ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
time_range(
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
true,
DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)),
true,
),
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
);
assert_eq!(v.len(), 3);
assert_eq!(*v[0].1, trips[1]);
assert_eq!(*v[1].1, trips[2]);
assert_eq!(*v[2].1, trips[3]);
}
})
}
#[test]
pub fn can_write_to_existing_file() {
run_test(|path| {
let trips = mk_trips();
{
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
for trip in &trips[0..=2] {
ts.put(trip.clone()).expect("expect a successful put");
}
}
{
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
time_range(
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
true,
DateTimeTz(UTC.ymd(2011, 11, 04).and_hms(0, 0, 0)),
true,
),
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
);
assert_eq!(v.len(), 2);
assert_eq!(*v[0].1, trips[1]);
assert_eq!(*v[1].1, trips[2]);
ts.put(trips[3].clone()).expect("expect a successful put");
ts.put(trips[4].clone()).expect("expect a successful put");
}
{
let ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
let v: Vec<(&UniqueId, &BikeTrip)> = ts.search_sorted(
time_range(
DateTimeTz(UTC.ymd(2011, 10, 31).and_hms(0, 0, 0)),
true,
DateTimeTz(UTC.ymd(2011, 11, 05).and_hms(0, 0, 0)),
true,
),
|l, r| l.1.timestamp().cmp(&r.1.timestamp()),
);
assert_eq!(v.len(), 4);
assert_eq!(*v[0].1, trips[1]);
assert_eq!(*v[1].1, trips[2]);
assert_eq!(*v[2].1, trips[3]);
assert_eq!(*v[3].1, trips[4]);
}
})
}
#[test]
pub fn can_overwrite_existing_entry() {
run_test(|path| {
let trips = mk_trips();
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
ts.put(trips[0].clone()).expect("expect a successful put");
ts.put(trips[1].clone()).expect("expect a successful put");
let trip_id = ts.put(trips[2].clone()).expect("expect a successful put");
match ts.get(&trip_id) {
None => assert!(false, "record not found"),
Some(mut trip) => {
trip.distance = Distance(50000.0 * M);
ts.update(trip_id.clone(), trip)
.expect("expect record to update");
}
};
match ts.get(&trip_id) {
None => assert!(false, "record not found"),
Some(trip) => {
assert_eq!(
trip.datetime,
DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0))
);
assert_eq!(trip.distance, Distance(50000.0 * M));
assert_eq!(trip.duration, Duration(7020.0 * S));
assert_eq!(trip.comments, String::from("Do Some Distance!"));
}
}
})
}
#[test]
pub fn record_overwrites_get_persisted() {
run_test(|path| {
let trips = mk_trips();
{
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
ts.put(trips[0].clone()).expect("expect a successful put");
ts.put(trips[1].clone()).expect("expect a successful put");
let trip_id = ts.put(trips[2].clone()).expect("expect a successful put");
match ts.get(&trip_id) {
None => assert!(false, "record not found"),
Some(mut trip) => {
trip.distance = Distance(50000.0 * M);
ts.update(trip_id, trip).expect("expect record to update");
}
};
}
{
let ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
let trips: Vec<(&UniqueId, &BikeTrip)> = ts.records().collect();
assert_eq!(trips.len(), 3);
let trips: Vec<(&UniqueId, &BikeTrip)> = ts
.search(exact_time(DateTimeTz(
UTC.ymd(2011, 11, 02).and_hms(0, 0, 0),
)))
.collect();
assert_eq!(trips.len(), 1);
assert_eq!(
trips[0].1.datetime,
DateTimeTz(UTC.ymd(2011, 11, 02).and_hms(0, 0, 0))
);
assert_eq!(trips[0].1.distance, Distance(50000.0 * M));
assert_eq!(trips[0].1.duration, Duration(7020.0 * S));
assert_eq!(trips[0].1.comments, String::from("Do Some Distance!"));
}
})
}
#[test]
pub fn can_delete_an_entry() {
run_test(|path| {
let trips = mk_trips();
{
let mut ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
let trip_id = ts.put(trips[0].clone()).expect("expect a successful put");
ts.put(trips[1].clone()).expect("expect a successful put");
ts.put(trips[2].clone()).expect("expect a successful put");
ts.delete(&trip_id).expect("successful delete");
let recs: Vec<(&UniqueId, &BikeTrip)> = ts.records().collect();
assert_eq!(recs.len(), 2);
}
{
let ts: Series<BikeTrip> = Series::open(&path.to_string_lossy())
.expect("expect the time series to open correctly");
let recs: Vec<(&UniqueId, &BikeTrip)> = ts.records().collect();
assert_eq!(recs.len(), 2);
}
})
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Weight(Kilogram<f64>);
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct WeightRecord {
pub date: DateTimeTz,
pub weight: Weight,
}
impl Recordable for WeightRecord {
fn timestamp(&self) -> DateTimeTz {
self.date.clone()
}
fn tags(&self) -> Vec<String> {
Vec::new()
}
}
#[test]
pub fn legacy_file_load() {
let ts: Series<WeightRecord> =
Series::open("fixtures/weight.json").expect("legacy series should open correctly");
let uid = "3330c5b0-783f-4919-b2c4-8169c38f65ff"
.parse()
.expect("something is wrong with this ID");
let rec = ts.get(&uid);
match rec {
None => assert!(false, "no record found"),
Some(rec) => assert_eq!(rec.weight, Weight(77.79109 * KG)),
}
}
}