Rewrite inlay hint collab tests to remove races

This commit is contained in:
Kirill Bulatov 2023-08-31 00:37:00 +03:00
parent 166585a2a8
commit ab49f8c592

View file

@ -33,7 +33,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::{ sync::{
atomic::{AtomicBool, AtomicU32, Ordering::SeqCst}, atomic::{self, AtomicBool, AtomicUsize, Ordering::SeqCst},
Arc, Arc,
}, },
}; };
@ -7799,7 +7799,7 @@ async fn test_on_input_format_from_guest_to_host(
}); });
} }
#[gpui::test] #[gpui::test(iterations = 10)]
async fn test_mutual_editor_inlay_hint_cache_update( async fn test_mutual_editor_inlay_hint_cache_update(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
@ -7913,30 +7913,27 @@ async fn test_mutual_editor_inlay_hint_cache_update(
.unwrap(); .unwrap();
// Set up the language server to return an additional inlay hint on each request. // Set up the language server to return an additional inlay hint on each request.
let next_call_id = Arc::new(AtomicU32::new(0)); let edits_made = Arc::new(AtomicUsize::new(0));
let closure_edits_made = Arc::clone(&edits_made);
fake_language_server fake_language_server
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| { .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
let task_next_call_id = Arc::clone(&next_call_id); let task_edits_made = Arc::clone(&closure_edits_made);
async move { async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path("/a/main.rs").unwrap(),
); );
let call_count = task_next_call_id.fetch_add(1, SeqCst); let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
Ok(Some( Ok(Some(vec![lsp::InlayHint {
(0..=call_count) position: lsp::Position::new(0, edits_made as u32),
.map(|ix| lsp::InlayHint { label: lsp::InlayHintLabel::String(edits_made.to_string()),
position: lsp::Position::new(0, ix),
label: lsp::InlayHintLabel::String(ix.to_string()),
kind: None, kind: None,
text_edits: None, text_edits: None,
tooltip: None, tooltip: None,
padding_left: None, padding_left: None,
padding_right: None, padding_right: None,
data: None, data: None,
}) }]))
.collect(),
))
} }
}) })
.next() .next()
@ -7945,17 +7942,17 @@ async fn test_mutual_editor_inlay_hint_cache_update(
deterministic.run_until_parked(); deterministic.run_until_parked();
let mut edits_made = 1; let initial_edit = edits_made.load(atomic::Ordering::Acquire);
editor_a.update(cx_a, |editor, _| { editor_a.update(cx_a, |editor, _| {
assert_eq!( assert_eq!(
vec!["0".to_string()], vec![initial_edit.to_string()],
extract_hint_labels(editor), extract_hint_labels(editor),
"Host should get its first hints when opens an editor" "Host should get its first hints when opens an editor"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
edits_made, 1,
"Host editor update the cache version after every cache/view change", "Host editor update the cache version after every cache/view change",
); );
}); });
@ -7972,144 +7969,104 @@ async fn test_mutual_editor_inlay_hint_cache_update(
deterministic.run_until_parked(); deterministic.run_until_parked();
editor_b.update(cx_b, |editor, _| { editor_b.update(cx_b, |editor, _| {
assert_eq!( assert_eq!(
vec!["0".to_string(), "1".to_string()], vec![initial_edit.to_string()],
extract_hint_labels(editor), extract_hint_labels(editor),
"Client should get its first hints when opens an editor" "Client should get its first hints when opens an editor"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
edits_made, 1,
"Guest editor update the cache version after every cache/view change" "Guest editor update the cache version after every cache/view change"
); );
}); });
let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_b.update(cx_b, |editor, cx| { editor_b.update(cx_b, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone())); editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
editor.handle_input(":", cx); editor.handle_input(":", cx);
cx.focus(&editor_b); cx.focus(&editor_b);
edits_made += 1;
}); });
deterministic.run_until_parked(); deterministic.run_until_parked();
editor_a.update(cx_a, |editor, _| { editor_a.update(cx_a, |editor, _| {
assert_eq!( assert_eq!(
vec![ vec![after_client_edit.to_string()],
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string()
],
extract_hint_labels(editor), extract_hint_labels(editor),
"Guest should get hints the 1st edit and 2nd LSP query"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version(), edits_made); assert_eq!(inlay_cache.version(), 2);
}); });
editor_b.update(cx_b, |editor, _| { editor_b.update(cx_b, |editor, _| {
assert_eq!( assert_eq!(
vec!["0".to_string(), "1".to_string(), "2".to_string(),], vec![after_client_edit.to_string()],
extract_hint_labels(editor), extract_hint_labels(editor),
"Guest should get hints the 1st edit and 2nd LSP query"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version(), edits_made); assert_eq!(inlay_cache.version(), 2);
}); });
let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
editor_a.update(cx_a, |editor, cx| { editor_a.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
editor.handle_input("a change to increment both buffers' versions", cx); editor.handle_input("a change to increment both buffers' versions", cx);
cx.focus(&editor_a); cx.focus(&editor_a);
edits_made += 1;
}); });
deterministic.run_until_parked(); deterministic.run_until_parked();
editor_a.update(cx_a, |editor, _| { editor_a.update(cx_a, |editor, _| {
assert_eq!( assert_eq!(
vec![ vec![after_host_edit.to_string()],
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string()
],
extract_hint_labels(editor), extract_hint_labels(editor),
"Host should get hints from 3rd edit, 5th LSP query: \
4th query was made by guest (but not applied) due to cache invalidation logic"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version(), edits_made); assert_eq!(inlay_cache.version(), 3);
}); });
editor_b.update(cx_b, |editor, _| { editor_b.update(cx_b, |editor, _| {
assert_eq!( assert_eq!(
vec![ vec![after_host_edit.to_string()],
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
"5".to_string(),
],
extract_hint_labels(editor), extract_hint_labels(editor),
"Guest should get hints from 3rd edit, 6th LSP query"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!(inlay_cache.version(), edits_made); assert_eq!(inlay_cache.version(), 3);
}); });
let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
fake_language_server fake_language_server
.request::<lsp::request::InlayHintRefreshRequest>(()) .request::<lsp::request::InlayHintRefreshRequest>(())
.await .await
.expect("inlay refresh request failed"); .expect("inlay refresh request failed");
edits_made += 1;
deterministic.run_until_parked(); deterministic.run_until_parked();
editor_a.update(cx_a, |editor, _| { editor_a.update(cx_a, |editor, _| {
assert_eq!( assert_eq!(
vec![ vec![after_special_edit_for_refresh.to_string()],
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
"5".to_string(),
"6".to_string(),
],
extract_hint_labels(editor), extract_hint_labels(editor),
"Host should react to /refresh LSP request and get new hints from 7th LSP query" "Host should react to /refresh LSP request"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
edits_made, 4,
"Host should accepted all edits and bump its cache version every time" "Host should accepted all edits and bump its cache version every time"
); );
}); });
editor_b.update(cx_b, |editor, _| { editor_b.update(cx_b, |editor, _| {
assert_eq!( assert_eq!(
vec![ vec![after_special_edit_for_refresh.to_string()],
"0".to_string(),
"1".to_string(),
"2".to_string(),
"3".to_string(),
"4".to_string(),
"5".to_string(),
"6".to_string(),
"7".to_string(),
],
extract_hint_labels(editor), extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query" "Guest should get a /refresh LSP request propagated by host"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
edits_made, 4,
"Guest should accepted all edits and bump its cache version every time" "Guest should accepted all edits and bump its cache version every time"
); );
}); });
} }
#[gpui::test] #[gpui::test(iterations = 10)]
async fn test_inlay_hint_refresh_is_forwarded( async fn test_inlay_hint_refresh_is_forwarded(
deterministic: Arc<Deterministic>, deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext, cx_a: &mut TestAppContext,
@ -8223,35 +8180,34 @@ async fn test_inlay_hint_refresh_is_forwarded(
.downcast::<Editor>() .downcast::<Editor>()
.unwrap(); .unwrap();
let other_hints = Arc::new(AtomicBool::new(false));
let fake_language_server = fake_language_servers.next().await.unwrap(); let fake_language_server = fake_language_servers.next().await.unwrap();
let next_call_id = Arc::new(AtomicU32::new(0)); let closure_other_hints = Arc::clone(&other_hints);
fake_language_server fake_language_server
.handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| { .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
let task_next_call_id = Arc::clone(&next_call_id); let task_other_hints = Arc::clone(&closure_other_hints);
async move { async move {
assert_eq!( assert_eq!(
params.text_document.uri, params.text_document.uri,
lsp::Url::from_file_path("/a/main.rs").unwrap(), lsp::Url::from_file_path("/a/main.rs").unwrap(),
); );
let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst); let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
let mut new_hints = Vec::with_capacity(current_call_id as usize); let character = if other_hints { 0 } else { 2 };
loop { let label = if other_hints {
new_hints.push(lsp::InlayHint { "other hint"
position: lsp::Position::new(0, current_call_id), } else {
label: lsp::InlayHintLabel::String(current_call_id.to_string()), "initial hint"
};
Ok(Some(vec![lsp::InlayHint {
position: lsp::Position::new(0, character),
label: lsp::InlayHintLabel::String(label.to_string()),
kind: None, kind: None,
text_edits: None, text_edits: None,
tooltip: None, tooltip: None,
padding_left: None, padding_left: None,
padding_right: None, padding_right: None,
data: None, data: None,
}); }]))
if current_call_id == 0 {
break;
}
current_call_id -= 1;
}
Ok(Some(new_hints))
} }
}) })
.next() .next()
@ -8270,26 +8226,26 @@ async fn test_inlay_hint_refresh_is_forwarded(
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
0, 0,
"Host should not increment its cache version due to no changes", "Turned off hints should not generate version updates"
); );
}); });
let mut edits_made = 1;
cx_b.foreground().run_until_parked(); cx_b.foreground().run_until_parked();
editor_b.update(cx_b, |editor, _| { editor_b.update(cx_b, |editor, _| {
assert_eq!( assert_eq!(
vec!["0".to_string()], vec!["initial hint".to_string()],
extract_hint_labels(editor), extract_hint_labels(editor),
"Client should get its first hints when opens an editor" "Client should get its first hints when opens an editor"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
edits_made, 1,
"Guest editor update the cache version after every cache/view change" "Should update cache verison after first hints"
); );
}); });
other_hints.fetch_or(true, atomic::Ordering::Release);
fake_language_server fake_language_server
.request::<lsp::request::InlayHintRefreshRequest>(()) .request::<lsp::request::InlayHintRefreshRequest>(())
.await .await
@ -8304,22 +8260,21 @@ async fn test_inlay_hint_refresh_is_forwarded(
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
0, 0,
"Host should not increment its cache version due to no changes", "Turned off hints should not generate version updates, again"
); );
}); });
edits_made += 1;
cx_b.foreground().run_until_parked(); cx_b.foreground().run_until_parked();
editor_b.update(cx_b, |editor, _| { editor_b.update(cx_b, |editor, _| {
assert_eq!( assert_eq!(
vec!["0".to_string(), "1".to_string(),], vec!["other hint".to_string()],
extract_hint_labels(editor), extract_hint_labels(editor),
"Guest should get a /refresh LSP request propagated by host despite host hints are off" "Guest should get a /refresh LSP request propagated by host despite host hints are off"
); );
let inlay_cache = editor.inlay_hint_cache(); let inlay_cache = editor.inlay_hint_cache();
assert_eq!( assert_eq!(
inlay_cache.version(), inlay_cache.version(),
edits_made, 2,
"Guest should accepted all edits and bump its cache version every time" "Guest should accepted all edits and bump its cache version every time"
); );
}); });