Prevent randomized test from failing if another guest disconnects

Antonio Scandurra created

Change summary

crates/collab/src/integration_tests.rs | 971 ++++++++++++++-------------
1 file changed, 495 insertions(+), 476 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -5858,7 +5858,7 @@ async fn test_random_collaboration(
     while operations < max_operations {
         let distribution = rng.lock().gen_range(0..100);
         match distribution {
-            0..=19 if !available_guests.is_empty() => {
+            _ if !available_guests.is_empty() => {
                 let guest_ix = rng.lock().gen_range(0..available_guests.len());
                 let guest_username = available_guests.remove(guest_ix);
                 log::info!("Adding new connection for {}", guest_username);
@@ -5906,7 +5906,21 @@ async fn test_random_collaboration(
                 log::info!("Added connection for {}", guest_username);
                 operations += 1;
             }
-            20..=29 if clients.len() > 1 => {
+            0..=69 if !op_start_signals.is_empty() => {
+                while operations < max_operations && rng.lock().gen_bool(0.7) {
+                    op_start_signals
+                        .choose(&mut *rng.lock())
+                        .unwrap()
+                        .unbounded_send(())
+                        .unwrap();
+                    operations += 1;
+                }
+
+                if rng.lock().gen_bool(0.8) {
+                    deterministic.run_until_parked();
+                }
+            }
+            70..=79 if clients.len() > 1 => {
                 let guest_ix = rng.lock().gen_range(1..clients.len());
                 log::info!("Removing guest {}", user_ids[guest_ix]);
                 let removed_guest_id = user_ids.remove(guest_ix);
@@ -5918,14 +5932,10 @@ async fn test_random_collaboration(
                 deterministic.advance_clock(RECEIVE_TIMEOUT);
                 deterministic.start_waiting();
                 log::info!("Waiting for guest {} to exit...", removed_guest_id);
-                let (guest, mut guest_cx, guest_err) = guest.await;
+                let (guest, mut guest_cx) = guest.await;
                 deterministic.finish_waiting();
                 server.allow_connections();
 
-                if let Some(guest_err) = guest_err {
-                    log::error!("{} error - {:?}", guest.username, guest_err);
-                }
-
                 for project in &guest.remote_projects {
                     project.read_with(&guest_cx, |project, _| assert!(project.is_read_only()));
                 }
@@ -5956,20 +5966,6 @@ async fn test_random_collaboration(
 
                 operations += 1;
             }
-            _ if !op_start_signals.is_empty() => {
-                while operations < max_operations && rng.lock().gen_bool(0.7) {
-                    op_start_signals
-                        .choose(&mut *rng.lock())
-                        .unwrap()
-                        .unbounded_send(())
-                        .unwrap();
-                    operations += 1;
-                }
-
-                if rng.lock().gen_bool(0.8) {
-                    deterministic.run_until_parked();
-                }
-            }
             _ => {}
         }
     }
@@ -5980,14 +5976,10 @@ async fn test_random_collaboration(
     deterministic.finish_waiting();
     deterministic.run_until_parked();
 
-    for (guest_client, guest_cx, guest_err) in &clients {
-        if let Some(guest_err) = guest_err {
-            panic!("{} error - {:?}", guest_client.username, guest_err);
-        }
-
+    for (guest_client, guest_cx) in &clients {
         for guest_project in &guest_client.remote_projects {
             guest_project.read_with(guest_cx, |guest_project, cx| {
-                let host_project = clients.iter().find_map(|(client, cx, _)| {
+                let host_project = clients.iter().find_map(|(client, cx)| {
                     let project = client.local_projects.iter().find(|host_project| {
                         host_project.read_with(cx, |host_project, _| {
                             host_project.remote_id() == guest_project.remote_id()
@@ -6049,7 +6041,7 @@ async fn test_random_collaboration(
         }
 
         for (project_id, guest_buffers) in &guest_client.buffers {
-            let host_project = clients.iter().find_map(|(client, cx, _)| {
+            let host_project = clients.iter().find_map(|(client, cx)| {
                 let project = client.local_projects.iter().find(|host_project| {
                     host_project.read_with(cx, |host_project, _| {
                         host_project.remote_id() == Some(*project_id)
@@ -6099,7 +6091,7 @@ async fn test_random_collaboration(
         }
     }
 
-    for (client, mut cx, _) in clients {
+    for (client, mut cx) in clients {
         cx.update(|cx| {
             cx.clear_globals();
             drop(client);
@@ -6517,490 +6509,517 @@ impl TestClient {
     pub async fn simulate(
         mut self,
         username: String,
-        op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
+        mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
         rng: Arc<Mutex<StdRng>>,
         mut cx: TestAppContext,
-    ) -> (Self, TestAppContext, Option<anyhow::Error>) {
-        async fn simulate_internal(
+    ) -> (Self, TestAppContext) {
+        async fn tick(
             client: &mut TestClient,
             username: &str,
-            mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
             rng: Arc<Mutex<StdRng>>,
             cx: &mut TestAppContext,
         ) -> anyhow::Result<()> {
-            // Setup language server
-            let mut language = Language::new(
-                LanguageConfig {
-                    name: "Rust".into(),
-                    path_suffixes: vec!["rs".to_string()],
-                    ..Default::default()
-                },
-                None,
-            );
-            language
-                .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-                    name: "the-fake-language-server",
-                    capabilities: lsp::LanguageServer::full_capabilities(),
-                    initializer: Some(Box::new({
-                        let rng = rng.clone();
-                        let fs = client.fs.clone();
-                        move |fake_server: &mut FakeLanguageServer| {
-                            fake_server.handle_request::<lsp::request::Completion, _, _>(
-                                |_, _| async move {
-                                    Ok(Some(lsp::CompletionResponse::Array(vec![
-                                        lsp::CompletionItem {
-                                            text_edit: Some(lsp::CompletionTextEdit::Edit(
-                                                lsp::TextEdit {
-                                                    range: lsp::Range::new(
-                                                        lsp::Position::new(0, 0),
-                                                        lsp::Position::new(0, 0),
-                                                    ),
-                                                    new_text: "the-new-text".to_string(),
-                                                },
-                                            )),
-                                            ..Default::default()
-                                        },
-                                    ])))
-                                },
-                            );
-
-                            fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
-                                |_, _| async move {
-                                    Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
-                                        lsp::CodeAction {
-                                            title: "the-code-action".to_string(),
-                                            ..Default::default()
-                                        },
-                                    )]))
-                                },
-                            );
-
-                            fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
-                                |params, _| async move {
-                                    Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
-                                        params.position,
-                                        params.position,
-                                    ))))
-                                },
-                            );
-
-                            fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
-                                let fs = fs.clone();
-                                let rng = rng.clone();
-                                move |_, _| {
-                                    let fs = fs.clone();
-                                    let rng = rng.clone();
-                                    async move {
-                                        let files = fs.files().await;
-                                        let mut rng = rng.lock();
-                                        let count = rng.gen_range::<usize, _>(1..3);
-                                        let files = (0..count)
-                                            .map(|_| files.choose(&mut *rng).unwrap())
-                                            .collect::<Vec<_>>();
-                                        log::info!(
-                                            "LSP: Returning definitions in files {:?}",
-                                            &files
-                                        );
-                                        Ok(Some(lsp::GotoDefinitionResponse::Array(
-                                            files
-                                                .into_iter()
-                                                .map(|file| lsp::Location {
-                                                    uri: lsp::Url::from_file_path(file).unwrap(),
-                                                    range: Default::default(),
-                                                })
-                                                .collect(),
-                                        )))
-                                    }
-                                }
-                            });
-
-                            fake_server
-                                .handle_request::<lsp::request::DocumentHighlightRequest, _, _>({
-                                    let rng = rng.clone();
-                                    move |_, _| {
-                                        let mut highlights = Vec::new();
-                                        let highlight_count = rng.lock().gen_range(1..=5);
-                                        for _ in 0..highlight_count {
-                                            let start = PointUtf16::new(
-                                                rng.lock().gen_range(0..100),
-                                                rng.lock().gen_range(0..100),
-                                            );
-                                            let end = PointUtf16::new(
-                                                rng.lock().gen_range(0..100),
-                                                rng.lock().gen_range(0..100),
-                                            );
-                                            let range =
-                                                if start > end { end..start } else { start..end };
-                                            highlights.push(lsp::DocumentHighlight {
-                                                range: range_to_lsp(range.clone()),
-                                                kind: Some(lsp::DocumentHighlightKind::READ),
-                                            });
-                                        }
-                                        highlights.sort_unstable_by_key(|highlight| {
-                                            (highlight.range.start, highlight.range.end)
-                                        });
-                                        async move { Ok(Some(highlights)) }
-                                    }
-                                });
-                        }
-                    })),
-                    ..Default::default()
-                }))
-                .await;
-            client.language_registry.add(Arc::new(language));
-
-            while op_start_signal.next().await.is_some() {
-                let active_call = cx.read(ActiveCall::global);
-                let room = active_call.read_with(cx, |call, _| call.room().unwrap().clone());
-                let remote_projects = room.read_with(cx, |room, _| {
-                    room.remote_participants()
-                        .values()
-                        .flat_map(|participant| participant.projects.clone())
-                        .collect::<Vec<_>>()
-                });
-                let project = if remote_projects.is_empty() || rng.lock().gen() {
-                    if client.local_projects.is_empty() || rng.lock().gen() {
-                        let paths = client.fs.paths().await;
-                        let local_project = if paths.is_empty() || rng.lock().gen() {
-                            let root_path = format!(
-                                "/{}-root-{}",
-                                username,
-                                post_inc(&mut client.next_root_dir_id)
-                            );
-                            client.fs.create_dir(Path::new(&root_path)).await.unwrap();
-                            client.build_local_project(root_path, cx).await.0
-                        } else {
-                            let root_path = paths.choose(&mut *rng.lock()).unwrap();
-                            client.build_local_project(root_path, cx).await.0
-                        };
-                        client.local_projects.push(local_project.clone());
-                        local_project
-                    } else {
+            let active_call = cx.read(ActiveCall::global);
+            let room = active_call.read_with(cx, |call, _| {
+                call.room()
+                    .ok_or_else(|| anyhow!("no active call"))
+                    .cloned()
+            })?;
+            let remote_projects = room.read_with(cx, |room, _| {
+                room.remote_participants()
+                    .values()
+                    .flat_map(|participant| participant.projects.clone())
+                    .collect::<Vec<_>>()
+            });
+            let project = if remote_projects.is_empty() || rng.lock().gen() {
+                if client.local_projects.is_empty() || rng.lock().gen() {
+                    let paths = client.fs.paths().await;
+                    let local_project = if paths.is_empty() || rng.lock().gen() {
+                        let root_path = format!(
+                            "/{}-root-{}",
+                            username,
+                            post_inc(&mut client.next_root_dir_id)
+                        );
+                        let root_path = Path::new(&root_path);
+                        client.fs.create_dir(root_path).await.unwrap();
                         client
-                            .local_projects
-                            .choose(&mut *rng.lock())
-                            .unwrap()
-                            .clone()
-                    }
-                } else {
-                    if client.remote_projects.is_empty() || rng.lock().gen() {
-                        let remote_project_id =
-                            remote_projects.choose(&mut *rng.lock()).unwrap().id;
-                        let remote_project =
-                            client.build_remote_project(remote_project_id, cx).await;
-                        client.remote_projects.push(remote_project.clone());
-                        remote_project
+                            .fs
+                            .create_file(&root_path.join("main.rs"), Default::default())
+                            .await
+                            .unwrap();
+                        log::info!("{}: opening local project at {:?}", username, root_path);
+                        client.build_local_project(root_path, cx).await.0
                     } else {
-                        client
-                            .remote_projects
-                            .choose(&mut *rng.lock())
-                            .unwrap()
-                            .clone()
-                    }
-                };
-                let project_id = active_call
-                    .update(cx, |call, cx| call.share_project(project.clone(), cx))
-                    .await
-                    .unwrap();
-
-                let buffers = client.buffers.entry(project_id).or_default();
-                let buffer = if buffers.is_empty() || rng.lock().gen() {
-                    let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
-                        project
-                            .worktrees(cx)
-                            .filter(|worktree| {
-                                let worktree = worktree.read(cx);
-                                worktree.is_visible()
-                                    && worktree.entries(false).any(|e| e.is_file())
+                        let root_path = paths.choose(&mut *rng.lock()).unwrap();
+                        log::info!("{}: opening local project at {:?}", username, root_path);
+                        client.build_local_project(root_path, cx).await.0
+                    };
+                    client.local_projects.push(local_project.clone());
+                    local_project
+                } else {
+                    client
+                        .local_projects
+                        .choose(&mut *rng.lock())
+                        .unwrap()
+                        .clone()
+                }
+            } else {
+                if client.remote_projects.is_empty() || rng.lock().gen() {
+                    let remote_project_id = remote_projects.choose(&mut *rng.lock()).unwrap().id;
+                    let remote_project = if let Some(project) =
+                        client.remote_projects.iter().find(|project| {
+                            project.read_with(cx, |project, _| {
+                                project.remote_id() == Some(remote_project_id)
                             })
-                            .choose(&mut *rng.lock())
-                    }) {
-                        worktree
+                        }) {
+                        project.clone()
                     } else {
-                        cx.background().simulate_random_delay().await;
-                        continue;
+                        log::info!("{}: opening remote project {}", username, remote_project_id);
+                        let remote_project = Project::remote(
+                            remote_project_id,
+                            client.client.clone(),
+                            client.user_store.clone(),
+                            client.project_store.clone(),
+                            client.language_registry.clone(),
+                            FakeFs::new(cx.background()),
+                            cx.to_async(),
+                        )
+                        .await?;
+                        client.remote_projects.push(remote_project.clone());
+                        remote_project
                     };
 
-                    let (worktree_root_name, project_path) =
-                        worktree.read_with(cx, |worktree, _| {
-                            let entry = worktree
-                                .entries(false)
-                                .filter(|e| e.is_file())
-                                .choose(&mut *rng.lock())
-                                .unwrap();
-                            (
-                                worktree.root_name().to_string(),
-                                (worktree.id(), entry.path.clone()),
-                            )
-                        });
-                    log::info!(
-                        "{}: opening path {:?} in worktree {} ({})",
-                        username,
-                        project_path.1,
-                        project_path.0,
-                        worktree_root_name,
-                    );
-                    let buffer = project
-                        .update(cx, |project, cx| {
-                            project.open_buffer(project_path.clone(), cx)
+                    remote_project
+                } else {
+                    client
+                        .remote_projects
+                        .choose(&mut *rng.lock())
+                        .unwrap()
+                        .clone()
+                }
+            };
+            let project_id = active_call
+                .update(cx, |call, cx| call.share_project(project.clone(), cx))
+                .await?;
+
+            let buffers = client.buffers.entry(project_id).or_default();
+            let buffer = if buffers.is_empty() || rng.lock().gen() {
+                let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
+                    project
+                        .worktrees(cx)
+                        .filter(|worktree| {
+                            let worktree = worktree.read(cx);
+                            worktree.is_visible() && worktree.entries(false).any(|e| e.is_file())
                         })
-                        .await?;
-                    log::info!(
-                        "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
-                        username,
-                        project_path.1,
-                        project_path.0,
-                        worktree_root_name,
-                        buffer.read_with(cx, |buffer, _| buffer.remote_id())
-                    );
-                    buffers.insert(buffer.clone());
-                    buffer
+                        .choose(&mut *rng.lock())
+                }) {
+                    worktree
                 } else {
-                    buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
+                    cx.background().simulate_random_delay().await;
+                    return Ok(());
                 };
 
-                let choice = rng.lock().gen_range(0..100);
-                match choice {
-                    0..=9 => {
-                        cx.update(|cx| {
-                            log::info!(
-                                "{}: dropping buffer {:?}",
-                                username,
-                                buffer.read(cx).file().unwrap().full_path(cx)
-                            );
-                            buffers.remove(&buffer);
-                            drop(buffer);
-                        });
+                let (worktree_root_name, project_path) = worktree.read_with(cx, |worktree, _| {
+                    let entry = worktree
+                        .entries(false)
+                        .filter(|e| e.is_file())
+                        .choose(&mut *rng.lock())
+                        .unwrap();
+                    (
+                        worktree.root_name().to_string(),
+                        (worktree.id(), entry.path.clone()),
+                    )
+                });
+                log::info!(
+                    "{}: opening path {:?} in worktree {} ({})",
+                    username,
+                    project_path.1,
+                    project_path.0,
+                    worktree_root_name,
+                );
+                let buffer = project
+                    .update(cx, |project, cx| {
+                        project.open_buffer(project_path.clone(), cx)
+                    })
+                    .await?;
+                log::info!(
+                    "{}: opened path {:?} in worktree {} ({}) with buffer id {}",
+                    username,
+                    project_path.1,
+                    project_path.0,
+                    worktree_root_name,
+                    buffer.read_with(cx, |buffer, _| buffer.remote_id())
+                );
+                buffers.insert(buffer.clone());
+                buffer
+            } else {
+                buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
+            };
+
+            let choice = rng.lock().gen_range(0..100);
+            match choice {
+                0..=9 => {
+                    cx.update(|cx| {
+                        log::info!(
+                            "{}: dropping buffer {:?}",
+                            username,
+                            buffer.read(cx).file().unwrap().full_path(cx)
+                        );
+                        buffers.remove(&buffer);
+                        drop(buffer);
+                    });
+                }
+                10..=19 => {
+                    let completions = project.update(cx, |project, cx| {
+                        log::info!(
+                            "{}: requesting completions for buffer {} ({:?})",
+                            username,
+                            buffer.read(cx).remote_id(),
+                            buffer.read(cx).file().unwrap().full_path(cx)
+                        );
+                        let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
+                        project.completions(&buffer, offset, cx)
+                    });
+                    let completions = cx.background().spawn(async move {
+                        completions
+                            .await
+                            .map_err(|err| anyhow!("completions request failed: {:?}", err))
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching completions request", username);
+                        cx.update(|cx| completions.detach_and_log_err(cx));
+                    } else {
+                        completions.await?;
                     }
-                    10..=19 => {
-                        let completions = project.update(cx, |project, cx| {
-                            log::info!(
-                                "{}: requesting completions for buffer {} ({:?})",
-                                username,
-                                buffer.read(cx).remote_id(),
-                                buffer.read(cx).file().unwrap().full_path(cx)
-                            );
-                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
-                            project.completions(&buffer, offset, cx)
-                        });
-                        let completions = cx.background().spawn(async move {
-                            completions
-                                .await
-                                .map_err(|err| anyhow!("completions request failed: {:?}", err))
-                        });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching completions request", username);
-                            cx.update(|cx| completions.detach_and_log_err(cx));
-                        } else {
-                            completions.await?;
-                        }
+                }
+                20..=29 => {
+                    let code_actions = project.update(cx, |project, cx| {
+                        log::info!(
+                            "{}: requesting code actions for buffer {} ({:?})",
+                            username,
+                            buffer.read(cx).remote_id(),
+                            buffer.read(cx).file().unwrap().full_path(cx)
+                        );
+                        let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
+                        project.code_actions(&buffer, range, cx)
+                    });
+                    let code_actions = cx.background().spawn(async move {
+                        code_actions
+                            .await
+                            .map_err(|err| anyhow!("code actions request failed: {:?}", err))
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching code actions request", username);
+                        cx.update(|cx| code_actions.detach_and_log_err(cx));
+                    } else {
+                        code_actions.await?;
                     }
-                    20..=29 => {
-                        let code_actions = project.update(cx, |project, cx| {
-                            log::info!(
-                                "{}: requesting code actions for buffer {} ({:?})",
-                                username,
-                                buffer.read(cx).remote_id(),
-                                buffer.read(cx).file().unwrap().full_path(cx)
-                            );
-                            let range = buffer.read(cx).random_byte_range(0, &mut *rng.lock());
-                            project.code_actions(&buffer, range, cx)
-                        });
-                        let code_actions = cx.background().spawn(async move {
-                            code_actions
-                                .await
-                                .map_err(|err| anyhow!("code actions request failed: {:?}", err))
-                        });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching code actions request", username);
-                            cx.update(|cx| code_actions.detach_and_log_err(cx));
-                        } else {
-                            code_actions.await?;
-                        }
+                }
+                30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
+                    let (requested_version, save) = buffer.update(cx, |buffer, cx| {
+                        log::info!(
+                            "{}: saving buffer {} ({:?})",
+                            username,
+                            buffer.remote_id(),
+                            buffer.file().unwrap().full_path(cx)
+                        );
+                        (buffer.version(), buffer.save(cx))
+                    });
+                    let save = cx.background().spawn(async move {
+                        let (saved_version, _, _) = save
+                            .await
+                            .map_err(|err| anyhow!("save request failed: {:?}", err))?;
+                        assert!(saved_version.observed_all(&requested_version));
+                        Ok::<_, anyhow::Error>(())
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching save request", username);
+                        cx.update(|cx| save.detach_and_log_err(cx));
+                    } else {
+                        save.await?;
                     }
-                    30..=39 if buffer.read_with(cx, |buffer, _| buffer.is_dirty()) => {
-                        let (requested_version, save) = buffer.update(cx, |buffer, cx| {
-                            log::info!(
-                                "{}: saving buffer {} ({:?})",
-                                username,
-                                buffer.remote_id(),
-                                buffer.file().unwrap().full_path(cx)
-                            );
-                            (buffer.version(), buffer.save(cx))
-                        });
-                        let save = cx.background().spawn(async move {
-                            let (saved_version, _, _) = save
-                                .await
-                                .map_err(|err| anyhow!("save request failed: {:?}", err))?;
-                            assert!(saved_version.observed_all(&requested_version));
-                            Ok::<_, anyhow::Error>(())
-                        });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching save request", username);
-                            cx.update(|cx| save.detach_and_log_err(cx));
-                        } else {
-                            save.await?;
-                        }
+                }
+                40..=44 => {
+                    let prepare_rename = project.update(cx, |project, cx| {
+                        log::info!(
+                            "{}: preparing rename for buffer {} ({:?})",
+                            username,
+                            buffer.read(cx).remote_id(),
+                            buffer.read(cx).file().unwrap().full_path(cx)
+                        );
+                        let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
+                        project.prepare_rename(buffer, offset, cx)
+                    });
+                    let prepare_rename = cx.background().spawn(async move {
+                        prepare_rename
+                            .await
+                            .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching prepare rename request", username);
+                        cx.update(|cx| prepare_rename.detach_and_log_err(cx));
+                    } else {
+                        prepare_rename.await?;
                     }
-                    40..=44 => {
-                        let prepare_rename = project.update(cx, |project, cx| {
-                            log::info!(
-                                "{}: preparing rename for buffer {} ({:?})",
-                                username,
-                                buffer.read(cx).remote_id(),
-                                buffer.read(cx).file().unwrap().full_path(cx)
-                            );
-                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
-                            project.prepare_rename(buffer, offset, cx)
-                        });
-                        let prepare_rename = cx.background().spawn(async move {
-                            prepare_rename
-                                .await
-                                .map_err(|err| anyhow!("prepare rename request failed: {:?}", err))
-                        });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching prepare rename request", username);
-                            cx.update(|cx| prepare_rename.detach_and_log_err(cx));
-                        } else {
-                            prepare_rename.await?;
-                        }
+                }
+                45..=49 => {
+                    let definitions = project.update(cx, |project, cx| {
+                        log::info!(
+                            "{}: requesting definitions for buffer {} ({:?})",
+                            username,
+                            buffer.read(cx).remote_id(),
+                            buffer.read(cx).file().unwrap().full_path(cx)
+                        );
+                        let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
+                        project.definition(&buffer, offset, cx)
+                    });
+                    let definitions = cx.background().spawn(async move {
+                        definitions
+                            .await
+                            .map_err(|err| anyhow!("definitions request failed: {:?}", err))
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching definitions request", username);
+                        cx.update(|cx| definitions.detach_and_log_err(cx));
+                    } else {
+                        buffers.extend(definitions.await?.into_iter().map(|loc| loc.target.buffer));
                     }
-                    45..=49 => {
-                        let definitions = project.update(cx, |project, cx| {
-                            log::info!(
-                                "{}: requesting definitions for buffer {} ({:?})",
-                                username,
-                                buffer.read(cx).remote_id(),
-                                buffer.read(cx).file().unwrap().full_path(cx)
-                            );
-                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
-                            project.definition(&buffer, offset, cx)
-                        });
-                        let definitions = cx.background().spawn(async move {
-                            definitions
-                                .await
-                                .map_err(|err| anyhow!("definitions request failed: {:?}", err))
-                        });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching definitions request", username);
-                            cx.update(|cx| definitions.detach_and_log_err(cx));
-                        } else {
-                            buffers.extend(
-                                definitions.await?.into_iter().map(|loc| loc.target.buffer),
-                            );
-                        }
+                }
+                50..=54 => {
+                    let highlights = project.update(cx, |project, cx| {
+                        log::info!(
+                            "{}: requesting highlights for buffer {} ({:?})",
+                            username,
+                            buffer.read(cx).remote_id(),
+                            buffer.read(cx).file().unwrap().full_path(cx)
+                        );
+                        let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
+                        project.document_highlights(&buffer, offset, cx)
+                    });
+                    let highlights = cx.background().spawn(async move {
+                        highlights
+                            .await
+                            .map_err(|err| anyhow!("highlights request failed: {:?}", err))
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching highlights request", username);
+                        cx.update(|cx| highlights.detach_and_log_err(cx));
+                    } else {
+                        highlights.await?;
                     }
-                    50..=54 => {
-                        let highlights = project.update(cx, |project, cx| {
-                            log::info!(
-                                "{}: requesting highlights for buffer {} ({:?})",
-                                username,
-                                buffer.read(cx).remote_id(),
-                                buffer.read(cx).file().unwrap().full_path(cx)
-                            );
-                            let offset = rng.lock().gen_range(0..=buffer.read(cx).len());
-                            project.document_highlights(&buffer, offset, cx)
-                        });
-                        let highlights = cx.background().spawn(async move {
-                            highlights
-                                .await
-                                .map_err(|err| anyhow!("highlights request failed: {:?}", err))
-                        });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching highlights request", username);
-                            cx.update(|cx| highlights.detach_and_log_err(cx));
-                        } else {
-                            highlights.await?;
-                        }
+                }
+                55..=59 => {
+                    let search = project.update(cx, |project, cx| {
+                        let query = rng.lock().gen_range('a'..='z');
+                        log::info!("{}: project-wide search {:?}", username, query);
+                        project.search(SearchQuery::text(query, false, false), cx)
+                    });
+                    let search = cx.background().spawn(async move {
+                        search
+                            .await
+                            .map_err(|err| anyhow!("search request failed: {:?}", err))
+                    });
+                    if rng.lock().gen_bool(0.3) {
+                        log::info!("{}: detaching search request", username);
+                        cx.update(|cx| search.detach_and_log_err(cx));
+                    } else {
+                        buffers.extend(search.await?.into_keys());
                     }
-                    55..=59 => {
-                        let search = project.update(cx, |project, cx| {
-                            let query = rng.lock().gen_range('a'..='z');
-                            log::info!("{}: project-wide search {:?}", username, query);
-                            project.search(SearchQuery::text(query, false, false), cx)
-                        });
-                        let search = cx.background().spawn(async move {
-                            search
-                                .await
-                                .map_err(|err| anyhow!("search request failed: {:?}", err))
+                }
+                60..=69 => {
+                    let worktree = project
+                        .read_with(cx, |project, cx| {
+                            project
+                                .worktrees(cx)
+                                .filter(|worktree| {
+                                    let worktree = worktree.read(cx);
+                                    worktree.is_visible()
+                                        && worktree.entries(false).any(|e| e.is_file())
+                                        && worktree.root_entry().map_or(false, |e| e.is_dir())
+                                })
+                                .choose(&mut *rng.lock())
+                        })
+                        .unwrap();
+                    let (worktree_id, worktree_root_name) = worktree
+                        .read_with(cx, |worktree, _| {
+                            (worktree.id(), worktree.root_name().to_string())
                         });
-                        if rng.lock().gen_bool(0.3) {
-                            log::info!("{}: detaching search request", username);
-                            cx.update(|cx| search.detach_and_log_err(cx));
-                        } else {
-                            buffers.extend(search.await?.into_keys());
-                        }
+
+                    let mut new_name = String::new();
+                    for _ in 0..10 {
+                        let letter = rng.lock().gen_range('a'..='z');
+                        new_name.push(letter);
                     }
-                    60..=69 => {
-                        let worktree = project
-                            .read_with(cx, |project, cx| {
-                                project
-                                    .worktrees(cx)
-                                    .filter(|worktree| {
-                                        let worktree = worktree.read(cx);
-                                        worktree.is_visible()
-                                            && worktree.entries(false).any(|e| e.is_file())
-                                            && worktree.root_entry().map_or(false, |e| e.is_dir())
-                                    })
-                                    .choose(&mut *rng.lock())
-                            })
-                            .unwrap();
-                        let (worktree_id, worktree_root_name) = worktree
-                            .read_with(cx, |worktree, _| {
-                                (worktree.id(), worktree.root_name().to_string())
-                            });
-
-                        let mut new_name = String::new();
-                        for _ in 0..10 {
-                            let letter = rng.lock().gen_range('a'..='z');
-                            new_name.push(letter);
-                        }
 
-                        let is_dir = rng.lock().gen::<bool>();
-                        let mut new_path = PathBuf::new();
-                        new_path.push(new_name);
-                        if !is_dir {
-                            new_path.set_extension("rs");
-                        }
+                    let is_dir = rng.lock().gen::<bool>();
+                    let mut new_path = PathBuf::new();
+                    new_path.push(new_name);
+                    if !is_dir {
+                        new_path.set_extension("rs");
+                    }
+                    log::info!(
+                        "{}: creating {:?} in worktree {} ({})",
+                        username,
+                        new_path,
+                        worktree_id,
+                        worktree_root_name,
+                    );
+                    project
+                        .update(cx, |project, cx| {
+                            project.create_entry((worktree_id, new_path), is_dir, cx)
+                        })
+                        .unwrap()
+                        .await?;
+                }
+                _ => {
+                    buffer.update(cx, |buffer, cx| {
                         log::info!(
-                            "{}: creating {:?} in worktree {} ({})",
+                            "{}: updating buffer {} ({:?})",
                             username,
-                            new_path,
-                            worktree_id,
-                            worktree_root_name,
+                            buffer.remote_id(),
+                            buffer.file().unwrap().full_path(cx)
                         );
-                        project
-                            .update(cx, |project, cx| {
-                                project.create_entry((worktree_id, new_path), is_dir, cx)
-                            })
-                            .unwrap()
-                            .await?;
-                    }
-                    _ => {
-                        buffer.update(cx, |buffer, cx| {
-                            log::info!(
-                                "{}: updating buffer {} ({:?})",
-                                username,
-                                buffer.remote_id(),
-                                buffer.file().unwrap().full_path(cx)
-                            );
-                            if rng.lock().gen_bool(0.7) {
-                                buffer.randomly_edit(&mut *rng.lock(), 5, cx);
-                            } else {
-                                buffer.randomly_undo_redo(&mut *rng.lock(), cx);
-                            }
-                        });
-                    }
+                        if rng.lock().gen_bool(0.7) {
+                            buffer.randomly_edit(&mut *rng.lock(), 5, cx);
+                        } else {
+                            buffer.randomly_undo_redo(&mut *rng.lock(), cx);
+                        }
+                    });
                 }
-                cx.background().simulate_random_delay().await;
             }
+
             Ok(())
         }
 
-        let result = simulate_internal(&mut self, &username, op_start_signal, rng, &mut cx).await;
+        // Setup language server
+        let mut language = Language::new(
+            LanguageConfig {
+                name: "Rust".into(),
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
+            None,
+        );
+        language
+            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+                name: "the-fake-language-server",
+                capabilities: lsp::LanguageServer::full_capabilities(),
+                initializer: Some(Box::new({
+                    let rng = rng.clone();
+                    let fs = self.fs.clone();
+                    move |fake_server: &mut FakeLanguageServer| {
+                        fake_server.handle_request::<lsp::request::Completion, _, _>(
+                            |_, _| async move {
+                                Ok(Some(lsp::CompletionResponse::Array(vec![
+                                    lsp::CompletionItem {
+                                        text_edit: Some(lsp::CompletionTextEdit::Edit(
+                                            lsp::TextEdit {
+                                                range: lsp::Range::new(
+                                                    lsp::Position::new(0, 0),
+                                                    lsp::Position::new(0, 0),
+                                                ),
+                                                new_text: "the-new-text".to_string(),
+                                            },
+                                        )),
+                                        ..Default::default()
+                                    },
+                                ])))
+                            },
+                        );
+
+                        fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
+                            |_, _| async move {
+                                Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
+                                    lsp::CodeAction {
+                                        title: "the-code-action".to_string(),
+                                        ..Default::default()
+                                    },
+                                )]))
+                            },
+                        );
+
+                        fake_server.handle_request::<lsp::request::PrepareRenameRequest, _, _>(
+                            |params, _| async move {
+                                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
+                                    params.position,
+                                    params.position,
+                                ))))
+                            },
+                        );
+
+                        fake_server.handle_request::<lsp::request::GotoDefinition, _, _>({
+                            let fs = fs.clone();
+                            let rng = rng.clone();
+                            move |_, _| {
+                                let fs = fs.clone();
+                                let rng = rng.clone();
+                                async move {
+                                    let files = fs.files().await;
+                                    let mut rng = rng.lock();
+                                    let count = rng.gen_range::<usize, _>(1..3);
+                                    let files = (0..count)
+                                        .map(|_| files.choose(&mut *rng).unwrap())
+                                        .collect::<Vec<_>>();
+                                    log::info!("LSP: Returning definitions in files {:?}", &files);
+                                    Ok(Some(lsp::GotoDefinitionResponse::Array(
+                                        files
+                                            .into_iter()
+                                            .map(|file| lsp::Location {
+                                                uri: lsp::Url::from_file_path(file).unwrap(),
+                                                range: Default::default(),
+                                            })
+                                            .collect(),
+                                    )))
+                                }
+                            }
+                        });
+
+                        fake_server.handle_request::<lsp::request::DocumentHighlightRequest, _, _>(
+                            {
+                                let rng = rng.clone();
+                                move |_, _| {
+                                    let mut highlights = Vec::new();
+                                    let highlight_count = rng.lock().gen_range(1..=5);
+                                    for _ in 0..highlight_count {
+                                        let start = PointUtf16::new(
+                                            rng.lock().gen_range(0..100),
+                                            rng.lock().gen_range(0..100),
+                                        );
+                                        let end = PointUtf16::new(
+                                            rng.lock().gen_range(0..100),
+                                            rng.lock().gen_range(0..100),
+                                        );
+                                        let range =
+                                            if start > end { end..start } else { start..end };
+                                        highlights.push(lsp::DocumentHighlight {
+                                            range: range_to_lsp(range.clone()),
+                                            kind: Some(lsp::DocumentHighlightKind::READ),
+                                        });
+                                    }
+                                    highlights.sort_unstable_by_key(|highlight| {
+                                        (highlight.range.start, highlight.range.end)
+                                    });
+                                    async move { Ok(Some(highlights)) }
+                                }
+                            },
+                        );
+                    }
+                })),
+                ..Default::default()
+            }))
+            .await;
+        self.language_registry.add(Arc::new(language));
+
+        while op_start_signal.next().await.is_some() {
+            if let Err(error) = tick(&mut self, &username, rng.clone(), &mut cx).await {
+                log::info!("{} error: {:?}", username, error);
+            }
+
+            cx.background().simulate_random_delay().await;
+        }
         log::info!("{}: done", username);
 
-        (self, cx, result.err())
+        (self, cx)
     }
 }