#![cfg(all(feature = "builder", target_os = "linux"))]
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::time::{Duration, Instant};
use rsproperties::{build_trie, PropertyConfig, PropertyInfoEntry, SystemProperties, Timespec};
fn build_property_info(dir: &Path) {
std::fs::create_dir_all(dir).unwrap();
let contexts_path = dir.join("property_contexts");
File::create(&contexts_path)
.unwrap()
.write_all(b"test. u:object_r:test_prop:s0 prefix string\n")
.unwrap();
let (entries, errors) = PropertyInfoEntry::parse_from_file(&contexts_path, false).unwrap();
assert!(errors.is_empty(), "parse errors: {errors:?}");
let data = build_trie(&entries, "u:object_r:default_prop:s0", "string").unwrap();
File::create(dir.join("property_info"))
.unwrap()
.write_all(&data)
.unwrap();
}
fn timespec(d: Duration) -> Timespec {
Timespec {
tv_sec: d.as_secs() as _,
tv_nsec: d.subsec_nanos() as _,
}
}
#[test]
fn test_wait_wake_across_instances() {
let dir = std::env::temp_dir().join(format!("rsprops_waitwake_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
build_property_info(&dir);
let mut writer = SystemProperties::new_area(&dir).expect("writer new_area");
writer.add("test.wait.prop", "0").unwrap();
rsproperties::init(PropertyConfig::with_properties_dir(&dir));
let reader = rsproperties::system_properties();
let idx = reader
.find("test.wait.prop")
.unwrap()
.expect("property added by the writer must be visible to the reader");
let old = reader.serial(&idx).expect("initial serial");
let waiter = std::thread::spawn(move || {
let reader = rsproperties::system_properties();
reader.wait(
Some(&idx),
Some(old),
Some(×pec(Duration::from_secs(10))),
)
});
std::thread::sleep(Duration::from_millis(300));
writer.set("test.wait.prop", "1").unwrap();
let woken = waiter.join().expect("waiter thread panicked");
let new_serial = woken.expect("wait must return the post-update serial, not time out");
assert_ne!(new_serial, old, "serial must advance on update");
assert_eq!(reader.get_with_result("test.wait.prop").unwrap(), "1");
let old = reader.serial(&idx).unwrap();
let start = Instant::now();
let res = reader.wait(
Some(&idx),
Some(old),
Some(×pec(Duration::from_millis(300))),
);
let elapsed = start.elapsed();
assert!(res.is_none(), "nothing changed — wait must report timeout");
assert!(
elapsed >= Duration::from_millis(250),
"returned before the timeout: {elapsed:?}"
);
assert!(
elapsed < Duration::from_secs(3),
"overshot the timeout bound: {elapsed:?}"
);
let old = reader.serial(&idx).unwrap();
writer.set("test.wait.prop", "2").unwrap();
let start = Instant::now();
let res = reader.wait(
Some(&idx),
Some(old),
Some(×pec(Duration::from_secs(10))),
);
let elapsed = start.elapsed();
let new_serial = res.expect("already-changed serial must return immediately");
assert_ne!(new_serial, old);
assert!(
elapsed < Duration::from_secs(2),
"wait should have returned without parking: {elapsed:?}"
);
let old_global = reader.context_serial();
let waiter = std::thread::spawn(move || {
let reader = rsproperties::system_properties();
reader.wait(
None,
Some(old_global),
Some(×pec(Duration::from_secs(10))),
)
});
std::thread::sleep(Duration::from_millis(200));
writer.set("test.wait.prop", "3").unwrap();
let woken = waiter.join().expect("global waiter panicked");
assert!(
woken.is_some(),
"global-serial wait must observe the update"
);
let _ = std::fs::remove_dir_all(&dir);
}