Make TestAppContext and its dependencies available only in tests

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2022-03-01 17:01:52 +01:00
parent 0d6f6bf5bb
commit 83a3402235
7 changed files with 161 additions and 98 deletions

View file

@ -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"

View file

@ -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();

View file

@ -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) {

View file

@ -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,

View file

@ -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;

View file

@ -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() {

View file

@ -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
}
);
}