Fix outstanding compiler errors in randomized collaboration test
This commit is contained in:
parent
4c4ebbfa19
commit
eb6b545eeb
1 changed files with 191 additions and 661 deletions
|
@ -28,7 +28,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
|
||||||
LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, Rope,
|
LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, PointUtf16, Rope,
|
||||||
};
|
};
|
||||||
use live_kit_client::MacOSDisplay;
|
use live_kit_client::MacOSDisplay;
|
||||||
use lsp::{self, FakeLanguageServer};
|
use lsp::{self, FakeLanguageServer};
|
||||||
|
@ -5779,6 +5779,8 @@ async fn test_random_collaboration(
|
||||||
rng: StdRng,
|
rng: StdRng,
|
||||||
) {
|
) {
|
||||||
deterministic.forbid_parking();
|
deterministic.forbid_parking();
|
||||||
|
let rng = Arc::new(Mutex::new(rng));
|
||||||
|
|
||||||
let max_peers = env::var("MAX_PEERS")
|
let max_peers = env::var("MAX_PEERS")
|
||||||
.map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
|
.map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
|
||||||
.unwrap_or(5);
|
.unwrap_or(5);
|
||||||
|
@ -5788,14 +5790,6 @@ async fn test_random_collaboration(
|
||||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||||
.unwrap_or(10);
|
.unwrap_or(10);
|
||||||
|
|
||||||
let rng = Arc::new(Mutex::new(rng));
|
|
||||||
|
|
||||||
let guest_lang_registry = Arc::new(LanguageRegistry::test());
|
|
||||||
let host_language_registry = Arc::new(LanguageRegistry::test());
|
|
||||||
|
|
||||||
let fs = FakeFs::new(cx.background());
|
|
||||||
fs.insert_tree("/_collab", json!({"init": ""})).await;
|
|
||||||
|
|
||||||
let mut server = TestServer::start(cx.foreground(), cx.background()).await;
|
let mut server = TestServer::start(cx.foreground(), cx.background()).await;
|
||||||
let db = server.app_state.db.clone();
|
let db = server.app_state.db.clone();
|
||||||
|
|
||||||
|
@ -5858,140 +5852,8 @@ async fn test_random_collaboration(
|
||||||
let mut user_ids = Vec::new();
|
let mut user_ids = Vec::new();
|
||||||
let mut peer_ids = Vec::new();
|
let mut peer_ids = Vec::new();
|
||||||
let mut op_start_signals = Vec::new();
|
let mut op_start_signals = Vec::new();
|
||||||
|
|
||||||
let mut next_entity_id = 100000;
|
let mut next_entity_id = 100000;
|
||||||
|
|
||||||
// Set up fake language servers.
|
|
||||||
let mut language = Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let _fake_servers = 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 = fs.clone();
|
|
||||||
let project = host_project.downgrade();
|
|
||||||
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();
|
|
||||||
let project = project;
|
|
||||||
move |params, mut cx| {
|
|
||||||
let highlights = if let Some(project) = project.upgrade(&cx) {
|
|
||||||
project.update(&mut cx, |project, cx| {
|
|
||||||
let path = params
|
|
||||||
.text_document_position_params
|
|
||||||
.text_document
|
|
||||||
.uri
|
|
||||||
.to_file_path()
|
|
||||||
.unwrap();
|
|
||||||
let (worktree, relative_path) =
|
|
||||||
project.find_local_worktree(&path, cx)?;
|
|
||||||
let project_path =
|
|
||||||
ProjectPath::from((worktree.read(cx).id(), relative_path));
|
|
||||||
let buffer =
|
|
||||||
project.get_open_buffer(&project_path, cx)?.read(cx);
|
|
||||||
|
|
||||||
let mut highlights = Vec::new();
|
|
||||||
let highlight_count = rng.lock().gen_range(1..=5);
|
|
||||||
let mut prev_end = 0;
|
|
||||||
for _ in 0..highlight_count {
|
|
||||||
let range =
|
|
||||||
buffer.random_byte_range(prev_end, &mut *rng.lock());
|
|
||||||
|
|
||||||
highlights.push(lsp::DocumentHighlight {
|
|
||||||
range: range_to_lsp(range.to_point_utf16(buffer)),
|
|
||||||
kind: Some(lsp::DocumentHighlightKind::READ),
|
|
||||||
});
|
|
||||||
prev_end = range.end;
|
|
||||||
}
|
|
||||||
Some(highlights)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
async move { Ok(highlights) }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
}))
|
|
||||||
.await;
|
|
||||||
let language = Arc::new(language);
|
|
||||||
|
|
||||||
let op_start_signal = futures::channel::mpsc::unbounded();
|
|
||||||
|
|
||||||
let mut operations = 0;
|
let mut operations = 0;
|
||||||
while operations < max_operations {
|
while operations < max_operations {
|
||||||
let distribution = rng.lock().gen_range(0..100);
|
let distribution = rng.lock().gen_range(0..100);
|
||||||
|
@ -6014,7 +5876,6 @@ async fn test_random_collaboration(
|
||||||
|
|
||||||
deterministic.start_waiting();
|
deterministic.start_waiting();
|
||||||
let guest = server.create_client(&mut guest_cx, &guest_username).await;
|
let guest = server.create_client(&mut guest_cx, &guest_username).await;
|
||||||
guest.language_registry.add(language.clone());
|
|
||||||
let guest_user_id = guest.current_user_id(&guest_cx);
|
let guest_user_id = guest.current_user_id(&guest_cx);
|
||||||
|
|
||||||
active_call
|
active_call
|
||||||
|
@ -6095,7 +5956,7 @@ async fn test_random_collaboration(
|
||||||
|
|
||||||
operations += 1;
|
operations += 1;
|
||||||
}
|
}
|
||||||
_ => {
|
_ if !op_start_signals.is_empty() => {
|
||||||
while operations < max_operations && rng.lock().gen_bool(0.7) {
|
while operations < max_operations && rng.lock().gen_bool(0.7) {
|
||||||
op_start_signals
|
op_start_signals
|
||||||
.choose(&mut *rng.lock())
|
.choose(&mut *rng.lock())
|
||||||
|
@ -6109,32 +5970,33 @@ async fn test_random_collaboration(
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(op_start_signals);
|
drop(op_start_signals);
|
||||||
deterministic.start_waiting();
|
deterministic.start_waiting();
|
||||||
let mut clients = futures::future::join_all(clients).await;
|
let clients = futures::future::join_all(clients).await;
|
||||||
deterministic.finish_waiting();
|
deterministic.finish_waiting();
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
for (guest_client, mut guest_cx, guest_err) in &clients {
|
for (guest_client, guest_cx, guest_err) in &clients {
|
||||||
if let Some(guest_err) = guest_err {
|
if let Some(guest_err) = guest_err {
|
||||||
panic!("{} error - {:?}", guest_client.username, guest_err);
|
panic!("{} error - {:?}", guest_client.username, guest_err);
|
||||||
}
|
}
|
||||||
|
|
||||||
for guest_project in guest_client.remote_projects {
|
for guest_project in &guest_client.remote_projects {
|
||||||
guest_project.read_with(&guest_cx, |guest_project, cx| {
|
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| {
|
let project = client.local_projects.iter().find(|host_project| {
|
||||||
host_project.read_with(cx, |host_project, cx| {
|
host_project.read_with(cx, |host_project, _| {
|
||||||
host_project.remote_id() == guest_project.remote_id()
|
host_project.remote_id() == guest_project.remote_id()
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
Some((client, project, cx))
|
Some((project, cx))
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((host_client, host_project, host_cx)) = host_project {
|
if let Some((host_project, host_cx)) = host_project {
|
||||||
let host_worktree_snapshots =
|
let host_worktree_snapshots =
|
||||||
host_project.read_with(host_cx, |host_project, cx| {
|
host_project.read_with(host_cx, |host_project, cx| {
|
||||||
host_project
|
host_project
|
||||||
|
@ -6186,44 +6048,61 @@ async fn test_random_collaboration(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for guest_buffer in &guest_client.buffers {
|
for (project_id, guest_buffers) in &guest_client.buffers {
|
||||||
let buffer_id = guest_buffer.read_with(&guest_cx, |buffer, _| buffer.remote_id());
|
let host_project = clients.iter().find_map(|(client, cx, _)| {
|
||||||
let host_buffer = host_project.read_with(&host_cx, |project, cx| {
|
let project = client.local_projects.iter().find(|host_project| {
|
||||||
project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
|
host_project.read_with(cx, |host_project, _| {
|
||||||
panic!(
|
host_project.remote_id() == Some(*project_id)
|
||||||
"host does not have buffer for guest:{}, peer:{:?}, id:{}",
|
})
|
||||||
guest_client.username,
|
})?;
|
||||||
guest_client.peer_id(),
|
Some((project, cx))
|
||||||
buffer_id
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
let path =
|
|
||||||
host_buffer.read_with(&host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
|
|
||||||
|
|
||||||
assert_eq!(
|
let (host_project, host_cx) = if let Some((host_project, host_cx)) = host_project {
|
||||||
guest_buffer.read_with(&guest_cx, |buffer, _| buffer.deferred_ops_len()),
|
(host_project, host_cx)
|
||||||
0,
|
} else {
|
||||||
"{}, buffer {}, path {:?} has deferred operations",
|
continue;
|
||||||
guest_client.username,
|
};
|
||||||
buffer_id,
|
|
||||||
path,
|
for guest_buffer in guest_buffers {
|
||||||
);
|
let buffer_id = guest_buffer.read_with(guest_cx, |buffer, _| buffer.remote_id());
|
||||||
assert_eq!(
|
let host_buffer = host_project.read_with(host_cx, |project, cx| {
|
||||||
guest_buffer.read_with(&guest_cx, |buffer, _| buffer.text()),
|
project.buffer_for_id(buffer_id, cx).unwrap_or_else(|| {
|
||||||
host_buffer.read_with(&host_cx, |buffer, _| buffer.text()),
|
panic!(
|
||||||
"{}, buffer {}, path {:?}, differs from the host's buffer",
|
"host does not have buffer for guest:{}, peer:{:?}, id:{}",
|
||||||
guest_client.username,
|
guest_client.username,
|
||||||
buffer_id,
|
guest_client.peer_id(),
|
||||||
path
|
buffer_id
|
||||||
);
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let path = host_buffer
|
||||||
|
.read_with(host_cx, |buffer, cx| buffer.file().unwrap().full_path(cx));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
guest_buffer.read_with(guest_cx, |buffer, _| buffer.deferred_ops_len()),
|
||||||
|
0,
|
||||||
|
"{}, buffer {}, path {:?} has deferred operations",
|
||||||
|
guest_client.username,
|
||||||
|
buffer_id,
|
||||||
|
path,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
guest_buffer.read_with(guest_cx, |buffer, _| buffer.text()),
|
||||||
|
host_buffer.read_with(host_cx, |buffer, _| buffer.text()),
|
||||||
|
"{}, buffer {}, path {:?}, differs from the host's buffer",
|
||||||
|
guest_client.username,
|
||||||
|
buffer_id,
|
||||||
|
path
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (guest_client, guest_cx, _) in clients {
|
for (client, mut cx, _) in clients {
|
||||||
guest_cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.clear_globals();
|
cx.clear_globals();
|
||||||
drop(guest_client);
|
drop(client);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6520,7 +6399,7 @@ struct TestClient {
|
||||||
pub project_store: ModelHandle<ProjectStore>,
|
pub project_store: ModelHandle<ProjectStore>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
fs: Arc<FakeFs>,
|
fs: Arc<FakeFs>,
|
||||||
buffers: HashSet<ModelHandle<language::Buffer>>,
|
buffers: HashMap<u64, HashSet<ModelHandle<language::Buffer>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for TestClient {
|
impl Deref for TestClient {
|
||||||
|
@ -6635,472 +6514,6 @@ impl TestClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn simulate_host(
|
|
||||||
mut self,
|
|
||||||
project: ModelHandle<Project>,
|
|
||||||
op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
|
|
||||||
rng: Arc<Mutex<StdRng>>,
|
|
||||||
mut cx: TestAppContext,
|
|
||||||
) -> (
|
|
||||||
Self,
|
|
||||||
ModelHandle<Project>,
|
|
||||||
TestAppContext,
|
|
||||||
Option<anyhow::Error>,
|
|
||||||
) {
|
|
||||||
async fn simulate_host_internal(
|
|
||||||
client: &mut TestClient,
|
|
||||||
project: ModelHandle<Project>,
|
|
||||||
mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
|
|
||||||
rng: Arc<Mutex<StdRng>>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let fs = project.read_with(cx, |project, _| project.fs().clone());
|
|
||||||
|
|
||||||
while op_start_signal.next().await.is_some() {
|
|
||||||
let distribution = rng.lock().gen_range::<usize, _>(0..100);
|
|
||||||
let files = fs.as_fake().files().await;
|
|
||||||
match distribution {
|
|
||||||
0..=19 if !files.is_empty() => {
|
|
||||||
let path = files.choose(&mut *rng.lock()).unwrap();
|
|
||||||
let mut path = path.as_path();
|
|
||||||
while let Some(parent_path) = path.parent() {
|
|
||||||
path = parent_path;
|
|
||||||
if rng.lock().gen() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Host: find/create local worktree {:?}", path);
|
|
||||||
let find_or_create_worktree = project.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree(path, true, cx)
|
|
||||||
});
|
|
||||||
if rng.lock().gen() {
|
|
||||||
cx.background().spawn(find_or_create_worktree).detach();
|
|
||||||
} else {
|
|
||||||
find_or_create_worktree.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20..=79 if !files.is_empty() => {
|
|
||||||
let buffer = if client.buffers.is_empty() || rng.lock().gen() {
|
|
||||||
let file = files.choose(&mut *rng.lock()).unwrap();
|
|
||||||
let (worktree, path) = project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.find_or_create_local_worktree(file.clone(), true, cx)
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
let project_path =
|
|
||||||
worktree.read_with(cx, |worktree, _| (worktree.id(), path));
|
|
||||||
log::info!(
|
|
||||||
"Host: opening path {:?}, worktree {}, relative_path {:?}",
|
|
||||||
file,
|
|
||||||
project_path.0,
|
|
||||||
project_path.1
|
|
||||||
);
|
|
||||||
let buffer = project
|
|
||||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
client.buffers.insert(buffer.clone());
|
|
||||||
buffer
|
|
||||||
} else {
|
|
||||||
client
|
|
||||||
.buffers
|
|
||||||
.iter()
|
|
||||||
.choose(&mut *rng.lock())
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
if rng.lock().gen_bool(0.1) {
|
|
||||||
cx.update(|cx| {
|
|
||||||
log::info!(
|
|
||||||
"Host: dropping buffer {:?}",
|
|
||||||
buffer.read(cx).file().unwrap().full_path(cx)
|
|
||||||
);
|
|
||||||
client.buffers.remove(&buffer);
|
|
||||||
drop(buffer);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
log::info!(
|
|
||||||
"Host: updating buffer {:?} ({})",
|
|
||||||
buffer.file().unwrap().full_path(cx),
|
|
||||||
buffer.remote_id()
|
|
||||||
);
|
|
||||||
|
|
||||||
if rng.lock().gen_bool(0.7) {
|
|
||||||
buffer.randomly_edit(&mut *rng.lock(), 5, cx);
|
|
||||||
} else {
|
|
||||||
buffer.randomly_undo_redo(&mut *rng.lock(), cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => loop {
|
|
||||||
let path_component_count = rng.lock().gen_range::<usize, _>(1..=5);
|
|
||||||
let mut path = PathBuf::new();
|
|
||||||
path.push("/");
|
|
||||||
for _ in 0..path_component_count {
|
|
||||||
let letter = rng.lock().gen_range(b'a'..=b'z');
|
|
||||||
path.push(std::str::from_utf8(&[letter]).unwrap());
|
|
||||||
}
|
|
||||||
path.set_extension("rs");
|
|
||||||
let parent_path = path.parent().unwrap();
|
|
||||||
|
|
||||||
log::info!("Host: creating file {:?}", path,);
|
|
||||||
|
|
||||||
if fs.create_dir(parent_path).await.is_ok()
|
|
||||||
&& fs.create_file(&path, Default::default()).await.is_ok()
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
log::info!("Host: cannot create file");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.background().simulate_random_delay().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let result =
|
|
||||||
simulate_host_internal(&mut self, project.clone(), op_start_signal, rng, &mut cx).await;
|
|
||||||
log::info!("Host done");
|
|
||||||
(self, project, cx, result.err())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn simulate_guest(
|
|
||||||
mut self,
|
|
||||||
guest_username: String,
|
|
||||||
project: ModelHandle<Project>,
|
|
||||||
op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
|
|
||||||
rng: Arc<Mutex<StdRng>>,
|
|
||||||
mut cx: TestAppContext,
|
|
||||||
) -> (
|
|
||||||
Self,
|
|
||||||
ModelHandle<Project>,
|
|
||||||
TestAppContext,
|
|
||||||
Option<anyhow::Error>,
|
|
||||||
) {
|
|
||||||
async fn simulate_guest_internal(
|
|
||||||
client: &mut TestClient,
|
|
||||||
guest_username: &str,
|
|
||||||
project: ModelHandle<Project>,
|
|
||||||
mut op_start_signal: futures::channel::mpsc::UnboundedReceiver<()>,
|
|
||||||
rng: Arc<Mutex<StdRng>>,
|
|
||||||
cx: &mut TestAppContext,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
while op_start_signal.next().await.is_some() {
|
|
||||||
let buffer = if client.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())
|
|
||||||
})
|
|
||||||
.choose(&mut *rng.lock())
|
|
||||||
}) {
|
|
||||||
worktree
|
|
||||||
} else {
|
|
||||||
cx.background().simulate_random_delay().await;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {} ({})",
|
|
||||||
guest_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 {}",
|
|
||||||
guest_username,
|
|
||||||
project_path.1,
|
|
||||||
project_path.0,
|
|
||||||
worktree_root_name,
|
|
||||||
buffer.read_with(cx, |buffer, _| buffer.remote_id())
|
|
||||||
);
|
|
||||||
client.buffers.insert(buffer.clone());
|
|
||||||
buffer
|
|
||||||
} else {
|
|
||||||
client
|
|
||||||
.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 {:?}",
|
|
||||||
guest_username,
|
|
||||||
buffer.read(cx).file().unwrap().full_path(cx)
|
|
||||||
);
|
|
||||||
client.buffers.remove(&buffer);
|
|
||||||
drop(buffer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
10..=19 => {
|
|
||||||
let completions = project.update(cx, |project, cx| {
|
|
||||||
log::info!(
|
|
||||||
"{}: requesting completions for buffer {} ({:?})",
|
|
||||||
guest_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", guest_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 {} ({:?})",
|
|
||||||
guest_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", guest_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 {} ({:?})",
|
|
||||||
guest_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", guest_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 {} ({:?})",
|
|
||||||
guest_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", guest_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 {} ({:?})",
|
|
||||||
guest_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", guest_username);
|
|
||||||
cx.update(|cx| definitions.detach_and_log_err(cx));
|
|
||||||
} else {
|
|
||||||
client.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 {} ({:?})",
|
|
||||||
guest_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", guest_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 {:?}", guest_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", guest_username);
|
|
||||||
cx.update(|cx| search.detach_and_log_err(cx));
|
|
||||||
} else {
|
|
||||||
client.buffers.extend(search.await?.into_keys());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 mut new_path = PathBuf::new();
|
|
||||||
new_path.push(new_name);
|
|
||||||
new_path.set_extension("rs");
|
|
||||||
log::info!(
|
|
||||||
"{}: creating {:?} in worktree {} ({})",
|
|
||||||
guest_username,
|
|
||||||
new_path,
|
|
||||||
worktree_id,
|
|
||||||
worktree_root_name,
|
|
||||||
);
|
|
||||||
project
|
|
||||||
.update(cx, |project, cx| {
|
|
||||||
project.create_entry((worktree_id, new_path), false, cx)
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
log::info!(
|
|
||||||
"{}: updating buffer {} ({:?})",
|
|
||||||
guest_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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cx.background().simulate_random_delay().await;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = simulate_guest_internal(
|
|
||||||
&mut self,
|
|
||||||
&guest_username,
|
|
||||||
project.clone(),
|
|
||||||
op_start_signal,
|
|
||||||
rng,
|
|
||||||
&mut cx,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
log::info!("{}: done", guest_username);
|
|
||||||
|
|
||||||
(self, project, cx, result.err())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn simulate(
|
pub async fn simulate(
|
||||||
mut self,
|
mut self,
|
||||||
username: String,
|
username: String,
|
||||||
|
@ -7115,6 +6528,127 @@ impl TestClient {
|
||||||
rng: Arc<Mutex<StdRng>>,
|
rng: Arc<Mutex<StdRng>>,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) -> anyhow::Result<()> {
|
) -> 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() {
|
while op_start_signal.next().await.is_some() {
|
||||||
let active_call = cx.read(ActiveCall::global);
|
let active_call = cx.read(ActiveCall::global);
|
||||||
let room = active_call.read_with(cx, |call, _| call.room().unwrap().clone());
|
let room = active_call.read_with(cx, |call, _| call.room().unwrap().clone());
|
||||||
|
@ -7164,12 +6698,13 @@ impl TestClient {
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
active_call
|
let project_id = active_call
|
||||||
.update(cx, |call, cx| call.share_project(project.clone(), cx))
|
.update(cx, |call, cx| call.share_project(project.clone(), cx))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let buffer = if client.buffers.is_empty() || rng.lock().gen() {
|
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| {
|
let worktree = if let Some(worktree) = project.read_with(cx, |project, cx| {
|
||||||
project
|
project
|
||||||
.worktrees(cx)
|
.worktrees(cx)
|
||||||
|
@ -7218,15 +6753,10 @@ impl TestClient {
|
||||||
worktree_root_name,
|
worktree_root_name,
|
||||||
buffer.read_with(cx, |buffer, _| buffer.remote_id())
|
buffer.read_with(cx, |buffer, _| buffer.remote_id())
|
||||||
);
|
);
|
||||||
client.buffers.insert(buffer.clone());
|
buffers.insert(buffer.clone());
|
||||||
buffer
|
buffer
|
||||||
} else {
|
} else {
|
||||||
client
|
buffers.iter().choose(&mut *rng.lock()).unwrap().clone()
|
||||||
.buffers
|
|
||||||
.iter()
|
|
||||||
.choose(&mut *rng.lock())
|
|
||||||
.unwrap()
|
|
||||||
.clone()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let choice = rng.lock().gen_range(0..100);
|
let choice = rng.lock().gen_range(0..100);
|
||||||
|
@ -7238,7 +6768,7 @@ impl TestClient {
|
||||||
username,
|
username,
|
||||||
buffer.read(cx).file().unwrap().full_path(cx)
|
buffer.read(cx).file().unwrap().full_path(cx)
|
||||||
);
|
);
|
||||||
client.buffers.remove(&buffer);
|
buffers.remove(&buffer);
|
||||||
drop(buffer);
|
drop(buffer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7355,7 +6885,7 @@ impl TestClient {
|
||||||
log::info!("{}: detaching definitions request", username);
|
log::info!("{}: detaching definitions request", username);
|
||||||
cx.update(|cx| definitions.detach_and_log_err(cx));
|
cx.update(|cx| definitions.detach_and_log_err(cx));
|
||||||
} else {
|
} else {
|
||||||
client.buffers.extend(
|
buffers.extend(
|
||||||
definitions.await?.into_iter().map(|loc| loc.target.buffer),
|
definitions.await?.into_iter().map(|loc| loc.target.buffer),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7398,7 +6928,7 @@ impl TestClient {
|
||||||
log::info!("{}: detaching search request", username);
|
log::info!("{}: detaching search request", username);
|
||||||
cx.update(|cx| search.detach_and_log_err(cx));
|
cx.update(|cx| search.detach_and_log_err(cx));
|
||||||
} else {
|
} else {
|
||||||
client.buffers.extend(search.await?.into_keys());
|
buffers.extend(search.await?.into_keys());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
60..=69 => {
|
60..=69 => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue