diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 9d4c7b6252..65a2504ab8 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -1,12 +1,16 @@ mod async_context; mod entity_map; mod model_context; +#[cfg(any(test, feature = "test-support"))] +mod test_context; pub use async_context::*; pub use entity_map::*; pub use model_context::*; use refineable::Refineable; use smallvec::SmallVec; +#[cfg(any(test, feature = "test-support"))] +pub use test_context::*; use crate::{ current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs new file mode 100644 index 0000000000..d31efca2c0 --- /dev/null +++ b/crates/gpui2/src/app/test_context.rs @@ -0,0 +1,137 @@ +use crate::{ + AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread, + ModelContext, Result, Task, WindowContext, +}; +use parking_lot::Mutex; +use std::{any::Any, future::Future, sync::Arc}; + +#[derive(Clone)] +pub struct TestAppContext { + pub(crate) app: Arc>, + pub(crate) executor: Executor, +} + +impl Context for TestAppContext { + type EntityContext<'a, 'w, T> = ModelContext<'a, T>; + type Result = T; + + fn entity( + &mut self, + build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T, + ) -> Self::Result> + where + T: Any + Send + Sync, + { + let mut lock = self.app.lock(); + lock.entity(build_entity) + } + + fn update_entity( + &mut self, + handle: &Handle, + update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R, + ) -> Self::Result { + let mut lock = self.app.lock(); + lock.update_entity(handle, update) + } +} + +impl TestAppContext { + pub fn refresh(&mut self) -> Result<()> { + let mut lock = self.app.lock(); + lock.refresh(); + Ok(()) + } + + pub fn executor(&self) -> &Executor { + &self.executor + } + + pub fn update(&self, f: impl FnOnce(&mut AppContext) -> R) -> Result { + let mut lock = self.app.lock(); + Ok(f(&mut *lock)) + } + + pub fn read_window( + &self, + handle: AnyWindowHandle, + update: impl FnOnce(&WindowContext) -> R, + ) -> Result { + let mut app_context = self.app.lock(); + app_context.read_window(handle.id, update) + } + + pub fn update_window( + &self, + handle: AnyWindowHandle, + update: impl FnOnce(&mut WindowContext) -> R, + ) -> Result { + let mut app = self.app.lock(); + app.update_window(handle.id, update) + } + + pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task + where + Fut: Future + Send + 'static, + R: Send + 'static, + { + let cx = self.to_async(); + self.executor.spawn(async move { f(cx).await }) + } + + pub fn spawn_on_main( + &self, + f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static, + ) -> Task + where + Fut: Future + 'static, + R: Send + 'static, + { + let cx = self.to_async(); + self.executor.spawn_on_main(|| f(cx)) + } + + pub fn run_on_main( + &self, + f: impl FnOnce(&mut MainThread) -> R + Send + 'static, + ) -> Result> + where + R: Send + 'static, + { + let mut app_context = self.app.lock(); + Ok(app_context.run_on_main(f)) + } + + pub fn has_global(&self) -> Result { + let lock = self.app.lock(); + Ok(lock.has_global::()) + } + + pub fn read_global(&self, read: impl FnOnce(&G, &AppContext) -> R) -> Result { + let lock = self.app.lock(); + Ok(read(lock.global(), &lock)) + } + + pub fn try_read_global( + &self, + read: impl FnOnce(&G, &AppContext) -> R, + ) -> Option { + let lock = self.app.lock(); + Some(read(lock.try_global()?, &lock)) + } + + pub fn update_global( + &mut self, + update: impl FnOnce(&mut G, &mut AppContext) -> R, + ) -> Result { + let mut lock = self.app.lock(); + Ok(lock.update_global(update)) + } + + fn to_async(&self) -> AsyncAppContext { + AsyncAppContext { + app: Arc::downgrade(&self.app), + executor: self.executor.clone(), + } + } +} diff --git a/crates/gpui2/src/platform/test/dispatcher.rs b/crates/gpui2/src/platform/test/dispatcher.rs index 8aef3b9cd3..7abea51746 100644 --- a/crates/gpui2/src/platform/test/dispatcher.rs +++ b/crates/gpui2/src/platform/test/dispatcher.rs @@ -1,57 +1,87 @@ use crate::PlatformDispatcher; use async_task::Runnable; -use collections::{BTreeMap, VecDeque}; +use collections::{BTreeMap, HashMap, VecDeque}; use parking_lot::Mutex; use rand::prelude::*; -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use util::post_inc; -pub struct TestDispatcher(Mutex); +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct TestDispatcherId(usize); + +pub struct TestDispatcher { + id: TestDispatcherId, + state: Arc>, +} struct TestDispatcherState { random: StdRng, - foreground: VecDeque, + foreground: HashMap>, background: Vec, delayed: BTreeMap, time: Instant, is_main_thread: bool, + next_id: TestDispatcherId, } impl TestDispatcher { pub fn new(random: StdRng) -> Self { let state = TestDispatcherState { random, - foreground: VecDeque::new(), + foreground: HashMap::default(), background: Vec::new(), delayed: BTreeMap::new(), time: Instant::now(), is_main_thread: true, + next_id: TestDispatcherId(1), }; - TestDispatcher(Mutex::new(state)) + TestDispatcher { + id: TestDispatcherId(0), + state: Arc::new(Mutex::new(state)), + } + } +} + +impl Clone for TestDispatcher { + fn clone(&self) -> Self { + let id = post_inc(&mut self.state.lock().next_id.0); + Self { + id: TestDispatcherId(id), + state: self.state.clone(), + } } } impl PlatformDispatcher for TestDispatcher { fn is_main_thread(&self) -> bool { - self.0.lock().is_main_thread + self.state.lock().is_main_thread } fn dispatch(&self, runnable: Runnable) { - self.0.lock().background.push(runnable); + self.state.lock().background.push(runnable); } fn dispatch_on_main_thread(&self, runnable: Runnable) { - self.0.lock().foreground.push_back(runnable); + self.state + .lock() + .foreground + .entry(self.id) + .or_default() + .push_back(runnable); } fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) { - let mut state = self.0.lock(); + let mut state = self.state.lock(); let next_time = state.time + duration; state.delayed.insert(next_time, runnable); } fn poll(&self) -> bool { - let mut state = self.0.lock(); + let mut state = self.state.lock(); while let Some((deadline, _)) = state.delayed.first_key_value() { if *deadline > state.time { @@ -61,36 +91,48 @@ impl PlatformDispatcher for TestDispatcher { state.background.push(runnable); } - if state.foreground.is_empty() && state.background.is_empty() { + let foreground_len: usize = state + .foreground + .values() + .map(|runnables| runnables.len()) + .sum(); + let background_len = state.background.len(); + + if foreground_len == 0 && background_len == 0 { return false; } - let foreground_len = state.foreground.len(); - let background_len = state.background.len(); - let main_thread = background_len == 0 - || state - .random - .gen_ratio(foreground_len as u32, background_len as u32); + let main_thread = state.random.gen_ratio( + foreground_len as u32, + (foreground_len + background_len) as u32, + ); let was_main_thread = state.is_main_thread; state.is_main_thread = main_thread; let runnable = if main_thread { - state.foreground.pop_front().unwrap() + let state = &mut *state; + let runnables = state + .foreground + .values_mut() + .filter(|runnables| !runnables.is_empty()) + .choose(&mut state.random) + .unwrap(); + runnables.pop_front().unwrap() } else { let ix = state.random.gen_range(0..background_len); - state.background.remove(ix) + state.background.swap_remove(ix) }; drop(state); runnable.run(); - self.0.lock().is_main_thread = was_main_thread; + self.state.lock().is_main_thread = was_main_thread; true } fn advance_clock(&self, by: Duration) { - self.0.lock().time += by; + self.state.lock().time += by; } }