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