Test ordering of responses with respect to uni-directional messages

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Nathan Sobo 2022-01-12 11:19:17 -07:00
parent 66694b4c9a
commit 30225678c0
3 changed files with 279 additions and 167 deletions

1
Cargo.lock generated
View File

@ -3837,6 +3837,7 @@ dependencies = [
"async-tungstenite", "async-tungstenite",
"base64 0.13.0", "base64 0.13.0",
"futures", "futures",
"gpui",
"log", "log",
"parking_lot", "parking_lot",
"postage", "postage",

View File

@ -30,5 +30,6 @@ zstd = "0.9"
prost-build = "0.8" prost-build = "0.8"
[dev-dependencies] [dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
smol = "1.2.5" smol = "1.2.5"
tempdir = "0.3.7" tempdir = "0.3.7"

View File

@ -342,201 +342,311 @@ mod tests {
use super::*; use super::*;
use crate::TypedEnvelope; use crate::TypedEnvelope;
use async_tungstenite::tungstenite::Message as WebSocketMessage; use async_tungstenite::tungstenite::Message as WebSocketMessage;
use gpui::TestAppContext;
#[test] #[gpui::test(iterations = 10)]
fn test_request_response() { async fn test_request_response(cx: TestAppContext) {
smol::block_on(async move { let executor = cx.foreground();
// create 2 clients connected to 1 server
let server = Peer::new();
let client1 = Peer::new();
let client2 = Peer::new();
let (client1_to_server_conn, server_to_client_1_conn, _) = Connection::in_memory(); // create 2 clients connected to 1 server
let (client1_conn_id, io_task1, client1_incoming) = let server = Peer::new();
client1.add_connection(client1_to_server_conn).await; let client1 = Peer::new();
let (_, io_task2, server_incoming1) = let client2 = Peer::new();
server.add_connection(server_to_client_1_conn).await;
let (client2_to_server_conn, server_to_client_2_conn, _) = Connection::in_memory(); let (client1_to_server_conn, server_to_client_1_conn, _) = Connection::in_memory();
let (client2_conn_id, io_task3, client2_incoming) = let (client1_conn_id, io_task1, client1_incoming) =
client2.add_connection(client2_to_server_conn).await; client1.add_connection(client1_to_server_conn).await;
let (_, io_task4, server_incoming2) = let (_, io_task2, server_incoming1) = server.add_connection(server_to_client_1_conn).await;
server.add_connection(server_to_client_2_conn).await;
smol::spawn(io_task1).detach(); let (client2_to_server_conn, server_to_client_2_conn, _) = Connection::in_memory();
smol::spawn(io_task2).detach(); let (client2_conn_id, io_task3, client2_incoming) =
smol::spawn(io_task3).detach(); client2.add_connection(client2_to_server_conn).await;
smol::spawn(io_task4).detach(); let (_, io_task4, server_incoming2) = server.add_connection(server_to_client_2_conn).await;
smol::spawn(handle_messages(server_incoming1, server.clone())).detach();
smol::spawn(handle_messages(client1_incoming, client1.clone())).detach();
smol::spawn(handle_messages(server_incoming2, server.clone())).detach();
smol::spawn(handle_messages(client2_incoming, client2.clone())).detach();
assert_eq!( executor.spawn(io_task1).detach();
client1 executor.spawn(io_task2).detach();
.request(client1_conn_id, proto::Ping {},) executor.spawn(io_task3).detach();
.await executor.spawn(io_task4).detach();
.unwrap(), executor
proto::Ack {} .spawn(handle_messages(server_incoming1, server.clone()))
); .detach();
executor
.spawn(handle_messages(client1_incoming, client1.clone()))
.detach();
executor
.spawn(handle_messages(server_incoming2, server.clone()))
.detach();
executor
.spawn(handle_messages(client2_incoming, client2.clone()))
.detach();
assert_eq!( assert_eq!(
client2 client1
.request(client2_conn_id, proto::Ping {},) .request(client1_conn_id, proto::Ping {},)
.await .await
.unwrap(), .unwrap(),
proto::Ack {} proto::Ack {}
); );
assert_eq!( assert_eq!(
client1 client2
.request( .request(client2_conn_id, proto::Ping {},)
client1_conn_id, .await
proto::OpenBuffer { .unwrap(),
project_id: 0, proto::Ack {}
worktree_id: 1, );
path: "path/one".to_string(),
},
)
.await
.unwrap(),
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 101,
visible_text: "path/one content".to_string(),
..Default::default()
}),
}
);
assert_eq!( assert_eq!(
client2 client1
.request( .request(
client2_conn_id, client1_conn_id,
proto::OpenBuffer { proto::OpenBuffer {
project_id: 0, project_id: 0,
worktree_id: 2, worktree_id: 1,
path: "path/two".to_string(), path: "path/one".to_string(),
}, },
) )
.await .await
.unwrap(), .unwrap(),
proto::OpenBufferResponse { proto::OpenBufferResponse {
buffer: Some(proto::Buffer { buffer: Some(proto::Buffer {
id: 102, id: 101,
visible_text: "path/two content".to_string(), visible_text: "path/one content".to_string(),
..Default::default() ..Default::default()
}), }),
}
);
client1.disconnect(client1_conn_id);
client2.disconnect(client1_conn_id);
async fn handle_messages(
mut messages: BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
peer: Arc<Peer>,
) -> Result<()> {
while let Some(envelope) = messages.next().await {
let envelope = envelope.into_any();
if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Ping>>() {
let receipt = envelope.receipt();
peer.respond(receipt, proto::Ack {}).await?
} else if let Some(envelope) =
envelope.downcast_ref::<TypedEnvelope<proto::OpenBuffer>>()
{
let message = &envelope.payload;
let receipt = envelope.receipt();
let response = match message.path.as_str() {
"path/one" => {
assert_eq!(message.worktree_id, 1);
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 101,
visible_text: "path/one content".to_string(),
..Default::default()
}),
}
}
"path/two" => {
assert_eq!(message.worktree_id, 2);
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 102,
visible_text: "path/two content".to_string(),
..Default::default()
}),
}
}
_ => {
panic!("unexpected path {}", message.path);
}
};
peer.respond(receipt, response).await?
} else {
panic!("unknown message type");
}
}
Ok(())
} }
}); );
assert_eq!(
client2
.request(
client2_conn_id,
proto::OpenBuffer {
project_id: 0,
worktree_id: 2,
path: "path/two".to_string(),
},
)
.await
.unwrap(),
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 102,
visible_text: "path/two content".to_string(),
..Default::default()
}),
}
);
client1.disconnect(client1_conn_id);
client2.disconnect(client1_conn_id);
async fn handle_messages(
mut messages: BoxStream<'static, Box<dyn AnyTypedEnvelope>>,
peer: Arc<Peer>,
) -> Result<()> {
while let Some(envelope) = messages.next().await {
let envelope = envelope.into_any();
if let Some(envelope) = envelope.downcast_ref::<TypedEnvelope<proto::Ping>>() {
let receipt = envelope.receipt();
peer.respond(receipt, proto::Ack {}).await?
} else if let Some(envelope) =
envelope.downcast_ref::<TypedEnvelope<proto::OpenBuffer>>()
{
let message = &envelope.payload;
let receipt = envelope.receipt();
let response = match message.path.as_str() {
"path/one" => {
assert_eq!(message.worktree_id, 1);
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 101,
visible_text: "path/one content".to_string(),
..Default::default()
}),
}
}
"path/two" => {
assert_eq!(message.worktree_id, 2);
proto::OpenBufferResponse {
buffer: Some(proto::Buffer {
id: 102,
visible_text: "path/two content".to_string(),
..Default::default()
}),
}
}
_ => {
panic!("unexpected path {}", message.path);
}
};
peer.respond(receipt, response).await?
} else {
panic!("unknown message type");
}
}
Ok(())
}
} }
#[test] #[gpui::test(iterations = 10)]
fn test_disconnect() { async fn test_order_of_response_and_incoming(cx: TestAppContext) {
smol::block_on(async move { let executor = cx.foreground();
let (client_conn, mut server_conn, _) = Connection::in_memory(); let server = Peer::new();
let client = Peer::new();
let client = Peer::new(); let (client_to_server_conn, server_to_client_conn, _) = Connection::in_memory();
let (connection_id, io_handler, mut incoming) = let (client_to_server_conn_id, io_task1, mut client_incoming) =
client.add_connection(client_conn).await; client.add_connection(client_to_server_conn).await;
let (server_to_client_conn_id, io_task2, mut server_incoming) =
server.add_connection(server_to_client_conn).await;
let (mut io_ended_tx, mut io_ended_rx) = postage::barrier::channel(); executor.spawn(io_task1).detach();
smol::spawn(async move { executor.spawn(io_task2).detach();
executor
.spawn(async move {
let request = server_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Ping>>()
.unwrap();
server
.send(
server_to_client_conn_id,
proto::Error {
message: "message 1".to_string(),
},
)
.await
.unwrap();
server
.send(
server_to_client_conn_id,
proto::Error {
message: "message 2".to_string(),
},
)
.await
.unwrap();
server
.respond(request.receipt(), proto::Ack {})
.await
.unwrap();
// Prevent the connection from being dropped
server_incoming.next().await;
})
.detach();
let events = Arc::new(Mutex::new(Vec::new()));
let response = client.request(client_to_server_conn_id, proto::Ping {});
let response_task = executor.spawn({
let events = events.clone();
async move {
response.await.unwrap();
events.lock().push("response".to_string());
}
});
executor
.spawn({
let events = events.clone();
async move {
let incoming1 = client_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Error>>()
.unwrap();
events.lock().push(incoming1.payload.message);
let incoming2 = client_incoming
.next()
.await
.unwrap()
.into_any()
.downcast::<TypedEnvelope<proto::Error>>()
.unwrap();
events.lock().push(incoming2.payload.message);
// Prevent the connection from being dropped
client_incoming.next().await;
}
})
.detach();
response_task.await;
assert_eq!(
&*events.lock(),
&[
"message 1".to_string(),
"message 2".to_string(),
"response".to_string()
]
);
}
#[gpui::test(iterations = 10)]
async fn test_disconnect(cx: TestAppContext) {
let executor = cx.foreground();
let (client_conn, mut server_conn, _) = Connection::in_memory();
let client = Peer::new();
let (connection_id, io_handler, mut incoming) = client.add_connection(client_conn).await;
let (mut io_ended_tx, mut io_ended_rx) = postage::barrier::channel();
executor
.spawn(async move {
io_handler.await.ok(); io_handler.await.ok();
io_ended_tx.send(()).await.unwrap(); io_ended_tx.send(()).await.unwrap();
}) })
.detach(); .detach();
let (mut messages_ended_tx, mut messages_ended_rx) = postage::barrier::channel(); let (mut messages_ended_tx, mut messages_ended_rx) = postage::barrier::channel();
smol::spawn(async move { executor
.spawn(async move {
incoming.next().await; incoming.next().await;
messages_ended_tx.send(()).await.unwrap(); messages_ended_tx.send(()).await.unwrap();
}) })
.detach(); .detach();
client.disconnect(connection_id); client.disconnect(connection_id);
io_ended_rx.recv().await; io_ended_rx.recv().await;
messages_ended_rx.recv().await; messages_ended_rx.recv().await;
assert!(server_conn assert!(server_conn
.send(WebSocketMessage::Binary(vec![])) .send(WebSocketMessage::Binary(vec![]))
.await .await
.is_err()); .is_err());
});
} }
#[test] #[gpui::test(iterations = 10)]
fn test_io_error() { async fn test_io_error(cx: TestAppContext) {
smol::block_on(async move { let executor = cx.foreground();
let (client_conn, mut server_conn, _) = Connection::in_memory(); let (client_conn, mut server_conn, _) = Connection::in_memory();
let client = Peer::new(); let client = Peer::new();
let (connection_id, io_handler, mut incoming) = let (connection_id, io_handler, mut incoming) = client.add_connection(client_conn).await;
client.add_connection(client_conn).await; executor.spawn(io_handler).detach();
smol::spawn(io_handler).detach(); executor
smol::spawn(async move { incoming.next().await }).detach(); .spawn(async move { incoming.next().await })
.detach();
let response = smol::spawn(client.request(connection_id, proto::Ping {})); let response = executor.spawn(client.request(connection_id, proto::Ping {}));
let _request = server_conn.rx.next().await.unwrap().unwrap(); let _request = server_conn.rx.next().await.unwrap().unwrap();
drop(server_conn); drop(server_conn);
assert_eq!( assert_eq!(
response.await.unwrap_err().to_string(), response.await.unwrap_err().to_string(),
"connection was closed" "connection was closed"
); );
});
} }
} }