Achieve end to end channel buffer synchronization
co-authored-by: max <max@zed.dev>
This commit is contained in:
parent
95ea664725
commit
5a0315c4d5
10 changed files with 425 additions and 128 deletions
|
@ -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())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue