zng/tests/config.rs

502 lines
15 KiB
Rust

use std::path::{Path, PathBuf};
use zng::{
app::AppId,
config::*,
keyboard::{Key, KeyState},
layout::*,
mouse::{ButtonState, CursorIcon, MouseButton, MouseScrollDelta},
prelude::*,
render::FrameId,
touch::{TouchForce, TouchPhase},
widget::{BorderSides, BorderStyle, LineStyle},
window::WindowState,
};
#[test]
fn json() {
test_config("test.config.json", |p| JsonConfig::sync(p));
}
#[test]
fn toml() {
test_config("test.config.toml", |p| TomlConfig::sync(p));
}
#[test]
fn ron() {
test_config("test.config.ron", |p| RonConfig::sync(p));
}
#[test]
fn yaml() {
test_config("test.config.yml", |p| YamlConfig::sync(p));
}
fn test_config<C: AnyConfig>(file: &str, source: impl Fn(&Path) -> C) {
let file = PathBuf::from("../target/tmp").join(file);
fn run<C: AnyConfig>(source: impl Fn() -> C, test_read: bool) {
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(source());
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
TEST_READ.set(test_read);
test_all();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
app.exit();
}
rmv_file_assert(&file);
run(|| source(&file), false);
assert!(file.exists());
assert_ne!(std::fs::metadata(&file).unwrap().len(), 0);
run(|| source(&file), true);
assert!(file.exists());
assert_ne!(std::fs::metadata(&file).unwrap().len(), 0);
}
zng::app::app_local! {
static TEST_READ: bool = false;
}
macro_rules! test_config {
($key:expr => $($init:tt)*) => {
if TEST_READ.get() {
let key = Txt::from_static($key);
assert!(CONFIG.contains_key(key.clone()).get(), "did not find {key}");
let read_value = CONFIG.get(key, || $($init)*).get();
let expected_value = { $($init)* };
let read_value = format!("{read_value:#?}");
let expected_value = format!("{expected_value:#?}");
pretty_assertions::assert_eq!(expected_value, read_value, "test read {}", stringify!($key));
} else {
CONFIG
.get($key, || $($init)*)
.update()
.unwrap();
}
};
($type:ident $($init:tt)*) => {
test_config!(stringify!($type) => $type $($init)*)
};
}
fn test_all() {
test_view_api_units();
test_view_api_types();
test_core_app();
test_core_units();
test_core_border();
}
fn test_view_api_units() {
test_config!(Px(32));
test_config!(Dip::new(32));
test_config!(PxPoint::new(Px(100), Px(200)));
test_config!(DipPoint::new(Dip::new(100), Dip::new(200)));
test_config!(PxVector::new(Px(100), Px(200)));
test_config!(DipVector::new(Dip::new(100), Dip::new(200)));
test_config!(PxSize::new(Px(100), Px(200)));
test_config!(DipSize::new(Dip::new(100), Dip::new(200)));
test_config!(PxRect::new(PxPoint::new(Px(100), Px(200)), PxSize::new(Px(1000), Px(2000))));
test_config!(DipRect::new(
DipPoint::new(Dip::new(100), Dip::new(200)),
DipSize::new(Dip::new(100), Dip::new(200)),
));
test_config!(PxBox::new(PxPoint::new(Px(100), Px(200)), PxPoint::new(Px(1000), Px(2000))));
test_config!(DipBox::new(
DipPoint::new(Dip::new(100), Dip::new(200)),
DipPoint::new(Dip::new(100), Dip::new(200)),
));
test_config!(PxSideOffsets::new(Px(1), Px(2), Px(3), Px(4)));
test_config!(DipSideOffsets::new(Dip::new(1), Dip::new(2), Dip::new(3), Dip::new(4)));
test_config!(PxCornerRadius::new(
PxSize::splat(Px(1)),
PxSize::splat(Px(2)),
PxSize::splat(Px(3)),
PxSize::splat(Px(4))
));
test_config!(DipCornerRadius::new(
DipSize::splat(Dip::new(1)),
DipSize::splat(Dip::new(2)),
DipSize::splat(Dip::new(3)),
DipSize::splat(Dip::new(4))
));
test_config!("PxTransform::identity" => PxTransform::identity());
test_config!("PxTransform::translation" => PxTransform::translation(10.0, 20.0));
test_config!("PxTransform::rotation" => PxTransform::rotation(1.0, 2.0, layout::AngleRadian::from(40.deg()).into()));
}
fn test_view_api_types() {
test_config!(FrameId::first().next().next_update().next());
test_config!(KeyState::Pressed);
test_config!(ButtonState::Pressed);
test_config!(MouseButton::Left);
test_config!("MouseButton::Other" => MouseButton::Other(564));
test_config!(TouchPhase::Start);
test_config!(MouseScrollDelta::LineDelta(32.0, 34.0));
test_config!(TouchForce::Calibrated {
force: 5.0,
max_possible_force: 10.0,
altitude_angle: None
});
test_config!(Key::Char('G'));
test_config!(CursorIcon::Alias);
test_config!(WindowState::Normal);
test_config!(zng_wgt_webrender_debug::RendererDebug {
flags: zng_wgt_webrender_debug::DebugFlags::DISABLE_ALPHA_PASS | zng_wgt_webrender_debug::DebugFlags::DISABLE_BATCHING,
profiler_ui: "default".to_owned()
});
}
fn test_core_app() {
let id = AppId::named("app-name");
test_config!("AppId" => id);
}
fn test_core_units() {
test_config!("Factor" => 1.fct());
test_config!("FactorPercent" => 100.pct());
test_config!(Align::TOP_LEFT);
test_config!("AlignCustom" => Align { x: 0.1.fct(), x_rtl_aware: true, y: 0.4.fct() });
test_config!("AngleRadian" => 1.rad());
test_config!("AngleGradian" => 50.grad());
test_config!("AngleDegree" => 50.deg());
test_config!("AngleTurn" => 2.turn());
test_config!("ByteLength" => 1.megabytes());
test_config!(PxConstraints::new_exact(Px(40)));
test_config!("PxConstraints::unbounded" => PxConstraints::new_unbounded());
test_config!(PxConstraints2d::new_bounded_size(PxSize::splat(Px(50))));
test_config!("Length::Default" => Length::Default);
test_config!("Length::Px" => Length::Px(Px(300)));
test_config!("Length::Expr" => Length::Default * 20.pct());
test_config!(GridSpacing::new(4.dip(), 5.dip()));
test_config!("Ppi" => 96.ppi());
}
fn test_core_border() {
test_config!(LineStyle::Dashed);
test_config!("LineStyle::Wavy" => LineStyle::Wavy(4.0));
test_config!(BorderStyle::Dotted);
test_config!(BorderSides::new_all(colors::RED));
}
#[test]
fn concurrent_read_write() {
let file = PathBuf::from("../target/tmp/test.concurrent.json");
{
// setup
rmv_file_assert(&file);
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(JsonConfig::sync(&file));
CONFIG.get("key", || Txt::from_static("default/custom")).set("custom").unwrap();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
app.exit();
}
// tests
let threads: Vec<_> = (0..10)
.map(|_| {
std::thread::spawn(clmv!(file, || {
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(JsonConfig::sync(file));
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let var = CONFIG.get("key", || Txt::from_static("default/get"));
for _ in 0..8 {
assert_eq!("custom", var.get());
var.set("custom").unwrap();
app.update(false).assert_wait();
}
app.exit();
}))
})
.collect();
for t in threads {
t.join().unwrap();
}
}
#[test]
fn fallback_swap() {
let main_cfg = PathBuf::from("../target/tmp/test.fallback_swap.target.json");
let main_prepared_cfg = PathBuf::from("../target/tmp/test.fallback_swap.prep.json");
let fallback_cfg = PathBuf::from("../target/tmp/test.fallback_swap.fallback.json");
{
// setup
rmv_file_assert(&main_cfg);
rmv_file_assert(&main_prepared_cfg);
rmv_file_assert(&fallback_cfg);
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(JsonConfig::sync(&fallback_cfg));
CONFIG.get("key", || Txt::from_static("default/fallback")).set("fallback").unwrap();
app.update(false).assert_wait();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
CONFIG.load(JsonConfig::sync(&main_prepared_cfg));
CONFIG.get("key", || Txt::from_static("default/main")).set("main").unwrap();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
app.exit();
}
// test
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(FallbackConfig::new(JsonConfig::sync(&main_cfg), JsonConfig::sync(fallback_cfg)));
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status != ConfigStatus::Loaded {
panic!("{status}");
}
app.update(false).assert_wait();
let key = CONFIG.get("key", || Txt::from_static("default/get"));
assert_eq!("fallback", key.get());
std::fs::rename(main_prepared_cfg, main_cfg).unwrap();
app.update(false).assert_wait();
app.run_task(async {
task::deadline(1.60.secs()).await; // wait for system rename event (+ debounce)
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status != ConfigStatus::Loaded {
panic!("{status}");
}
assert_eq!("main", key.get());
app.exit();
}
#[test]
fn fallback_reset() {
let main_cfg = PathBuf::from("../target/tmp/test.fallback_reset.target.json");
let fallback_cfg = PathBuf::from("../target/tmp/test.fallback_reset.fallback.json");
{
// setup
rmv_file_assert(&main_cfg);
rmv_file_assert(&fallback_cfg);
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(JsonConfig::sync(&fallback_cfg));
CONFIG.get("key", || Txt::from_static("default/fallback")).set("fallback").unwrap();
app.update(false).assert_wait();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
CONFIG.load(JsonConfig::sync(&main_cfg));
CONFIG.get("key", || Txt::from_static("default/main")).set("main").unwrap();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
app.exit();
}
// test
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(FallbackConfig::new(JsonConfig::sync(&main_cfg), JsonConfig::sync(&fallback_cfg)));
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status != ConfigStatus::Loaded {
panic!("{status}");
}
app.update(false).assert_wait();
let key = CONFIG.get("key", || Txt::from_static("default/get"));
assert_eq!("main", key.get());
CONFIG.load(MemoryConfig::default());
app.update(false).assert_wait();
rmv_file_assert(&main_cfg);
CONFIG.load(FallbackConfig::new(JsonConfig::sync(&main_cfg), JsonConfig::sync(&fallback_cfg)));
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status != ConfigStatus::Loaded {
panic!("{status}");
}
assert_eq!("fallback", key.get());
app.exit();
}
#[test]
fn fallback_reset_entry() {
let main_cfg = PathBuf::from("../target/tmp/test.fallback_reset_entry.target.json");
let fallback_cfg = PathBuf::from("../target/tmp/test.fallback_reset_entry.fallback.json");
{
// setup
rmv_file_assert(&main_cfg);
rmv_file_assert(&fallback_cfg);
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
CONFIG.load(JsonConfig::sync(&fallback_cfg));
CONFIG.get("key", || Txt::from_static("default/fallback")).set("fallback").unwrap();
app.update(false).assert_wait();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
CONFIG.load(JsonConfig::sync(&main_cfg));
CONFIG.get("key", || Txt::from_static("default/main")).set("main").unwrap();
app.run_task(async {
task::with_deadline(CONFIG.wait_idle(), 60.secs()).await.unwrap();
});
let status = CONFIG.status().get();
if status.is_err() {
panic!("{status}");
}
app.exit();
}
// test
let mut app = APP.defaults().run_headless(false);
zng::app::test_log();
let mut cfg = FallbackConfig::new(JsonConfig::sync(&main_cfg), JsonConfig::sync(&fallback_cfg));
// wait_idle
let status = cfg.status();
app.run_task(async move {
task::with_deadline(
async move {
while !status.get().is_idle() {
status.wait_update().await;
}
},
5.secs(),
)
.await
.unwrap();
});
let key = cfg.get("key", || Txt::from_static("default/get"));
assert_eq!("main", key.get());
cfg.reset(&ConfigKey::from_static("key"));
app.update(false).assert_wait();
assert_eq!("fallback", key.get());
// wait_idle
let status = cfg.status();
app.run_task(async move {
task::with_deadline(
async move {
while !status.get().is_idle() {
status.wait_update().await;
}
},
60.secs(),
)
.await
.unwrap();
});
let raw_config = std::fs::read_to_string(&main_cfg).unwrap();
assert!(!raw_config.contains("key"));
app.exit();
}
fn rmv_file_assert(path: &Path) {
if let Err(e) = std::fs::remove_file(path) {
if !matches!(e.kind(), std::io::ErrorKind::NotFound) {
panic!("cannot remove `{}`\n{e}", path.display());
}
}
}