monorepo/emseries/tests/test_io.rs

472 lines
17 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 {
2023-12-26 20:37:53 +00:00
use chrono::{format::Fixed, prelude::*};
use chrono_tz::Etc::UTC;
2023-10-05 15:19:55 +00:00
use dimensioned::si::{Kilogram, Meter, Second, 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 {
2023-12-26 20:37:53 +00:00
datetime: DateTime<FixedOffset>,
distance: Distance,
duration: Duration,
comments: String,
}
impl Recordable for BikeTrip {
fn timestamp(&self) -> Timestamp {
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(self.datetime.clone())
}
fn tags(&self) -> Vec<String> {
Vec::new()
}
}
fn mk_trips() -> [BikeTrip; 5] {
[
BikeTrip {
2023-12-26 20:37:53 +00:00
datetime: UTC
.with_ymd_and_hms(2011, 10, 29, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
distance: Distance(58741.055 * M),
duration: Duration(11040.0 * S),
comments: String::from("long time ago"),
},
BikeTrip {
2023-12-26 20:37:53 +00:00
datetime: UTC
.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
distance: Distance(17702.0 * M),
duration: Duration(2880.0 * S),
comments: String::from("day 2"),
},
BikeTrip {
2023-12-26 20:37:53 +00:00
datetime: UTC
.with_ymd_and_hms(2011, 11, 02, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
distance: Distance(41842.945 * M),
duration: Duration(7020.0 * S),
comments: String::from("Do Some Distance!"),
},
BikeTrip {
2023-12-26 20:37:53 +00:00
datetime: UTC
.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
distance: Distance(34600.895 * M),
duration: Duration(5580.0 * S),
comments: String::from("I did a lot of distance back then"),
},
BikeTrip {
2023-12-26 20:37:53 +00:00
datetime: UTC
.with_ymd_and_hms(2011, 11, 05, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
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).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(),
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 10, 29, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
)
);
assert_eq!(tr.data.duration, Duration(11040.0 * S));
assert_eq!(tr.data.comments, String::from("long time ago"));
assert_eq!(tr.data, 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).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<&Record<BikeTrip>> = ts
2023-12-26 20:37:53 +00:00
.search(exact_time(Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
)))
.collect();
assert_eq!(v.len(), 1);
assert_eq!(v[0].data, 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).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<&Record<BikeTrip>> = ts.search_sorted(
time_range(
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
),
|l, r| l.timestamp().cmp(&r.timestamp()),
);
assert_eq!(v.len(), 3);
assert_eq!(v[0].data, trips[1]);
assert_eq!(v[1].data, trips[2]);
assert_eq!(v[2].data, 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).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).expect("expect the time series to open correctly");
let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
time_range(
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
),
|l, r| l.timestamp().cmp(&r.timestamp()),
);
assert_eq!(v.len(), 3);
assert_eq!(v[0].data, trips[1]);
assert_eq!(v[1].data, trips[2]);
assert_eq!(v[2].data, 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).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).expect("expect the time series to open correctly");
let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
time_range(
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
)
.into(),
true,
),
|l, r| l.timestamp().cmp(&r.timestamp()),
);
assert_eq!(v.len(), 2);
assert_eq!(v[0].data, trips[1]);
assert_eq!(v[1].data, 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).expect("expect the time series to open correctly");
let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
time_range(
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
2023-12-26 20:37:53 +00:00
Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 11, 05, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
),
true,
),
|l, r| l.timestamp().cmp(&r.timestamp()),
);
assert_eq!(v.len(), 4);
assert_eq!(v[0].data, trips[1]);
assert_eq!(v[1].data, trips[2]);
assert_eq!(v[2].data, trips[3]);
assert_eq!(v[3].data, trips[4]);
}
})
}
#[test]
pub fn can_overwrite_existing_entry() {
run_test(|path| {
let trips = mk_trips();
let mut ts: Series<BikeTrip> =
Series::open(&path).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.data.distance = Distance(50000.0 * M);
ts.update(trip).expect("expect record to update");
}
};
match ts.get(&trip_id) {
None => assert!(false, "record not found"),
Some(trip) => {
assert_eq!(
trip.data.datetime,
2023-12-26 20:37:53 +00:00
UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()
);
assert_eq!(trip.data.distance, Distance(50000.0 * M));
assert_eq!(trip.data.duration, Duration(7020.0 * S));
assert_eq!(trip.data.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).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.data.distance = Distance(50000.0 * M);
ts.update(trip).expect("expect record to update");
}
};
}
{
let ts: Series<BikeTrip> =
Series::open(&path).expect("expect the time series to open correctly");
let trips: Vec<&Record<BikeTrip>> = ts.records().collect();
assert_eq!(trips.len(), 3);
let trips: Vec<&Record<BikeTrip>> = ts
2023-12-26 20:37:53 +00:00
.search(exact_time(Timestamp::DateTime(
UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap()),
)))
.collect();
assert_eq!(trips.len(), 1);
assert_eq!(
trips[0].data.datetime,
2023-12-26 20:37:53 +00:00
UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0)
.unwrap()
.with_timezone(&FixedOffset::east_opt(0).unwrap())
);
assert_eq!(trips[0].data.distance, Distance(50000.0 * M));
assert_eq!(trips[0].data.duration, Duration(7020.0 * S));
assert_eq!(trips[0].data.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).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<&Record<BikeTrip>> = ts.records().collect();
assert_eq!(recs.len(), 2);
}
{
let ts: Series<BikeTrip> =
Series::open(&path).expect("expect the time series to open correctly");
let recs: Vec<&Record<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: chrono::NaiveDate,
pub weight: Weight,
}
impl Recordable for WeightRecord {
fn timestamp(&self) -> Timestamp {
2023-12-26 20:37:53 +00:00
Timestamp::Date(self.date)
}
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)),
}
}
*/
}