diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index d42c4b3ebc..5c19226960 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -32,7 +32,10 @@ use std::{ env, future, mem, path::{Path, PathBuf}, rc::Rc, - sync::Arc, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, + }, }; use unindent::Unindent as _; use workspace::{ @@ -3636,6 +3639,141 @@ async fn test_collaborating_with_diagnostics( }); } +#[gpui::test(iterations = 10)] +async fn test_collaborating_with_lsp_progress_updates_and_diagnostics_ordering( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + disk_based_diagnostics_progress_token: Some("the-disk-based-token".into()), + disk_based_diagnostics_sources: vec!["the-disk-based-diagnostics-source".into()], + ..Default::default() + })) + .await; + client_a.language_registry.add(Arc::new(language)); + + let file_names = &["one.rs", "two.rs", "three.rs", "four.rs", "five.rs"]; + client_a + .fs + .insert_tree( + "/test", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = 2;", + "three.rs": "const THREE: usize = 3;", + "four.rs": "const FOUR: usize = 3;", + "five.rs": "const FIVE: usize = 3;", + }), + ) + .await; + + let (project_a, worktree_id) = client_a.build_local_project("/test", cx_a).await; + + // Share a project as client A + let active_call_a = cx_a.read(ActiveCall::global); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + // Join the project as client B and open all three files. + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let guest_buffers = futures::future::try_join_all(file_names.iter().map(|file_name| { + project_b.update(cx_b, |p, cx| p.open_buffer((worktree_id, file_name), cx)) + })) + .await + .unwrap(); + + // Simulate a language server reporting errors for a file. + let fake_language_server = fake_language_servers.next().await.unwrap(); + fake_language_server + .request::(lsp::WorkDoneProgressCreateParams { + token: lsp::NumberOrString::String("the-disk-based-token".to_string()), + }) + .await + .unwrap(); + fake_language_server.notify::(lsp::ProgressParams { + token: lsp::NumberOrString::String("the-disk-based-token".to_string()), + value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Begin( + lsp::WorkDoneProgressBegin { + title: "Progress Began".into(), + ..Default::default() + }, + )), + }); + for file_name in file_names { + fake_language_server.notify::( + lsp::PublishDiagnosticsParams { + uri: lsp::Url::from_file_path(Path::new("/test").join(file_name)).unwrap(), + version: None, + diagnostics: vec![lsp::Diagnostic { + severity: Some(lsp::DiagnosticSeverity::WARNING), + source: Some("the-disk-based-diagnostics-source".into()), + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), + message: "message one".to_string(), + ..Default::default() + }], + }, + ); + } + fake_language_server.notify::(lsp::ProgressParams { + token: lsp::NumberOrString::String("the-disk-based-token".to_string()), + value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::End( + lsp::WorkDoneProgressEnd { message: None }, + )), + }); + + // When the "disk base diagnostics finished" message is received, the buffers' + // diagnostics are expected to be present. + let disk_based_diagnostics_finished = Arc::new(AtomicBool::new(false)); + project_b.update(cx_b, { + let project_b = project_b.clone(); + let disk_based_diagnostics_finished = disk_based_diagnostics_finished.clone(); + move |_, cx| { + cx.subscribe(&project_b, move |_, _, event, cx| { + if let project::Event::DiskBasedDiagnosticsFinished { .. } = event { + disk_based_diagnostics_finished.store(true, SeqCst); + for buffer in &guest_buffers { + assert_eq!( + buffer + .read(cx) + .snapshot() + .diagnostics_in_range::<_, usize>(0..5, false) + .count(), + 1, + "expected a diagnostic for buffer {:?}", + buffer.read(cx).file().unwrap().path(), + ); + } + } + }) + .detach(); + } + }); + + deterministic.run_until_parked(); + assert!(disk_based_diagnostics_finished.load(SeqCst)); +} + #[gpui::test(iterations = 10)] async fn test_collaborating_with_completion( deterministic: Arc,