diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 7a0e356d2a..95769cd470 100644 --- a/crates/gpui/Cargo.toml +++ b/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" diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 3aa628e253..ffe198a503 100644 --- a/crates/gpui/src/app.rs +++ b/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>); #[derive(Clone)] pub struct AsyncAppContext(Rc>); +#[cfg(any(test, feature = "test-support"))] pub struct TestAppContext { cx: Rc>, foreground_platform: Rc, @@ -384,6 +384,7 @@ impl App { } } +#[cfg(any(test, feature = "test-support"))] impl TestAppContext { pub fn new( foreground_platform: Rc, @@ -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> { 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(&self, f: F) -> Task where @@ -684,6 +681,7 @@ impl ReadViewWith for AsyncAppContext { } } +#[cfg(any(test, feature = "test-support"))] impl UpdateModel for TestAppContext { fn update_model( &mut self, @@ -694,6 +692,7 @@ impl UpdateModel for TestAppContext { } } +#[cfg(any(test, feature = "test-support"))] impl ReadModelWith for TestAppContext { fn read_model_with( &self, @@ -706,6 +705,7 @@ impl ReadModelWith for TestAppContext { } } +#[cfg(any(test, feature = "test-support"))] impl UpdateView for TestAppContext { fn update_view( &mut self, @@ -719,6 +719,7 @@ impl UpdateView for TestAppContext { } } +#[cfg(any(test, feature = "test-support"))] impl ReadViewWith for TestAppContext { fn read_view_with( &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> { self.cx.ref_counts.lock().leak_detector.clone() } @@ -2841,7 +2842,7 @@ pub struct ModelHandle { model_type: PhantomData, ref_counts: Arc>, - #[cfg(feature = "test-support")] + #[cfg(any(test, feature = "test-support"))] handle_id: usize, } @@ -2849,7 +2850,7 @@ impl ModelHandle { fn new(model_id: usize, ref_counts: &Arc>) -> 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 ModelHandle { 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 ModelHandle { }) } + #[cfg(any(test, feature = "test-support"))] pub fn next_notification(&self, cx: &TestAppContext) -> impl Future { - 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 ModelHandle { }; 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 ModelHandle { } } + #[cfg(any(test, feature = "test-support"))] pub fn next_event(&self, cx: &TestAppContext) -> impl Future 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 ModelHandle { }; 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 ModelHandle { } } + #[cfg(any(test, feature = "test-support"))] pub fn condition( &self, cx: &TestAppContext, mut predicate: impl FnMut(&T, &AppContext) -> bool, ) -> impl Future { - 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 ModelHandle { }; async move { - timeout(duration, async move { + crate::util::timeout(duration, async move { loop { { let cx = cx.borrow(); @@ -3059,7 +3069,7 @@ impl Drop for ModelHandle { 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 { view_id: usize, view_type: PhantomData, ref_counts: Arc>, - #[cfg(feature = "test-support")] + #[cfg(any(test, feature = "test-support"))] handle_id: usize, } impl ViewHandle { fn new(window_id: usize, view_id: usize, ref_counts: &Arc>) -> 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 ViewHandle { 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 ViewHandle { .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 { - 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 ViewHandle { }; 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 ViewHandle { } } + #[cfg(any(test, feature = "test-support"))] pub fn condition( &self, cx: &TestAppContext, mut predicate: impl FnMut(&T, &AppContext) -> bool, ) -> impl Future { - 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 ViewHandle { }; async move { - timeout(duration, async move { + crate::util::timeout(duration, async move { loop { { let cx = cx.borrow(); @@ -3344,7 +3360,7 @@ impl Drop for ViewHandle { 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>, - #[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 From> for AnyViewHandle { view_type: TypeId::of::(), type_name: any::type_name::(), 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>, - #[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>) -> 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, HashMap>)>, + handle_backtraces: HashMap< + usize, + ( + Option<&'static str>, + HashMap>, + ), + >, } +#[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, - #[cfg(feature = "test-support")] + #[cfg(any(test, feature = "test-support"))] leak_detector: Arc>, } @@ -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(); diff --git a/crates/gpui/src/executor.rs b/crates/gpui/src/executor.rs index bfe3eff3d4..e73f403dc3 100644 --- a/crates/gpui/src/executor.rs +++ b/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, _not_send_or_sync: PhantomData>, }, + #[cfg(any(test, feature = "test-support"))] Deterministic { cx_id: usize, executor: Arc, @@ -41,9 +32,8 @@ pub enum Foreground { } pub enum Background { - Deterministic { - executor: Arc, - }, + #[cfg(any(test, feature = "test-support"))] + Deterministic { executor: Arc }, Production { executor: Arc>, _stop: channel::Sender<()>, @@ -70,39 +60,45 @@ pub enum Task { unsafe impl Send for Task {} +#[cfg(any(test, feature = "test-support"))] struct DeterministicState { - rng: StdRng, + rng: rand::prelude::StdRng, seed: u64, - scheduled_from_foreground: HashMap>, + scheduled_from_foreground: collections::HashMap>, scheduled_from_background: Vec, forbid_parking: bool, - block_on_ticks: RangeInclusive, - now: Instant, - pending_timers: Vec<(Instant, barrier::Sender)>, - waiting_backtrace: Option, + block_on_ticks: std::ops::RangeInclusive, + now: std::time::Instant, + pending_timers: Vec<(std::time::Instant, postage::barrier::Sender)>, + waiting_backtrace: Option, } +#[cfg(any(test, feature = "test-support"))] struct ForegroundRunnable { runnable: Runnable, main: bool, } +#[cfg(any(test, feature = "test-support"))] pub struct Deterministic { - state: Arc>, - parker: Mutex, + state: Arc>, + parker: parking_lot::Mutex, } +#[cfg(any(test, feature = "test-support"))] impl Deterministic { pub fn new(seed: u64) -> Arc { + 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 { + 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, + woken: Arc, mut main_task: Option<&mut AnyLocalTask>, ) -> Option> { + 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, { + 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(&self, future: impl Future + 'static) -> Task { 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(&self, future: impl Future) -> T { let future = async move { Box::new(future.await) as Box }.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) { + #[cfg(any(test, feature = "test-support"))] + pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive) { 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) { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 3e3ec60acb..706439a955 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -39,6 +39,7 @@ pub struct Window { pub(crate) last_prompt: Cell>>, } +#[cfg(any(test, feature = "test-support"))] impl ForegroundPlatform { pub(crate) fn simulate_new_path_selection( &self, diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 788084067d..c8a69c4e7d 100644 --- a/crates/gpui/src/test.rs +++ b/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; diff --git a/crates/gpui/src/util.rs b/crates/gpui/src/util.rs index 9677be56be..9e59c387e8 100644 --- a/crates/gpui/src/util.rs +++ b/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() { diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index 83bb33b756..c107175dca 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/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 } ); }