From 559e14799c7a3210309ac265eebcb7d434ee3762 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 22 Dec 2022 17:23:26 +0100 Subject: [PATCH] Restructure randomized test to be a bit clearer and test more stuff --- .../src/tests/randomized_integration_tests.rs | 257 +++++++++++++----- crates/fs/src/fs.rs | 15 + 2 files changed, 209 insertions(+), 63 deletions(-) diff --git a/crates/collab/src/tests/randomized_integration_tests.rs b/crates/collab/src/tests/randomized_integration_tests.rs index 0699684418..ed58b3371e 100644 --- a/crates/collab/src/tests/randomized_integration_tests.rs +++ b/crates/collab/src/tests/randomized_integration_tests.rs @@ -3,17 +3,17 @@ use crate::{ rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT}, tests::{TestClient, TestServer}, }; -use anyhow::anyhow; +use anyhow::{anyhow, Result}; use call::ActiveCall; use client::RECEIVE_TIMEOUT; use collections::BTreeMap; use fs::{FakeFs, Fs as _}; use futures::StreamExt as _; -use gpui::{executor::Deterministic, TestAppContext}; +use gpui::{executor::Deterministic, ModelHandle, TestAppContext}; use language::{range_to_lsp, FakeLspAdapter, Language, LanguageConfig, PointUtf16}; use lsp::FakeLanguageServer; use parking_lot::Mutex; -use project::search::SearchQuery; +use project::{search::SearchQuery, Project}; use rand::prelude::*; use std::{env, path::PathBuf, sync::Arc}; @@ -77,6 +77,9 @@ async fn test_random_collaboration( let mut user_ids = Vec::new(); let mut op_start_signals = Vec::new(); let mut next_entity_id = 100000; + let allow_server_restarts = rng.lock().gen_bool(0.7); + let allow_client_reconnection = rng.lock().gen_bool(0.7); + let allow_client_disconnection = rng.lock().gen_bool(0.1); let mut operations = 0; while operations < max_operations { @@ -105,6 +108,7 @@ async fn test_random_collaboration( clients.push(client_cx.foreground().spawn(simulate_client( client, op_start_signal.1, + allow_client_disconnection, rng.clone(), client_cx, ))); @@ -113,7 +117,7 @@ async fn test_random_collaboration( operations += 1; } - 20..=24 if clients.len() > 1 => { + 20..=24 if clients.len() > 1 && allow_client_disconnection => { let client_ix = rng.lock().gen_range(1..clients.len()); log::info!( "Simulating full disconnection of user {}", @@ -172,7 +176,7 @@ async fn test_random_collaboration( operations += 1; } - 25..=29 if clients.len() > 1 => { + 25..=29 if clients.len() > 1 && allow_client_reconnection => { let client_ix = rng.lock().gen_range(1..clients.len()); let user_id = user_ids[client_ix]; log::info!("Simulating temporary disconnection of user {}", user_id); @@ -188,7 +192,7 @@ async fn test_random_collaboration( operations += 1; } - 30..=34 => { + 30..=34 if allow_server_restarts => { log::info!("Simulating server restart"); server.reset().await; deterministic.advance_clock(RECEIVE_TIMEOUT); @@ -384,6 +388,7 @@ async fn test_random_collaboration( async fn simulate_client( mut client: TestClient, mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>, + can_hang_up: bool, rng: Arc>, mut cx: TestAppContext, ) -> (TestClient, TestAppContext) { @@ -500,7 +505,9 @@ async fn simulate_client( client.language_registry.add(Arc::new(language)); while op_start_signal.next().await.is_some() { - if let Err(error) = randomly_mutate_client(&mut client, rng.clone(), &mut cx).await { + if let Err(error) = + randomly_mutate_client(&mut client, can_hang_up, rng.clone(), &mut cx).await + { log::error!("{} error: {:?}", client.username, error); } @@ -513,12 +520,35 @@ async fn simulate_client( async fn randomly_mutate_client( client: &mut TestClient, + can_hang_up: bool, rng: Arc>, cx: &mut TestAppContext, -) -> anyhow::Result<()> { +) -> Result<()> { + let choice = rng.lock().gen_range(0..100); + match choice { + 0..=19 => randomly_mutate_active_call(client, can_hang_up, &rng, cx).await?, + 20..=49 => randomly_mutate_projects(client, &rng, cx).await?, + 50..=59 if !client.local_projects.is_empty() || !client.remote_projects.is_empty() => { + randomly_mutate_worktrees(client, &rng, cx).await?; + } + 60..=84 if !client.local_projects.is_empty() || !client.remote_projects.is_empty() => { + randomly_query_and_mutate_buffers(client, &rng, cx).await?; + } + _ => randomly_mutate_fs(client, &rng).await, + } + + Ok(()) +} + +async fn randomly_mutate_active_call( + client: &mut TestClient, + can_hang_up: bool, + rng: &Mutex, + cx: &mut TestAppContext, +) -> Result<()> { let active_call = cx.read(ActiveCall::global); if active_call.read_with(cx, |call, _| call.incoming().borrow().is_some()) { - if rng.lock().gen() { + if rng.lock().gen_bool(0.7) { log::info!("{}: accepting incoming call", client.username); active_call .update(cx, |call, cx| call.accept_incoming(cx)) @@ -550,7 +580,9 @@ async fn randomly_mutate_client( .update(cx, |call, cx| call.invite(contact.user.id, None, cx)) .await?; } - 30..=39 if active_call.read_with(cx, |call, _| call.room().is_some()) => { + 30..=39 + if can_hang_up && active_call.read_with(cx, |call, _| call.room().is_some()) => + { log::info!("{}: hanging up", client.username); active_call.update(cx, |call, cx| call.hang_up(cx))?; } @@ -558,6 +590,39 @@ async fn randomly_mutate_client( } } + Ok(()) +} + +async fn randomly_mutate_fs(client: &mut TestClient, rng: &Mutex) { + let is_dir = rng.lock().gen::(); + let mut new_path = client + .fs + .directories() + .await + .choose(&mut *rng.lock()) + .unwrap() + .clone(); + new_path.push(gen_file_name(rng)); + if is_dir { + log::info!("{}: creating local dir at {:?}", client.username, new_path); + client.fs.create_dir(&new_path).await.unwrap(); + } else { + new_path.set_extension("rs"); + log::info!("{}: creating local file at {:?}", client.username, new_path); + client + .fs + .create_file(&new_path, Default::default()) + .await + .unwrap(); + } +} + +async fn randomly_mutate_projects( + client: &mut TestClient, + rng: &Mutex, + cx: &mut TestAppContext, +) -> Result<()> { + let active_call = cx.read(ActiveCall::global); let remote_projects = if let Some(room) = active_call.read_with(cx, |call, _| call.room().cloned()) { room.read_with(cx, |room, _| { @@ -572,8 +637,8 @@ async fn randomly_mutate_client( let project = if remote_projects.is_empty() || rng.lock().gen() { if client.local_projects.is_empty() || rng.lock().gen() { - let dir_paths = client.fs.directories().await; - let local_project = if dir_paths.is_empty() || rng.lock().gen() { + let paths = client.fs.paths().await; + let local_project = if paths.is_empty() || rng.lock().gen() { let root_path = client.create_new_root_dir(); client.fs.create_dir(&root_path).await.unwrap(); client @@ -588,7 +653,7 @@ async fn randomly_mutate_client( ); client.build_local_project(root_path, cx).await.0 } else { - let root_path = dir_paths.choose(&mut *rng.lock()).unwrap(); + let root_path = paths.choose(&mut *rng.lock()).unwrap(); log::info!( "{}: opening local project at {:?}", client.username, @@ -647,7 +712,9 @@ async fn randomly_mutate_client( } }; - if active_call.read_with(cx, |call, _| call.room().is_some()) { + if active_call.read_with(cx, |call, _| call.room().is_some()) + && project.read_with(cx, |project, _| project.is_local()) + { if let Err(error) = active_call .update(cx, |call, cx| call.share_project(project.clone(), cx)) .await @@ -656,9 +723,99 @@ async fn randomly_mutate_client( } } + let choice = rng.lock().gen_range(0..100); + match choice { + 0..=19 if project.read_with(cx, |project, _| project.is_local()) => { + let paths = client.fs.paths().await; + let path = paths.choose(&mut *rng.lock()).unwrap(); + log::info!( + "{}: find or create local worktree for path {:?}", + client.username, + path + ); + project + .update(cx, |project, cx| { + project.find_or_create_local_worktree(&path, true, cx) + }) + .await + .unwrap(); + } + 20..=24 if project.read_with(cx, |project, _| project.is_remote()) => { + log::info!( + "{}: dropping remote project {}", + client.username, + project.read_with(cx, |project, _| project.remote_id().unwrap()) + ); + + cx.update(|_| { + client + .remote_projects + .retain(|remote_project| *remote_project != project); + client.buffers.remove(&project); + drop(project); + }); + } + _ => {} + } + + Ok(()) +} + +async fn randomly_mutate_worktrees( + client: &mut TestClient, + rng: &Mutex, + cx: &mut TestAppContext, +) -> Result<()> { + let project = choose_random_project(client, rng).unwrap(); + 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()) + && worktree.root_entry().map_or(false, |e| e.is_dir()) + }) + .choose(&mut *rng.lock()) + }) else { + return Ok(()) + }; + + let (worktree_id, worktree_root_name) = worktree.read_with(cx, |worktree, _| { + (worktree.id(), worktree.root_name().to_string()) + }); + + let is_dir = rng.lock().gen::(); + let mut new_path = PathBuf::new(); + new_path.push(gen_file_name(rng)); + if !is_dir { + new_path.set_extension("rs"); + } + log::info!( + "{}: creating {:?} in worktree {} ({})", + client.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?; + Ok(()) +} + +async fn randomly_query_and_mutate_buffers( + client: &mut TestClient, + rng: &Mutex, + cx: &mut TestAppContext, +) -> Result<()> { + let project = choose_random_project(client, rng).unwrap(); let buffers = client.buffers.entry(project.clone()).or_default(); let buffer = if buffers.is_empty() || rng.lock().gen() { - let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| { + let Some(worktree) = project.read_with(cx, |project, cx| { project .worktrees(cx) .filter(|worktree| { @@ -666,10 +823,7 @@ async fn randomly_mutate_client( worktree.is_visible() && worktree.entries(false).any(|e| e.is_file()) }) .choose(&mut *rng.lock()) - }) { - worktree - } else { - cx.background().simulate_random_delay().await; + }) else { return Ok(()); }; @@ -880,50 +1034,6 @@ async fn randomly_mutate_client( buffers.extend(search.await?.into_keys()); } } - 60..=79 => { - 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::(); - let mut new_path = PathBuf::new(); - new_path.push(new_name); - if !is_dir { - new_path.set_extension("rs"); - } - log::info!( - "{}: creating {:?} in worktree {} ({})", - client.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!( @@ -943,3 +1053,24 @@ async fn randomly_mutate_client( Ok(()) } + +fn choose_random_project( + client: &mut TestClient, + rng: &Mutex, +) -> Option> { + client + .local_projects + .iter() + .chain(&client.remote_projects) + .choose(&mut *rng.lock()) + .cloned() +} + +fn gen_file_name(rng: &Mutex) -> String { + let mut name = String::new(); + for _ in 0..10 { + let letter = rng.lock().gen_range('a'..='z'); + name.push(letter); + } + name +} diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 7b1287247c..97bb836b6d 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -635,6 +635,21 @@ impl FakeFs { } } + pub async fn paths(&self) -> Vec { + let mut result = Vec::new(); + let mut queue = collections::VecDeque::new(); + queue.push_back((PathBuf::from("/"), self.state.lock().await.root.clone())); + while let Some((path, entry)) = queue.pop_front() { + if let FakeFsEntry::Dir { entries, .. } = &*entry.lock().await { + for (name, entry) in entries { + queue.push_back((path.join(name), entry.clone())); + } + } + result.push(path); + } + result + } + pub async fn directories(&self) -> Vec { let mut result = Vec::new(); let mut queue = collections::VecDeque::new();