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

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