GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
Fix config test flake: serialize tests that mutate global color state
test_plate_root_canonical_spelling failed ~3/10 runs: it and
test_backplate_menubar_statusbar_styling both reload_colors() into the
process-global RwLock statics with conflicting corner_radius values, then
assert getters, on parallel harness threads — one test's reload landing
between the other's reload and asserts read 12.0 where 17.0 was expected.
Add color::test_color_state_lock(), a test-only mutex every
reload-then-assert test holds across its reload + asserts. Inside the
lock, before reloading, each test also fires both once-per-process
live-config loads (load_colors_once via a getter, and
layout::lazy_init_style_registry — a third writer any layout/graph test
can trigger) so neither can rewrite the state from the live config file
mid-assert.
Verified: the narrowed filter (cargo test -p cce-ui --lib config::tests)
reproduced 6/30 failures before, 0/30 after; full --lib suite 0/10.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/color.rs | 13 +++++++++++++
src/config.rs | 11 ++++++++++-
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/color.rs b/src/color.rs
index 2457a6f..e15d8eb 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -661,6 +661,19 @@ pub fn reload_colors(content: &str) {
parse_and_set_colors(content);
}
+/// Test-only: the color state is process-global, so any test that calls
+/// [`reload_colors`] and then asserts getter values races every other such
+/// test on the parallel harness. Each of those tests must hold this lock
+/// across its reload + asserts, and should fire the once-per-process config
+/// loads ([`load_colors_once`] via any getter, and
+/// `layout::lazy_init_style_registry`) inside the lock BEFORE its reload, so
+/// neither can rewrite the state from the live config file mid-assert.
+#[cfg(test)]
+pub(crate) fn test_color_state_lock() -> std::sync::MutexGuard<'static, ()> {
+ static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
+ LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
+}
+
pub fn dropdown_background_color() -> [f32; 4] {
load_colors_once();
*DROPDOWN_BACKGROUND_COLOR.read().unwrap()
diff --git a/src/config.rs b/src/config.rs
index c18a935..7f6b412 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -944,8 +944,12 @@ mod tests {
#[test]
fn test_backplate_menubar_statusbar_styling() {
- // Trigger load_colors_once first to initialize the Once block from the real config file
+ // Global color state: serialize against the other reload_colors tests,
+ // and fire both once-per-process live-config loads before our reload
+ // so neither can rewrite the state mid-assert.
+ let _guard = crate::color::test_color_state_lock();
let _ = crate::color::backplate_statusbar_blur();
+ crate::layout::lazy_init_style_registry();
let content = r##"
style {
@@ -990,7 +994,12 @@ mod tests {
/// both spellings are present (canonical-first pointer chains).
#[test]
fn test_plate_root_canonical_spelling() {
+ // Global color state: serialize against the other reload_colors tests,
+ // and fire both once-per-process live-config loads before our reload
+ // so neither can rewrite the state mid-assert.
+ let _guard = crate::color::test_color_state_lock();
let _ = crate::color::root_plate_corner_radius();
+ crate::layout::lazy_init_style_registry();
let content = r##"
style {