fix(ci): stabilize macOS dialog and EXR cache tests (#3704)

* fix(ci): stabilize macOS dialog and EXR cache tests

* style: format file dialog test helpers
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-14 20:08:47 -05:00
1 parent e8a68671ae
commit dc2380b070
2 files changed
+38 -6

No files matched your search

+15 -5
View File
@@ -88,7 +88,11 @@ struct Errors {
/// Run blocking dialog `what`; one that can't open sends why to `errors` and counts as
/// cancelled (the blocking hooks can't return an error).
fn blocking<T>(errors: &Errors, what: &str, open: impl FnOnce() -> Option<T>) -> Option<T> {
guarded(what, open).unwrap_or_else(|e| {
blocking_on(errors, what, on_main_thread(), open)
}
fn blocking_on<T>(errors: &Errors, what: &str, main_thread: bool, open: impl FnOnce() -> Option<T>) -> Option<T> {
guarded_on(main_thread, what, open).unwrap_or_else(|e| {
let _ = errors.tx.send(e);
errors.ctx.request_repaint();
None
@@ -175,8 +179,12 @@ fn title(dialog: &FileDialog) -> &'static str {
/// Open `dialog` with `open` through [`guarded`] and wait for its answer on a worker thread.
/// `done` always gets an answer: the paths, or why the dialog couldn't open or answer.
fn show(dialog: FileDialog, done: DialogDone, ctx: egui::Context, open: impl FnOnce(FileDialog) -> Answer) {
show_on(dialog, done, ctx, on_main_thread(), open);
}
fn show_on(dialog: FileDialog, done: DialogDone, ctx: egui::Context, main_thread: bool, open: impl FnOnce(FileDialog) -> Answer) {
let what = title(&dialog);
let answer = match guarded(what, || open(dialog)) {
let answer = match guarded_on(main_thread, what, || open(dialog)) {
Ok(answer) => answer,
Err(e) => {
done(Err(e));
@@ -282,7 +290,7 @@ mod tests {
fn answered(dialog: FileDialog, open: impl FnOnce(FileDialog) -> Answer) -> Result<Vec<String>, String> {
let (tx, rx) = channel();
show(dialog, Box::new(move |r| tx.send(r).unwrap()), egui::Context::default(), open);
show_on(dialog, Box::new(move |r| tx.send(r).unwrap()), egui::Context::default(), true, open);
rx.recv_timeout(Duration::from_secs(10)).expect("the dialog answers")
}
@@ -328,10 +336,12 @@ mod tests {
fn a_blocking_picker_that_cannot_open_reports_why() {
let (tx, rx) = channel();
let tx = Errors { tx, ctx: egui::Context::default() };
assert_eq!(blocking(&tx, "Open", || -> Option<u8> { panic!("{NIL_PANEL}") }), None);
assert_eq!(blocking_on(&tx, "Open", true, || -> Option<u8> { panic!("{NIL_PANEL}") }), None);
assert!(rx.try_recv().is_ok_and(|e| e.contains(NIL_PANEL)));
assert_eq!(blocking(&tx, "Open", || Some(3)), Some(3));
assert_eq!(blocking_on(&tx, "Open", true, || Some(3)), Some(3));
assert!(rx.try_recv().is_err(), "nothing to report");
assert_eq!(blocking_on(&tx, "Open", false, || Some(5)), None);
assert!(rx.try_recv().is_ok_and(|e| e.contains("off the main thread")));
}
/// #3084: every rfd dialog in the desktop app goes through this module (and so through
+23 -1
View File
@@ -539,8 +539,15 @@ struct Entry {
impl Entry {
/// Dropping it would free nothing or lose a decode in progress: an effect (or a picture
/// waiting for its effects) still holds its planes, or it is decoding.
/// waiting for its effects) still holds its planes, someone is using the slot, or it is decoding.
fn in_use(&self) -> bool {
// `get_or_init` hands an active caller a borrow of the OnceLock value before that caller
// can clone the decoded Arc. Keep the entry while any caller still holds this slot, or a
// concurrent account/eviction can remove it in that gap and make the next request decode
// the same file again.
if Arc::strong_count(&self.slot) > 1 {
return true;
}
match self.slot.get() {
None => true,
Some((None, _)) => false,
@@ -967,6 +974,21 @@ mod tests {
assert_eq!(reads.load(Ordering::Relaxed), frames as u64);
}
#[test]
fn cache_does_not_evict_a_slot_while_a_caller_is_using_it() {
let bytes: Arc<[u8]> = flat(&["R", "G", "B", "A"], (32, 32), 0.25).into();
let one = 4 * 32 * 32 * 4 + 4;
let cache = ChannelCache::new(one);
drop(cache.get("/active.exr", Use::Channels, || Some(bytes.clone())));
let active_slot = cache.lock().entries.iter().find(|entry| entry.path == "/active.exr").unwrap().slot.clone();
drop(cache.get("/next.exr", Use::Channels, || Some(bytes.clone())));
assert!(cache.cached().contains(&"/active.exr".to_string()), "an active caller pins the slot");
drop(active_slot);
drop(cache.get("/newest.exr", Use::Channels, || Some(bytes.clone())));
assert!(!cache.cached().contains(&"/active.exr".to_string()), "an idle slot is eligible for eviction");
}
/// #474: a decode that panics doesn't leave its file "decoding" in the cache (never dropped,
/// its planes counted): the entry goes, and the next request decodes the file.
#[test]