mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-10 20:41:59 +00:00
Make TestAppContext
and its dependencies available only in tests
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
0d6f6bf5bb
commit
83a3402235
7 changed files with 161 additions and 98 deletions
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue