use amethystate::store::builder::{Backend, StoreBuilder};
use amethystate::{ReactiveMap, Store};
use amethystate_core::test_utils::TempPath;
use amethystate_test_macros::backends;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::mpsc;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;
fn open(backend: Backend, tag: &str) -> (TempPath, Store, ReactiveMap<String, u64>) {
let path = TempPath::new(tag);
let store = StoreBuilder::new(path.path())
.backend(backend)
.build()
.unwrap();
let widths = store.kv().map::<String, u64>("columns").unwrap();
(path, store, widths)
}
#[cfg(not(target_arch = "wasm32"))]
fn answered<T: Send + 'static>(work: impl FnOnce() -> T + Send + 'static) -> Option<T> {
let (report, outcome) = mpsc::channel();
std::thread::spawn(move || {
let _ = report.send(work());
});
outcome.recv_timeout(Duration::from_secs(30)).ok()
}
#[cfg(target_arch = "wasm32")]
fn answered<T>(work: impl FnOnce() -> T) -> Option<T> {
Some(work())
}
#[backends(all)]
fn a_write_during_a_walk_lands_and_the_walk_keeps_its_own_version(backend: Backend) {
let outcome = answered(move || {
let (_path, _store, widths) = open(backend, "walk_write_same_thread");
widths.insert("cpu".into(), &120).unwrap();
widths.insert("mem".into(), &80).unwrap();
let mut walked = Vec::new();
for (key, width) in widths.entries() {
widths.insert("disk".into(), &60).unwrap();
walked.push((key, width));
}
(walked, widths.keys().collect::<Vec<_>>())
});
match outcome {
Some((walked, after)) => {
assert_eq!(
walked,
vec![("cpu".to_string(), 120), ("mem".to_string(), 80)]
);
assert_eq!(after, ["cpu", "disk", "mem"]);
}
None => panic!("the write inside the walk never returned"),
}
}
#[cfg(not(target_arch = "wasm32"))]
#[backends(all)]
fn a_write_from_another_thread_does_not_wait_for_the_walk(backend: Backend) {
let (_path, _store, widths) = open(backend, "walk_write_other_thread");
widths.insert("cpu".into(), &120).unwrap();
widths.insert("mem".into(), &80).unwrap();
let walk = widths.entries();
let (report, outcome) = mpsc::channel();
let writer = widths.clone();
std::thread::spawn(move || {
let _ = report.send(writer.remove("cpu").is_ok());
});
match outcome.recv_timeout(Duration::from_secs(30)) {
Ok(landed) => assert!(landed, "the other thread's write failed"),
Err(_) => panic!("the other thread's write waited for a walk that holds no lock"),
}
assert_eq!(walk.count(), 2);
assert_eq!(widths.get("cpu"), None);
assert_eq!(widths.len(), 1);
}
#[backends(all)]
fn a_walk_that_removes_every_key_offers_each_one_once(backend: Backend) {
let (_path, _store, widths) = open(backend, "walk_removes_all");
widths.insert("cpu".into(), &120).unwrap();
widths.insert("disk".into(), &60).unwrap();
widths.insert("mem".into(), &80).unwrap();
let mut walked = Vec::new();
for (key, width) in widths.entries() {
widths.remove(&key).unwrap();
walked.push((key, width));
}
assert_eq!(
walked,
vec![
("cpu".to_string(), 120),
("disk".to_string(), 60),
("mem".to_string(), 80)
]
);
assert!(widths.is_empty());
}
#[backends(all)]
fn a_view_held_across_a_write_keeps_what_it_was_given(backend: Backend) {
let (_path, _store, widths) = open(backend, "walk_view_pinned");
widths.insert("cpu".into(), &120).unwrap();
widths.insert("mem".into(), &80).unwrap();
let held = widths.view();
widths.insert("disk".into(), &60).unwrap();
widths.remove("cpu").unwrap();
assert_eq!(held.len(), 2);
assert_eq!(
held.iter().map(|(key, _)| key.clone()).collect::<Vec<_>>(),
["cpu", "mem"]
);
assert_eq!(widths.keys().collect::<Vec<_>>(), ["disk", "mem"]);
}