Enable client tests

* implement Executor::advance_clock

Co-authored-by: Conrad <conrad@zed.dev>
Co-authored-by:  Kyle <kyle@zed.dev>
Co-authored-by:  Joseph <joseph@zed.dev>
This commit is contained in:
Max Brunsfeld 2023-10-26 14:43:28 +02:00
parent 0eafb8886d
commit 69e5ecc015
6 changed files with 479 additions and 465 deletions

View file

@ -1377,290 +1377,275 @@ pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> {
Some((id, access_token.to_string())) Some((id, access_token.to_string()))
} }
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
// use super::*; use super::*;
// use crate::test::FakeServer; use crate::test::FakeServer;
// use gpui::{executor::Deterministic, TestAppContext};
// use parking_lot::Mutex;
// use std::future;
// use util::http::FakeHttpClient;
// #[gpui::test(iterations = 10)] use gpui2::{Context, Executor, TestAppContext};
// async fn test_reconnection(cx: &mut TestAppContext) { use parking_lot::Mutex;
// cx.foreground().forbid_parking(); use std::future;
use util::http::FakeHttpClient;
// let user_id = 5; #[gpui2::test(iterations = 10)]
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); async fn test_reconnection(cx: &mut TestAppContext) {
// let server = FakeServer::for_client(user_id, &client, cx).await; let user_id = 5;
// let mut status = client.status(); let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// assert!(matches!( let server = FakeServer::for_client(user_id, &client, cx).await;
// status.next().await, let mut status = client.status();
// Some(Status::Connected { .. }) assert!(matches!(
// )); status.next().await,
// assert_eq!(server.auth_count(), 1); Some(Status::Connected { .. })
));
assert_eq!(server.auth_count(), 1);
// server.forbid_connections(); server.forbid_connections();
// server.disconnect(); server.disconnect();
// while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {} while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// server.allow_connections(); server.allow_connections();
// cx.foreground().advance_clock(Duration::from_secs(10)); cx.executor().advance_clock(Duration::from_secs(10));
// while !matches!(status.next().await, Some(Status::Connected { .. })) {} while !matches!(status.next().await, Some(Status::Connected { .. })) {}
// assert_eq!(server.auth_count(), 1); // Client reused the cached credentials when reconnecting assert_eq!(server.auth_count(), 1); // Client reused the cached credentials when reconnecting
// server.forbid_connections(); server.forbid_connections();
// server.disconnect(); server.disconnect();
// while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {} while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// // Clear cached credentials after authentication fails // Clear cached credentials after authentication fails
// server.roll_access_token(); server.roll_access_token();
// server.allow_connections(); server.allow_connections();
// cx.foreground().advance_clock(Duration::from_secs(10)); cx.executor().run_until_parked();
// while !matches!(status.next().await, Some(Status::Connected { .. })) {} cx.executor().advance_clock(Duration::from_secs(10));
// assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token while !matches!(status.next().await, Some(Status::Connected { .. })) {}
// } assert_eq!(server.auth_count(), 2); // Client re-authenticated due to an invalid token
}
// #[gpui::test(iterations = 10)] #[gpui2::test(iterations = 10)]
// async fn test_connection_timeout(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) { async fn test_connection_timeout(executor: Executor, cx: &mut TestAppContext) {
// deterministic.forbid_parking(); let user_id = 5;
let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
let mut status = client.status();
// let user_id = 5; // Time out when client tries to connect.
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); client.override_authenticate(move |cx| {
// let mut status = client.status(); cx.executor().spawn(async move {
Ok(Credentials {
user_id,
access_token: "token".into(),
})
})
});
client.override_establish_connection(|_, cx| {
cx.executor().spawn(async move {
future::pending::<()>().await;
unreachable!()
})
});
let auth_and_connect = cx.spawn({
let client = client.clone();
|cx| async move { client.authenticate_and_connect(false, &cx).await }
});
executor.run_until_parked();
assert!(matches!(status.next().await, Some(Status::Connecting)));
// // Time out when client tries to connect. executor.advance_clock(CONNECTION_TIMEOUT);
// client.override_authenticate(move |cx| { assert!(matches!(
// cx.foreground().spawn(async move { status.next().await,
// Ok(Credentials { Some(Status::ConnectionError { .. })
// user_id, ));
// access_token: "token".into(), auth_and_connect.await.unwrap_err();
// })
// })
// });
// client.override_establish_connection(|_, cx| {
// cx.foreground().spawn(async move {
// future::pending::<()>().await;
// unreachable!()
// })
// });
// let auth_and_connect = cx.spawn({
// let client = client.clone();
// |cx| async move { client.authenticate_and_connect(false, &cx).await }
// });
// deterministic.run_until_parked();
// assert!(matches!(status.next().await, Some(Status::Connecting)));
// deterministic.advance_clock(CONNECTION_TIMEOUT); // Allow the connection to be established.
// assert!(matches!( let server = FakeServer::for_client(user_id, &client, cx).await;
// status.next().await, assert!(matches!(
// Some(Status::ConnectionError { .. }) status.next().await,
// )); Some(Status::Connected { .. })
// auth_and_connect.await.unwrap_err(); ));
// // Allow the connection to be established. // Disconnect client.
// let server = FakeServer::for_client(user_id, &client, cx).await; server.forbid_connections();
// assert!(matches!( server.disconnect();
// status.next().await, while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {}
// Some(Status::Connected { .. })
// ));
// // Disconnect client. // Time out when re-establishing the connection.
// server.forbid_connections(); server.allow_connections();
// server.disconnect(); client.override_establish_connection(|_, cx| {
// while !matches!(status.next().await, Some(Status::ReconnectionError { .. })) {} cx.executor().spawn(async move {
future::pending::<()>().await;
unreachable!()
})
});
executor.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
assert!(matches!(
status.next().await,
Some(Status::Reconnecting { .. })
));
// // Time out when re-establishing the connection. executor.advance_clock(CONNECTION_TIMEOUT);
// server.allow_connections(); assert!(matches!(
// client.override_establish_connection(|_, cx| { status.next().await,
// cx.foreground().spawn(async move { Some(Status::ReconnectionError { .. })
// future::pending::<()>().await; ));
// unreachable!() }
// })
// });
// deterministic.advance_clock(2 * INITIAL_RECONNECTION_DELAY);
// assert!(matches!(
// status.next().await,
// Some(Status::Reconnecting { .. })
// ));
// deterministic.advance_clock(CONNECTION_TIMEOUT); #[gpui2::test(iterations = 10)]
// assert!(matches!( async fn test_authenticating_more_than_once(cx: &mut TestAppContext, executor: Executor) {
// status.next().await, let auth_count = Arc::new(Mutex::new(0));
// Some(Status::ReconnectionError { .. }) let dropped_auth_count = Arc::new(Mutex::new(0));
// )); let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// } client.override_authenticate({
let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone();
move |cx| {
let auth_count = auth_count.clone();
let dropped_auth_count = dropped_auth_count.clone();
cx.executor().spawn(async move {
*auth_count.lock() += 1;
let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
future::pending::<()>().await;
unreachable!()
})
}
});
// #[gpui::test(iterations = 10)] let _authenticate = cx.spawn({
// async fn test_authenticating_more_than_once( let client = client.clone();
// cx: &mut TestAppContext, move |cx| async move { client.authenticate_and_connect(false, &cx).await }
// deterministic: Arc<Deterministic>, });
// ) { executor.run_until_parked();
// cx.foreground().forbid_parking(); assert_eq!(*auth_count.lock(), 1);
assert_eq!(*dropped_auth_count.lock(), 0);
// let auth_count = Arc::new(Mutex::new(0)); let _authenticate = cx.spawn({
// let dropped_auth_count = Arc::new(Mutex::new(0)); let client = client.clone();
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); |cx| async move { client.authenticate_and_connect(false, &cx).await }
// client.override_authenticate({ });
// let auth_count = auth_count.clone(); executor.run_until_parked();
// let dropped_auth_count = dropped_auth_count.clone(); assert_eq!(*auth_count.lock(), 2);
// move |cx| { assert_eq!(*dropped_auth_count.lock(), 1);
// let auth_count = auth_count.clone(); }
// let dropped_auth_count = dropped_auth_count.clone();
// cx.foreground().spawn(async move {
// *auth_count.lock() += 1;
// let _drop = util::defer(move || *dropped_auth_count.lock() += 1);
// future::pending::<()>().await;
// unreachable!()
// })
// }
// });
// let _authenticate = cx.spawn(|cx| { #[test]
// let client = client.clone(); fn test_encode_and_decode_worktree_url() {
// async move { client.authenticate_and_connect(false, &cx).await } let url = encode_worktree_url(5, "deadbeef");
// }); assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string())));
// deterministic.run_until_parked(); assert_eq!(
// assert_eq!(*auth_count.lock(), 1); decode_worktree_url(&format!("\n {}\t", url)),
// assert_eq!(*dropped_auth_count.lock(), 0); Some((5, "deadbeef".to_string()))
);
assert_eq!(decode_worktree_url("not://the-right-format"), None);
}
// let _authenticate = cx.spawn(|cx| { #[gpui2::test]
// let client = client.clone(); async fn test_subscribing_to_entity(cx: &mut TestAppContext) {
// async move { client.authenticate_and_connect(false, &cx).await } let user_id = 5;
// }); let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// deterministic.run_until_parked(); let server = FakeServer::for_client(user_id, &client, cx).await;
// assert_eq!(*auth_count.lock(), 2);
// assert_eq!(*dropped_auth_count.lock(), 1);
// }
// #[test] let (done_tx1, mut done_rx1) = smol::channel::unbounded();
// fn test_encode_and_decode_worktree_url() { let (done_tx2, mut done_rx2) = smol::channel::unbounded();
// let url = encode_worktree_url(5, "deadbeef"); client.add_model_message_handler(
// assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string()))); move |model: Handle<Model>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
// assert_eq!( match model.update(&mut cx, |model, _| model.id).unwrap() {
// decode_worktree_url(&format!("\n {}\t", url)), 1 => done_tx1.try_send(()).unwrap(),
// Some((5, "deadbeef".to_string())) 2 => done_tx2.try_send(()).unwrap(),
// ); _ => unreachable!(),
// assert_eq!(decode_worktree_url("not://the-right-format"), None); }
// } async { Ok(()) }
},
);
let model1 = cx.entity(|_| Model {
id: 1,
subscription: None,
});
let model2 = cx.entity(|_| Model {
id: 2,
subscription: None,
});
let model3 = cx.entity(|_| Model {
id: 3,
subscription: None,
});
// #[gpui::test] let _subscription1 = client
// async fn test_subscribing_to_entity(cx: &mut TestAppContext) { .subscribe_to_entity(1)
// cx.foreground().forbid_parking(); .unwrap()
.set_model(&model1, &mut cx.to_async());
let _subscription2 = client
.subscribe_to_entity(2)
.unwrap()
.set_model(&model2, &mut cx.to_async());
// Ensure dropping a subscription for the same entity type still allows receiving of
// messages for other entity IDs of the same type.
let subscription3 = client
.subscribe_to_entity(3)
.unwrap()
.set_model(&model3, &mut cx.to_async());
drop(subscription3);
// let user_id = 5; server.send(proto::JoinProject { project_id: 1 });
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); server.send(proto::JoinProject { project_id: 2 });
// let server = FakeServer::for_client(user_id, &client, cx).await; done_rx1.next().await.unwrap();
done_rx2.next().await.unwrap();
}
// let (done_tx1, mut done_rx1) = smol::channel::unbounded(); #[gpui2::test]
// let (done_tx2, mut done_rx2) = smol::channel::unbounded(); async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) {
// client.add_model_message_handler( let user_id = 5;
// move |model: ModelHandle<Model>, _: TypedEnvelope<proto::JoinProject>, _, cx| { let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// match model.read_with(&cx, |model, _| model.id) { let server = FakeServer::for_client(user_id, &client, cx).await;
// 1 => done_tx1.try_send(()).unwrap(),
// 2 => done_tx2.try_send(()).unwrap(),
// _ => unreachable!(),
// }
// async { Ok(()) }
// },
// );
// let model1 = cx.add_model(|_| Model {
// id: 1,
// subscription: None,
// });
// let model2 = cx.add_model(|_| Model {
// id: 2,
// subscription: None,
// });
// let model3 = cx.add_model(|_| Model {
// id: 3,
// subscription: None,
// });
// let _subscription1 = client let model = cx.entity(|_| Model::default());
// .subscribe_to_entity(1) let (done_tx1, _done_rx1) = smol::channel::unbounded();
// .unwrap() let (done_tx2, mut done_rx2) = smol::channel::unbounded();
// .set_model(&model1, &mut cx.to_async()); let subscription1 = client.add_message_handler(
// let _subscription2 = client model.downgrade(),
// .subscribe_to_entity(2) move |_, _: TypedEnvelope<proto::Ping>, _, _| {
// .unwrap() done_tx1.try_send(()).unwrap();
// .set_model(&model2, &mut cx.to_async()); async { Ok(()) }
// // Ensure dropping a subscription for the same entity type still allows receiving of },
// // messages for other entity IDs of the same type. );
// let subscription3 = client drop(subscription1);
// .subscribe_to_entity(3) let _subscription2 = client.add_message_handler(
// .unwrap() model.downgrade(),
// .set_model(&model3, &mut cx.to_async()); move |_, _: TypedEnvelope<proto::Ping>, _, _| {
// drop(subscription3); done_tx2.try_send(()).unwrap();
async { Ok(()) }
},
);
server.send(proto::Ping {});
done_rx2.next().await.unwrap();
}
// server.send(proto::JoinProject { project_id: 1 }); #[gpui2::test]
// server.send(proto::JoinProject { project_id: 2 }); async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
// done_rx1.next().await.unwrap(); let user_id = 5;
// done_rx2.next().await.unwrap(); let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// } let server = FakeServer::for_client(user_id, &client, cx).await;
// #[gpui::test] let model = cx.entity(|_| Model::default());
// async fn test_subscribing_after_dropping_subscription(cx: &mut TestAppContext) { let (done_tx, mut done_rx) = smol::channel::unbounded();
// cx.foreground().forbid_parking(); let subscription = client.add_message_handler(
model.clone().downgrade(),
move |model: Handle<Model>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
model
.update(&mut cx, |model, _| model.subscription.take())
.unwrap();
done_tx.try_send(()).unwrap();
async { Ok(()) }
},
);
model.update(cx, |model, _| {
model.subscription = Some(subscription);
});
server.send(proto::Ping {});
done_rx.next().await.unwrap();
}
// let user_id = 5; #[derive(Default)]
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx)); struct Model {
// let server = FakeServer::for_client(user_id, &client, cx).await; id: usize,
subscription: Option<Subscription>,
// let model = cx.add_model(|_| Model::default()); }
// let (done_tx1, _done_rx1) = smol::channel::unbounded(); }
// let (done_tx2, mut done_rx2) = smol::channel::unbounded();
// let subscription1 = client.add_message_handler(
// model.clone(),
// move |_, _: TypedEnvelope<proto::Ping>, _, _| {
// done_tx1.try_send(()).unwrap();
// async { Ok(()) }
// },
// );
// drop(subscription1);
// let _subscription2 = client.add_message_handler(
// model.clone(),
// move |_, _: TypedEnvelope<proto::Ping>, _, _| {
// done_tx2.try_send(()).unwrap();
// async { Ok(()) }
// },
// );
// server.send(proto::Ping {});
// done_rx2.next().await.unwrap();
// }
// #[gpui::test]
// async fn test_dropping_subscription_in_handler(cx: &mut TestAppContext) {
// cx.foreground().forbid_parking();
// let user_id = 5;
// let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
// let server = FakeServer::for_client(user_id, &client, cx).await;
// let model = cx.add_model(|_| Model::default());
// let (done_tx, mut done_rx) = smol::channel::unbounded();
// let subscription = client.add_message_handler(
// model.clone(),
// move |model, _: TypedEnvelope<proto::Ping>, _, mut cx| {
// model.update(&mut cx, |model, _| model.subscription.take());
// done_tx.try_send(()).unwrap();
// async { Ok(()) }
// },
// );
// model.update(cx, |model, _| {
// model.subscription = Some(subscription);
// });
// server.send(proto::Ping {});
// done_rx.next().await.unwrap();
// }
// #[derive(Default)]
// struct Model {
// id: usize,
// subscription: Option<Subscription>,
// }
// impl Entity for Model {
// type Event = ();
// }
// }

View file

@ -1,215 +1,216 @@
// use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore}; use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
// use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
// use futures::{stream::BoxStream, StreamExt}; use futures::{stream::BoxStream, StreamExt};
// use gpui2::{Executor, Handle, TestAppContext}; use gpui2::{Context, Executor, Handle, TestAppContext};
// use parking_lot::Mutex; use parking_lot::Mutex;
// use rpc::{ use rpc2::{
// proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse}, proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
// ConnectionId, Peer, Receipt, TypedEnvelope, ConnectionId, Peer, Receipt, TypedEnvelope,
// }; };
// use std::{rc::Rc, sync::Arc}; use std::sync::Arc;
// use util::http::FakeHttpClient; use util::http::FakeHttpClient;
// pub struct FakeServer { pub struct FakeServer {
// peer: Arc<Peer>, peer: Arc<Peer>,
// state: Arc<Mutex<FakeServerState>>, state: Arc<Mutex<FakeServerState>>,
// user_id: u64, user_id: u64,
// executor: Executor, executor: Executor,
// } }
// #[derive(Default)] #[derive(Default)]
// struct FakeServerState { struct FakeServerState {
// incoming: Option<BoxStream<'static, Box<dyn proto::AnyTypedEnvelope>>>, incoming: Option<BoxStream<'static, Box<dyn proto::AnyTypedEnvelope>>>,
// connection_id: Option<ConnectionId>, connection_id: Option<ConnectionId>,
// forbid_connections: bool, forbid_connections: bool,
// auth_count: usize, auth_count: usize,
// access_token: usize, access_token: usize,
// } }
// impl FakeServer { impl FakeServer {
// pub async fn for_client( pub async fn for_client(
// client_user_id: u64, client_user_id: u64,
// client: &Arc<Client>, client: &Arc<Client>,
// cx: &TestAppContext, cx: &TestAppContext,
// ) -> Self { ) -> Self {
// let server = Self { let server = Self {
// peer: Peer::new(0), peer: Peer::new(0),
// state: Default::default(), state: Default::default(),
// user_id: client_user_id, user_id: client_user_id,
// executor: cx.foreground(), executor: cx.executor().clone(),
// }; };
// client client
// .override_authenticate({ .override_authenticate({
// let state = Arc::downgrade(&server.state); let state = Arc::downgrade(&server.state);
// move |cx| { move |cx| {
// let state = state.clone(); let state = state.clone();
// cx.spawn(move |_| async move { cx.spawn(move |_| async move {
// let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?; let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
// let mut state = state.lock(); let mut state = state.lock();
// state.auth_count += 1; state.auth_count += 1;
// let access_token = state.access_token.to_string(); let access_token = state.access_token.to_string();
// Ok(Credentials { Ok(Credentials {
// user_id: client_user_id, user_id: client_user_id,
// access_token, access_token,
// }) })
// }) })
// } }
// }) })
// .override_establish_connection({ .override_establish_connection({
// let peer = Arc::downgrade(&server.peer); let peer = Arc::downgrade(&server.peer);
// let state = Arc::downgrade(&server.state); let state = Arc::downgrade(&server.state);
// move |credentials, cx| { move |credentials, cx| {
// let peer = peer.clone(); let peer = peer.clone();
// let state = state.clone(); let state = state.clone();
// let credentials = credentials.clone(); let credentials = credentials.clone();
// cx.spawn(move |cx| async move { cx.spawn(move |cx| async move {
// let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?; let state = state.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
// let peer = peer.upgrade().ok_or_else(|| anyhow!("server dropped"))?; let peer = peer.upgrade().ok_or_else(|| anyhow!("server dropped"))?;
// if state.lock().forbid_connections { if state.lock().forbid_connections {
// Err(EstablishConnectionError::Other(anyhow!( Err(EstablishConnectionError::Other(anyhow!(
// "server is forbidding connections" "server is forbidding connections"
// )))? )))?
// } }
// assert_eq!(credentials.user_id, client_user_id); assert_eq!(credentials.user_id, client_user_id);
// if credentials.access_token != state.lock().access_token.to_string() { if credentials.access_token != state.lock().access_token.to_string() {
// Err(EstablishConnectionError::Unauthorized)? Err(EstablishConnectionError::Unauthorized)?
// } }
// let (client_conn, server_conn, _) = Connection::in_memory(cx.background()); let (client_conn, server_conn, _) =
// let (connection_id, io, incoming) = Connection::in_memory(cx.executor().clone());
// peer.add_test_connection(server_conn, cx.background()); let (connection_id, io, incoming) =
// cx.background().spawn(io).detach(); peer.add_test_connection(server_conn, cx.executor().clone());
// { cx.executor().spawn(io).detach();
// let mut state = state.lock(); {
// state.connection_id = Some(connection_id); let mut state = state.lock();
// state.incoming = Some(incoming); state.connection_id = Some(connection_id);
// } state.incoming = Some(incoming);
// peer.send( }
// connection_id, peer.send(
// proto::Hello { connection_id,
// peer_id: Some(connection_id.into()), proto::Hello {
// }, peer_id: Some(connection_id.into()),
// ) },
// .unwrap(); )
.unwrap();
// Ok(client_conn) Ok(client_conn)
// }) })
// } }
// }); });
// client client
// .authenticate_and_connect(false, &cx.to_async()) .authenticate_and_connect(false, &cx.to_async())
// .await .await
// .unwrap(); .unwrap();
// server server
// } }
// pub fn disconnect(&self) { pub fn disconnect(&self) {
// if self.state.lock().connection_id.is_some() { if self.state.lock().connection_id.is_some() {
// self.peer.disconnect(self.connection_id()); self.peer.disconnect(self.connection_id());
// let mut state = self.state.lock(); let mut state = self.state.lock();
// state.connection_id.take(); state.connection_id.take();
// state.incoming.take(); state.incoming.take();
// } }
// } }
// pub fn auth_count(&self) -> usize { pub fn auth_count(&self) -> usize {
// self.state.lock().auth_count self.state.lock().auth_count
// } }
// pub fn roll_access_token(&self) { pub fn roll_access_token(&self) {
// self.state.lock().access_token += 1; self.state.lock().access_token += 1;
// } }
// pub fn forbid_connections(&self) { pub fn forbid_connections(&self) {
// self.state.lock().forbid_connections = true; self.state.lock().forbid_connections = true;
// } }
// pub fn allow_connections(&self) { pub fn allow_connections(&self) {
// self.state.lock().forbid_connections = false; self.state.lock().forbid_connections = false;
// } }
// pub fn send<T: proto::EnvelopedMessage>(&self, message: T) { pub fn send<T: proto::EnvelopedMessage>(&self, message: T) {
// self.peer.send(self.connection_id(), message).unwrap(); self.peer.send(self.connection_id(), message).unwrap();
// } }
// #[allow(clippy::await_holding_lock)] #[allow(clippy::await_holding_lock)]
// pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> { pub async fn receive<M: proto::EnvelopedMessage>(&self) -> Result<TypedEnvelope<M>> {
// self.executor.start_waiting(); self.executor.start_waiting();
// loop { loop {
// let message = self let message = self
// .state .state
// .lock() .lock()
// .incoming .incoming
// .as_mut() .as_mut()
// .expect("not connected") .expect("not connected")
// .next() .next()
// .await .await
// .ok_or_else(|| anyhow!("other half hung up"))?; .ok_or_else(|| anyhow!("other half hung up"))?;
// self.executor.finish_waiting(); self.executor.finish_waiting();
// let type_name = message.payload_type_name(); let type_name = message.payload_type_name();
// let message = message.into_any(); let message = message.into_any();
// if message.is::<TypedEnvelope<M>>() { if message.is::<TypedEnvelope<M>>() {
// return Ok(*message.downcast().unwrap()); return Ok(*message.downcast().unwrap());
// } }
// if message.is::<TypedEnvelope<GetPrivateUserInfo>>() { if message.is::<TypedEnvelope<GetPrivateUserInfo>>() {
// self.respond( self.respond(
// message message
// .downcast::<TypedEnvelope<GetPrivateUserInfo>>() .downcast::<TypedEnvelope<GetPrivateUserInfo>>()
// .unwrap() .unwrap()
// .receipt(), .receipt(),
// GetPrivateUserInfoResponse { GetPrivateUserInfoResponse {
// metrics_id: "the-metrics-id".into(), metrics_id: "the-metrics-id".into(),
// staff: false, staff: false,
// flags: Default::default(), flags: Default::default(),
// }, },
// ); );
// continue; continue;
// } }
// panic!( panic!(
// "fake server received unexpected message type: {:?}", "fake server received unexpected message type: {:?}",
// type_name type_name
// ); );
// } }
// } }
// pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) { pub fn respond<T: proto::RequestMessage>(&self, receipt: Receipt<T>, response: T::Response) {
// self.peer.respond(receipt, response).unwrap() self.peer.respond(receipt, response).unwrap()
// } }
// fn connection_id(&self) -> ConnectionId { fn connection_id(&self) -> ConnectionId {
// self.state.lock().connection_id.expect("not connected") self.state.lock().connection_id.expect("not connected")
// } }
// pub async fn build_user_store( pub async fn build_user_store(
// &self, &self,
// client: Arc<Client>, client: Arc<Client>,
// cx: &mut TestAppContext, cx: &mut TestAppContext,
// ) -> ModelHandle<UserStore> { ) -> Handle<UserStore> {
// let http_client = FakeHttpClient::with_404_response(); let http_client = FakeHttpClient::with_404_response();
// let user_store = cx.add_model(|cx| UserStore::new(client, http_client, cx)); let user_store = cx.entity(|cx| UserStore::new(client, http_client, cx));
// assert_eq!( assert_eq!(
// self.receive::<proto::GetUsers>() self.receive::<proto::GetUsers>()
// .await .await
// .unwrap() .unwrap()
// .payload .payload
// .user_ids, .user_ids,
// &[self.user_id] &[self.user_id]
// ); );
// user_store user_store
// } }
// } }
// impl Drop for FakeServer { impl Drop for FakeServer {
// fn drop(&mut self) { fn drop(&mut self) {
// self.disconnect(); self.disconnect();
// } }
// } }

View file

@ -143,7 +143,7 @@ impl TestAppContext {
lock.update_global(update) lock.update_global(update)
} }
fn to_async(&self) -> AsyncAppContext { pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext { AsyncAppContext {
app: Arc::downgrade(&self.app), app: Arc::downgrade(&self.app),
executor: self.executor.clone(), executor: self.executor.clone(),

View file

@ -146,7 +146,10 @@ impl Executor {
Poll::Ready(result) => return result, Poll::Ready(result) => return result,
Poll::Pending => { Poll::Pending => {
if !self.dispatcher.poll() { if !self.dispatcher.poll() {
// todo!("forbid_parking") #[cfg(any(test, feature = "test-support"))]
if let Some(_) = self.dispatcher.as_test() {
panic!("blocked with nothing left to run")
}
parker.park(); parker.park();
} }
} }
@ -206,11 +209,26 @@ impl Executor {
todo!("start_waiting") todo!("start_waiting")
} }
#[cfg(any(test, feature = "test-support"))]
pub fn finish_waiting(&self) {
todo!("finish_waiting")
}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> { pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
self.dispatcher.as_test().unwrap().simulate_random_delay() self.dispatcher.as_test().unwrap().simulate_random_delay()
} }
#[cfg(any(test, feature = "test-support"))]
pub fn advance_clock(&self, duration: Duration) {
self.dispatcher.as_test().unwrap().advance_clock(duration)
}
#[cfg(any(test, feature = "test-support"))]
pub fn run_until_parked(&self) {
self.dispatcher.as_test().unwrap().run_until_parked()
}
pub fn num_cpus(&self) -> usize { pub fn num_cpus(&self) -> usize {
num_cpus::get() num_cpus::get()
} }

View file

@ -8,7 +8,7 @@ use std::{
pin::Pin, pin::Pin,
sync::Arc, sync::Arc,
task::{Context, Poll}, task::{Context, Poll},
time::{Duration, Instant}, time::Duration,
}; };
use util::post_inc; use util::post_inc;
@ -24,8 +24,8 @@ struct TestDispatcherState {
random: StdRng, random: StdRng,
foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>, foreground: HashMap<TestDispatcherId, VecDeque<Runnable>>,
background: Vec<Runnable>, background: Vec<Runnable>,
delayed: Vec<(Instant, Runnable)>, delayed: Vec<(Duration, Runnable)>,
time: Instant, time: Duration,
is_main_thread: bool, is_main_thread: bool,
next_id: TestDispatcherId, next_id: TestDispatcherId,
} }
@ -37,7 +37,7 @@ impl TestDispatcher {
foreground: HashMap::default(), foreground: HashMap::default(),
background: Vec::new(), background: Vec::new(),
delayed: Vec::new(), delayed: Vec::new(),
time: Instant::now(), time: Duration::ZERO,
is_main_thread: true, is_main_thread: true,
next_id: TestDispatcherId(1), next_id: TestDispatcherId(1),
}; };
@ -49,7 +49,21 @@ impl TestDispatcher {
} }
pub fn advance_clock(&self, by: Duration) { pub fn advance_clock(&self, by: Duration) {
self.state.lock().time += by; let new_now = self.state.lock().time + by;
loop {
self.run_until_parked();
let state = self.state.lock();
let next_due_time = state.delayed.first().map(|(time, _)| *time);
drop(state);
if let Some(due_time) = next_due_time {
if due_time <= new_now {
self.state.lock().time = due_time;
continue;
}
}
break;
}
self.state.lock().time = new_now;
} }
pub fn simulate_random_delay(&self) -> impl Future<Output = ()> { pub fn simulate_random_delay(&self) -> impl Future<Output = ()> {
@ -137,10 +151,8 @@ impl PlatformDispatcher for TestDispatcher {
let background_len = state.background.len(); let background_len = state.background.len();
if foreground_len == 0 && background_len == 0 { if foreground_len == 0 && background_len == 0 {
eprintln!("no runnables to poll");
return false; return false;
} }
eprintln!("runnables {} {}", foreground_len, background_len);
let main_thread = state.random.gen_ratio( let main_thread = state.random.gen_ratio(
foreground_len as u32, foreground_len as u32,
@ -150,7 +162,6 @@ impl PlatformDispatcher for TestDispatcher {
state.is_main_thread = main_thread; state.is_main_thread = main_thread;
let runnable = if main_thread { let runnable = if main_thread {
eprintln!("running next main thread");
let state = &mut *state; let state = &mut *state;
let runnables = state let runnables = state
.foreground .foreground
@ -161,7 +172,6 @@ impl PlatformDispatcher for TestDispatcher {
runnables.pop_front().unwrap() runnables.pop_front().unwrap()
} else { } else {
let ix = state.random.gen_range(0..background_len); let ix = state.random.gen_range(0..background_len);
eprintln!("running background thread {ix}");
state.background.swap_remove(ix) state.background.swap_remove(ix)
}; };

View file

@ -173,14 +173,14 @@ impl Platform for TestPlatform {
} }
fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> { fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Result<()> {
unimplemented!() Ok(())
} }
fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> { fn read_credentials(&self, _url: &str) -> Result<Option<(String, Vec<u8>)>> {
unimplemented!() Ok(None)
} }
fn delete_credentials(&self, _url: &str) -> Result<()> { fn delete_credentials(&self, _url: &str) -> Result<()> {
unimplemented!() Ok(())
} }
} }