Achieve end to end channel buffer synchronization

co-authored-by: max <max@zed.dev>
This commit is contained in:
Mikayla 2023-08-22 13:25:31 -07:00
parent 95ea664725
commit 5a0315c4d5
No known key found for this signature in database
10 changed files with 425 additions and 128 deletions

View file

@ -1,11 +1,13 @@
use crate::tests::TestServer;
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
use channel::channel_buffer::ChannelBuffer;
use client::UserId;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
use std::{ops::Range, sync::Arc};
use rpc::{proto, RECEIVE_TIMEOUT};
use std::sync::Arc;
#[gpui::test]
async fn test_channel_buffers(
async fn test_core_channel_buffers(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
@ -19,60 +21,103 @@ async fn test_channel_buffers(
.make_channel("zed", (&client_a, cx_a), &mut [(&client_b, cx_b)])
.await;
// Client A joins the channel buffer
let channel_buffer_a = cx_a
.update(|cx| ChannelBuffer::for_channel(zed_id, client_a.client().to_owned(), cx))
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
.await
.unwrap();
// Client A edits the buffer
let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
edit_channel_buffer(&buffer_a, cx_a, [(0..0, "hello world")]);
edit_channel_buffer(&buffer_a, cx_a, [(5..5, ", cruel")]);
edit_channel_buffer(&buffer_a, cx_a, [(0..5, "goodbye")]);
undo_channel_buffer(&buffer_a, cx_a);
buffer_a.update(cx_a, |buffer, cx| {
buffer.edit([(0..0, "hello world")], None, cx)
});
buffer_a.update(cx_a, |buffer, cx| {
buffer.edit([(5..5, ", cruel")], None, cx)
});
buffer_a.update(cx_a, |buffer, cx| {
buffer.edit([(0..5, "goodbye")], None, cx)
});
buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
deterministic.run_until_parked();
assert_eq!(channel_buffer_text(&buffer_a, cx_a), "hello, cruel world");
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
// Client B joins the channel buffer
let channel_buffer_b = cx_b
.update(|cx| ChannelBuffer::for_channel(zed_id, client_b.client().to_owned(), cx))
.update(|cx| ChannelBuffer::join_channel(zed_id, client_b.client().to_owned(), cx))
.await
.unwrap();
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(
buffer.collaborators(),
&[client_a.user_id(), client_b.user_id()],
);
});
// Client B sees the correct text, and then edits it
let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
buffer_b.update(cx_b, |buffer, cx| {
buffer.edit([(7..12, "beautiful")], None, cx)
});
assert_eq!(channel_buffer_text(&buffer_b, cx_b), "hello, cruel world");
edit_channel_buffer(&buffer_b, cx_b, [(7..12, "beautiful")]);
// Both A and B see the new edit
deterministic.run_until_parked();
assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
// Client A closes the channel buffer.
cx_a.update(|_| drop(channel_buffer_a));
deterministic.run_until_parked();
// Client B sees that client A is gone from the channel buffer.
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
});
// Client A rejoins the channel buffer
let _channel_buffer_a = cx_a
.update(|cx| ChannelBuffer::join_channel(zed_id, client_a.client().to_owned(), cx))
.await
.unwrap();
deterministic.run_until_parked();
// Sanity test, make sure we saw A rejoining
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(
&buffer.collaborators(),
&[client_b.user_id(), client_a.user_id()],
);
});
// Client A loses connection.
server.forbid_connections();
server.disconnect_client(client_a.peer_id().unwrap());
deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
// Client B observes A disconnect
channel_buffer_b.read_with(cx_b, |buffer, _| {
assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
});
// TODO:
// - Test synchronizing offline updates, what happens to A's channel buffer?
}
#[track_caller]
fn assert_collaborators(collaborators: &[proto::Collaborator], ids: &[Option<UserId>]) {
assert_eq!(
channel_buffer_text(&buffer_a, cx_a),
"hello, beautiful world"
);
assert_eq!(
channel_buffer_text(&buffer_b, cx_b),
"hello, beautiful world"
collaborators
.into_iter()
.map(|collaborator| collaborator.user_id)
.collect::<Vec<_>>(),
ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
);
}
fn edit_channel_buffer<I>(
channel_buffer: &ModelHandle<language::Buffer>,
cx: &mut TestAppContext,
edits: I,
) where
I: IntoIterator<Item = (Range<usize>, &'static str)>,
{
channel_buffer.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
}
fn undo_channel_buffer(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) {
channel_buffer.update(cx, |buffer, cx| buffer.undo(cx));
}
fn channel_buffer_text(
channel_buffer: &ModelHandle<language::Buffer>,
cx: &mut TestAppContext,
) -> String {
fn buffer_text(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) -> String {
channel_buffer.read_with(cx, |buffer, _| buffer.text())
}