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
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue