Make `TestAppContext` and its dependencies available only in tests

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/gpui/Cargo.toml                |   5 
crates/gpui/src/app.rs                | 130 ++++++++++++++++------------
crates/gpui/src/executor.rs           |  95 +++++++++++++-------
crates/gpui/src/platform/test.rs      |   1 
crates/gpui/src/test.rs               |   5 
crates/gpui/src/util.rs               |  15 ++-
crates/gpui_macros/src/gpui_macros.rs |   8 +
7 files changed, 161 insertions(+), 98 deletions(-)

Detailed changes

crates/gpui/Cargo.toml 🔗

@@ -8,14 +8,14 @@ version = "0.1.0"
 path = "src/gpui.rs"
 
 [features]
-test-support = ["env_logger", "collections/test-support"]
+test-support = ["backtrace", "env_logger", "collections/test-support"]
 
 [dependencies]
 collections = { path = "../collections" }
 gpui_macros = { path = "../gpui_macros" }
 sum_tree = { path = "../sum_tree" }
 async-task = "4.0.3"
-backtrace = "0.3"
+backtrace = { version = "0.3", optional = true }
 ctor = "0.1"
 dhat = "0.3"
 env_logger = { version = "0.8", optional = true }
@@ -49,6 +49,7 @@ bindgen = "0.58.1"
 cc = "1.0.67"
 
 [dev-dependencies]
+backtrace = "0.3"
 collections = { path = "../collections", features = ["test-support"] }
 env_logger = "0.8"
 png = "0.16"

crates/gpui/src/app.rs 🔗

@@ -4,16 +4,15 @@ use crate::{
     keymap::{self, Keystroke},
     platform::{self, CursorStyle, Platform, PromptLevel, WindowOptions},
     presenter::Presenter,
-    util::{post_inc, timeout, CwdBacktrace},
+    util::post_inc,
     AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
 };
 use anyhow::{anyhow, Result};
-use backtrace::Backtrace;
 use keymap::MatchResult;
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use platform::Event;
-use postage::{mpsc, oneshot, sink::Sink as _, stream::Stream as _};
+use postage::oneshot;
 use smol::prelude::*;
 use std::{
     any::{self, type_name, Any, TypeId},
@@ -237,6 +236,7 @@ pub struct App(Rc<RefCell<MutableAppContext>>);
 #[derive(Clone)]
 pub struct AsyncAppContext(Rc<RefCell<MutableAppContext>>);
 
+#[cfg(any(test, feature = "test-support"))]
 pub struct TestAppContext {
     cx: Rc<RefCell<MutableAppContext>>,
     foreground_platform: Rc<platform::test::ForegroundPlatform>,
@@ -384,6 +384,7 @@ impl App {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl TestAppContext {
     pub fn new(
         foreground_platform: Rc<platform::test::ForegroundPlatform>,
@@ -401,7 +402,7 @@ impl TestAppContext {
             foreground_platform.clone(),
             font_cache,
             RefCounts {
-                #[cfg(feature = "test-support")]
+                #[cfg(any(test, feature = "test-support"))]
                 leak_detector,
                 ..Default::default()
             },
@@ -544,6 +545,8 @@ impl TestAppContext {
     }
 
     pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) {
+        use postage::prelude::Sink as _;
+
         let mut state = self.cx.borrow_mut();
         let (_, window) = state
             .presenters_and_platform_windows
@@ -560,18 +563,12 @@ impl TestAppContext {
         let _ = done_tx.try_send(answer);
     }
 
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
         self.cx.borrow().leak_detector()
     }
 }
 
-impl Drop for TestAppContext {
-    fn drop(&mut self) {
-        self.cx.borrow_mut().remove_all_windows();
-    }
-}
-
 impl AsyncAppContext {
     pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
     where
@@ -684,6 +681,7 @@ impl ReadViewWith for AsyncAppContext {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl UpdateModel for TestAppContext {
     fn update_model<T: Entity, O>(
         &mut self,
@@ -694,6 +692,7 @@ impl UpdateModel for TestAppContext {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl ReadModelWith for TestAppContext {
     fn read_model_with<E: Entity, T>(
         &self,
@@ -706,6 +705,7 @@ impl ReadModelWith for TestAppContext {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl UpdateView for TestAppContext {
     fn update_view<T, S>(
         &mut self,
@@ -719,6 +719,7 @@ impl UpdateView for TestAppContext {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl ReadViewWith for TestAppContext {
     fn read_view_with<V, T>(
         &self,
@@ -848,7 +849,7 @@ impl MutableAppContext {
         }
     }
 
-    fn remove_all_windows(&mut self) {
+    pub fn remove_all_windows(&mut self) {
         for (window_id, _) in self.cx.windows.drain() {
             self.presenters_and_platform_windows.remove(&window_id);
         }
@@ -1827,7 +1828,7 @@ impl MutableAppContext {
         self.cx.platform.read_from_clipboard()
     }
 
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
         self.cx.ref_counts.lock().leak_detector.clone()
     }
@@ -2841,7 +2842,7 @@ pub struct ModelHandle<T: Entity> {
     model_type: PhantomData<T>,
     ref_counts: Arc<Mutex<RefCounts>>,
 
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     handle_id: usize,
 }
 
@@ -2849,7 +2850,7 @@ impl<T: Entity> ModelHandle<T> {
     fn new(model_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
         ref_counts.lock().inc_model(model_id);
 
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         let handle_id = ref_counts
             .lock()
             .leak_detector
@@ -2861,7 +2862,7 @@ impl<T: Entity> ModelHandle<T> {
             model_type: PhantomData,
             ref_counts: ref_counts.clone(),
 
-            #[cfg(feature = "test-support")]
+            #[cfg(any(test, feature = "test-support"))]
             handle_id,
         }
     }
@@ -2902,8 +2903,11 @@ impl<T: Entity> ModelHandle<T> {
         })
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
-        let (mut tx, mut rx) = mpsc::channel(1);
+        use postage::prelude::{Sink as _, Stream as _};
+
+        let (mut tx, mut rx) = postage::mpsc::channel(1);
         let mut cx = cx.cx.borrow_mut();
         let subscription = cx.observe(self, move |_, _| {
             tx.try_send(()).ok();
@@ -2916,7 +2920,7 @@ impl<T: Entity> ModelHandle<T> {
         };
 
         async move {
-            let notification = timeout(duration, rx.recv())
+            let notification = crate::util::timeout(duration, rx.recv())
                 .await
                 .expect("next notification timed out");
             drop(subscription);
@@ -2924,11 +2928,14 @@ impl<T: Entity> ModelHandle<T> {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn next_event(&self, cx: &TestAppContext) -> impl Future<Output = T::Event>
     where
         T::Event: Clone,
     {
-        let (mut tx, mut rx) = mpsc::channel(1);
+        use postage::prelude::{Sink as _, Stream as _};
+
+        let (mut tx, mut rx) = postage::mpsc::channel(1);
         let mut cx = cx.cx.borrow_mut();
         let subscription = cx.subscribe(self, move |_, event, _| {
             tx.blocking_send(event.clone()).ok();
@@ -2941,7 +2948,7 @@ impl<T: Entity> ModelHandle<T> {
         };
 
         async move {
-            let event = timeout(duration, rx.recv())
+            let event = crate::util::timeout(duration, rx.recv())
                 .await
                 .expect("next event timed out");
             drop(subscription);
@@ -2949,12 +2956,15 @@ impl<T: Entity> ModelHandle<T> {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn condition(
         &self,
         cx: &TestAppContext,
         mut predicate: impl FnMut(&T, &AppContext) -> bool,
     ) -> impl Future<Output = ()> {
-        let (tx, mut rx) = mpsc::channel(1024);
+        use postage::prelude::{Sink as _, Stream as _};
+
+        let (tx, mut rx) = postage::mpsc::channel(1024);
 
         let mut cx = cx.cx.borrow_mut();
         let subscriptions = (
@@ -2981,7 +2991,7 @@ impl<T: Entity> ModelHandle<T> {
         };
 
         async move {
-            timeout(duration, async move {
+            crate::util::timeout(duration, async move {
                 loop {
                     {
                         let cx = cx.borrow();
@@ -3059,7 +3069,7 @@ impl<T: Entity> Drop for ModelHandle<T> {
         let mut ref_counts = self.ref_counts.lock();
         ref_counts.dec_model(self.model_id);
 
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         ref_counts
             .leak_detector
             .lock()
@@ -3149,14 +3159,14 @@ pub struct ViewHandle<T> {
     view_id: usize,
     view_type: PhantomData<T>,
     ref_counts: Arc<Mutex<RefCounts>>,
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     handle_id: usize,
 }
 
 impl<T: View> ViewHandle<T> {
     fn new(window_id: usize, view_id: usize, ref_counts: &Arc<Mutex<RefCounts>>) -> Self {
         ref_counts.lock().inc_view(window_id, view_id);
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         let handle_id = ref_counts
             .lock()
             .leak_detector
@@ -3169,7 +3179,7 @@ impl<T: View> ViewHandle<T> {
             view_type: PhantomData,
             ref_counts: ref_counts.clone(),
 
-            #[cfg(feature = "test-support")]
+            #[cfg(any(test, feature = "test-support"))]
             handle_id,
         }
     }
@@ -3230,8 +3240,11 @@ impl<T: View> ViewHandle<T> {
             .map_or(false, |focused_id| focused_id == self.view_id)
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
-        let (mut tx, mut rx) = mpsc::channel(1);
+        use postage::prelude::{Sink as _, Stream as _};
+
+        let (mut tx, mut rx) = postage::mpsc::channel(1);
         let mut cx = cx.cx.borrow_mut();
         let subscription = cx.observe(self, move |_, _| {
             tx.try_send(()).ok();
@@ -3244,7 +3257,7 @@ impl<T: View> ViewHandle<T> {
         };
 
         async move {
-            let notification = timeout(duration, rx.recv())
+            let notification = crate::util::timeout(duration, rx.recv())
                 .await
                 .expect("next notification timed out");
             drop(subscription);
@@ -3252,12 +3265,15 @@ impl<T: View> ViewHandle<T> {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn condition(
         &self,
         cx: &TestAppContext,
         mut predicate: impl FnMut(&T, &AppContext) -> bool,
     ) -> impl Future<Output = ()> {
-        let (tx, mut rx) = mpsc::channel(1024);
+        use postage::prelude::{Sink as _, Stream as _};
+
+        let (tx, mut rx) = postage::mpsc::channel(1024);
 
         let mut cx = cx.cx.borrow_mut();
         let subscriptions = self.update(&mut *cx, |_, cx| {
@@ -3286,7 +3302,7 @@ impl<T: View> ViewHandle<T> {
         };
 
         async move {
-            timeout(duration, async move {
+            crate::util::timeout(duration, async move {
                 loop {
                     {
                         let cx = cx.borrow();
@@ -3344,7 +3360,7 @@ impl<T> Drop for ViewHandle<T> {
         self.ref_counts
             .lock()
             .dec_view(self.window_id, self.view_id);
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         self.ref_counts
             .lock()
             .leak_detector
@@ -3382,7 +3398,7 @@ pub struct AnyViewHandle {
     view_type: TypeId,
     type_name: &'static str,
     ref_counts: Arc<Mutex<RefCounts>>,
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     handle_id: usize,
 }
 
@@ -3396,7 +3412,7 @@ impl AnyViewHandle {
     ) -> Self {
         ref_counts.lock().inc_view(window_id, view_id);
 
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         let handle_id = ref_counts
             .lock()
             .leak_detector
@@ -3409,7 +3425,7 @@ impl AnyViewHandle {
             view_type,
             type_name,
             ref_counts,
-            #[cfg(feature = "test-support")]
+            #[cfg(any(test, feature = "test-support"))]
             handle_id,
         }
     }
@@ -3434,7 +3450,7 @@ impl AnyViewHandle {
                 view_id: self.view_id,
                 ref_counts: self.ref_counts.clone(),
                 view_type: PhantomData,
-                #[cfg(feature = "test-support")]
+                #[cfg(any(test, feature = "test-support"))]
                 handle_id: self.handle_id,
             });
             unsafe {
@@ -3486,7 +3502,7 @@ impl<T: View> From<ViewHandle<T>> for AnyViewHandle {
             view_type: TypeId::of::<T>(),
             type_name: any::type_name::<T>(),
             ref_counts: handle.ref_counts.clone(),
-            #[cfg(feature = "test-support")]
+            #[cfg(any(test, feature = "test-support"))]
             handle_id: handle.handle_id,
         };
         unsafe {
@@ -3502,7 +3518,7 @@ impl Drop for AnyViewHandle {
         self.ref_counts
             .lock()
             .dec_view(self.window_id, self.view_id);
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         self.ref_counts
             .lock()
             .leak_detector
@@ -3516,7 +3532,7 @@ pub struct AnyModelHandle {
     model_type: TypeId,
     ref_counts: Arc<Mutex<RefCounts>>,
 
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     handle_id: usize,
 }
 
@@ -3524,7 +3540,7 @@ impl AnyModelHandle {
     fn new(model_id: usize, model_type: TypeId, ref_counts: Arc<Mutex<RefCounts>>) -> Self {
         ref_counts.lock().inc_model(model_id);
 
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         let handle_id = ref_counts
             .lock()
             .leak_detector
@@ -3536,7 +3552,7 @@ impl AnyModelHandle {
             model_type,
             ref_counts,
 
-            #[cfg(feature = "test-support")]
+            #[cfg(any(test, feature = "test-support"))]
             handle_id,
         }
     }
@@ -3548,7 +3564,7 @@ impl AnyModelHandle {
                 model_type: PhantomData,
                 ref_counts: self.ref_counts.clone(),
 
-                #[cfg(feature = "test-support")]
+                #[cfg(any(test, feature = "test-support"))]
                 handle_id: self.handle_id,
             });
             unsafe {
@@ -3594,7 +3610,7 @@ impl Drop for AnyModelHandle {
         let mut ref_counts = self.ref_counts.lock();
         ref_counts.dec_model(self.model_id);
 
-        #[cfg(feature = "test-support")]
+        #[cfg(any(test, feature = "test-support"))]
         ref_counts
             .leak_detector
             .lock()
@@ -3819,18 +3835,26 @@ lazy_static! {
         std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty());
 }
 
+#[cfg(any(test, feature = "test-support"))]
 #[derive(Default)]
 pub struct LeakDetector {
     next_handle_id: usize,
-    handle_backtraces: HashMap<usize, (Option<&'static str>, HashMap<usize, Option<Backtrace>>)>,
+    handle_backtraces: HashMap<
+        usize,
+        (
+            Option<&'static str>,
+            HashMap<usize, Option<backtrace::Backtrace>>,
+        ),
+    >,
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl LeakDetector {
     fn handle_created(&mut self, type_name: Option<&'static str>, entity_id: usize) -> usize {
         let handle_id = post_inc(&mut self.next_handle_id);
         let entry = self.handle_backtraces.entry(entity_id).or_default();
         let backtrace = if *LEAK_BACKTRACE {
-            Some(Backtrace::new_unresolved())
+            Some(backtrace::Backtrace::new_unresolved())
         } else {
             None
         };
@@ -3862,7 +3886,7 @@ impl LeakDetector {
             for trace in backtraces.values_mut() {
                 if let Some(trace) = trace {
                     trace.resolve();
-                    eprintln!("{:?}", CwdBacktrace(trace));
+                    eprintln!("{:?}", crate::util::CwdBacktrace(trace));
                 }
             }
             found_leaks = true;
@@ -3885,7 +3909,7 @@ struct RefCounts {
     dropped_views: HashSet<(usize, usize)>,
     dropped_element_states: HashSet<ElementStateId>,
 
-    #[cfg(feature = "test-support")]
+    #[cfg(any(test, feature = "test-support"))]
     leak_detector: Arc<Mutex<LeakDetector>>,
 }
 
@@ -4067,13 +4091,11 @@ mod tests {
 
         let handle_1 = cx.add_model(|_| Model::default());
         let handle_2 = cx.add_model(|_| Model::default());
-        let handle_2b = handle_2.clone();
-
         handle_1.update(cx, |_, c| {
-            c.subscribe(&handle_2, move |model: &mut Model, _, event, c| {
+            c.subscribe(&handle_2, move |model: &mut Model, emitter, event, c| {
                 model.events.push(*event);
 
-                c.subscribe(&handle_2b, |model, _, event, _| {
+                c.subscribe(&emitter, |model, _, event, _| {
                     model.events.push(*event * 2);
                 })
                 .detach();
@@ -4102,12 +4124,11 @@ mod tests {
 
         let handle_1 = cx.add_model(|_| Model::default());
         let handle_2 = cx.add_model(|_| Model::default());
-        let handle_2b = handle_2.clone();
 
         handle_1.update(cx, |_, c| {
             c.observe(&handle_2, move |model, observed, c| {
                 model.events.push(observed.read(c).count);
-                c.observe(&handle_2b, |model, observed, c| {
+                c.observe(&observed, |model, observed, c| {
                     model.events.push(observed.read(c).count * 2);
                 })
                 .detach();
@@ -4341,14 +4362,13 @@ mod tests {
 
         let (window_id, handle_1) = cx.add_window(Default::default(), |_| View::default());
         let handle_2 = cx.add_view(window_id, |_| View::default());
-        let handle_2b = handle_2.clone();
         let handle_3 = cx.add_model(|_| Model);
 
         handle_1.update(cx, |_, c| {
-            c.subscribe(&handle_2, move |me, _, event, c| {
+            c.subscribe(&handle_2, move |me, emitter, event, c| {
                 me.events.push(*event);
 
-                c.subscribe(&handle_2b, |me, _, event, _| {
+                c.subscribe(&emitter, |me, _, event, _| {
                     me.events.push(*event * 2);
                 })
                 .detach();

crates/gpui/src/executor.rs 🔗

@@ -1,28 +1,18 @@
 use anyhow::{anyhow, Result};
 use async_task::Runnable;
-use backtrace::Backtrace;
-use collections::HashMap;
-use parking_lot::Mutex;
-use postage::{barrier, prelude::Stream as _};
-use rand::prelude::*;
-use smol::{channel, future::yield_now, prelude::*, Executor, Timer};
+use smol::{channel, prelude::*, Executor, Timer};
 use std::{
     any::Any,
     fmt::{self, Display},
     marker::PhantomData,
     mem,
-    ops::RangeInclusive,
     pin::Pin,
     rc::Rc,
-    sync::{
-        atomic::{AtomicBool, Ordering::SeqCst},
-        Arc,
-    },
+    sync::Arc,
     task::{Context, Poll},
     thread,
-    time::{Duration, Instant},
+    time::Duration,
 };
-use waker_fn::waker_fn;
 
 use crate::{
     platform::{self, Dispatcher},
@@ -34,6 +24,7 @@ pub enum Foreground {
         dispatcher: Arc<dyn platform::Dispatcher>,
         _not_send_or_sync: PhantomData<Rc<()>>,
     },
+    #[cfg(any(test, feature = "test-support"))]
     Deterministic {
         cx_id: usize,
         executor: Arc<Deterministic>,
@@ -41,9 +32,8 @@ pub enum Foreground {
 }
 
 pub enum Background {
-    Deterministic {
-        executor: Arc<Deterministic>,
-    },
+    #[cfg(any(test, feature = "test-support"))]
+    Deterministic { executor: Arc<Deterministic> },
     Production {
         executor: Arc<smol::Executor<'static>>,
         _stop: channel::Sender<()>,
@@ -70,39 +60,45 @@ pub enum Task<T> {
 
 unsafe impl<T: Send> Send for Task<T> {}
 
+#[cfg(any(test, feature = "test-support"))]
 struct DeterministicState {
-    rng: StdRng,
+    rng: rand::prelude::StdRng,
     seed: u64,
-    scheduled_from_foreground: HashMap<usize, Vec<ForegroundRunnable>>,
+    scheduled_from_foreground: collections::HashMap<usize, Vec<ForegroundRunnable>>,
     scheduled_from_background: Vec<Runnable>,
     forbid_parking: bool,
-    block_on_ticks: RangeInclusive<usize>,
-    now: Instant,
-    pending_timers: Vec<(Instant, barrier::Sender)>,
-    waiting_backtrace: Option<Backtrace>,
+    block_on_ticks: std::ops::RangeInclusive<usize>,
+    now: std::time::Instant,
+    pending_timers: Vec<(std::time::Instant, postage::barrier::Sender)>,
+    waiting_backtrace: Option<backtrace::Backtrace>,
 }
 
+#[cfg(any(test, feature = "test-support"))]
 struct ForegroundRunnable {
     runnable: Runnable,
     main: bool,
 }
 
+#[cfg(any(test, feature = "test-support"))]
 pub struct Deterministic {
-    state: Arc<Mutex<DeterministicState>>,
-    parker: Mutex<parking::Parker>,
+    state: Arc<parking_lot::Mutex<DeterministicState>>,
+    parker: parking_lot::Mutex<parking::Parker>,
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl Deterministic {
     pub fn new(seed: u64) -> Arc<Self> {
+        use rand::prelude::*;
+
         Arc::new(Self {
-            state: Arc::new(Mutex::new(DeterministicState {
+            state: Arc::new(parking_lot::Mutex::new(DeterministicState {
                 rng: StdRng::seed_from_u64(seed),
                 seed,
                 scheduled_from_foreground: Default::default(),
                 scheduled_from_background: Default::default(),
                 forbid_parking: false,
                 block_on_ticks: 0..=1000,
-                now: Instant::now(),
+                now: std::time::Instant::now(),
                 pending_timers: Default::default(),
                 waiting_backtrace: None,
             })),
@@ -161,6 +157,8 @@ impl Deterministic {
         cx_id: usize,
         main_future: Pin<Box<dyn 'a + Future<Output = Box<dyn Any>>>>,
     ) -> Box<dyn Any> {
+        use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
+
         let woken = Arc::new(AtomicBool::new(false));
 
         let state = self.state.clone();
@@ -195,18 +193,22 @@ impl Deterministic {
         }
     }
 
-    pub(crate) fn run_until_parked(&self) {
+    pub fn run_until_parked(&self) {
+        use std::sync::atomic::AtomicBool;
         let woken = Arc::new(AtomicBool::new(false));
         self.run_internal(woken, None);
     }
 
     fn run_internal(
         &self,
-        woken: Arc<AtomicBool>,
+        woken: Arc<std::sync::atomic::AtomicBool>,
         mut main_task: Option<&mut AnyLocalTask>,
     ) -> Option<Box<dyn Any>> {
+        use rand::prelude::*;
+        use std::sync::atomic::Ordering::SeqCst;
+
         let unparker = self.parker.lock().unparker();
-        let waker = waker_fn(move || {
+        let waker = waker_fn::waker_fn(move || {
             woken.store(true, SeqCst);
             unparker.unpark();
         });
@@ -261,8 +263,10 @@ impl Deterministic {
     where
         F: Unpin + Future<Output = T>,
     {
+        use rand::prelude::*;
+
         let unparker = self.parker.lock().unparker();
-        let waker = waker_fn(move || {
+        let waker = waker_fn::waker_fn(move || {
             unparker.unpark();
         });
 
@@ -295,10 +299,12 @@ impl Deterministic {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl DeterministicState {
     fn will_park(&mut self) {
         if self.forbid_parking {
             let mut backtrace_message = String::new();
+            #[cfg(any(test, feature = "test-support"))]
             if let Some(backtrace) = self.waiting_backtrace.as_mut() {
                 backtrace.resolve();
                 backtrace_message = format!(
@@ -330,6 +336,7 @@ impl Foreground {
     pub fn spawn<T: 'static>(&self, future: impl Future<Output = T> + 'static) -> Task<T> {
         let future = any_local_future(future);
         let any_task = match self {
+            #[cfg(any(test, feature = "test-support"))]
             Self::Deterministic { cx_id, executor } => {
                 executor.spawn_from_foreground(*cx_id, future, false)
             }
@@ -351,6 +358,7 @@ impl Foreground {
         Task::local(any_task)
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn run<T: 'static>(&self, future: impl Future<Output = T>) -> T {
         let future = async move { Box::new(future.await) as Box<dyn Any> }.boxed_local();
         let result = match self {
@@ -360,6 +368,7 @@ impl Foreground {
         *result.downcast().unwrap()
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn run_until_parked(&self) {
         match self {
             Self::Deterministic { executor, .. } => executor.run_until_parked(),
@@ -367,6 +376,7 @@ impl Foreground {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn parking_forbidden(&self) -> bool {
         match self {
             Self::Deterministic { executor, .. } => executor.state.lock().forbid_parking,
@@ -374,15 +384,18 @@ impl Foreground {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn start_waiting(&self) {
         match self {
             Self::Deterministic { executor, .. } => {
-                executor.state.lock().waiting_backtrace = Some(Backtrace::new_unresolved());
+                executor.state.lock().waiting_backtrace =
+                    Some(backtrace::Backtrace::new_unresolved());
             }
             _ => panic!("this method can only be called on a deterministic executor"),
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn finish_waiting(&self) {
         match self {
             Self::Deterministic { executor, .. } => {
@@ -392,7 +405,10 @@ impl Foreground {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn forbid_parking(&self) {
+        use rand::prelude::*;
+
         match self {
             Self::Deterministic { executor, .. } => {
                 let mut state = executor.state.lock();
@@ -405,8 +421,11 @@ impl Foreground {
 
     pub async fn timer(&self, duration: Duration) {
         match self {
+            #[cfg(any(test, feature = "test-support"))]
             Self::Deterministic { executor, .. } => {
-                let (tx, mut rx) = barrier::channel();
+                use postage::prelude::Stream as _;
+
+                let (tx, mut rx) = postage::barrier::channel();
                 {
                     let mut state = executor.state.lock();
                     let wakeup_at = state.now + duration;
@@ -420,6 +439,7 @@ impl Foreground {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub fn advance_clock(&self, duration: Duration) {
         match self {
             Self::Deterministic { executor, .. } => {
@@ -438,7 +458,8 @@ impl Foreground {
         }
     }
 
-    pub fn set_block_on_ticks(&self, range: RangeInclusive<usize>) {
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
         match self {
             Self::Deterministic { executor, .. } => executor.state.lock().block_on_ticks = range,
             _ => panic!("this method can only be called on a deterministic executor"),
@@ -478,6 +499,7 @@ impl Background {
         let future = any_future(future);
         let any_task = match self {
             Self::Production { executor, .. } => executor.spawn(future),
+            #[cfg(any(test, feature = "test-support"))]
             Self::Deterministic { executor } => executor.spawn(future),
         };
         Task::send(any_task)
@@ -490,6 +512,7 @@ impl Background {
         smol::pin!(future);
         match self {
             Self::Production { .. } => smol::block_on(&mut future),
+            #[cfg(any(test, feature = "test-support"))]
             Self::Deterministic { executor, .. } => {
                 executor.block(&mut future, usize::MAX).unwrap()
             }
@@ -509,7 +532,9 @@ impl Background {
         if !timeout.is_zero() {
             let output = match self {
                 Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
+                #[cfg(any(test, feature = "test-support"))]
                 Self::Deterministic { executor, .. } => {
+                    use rand::prelude::*;
                     let max_ticks = {
                         let mut state = executor.state.lock();
                         let range = state.block_on_ticks.clone();
@@ -544,7 +569,11 @@ impl Background {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
     pub async fn simulate_random_delay(&self) {
+        use rand::prelude::*;
+        use smol::future::yield_now;
+
         match self {
             Self::Deterministic { executor, .. } => {
                 if executor.state.lock().rng.gen_bool(0.2) {

crates/gpui/src/platform/test.rs 🔗

@@ -39,6 +39,7 @@ pub struct Window {
     pub(crate) last_prompt: Cell<Option<oneshot::Sender<usize>>>,
 }
 
+#[cfg(any(test, feature = "test-support"))]
 impl ForegroundPlatform {
     pub(crate) fn simulate_new_path_selection(
         &self,

crates/gpui/src/test.rs 🔗

@@ -86,10 +86,13 @@ pub fn run_test(
                         deterministic.clone(),
                         seed,
                         is_last_iteration,
-                    )
+                    );
                 });
 
+                cx.update(|cx| cx.remove_all_windows());
                 deterministic.run_until_parked();
+                cx.update(|_| {}); // flush effects
+
                 leak_detector.lock().detect();
                 if is_last_iteration {
                     break;

crates/gpui/src/util.rs 🔗

@@ -1,6 +1,5 @@
-use backtrace::{Backtrace, BacktraceFmt, BytesOrWideString};
 use smol::future::FutureExt;
-use std::{fmt, future::Future, time::Duration};
+use std::{future::Future, time::Duration};
 
 pub fn post_inc(value: &mut usize) -> usize {
     let prev = *value;
@@ -20,14 +19,18 @@ where
     timer.race(future).await
 }
 
-pub struct CwdBacktrace<'a>(pub &'a Backtrace);
+#[cfg(any(test, feature = "test-support"))]
+pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
 
+#[cfg(any(test, feature = "test-support"))]
 impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        use backtrace::{BacktraceFmt, BytesOrWideString};
+
         let cwd = std::env::current_dir().unwrap();
         let cwd = cwd.parent().unwrap();
-        let mut print_path = |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
-            fmt::Display::fmt(&path, fmt)
+        let mut print_path = |fmt: &mut std::fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
+            std::fmt::Display::fmt(&path, fmt)
         };
         let mut fmt = BacktraceFmt::new(f, backtrace::PrintFmt::Full, &mut print_path);
         for frame in self.0.frames() {

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -66,6 +66,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
         // Pass to the test function the number of app contexts that it needs,
         // based on its parameter list.
         let mut cx_vars = proc_macro2::TokenStream::new();
+        let mut cx_teardowns = proc_macro2::TokenStream::new();
         let mut inner_fn_args = proc_macro2::TokenStream::new();
         for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
             if let FnArg::Typed(arg) = arg {
@@ -104,6 +105,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                                             #first_entity_id,
                                         );
                                     ));
+                                    cx_teardowns.extend(quote!(
+                                        #cx_varname.update(|cx| cx.remove_all_windows());
+                                        deterministic.run_until_parked();
+                                        #cx_varname.update(|_| {}); // flush effects
+                                    ));
                                     inner_fn_args.extend(quote!(&mut #cx_varname,));
                                 }
                                 _ => {
@@ -145,7 +151,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     &mut |cx, foreground_platform, deterministic, seed, is_last_iteration| {
                         #cx_vars
                         cx.foreground().run(#inner_fn_name(#inner_fn_args));
-                        cx.foreground().run_until_parked();
+                        #cx_teardowns
                     }
                 );
             }