288 lines · 10.5 KB · Rust
RawDownload
use crate::db::ChannelRole; use super::{RandomizedTest, TestClient, TestError, TestServer, UserTestPlan, run_randomized_test};use anyhow::Result;use async_trait::async_trait;use gpui::{BackgroundExecutor, SharedString, TestAppContext};use rand::prelude::*;use serde_derive::{Deserialize, Serialize};use std::{    ops::{Deref, DerefMut, Range},    rc::Rc,    sync::Arc,};use text::Bias; #[gpui::test(    iterations = 100,    on_failure = "crate::tests::save_randomized_test_plan")]async fn test_random_channel_buffers(    cx: &mut TestAppContext,    executor: BackgroundExecutor,    rng: StdRng,) {    run_randomized_test::<RandomChannelBufferTest>(cx, executor, rng).await;} struct RandomChannelBufferTest; #[derive(Clone, Serialize, Deserialize)]enum ChannelBufferOperation {    JoinChannelNotes {        channel_name: SharedString,    },    LeaveChannelNotes {        channel_name: SharedString,    },    EditChannelNotes {        channel_name: SharedString,        edits: Vec<(Range<usize>, Arc<str>)>,    },    Noop,} const CHANNEL_COUNT: usize = 3; #[async_trait(?Send)]impl RandomizedTest for RandomChannelBufferTest {    type Operation = ChannelBufferOperation;     async fn initialize(server: &mut TestServer, users: &[UserTestPlan]) {        let db = &server.app_state.db;        for ix in 0..CHANNEL_COUNT {            let id = db                .create_root_channel(&format!("channel-{ix}"), users[0].user_id)                .await                .unwrap();            for user in &users[1..] {                db.invite_channel_member(id, user.user_id, users[0].user_id, ChannelRole::Member)                    .await                    .unwrap();                db.respond_to_channel_invite(id, user.user_id, true)                    .await                    .unwrap();            }        }    }     fn generate_operation(        client: &TestClient,        rng: &mut StdRng,        _: &mut UserTestPlan,        cx: &TestAppContext,    ) -> ChannelBufferOperation {        let channel_store = client.channel_store().clone();        let mut channel_buffers = client.channel_buffers();         // When signed out, we can't do anything unless a channel buffer is        // already open.        if channel_buffers.deref_mut().is_empty()            && channel_store.read_with(cx, |store, _| store.channel_count() == 0)        {            return ChannelBufferOperation::Noop;        }         loop {            match rng.gen_range(0..100_u32) {                0..=29 => {                    let channel_name = client.channel_store().read_with(cx, |store, cx| {                        store.ordered_channels().find_map(|(_, channel)| {                            if store.has_open_channel_buffer(channel.id, cx) {                                None                            } else {                                Some(channel.name.clone())                            }                        })                    });                    if let Some(channel_name) = channel_name {                        break ChannelBufferOperation::JoinChannelNotes { channel_name };                    }                }                 30..=40 => {                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {                        let channel_name =                            buffer.read_with(cx, |b, cx| b.channel(cx).unwrap().name.clone());                        break ChannelBufferOperation::LeaveChannelNotes { channel_name };                    }                }                 _ => {                    if let Some(buffer) = channel_buffers.deref().iter().choose(rng) {                        break buffer.read_with(cx, |b, cx| {                            let channel_name = b.channel(cx).unwrap().name.clone();                            let edits = b                                .buffer()                                .read_with(cx, |buffer, _| buffer.get_random_edits(rng, 3));                            ChannelBufferOperation::EditChannelNotes {                                channel_name,                                edits,                            }                        });                    }                }            }        }    }     async fn apply_operation(        client: &TestClient,        operation: ChannelBufferOperation,        cx: &mut TestAppContext,    ) -> Result<(), TestError> {        match operation {            ChannelBufferOperation::JoinChannelNotes { channel_name } => {                let buffer = client.channel_store().update(cx, |store, cx| {                    let channel_id = store                        .ordered_channels()                        .find(|(_, c)| c.name == channel_name)                        .unwrap()                        .1                        .id;                    if store.has_open_channel_buffer(channel_id, cx) {                        Err(TestError::Inapplicable)                    } else {                        Ok(store.open_channel_buffer(channel_id, cx))                    }                })?;                 log::info!(                    "{}: opening notes for channel {channel_name}",                    client.username                );                client.channel_buffers().deref_mut().insert(buffer.await?);            }             ChannelBufferOperation::LeaveChannelNotes { channel_name } => {                let buffer = cx.update(|cx| {                    let mut left_buffer = Err(TestError::Inapplicable);                    client.channel_buffers().deref_mut().retain(|buffer| {                        if buffer.read(cx).channel(cx).unwrap().name == channel_name {                            left_buffer = Ok(buffer.clone());                            false                        } else {                            true                        }                    });                    left_buffer                })?;                 log::info!(                    "{}: closing notes for channel {channel_name}",                    client.username                );                cx.update(|_| drop(buffer));            }             ChannelBufferOperation::EditChannelNotes {                channel_name,                edits,            } => {                let channel_buffer = cx                    .read(|cx| {                        client                            .channel_buffers()                            .deref()                            .iter()                            .find(|buffer| {                                buffer.read(cx).channel(cx).unwrap().name == channel_name                            })                            .cloned()                    })                    .ok_or_else(|| TestError::Inapplicable)?;                 log::info!(                    "{}: editing notes for channel {channel_name} with {:?}",                    client.username,                    edits                );                 channel_buffer.update(cx, |buffer, cx| {                    let buffer = buffer.buffer();                    buffer.update(cx, |buffer, cx| {                        let snapshot = buffer.snapshot();                        buffer.edit(                            edits.into_iter().map(|(range, text)| {                                let start = snapshot.clip_offset(range.start, Bias::Left);                                let end = snapshot.clip_offset(range.end, Bias::Right);                                (start..end, text)                            }),                            None,                            cx,                        );                    });                });            }             ChannelBufferOperation::Noop => Err(TestError::Inapplicable)?,        }        Ok(())    }     async fn on_quiesce(server: &mut TestServer, clients: &mut [(Rc<TestClient>, TestAppContext)]) {        let channels = server.app_state.db.all_channels().await.unwrap();         for (client, client_cx) in clients.iter_mut() {            client_cx.update(|cx| {                client                    .channel_buffers()                    .deref_mut()                    .retain(|b| b.read(cx).is_connected());            });        }         for (channel_id, channel_name) in channels {            let mut prev_text: Option<(u64, String)> = None;             let mut collaborator_user_ids = server                .app_state                .db                .get_channel_buffer_collaborators(channel_id)                .await                .unwrap()                .into_iter()                .map(|id| id.to_proto())                .collect::<Vec<_>>();            collaborator_user_ids.sort();             for (client, client_cx) in clients.iter() {                let user_id = client.user_id().unwrap();                client_cx.read(|cx| {                    if let Some(channel_buffer) = client                        .channel_buffers()                        .deref()                        .iter()                        .find(|b| b.read(cx).channel_id.0 == channel_id.to_proto())                    {                        let channel_buffer = channel_buffer.read(cx);                         // Assert that channel buffer's text matches other clients' copies.                        let text = channel_buffer.buffer().read(cx).text();                        if let Some((prev_user_id, prev_text)) = &prev_text {                            assert_eq!(                                &text,                                prev_text,                                "client {user_id} has different text than client {prev_user_id} for channel {channel_name}",                            );                        } else {                            prev_text = Some((user_id, text.clone()));                        }                         // Assert that all clients and the server agree about who is present in the                        // channel buffer.                        let collaborators = channel_buffer.collaborators();                        let mut user_ids =                            collaborators.values().map(|c| c.user_id).collect::<Vec<_>>();                        user_ids.sort();                        assert_eq!(                            user_ids,                            collaborator_user_ids,                            "client {user_id} has different user ids for channel {channel_name} than the server",                        );                    }                });            }        }    }}