vim test redux (#11709)
This cleans up the neovim-backed vim tests: - removed exempted tests (we'll rely on bug reports to find missing edge cases) - moved all assertions into non-async fn's so that failures are reporting on the right file/line - removed the NeovimBackedBindingTestContext - renamed a few things to make them clearer - reduced the number of permutations tested in some cases to reduce slowest test from 60s to 5s Release Notes: - N/A
This commit is contained in:
parent
48cba328f2
commit
f550f23b97
38 changed files with 2311 additions and 6505 deletions
|
@ -1,4 +1,3 @@
|
|||
use editor::test::editor_test_context::ContextHandle;
|
||||
use gpui::{px, size, BorrowAppContext, Context};
|
||||
use indoc::indoc;
|
||||
use settings::SettingsStore;
|
||||
|
@ -7,57 +6,132 @@ use std::{
|
|||
panic, thread,
|
||||
};
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use language::language_settings::{AllLanguageSettings, SoftWrap};
|
||||
use util::test::marked_text_offsets;
|
||||
|
||||
use super::{neovim_connection::NeovimConnection, NeovimBackedBindingTestContext, VimTestContext};
|
||||
use super::{neovim_connection::NeovimConnection, VimTestContext};
|
||||
use crate::state::Mode;
|
||||
|
||||
pub const SUPPORTED_FEATURES: &[ExemptionFeatures] = &[];
|
||||
|
||||
/// Enum representing features we have tests for but which don't work, yet. Used
|
||||
/// to add exemptions and automatically
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum ExemptionFeatures {
|
||||
// MOTIONS
|
||||
// When an operator completes at the end of the file, an extra newline is left
|
||||
OperatorLastNewlineRemains,
|
||||
|
||||
// OBJECTS
|
||||
// Resulting position after the operation is slightly incorrect for unintuitive reasons.
|
||||
IncorrectLandingPosition,
|
||||
// Operator around the text object at the end of the line doesn't remove whitespace.
|
||||
AroundObjectLeavesWhitespaceAtEndOfLine,
|
||||
// Sentence object on empty lines
|
||||
SentenceOnEmptyLines,
|
||||
// Whitespace isn't included with text objects at the start of the line
|
||||
SentenceAtStartOfLineWithWhitespace,
|
||||
// Whitespace around sentences is slightly incorrect when starting between sentences
|
||||
AroundSentenceStartingBetweenIncludesWrongWhitespace,
|
||||
// Non empty selection with text objects in visual mode
|
||||
NonEmptyVisualTextObjects,
|
||||
// Sentence Doesn't backtrack when its at the end of the file
|
||||
SentenceAfterPunctuationAtEndOfFile,
|
||||
}
|
||||
|
||||
impl ExemptionFeatures {
|
||||
pub fn supported(&self) -> bool {
|
||||
SUPPORTED_FEATURES.contains(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NeovimBackedTestContext {
|
||||
cx: VimTestContext,
|
||||
// Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
|
||||
// bindings are exempted. If None, all bindings are ignored for that insertion text.
|
||||
exemptions: HashMap<String, Option<HashSet<String>>>,
|
||||
pub(crate) neovim: NeovimConnection,
|
||||
|
||||
last_set_state: Option<String>,
|
||||
recent_keystrokes: Vec<String>,
|
||||
}
|
||||
|
||||
is_dirty: bool,
|
||||
#[derive(Default)]
|
||||
pub struct SharedState {
|
||||
neovim: String,
|
||||
editor: String,
|
||||
initial: String,
|
||||
neovim_mode: Mode,
|
||||
editor_mode: Mode,
|
||||
recent_keystrokes: String,
|
||||
}
|
||||
|
||||
impl SharedState {
|
||||
#[track_caller]
|
||||
pub fn assert_matches(&self) {
|
||||
if self.neovim != self.editor || self.neovim_mode != self.editor_mode {
|
||||
panic!(
|
||||
indoc! {"Test failed (zed does not match nvim behaviour)
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# neovim ({}):
|
||||
{}
|
||||
# zed ({}):
|
||||
{}"},
|
||||
self.initial,
|
||||
self.recent_keystrokes,
|
||||
self.neovim_mode,
|
||||
self.neovim,
|
||||
self.editor_mode,
|
||||
self.editor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn assert_eq(&mut self, marked_text: &str) {
|
||||
let marked_text = marked_text.replace('•', " ");
|
||||
if self.neovim == marked_text
|
||||
&& self.neovim == self.editor
|
||||
&& self.neovim_mode == self.editor_mode
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let message = if self.neovim != marked_text {
|
||||
"Test is incorrect (currently expected != neovim_state)"
|
||||
} else {
|
||||
"Editor does not match nvim behaviour"
|
||||
};
|
||||
panic!(
|
||||
indoc! {"{}
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# currently expected:
|
||||
{}
|
||||
# neovim ({}):
|
||||
{}
|
||||
# zed ({}):
|
||||
{}"},
|
||||
message,
|
||||
self.initial,
|
||||
self.recent_keystrokes,
|
||||
marked_text.replace(" \n", "•\n"),
|
||||
self.neovim_mode,
|
||||
self.neovim.replace(" \n", "•\n"),
|
||||
self.editor_mode,
|
||||
self.editor.replace(" \n", "•\n"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SharedClipboard {
|
||||
neovim: String,
|
||||
editor: String,
|
||||
state: SharedState,
|
||||
}
|
||||
|
||||
impl SharedClipboard {
|
||||
#[track_caller]
|
||||
pub fn assert_eq(&self, expected: &str) {
|
||||
if expected == self.neovim && self.neovim == self.editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = if expected == self.neovim {
|
||||
"Test is incorrect (currently expected != neovim_state)"
|
||||
} else {
|
||||
"Editor does not match nvim behaviour"
|
||||
};
|
||||
|
||||
panic!(
|
||||
indoc! {"{}
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# currently expected:
|
||||
{}
|
||||
# neovim clipboard:
|
||||
{}
|
||||
# zed clipboard:
|
||||
{}"},
|
||||
message,
|
||||
self.state.initial,
|
||||
self.state.recent_keystrokes,
|
||||
expected,
|
||||
self.neovim,
|
||||
self.editor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl NeovimBackedTestContext {
|
||||
|
@ -78,49 +152,13 @@ impl NeovimBackedTestContext {
|
|||
.to_string();
|
||||
Self {
|
||||
cx: VimTestContext::new(cx, true).await,
|
||||
exemptions: Default::default(),
|
||||
neovim: NeovimConnection::new(test_name).await,
|
||||
|
||||
last_set_state: None,
|
||||
recent_keystrokes: Default::default(),
|
||||
is_dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_initial_state_exemptions(
|
||||
&mut self,
|
||||
marked_positions: &str,
|
||||
missing_feature: ExemptionFeatures, // Feature required to support this exempted test case
|
||||
) {
|
||||
if !missing_feature.supported() {
|
||||
let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
|
||||
|
||||
for cursor_offset in cursor_offsets.iter() {
|
||||
let mut marked_text = unmarked_text.clone();
|
||||
marked_text.insert(*cursor_offset, 'ˇ');
|
||||
|
||||
// None represents all key bindings being exempted for that initial state
|
||||
self.exemptions.insert(marked_text, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn simulate_shared_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
|
||||
self.neovim.send_keystroke(keystroke_text).await;
|
||||
self.simulate_keystroke(keystroke_text)
|
||||
}
|
||||
|
||||
pub async fn simulate_shared_keystrokes<const COUNT: usize>(
|
||||
&mut self,
|
||||
keystroke_texts: [&str; COUNT],
|
||||
) {
|
||||
for keystroke_text in keystroke_texts.into_iter() {
|
||||
self.recent_keystrokes.push(keystroke_text.to_string());
|
||||
self.neovim.send_keystroke(keystroke_text).await;
|
||||
}
|
||||
self.simulate_keystrokes(keystroke_texts);
|
||||
}
|
||||
|
||||
pub async fn set_shared_state(&mut self, marked_text: &str) {
|
||||
let mode = if marked_text.contains('»') {
|
||||
Mode::Visual
|
||||
|
@ -131,7 +169,21 @@ impl NeovimBackedTestContext {
|
|||
self.last_set_state = Some(marked_text.to_string());
|
||||
self.recent_keystrokes = Vec::new();
|
||||
self.neovim.set_state(marked_text).await;
|
||||
self.is_dirty = true;
|
||||
}
|
||||
|
||||
pub async fn simulate_shared_keystrokes(&mut self, keystroke_texts: &str) {
|
||||
for keystroke_text in keystroke_texts.split(' ') {
|
||||
self.recent_keystrokes.push(keystroke_text.to_string());
|
||||
self.neovim.send_keystroke(keystroke_text).await;
|
||||
}
|
||||
self.simulate_keystrokes(keystroke_texts);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub async fn simulate(&mut self, keystrokes: &str, initial_state: &str) -> SharedState {
|
||||
self.set_shared_state(initial_state).await;
|
||||
self.simulate_shared_keystrokes(keystrokes).await;
|
||||
self.shared_state().await
|
||||
}
|
||||
|
||||
pub async fn set_shared_wrap(&mut self, columns: u32) {
|
||||
|
@ -186,241 +238,51 @@ impl NeovimBackedTestContext {
|
|||
self.neovim.set_option(option).await;
|
||||
}
|
||||
|
||||
pub async fn assert_shared_state(&mut self, marked_text: &str) {
|
||||
self.is_dirty = false;
|
||||
let marked_text = marked_text.replace('•', " ");
|
||||
let neovim = self.neovim_state().await;
|
||||
let neovim_mode = self.neovim_mode().await;
|
||||
let editor = self.editor_state();
|
||||
let editor_mode = self.mode();
|
||||
if neovim == marked_text && neovim == editor && neovim_mode == editor_mode {
|
||||
return;
|
||||
}
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
|
||||
let message = if neovim != marked_text {
|
||||
"Test is incorrect (currently expected != neovim_state)"
|
||||
} else {
|
||||
"Editor does not match nvim behaviour"
|
||||
};
|
||||
panic!(
|
||||
indoc! {"{}
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# currently expected:
|
||||
{}
|
||||
# neovim ({}):
|
||||
{}
|
||||
# zed ({}):
|
||||
{}"},
|
||||
message,
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
marked_text.replace(" \n", "•\n"),
|
||||
neovim_mode,
|
||||
neovim.replace(" \n", "•\n"),
|
||||
editor_mode,
|
||||
editor.replace(" \n", "•\n"),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn assert_shared_clipboard(&mut self, text: &str) {
|
||||
let neovim = self.neovim.read_register('"').await;
|
||||
let editor = self.read_from_clipboard().unwrap().text().clone();
|
||||
|
||||
if text == neovim && text == editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let message = if neovim != text {
|
||||
"Test is incorrect (currently expected != neovim)"
|
||||
} else {
|
||||
"Editor does not match nvim behaviour"
|
||||
};
|
||||
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
|
||||
panic!(
|
||||
indoc! {"{}
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# currently expected:
|
||||
{}
|
||||
# neovim clipboard:
|
||||
{}
|
||||
# zed clipboard:
|
||||
{}"},
|
||||
message,
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
text,
|
||||
neovim,
|
||||
editor
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn neovim_state(&mut self) -> String {
|
||||
self.neovim.marked_text().await
|
||||
}
|
||||
|
||||
pub async fn neovim_mode(&mut self) -> Mode {
|
||||
self.neovim.mode().await.unwrap()
|
||||
}
|
||||
|
||||
pub async fn assert_shared_mode(&mut self, mode: Mode) {
|
||||
let neovim = self.neovim_mode().await;
|
||||
let editor = self.cx.mode();
|
||||
|
||||
if neovim != mode || editor != mode {
|
||||
panic!(
|
||||
indoc! {"Test failed (zed does not match nvim behaviour)
|
||||
# desired mode:
|
||||
{:?}
|
||||
# neovim mode:
|
||||
{:?}
|
||||
# zed mode:
|
||||
{:?}"},
|
||||
mode, neovim, editor,
|
||||
)
|
||||
#[must_use]
|
||||
pub async fn shared_clipboard(&mut self) -> SharedClipboard {
|
||||
SharedClipboard {
|
||||
state: self.shared_state().await,
|
||||
neovim: self.neovim.read_register('"').await,
|
||||
editor: self.read_from_clipboard().unwrap().text().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_state_matches(&mut self) {
|
||||
self.is_dirty = false;
|
||||
let neovim = self.neovim_state().await;
|
||||
let neovim_mode = self.neovim_mode().await;
|
||||
let editor = self.editor_state();
|
||||
let editor_mode = self.mode();
|
||||
let initial_state = self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.unwrap_or(&"N/A".to_string())
|
||||
.clone();
|
||||
|
||||
if neovim != editor || neovim_mode != editor_mode {
|
||||
panic!(
|
||||
indoc! {"Test failed (zed does not match nvim behaviour)
|
||||
# initial state:
|
||||
{}
|
||||
# keystrokes:
|
||||
{}
|
||||
# neovim ({}):
|
||||
{}
|
||||
# zed ({}):
|
||||
{}"},
|
||||
initial_state,
|
||||
self.recent_keystrokes.join(" "),
|
||||
neovim_mode,
|
||||
neovim,
|
||||
editor_mode,
|
||||
editor,
|
||||
)
|
||||
#[must_use]
|
||||
pub async fn shared_state(&mut self) -> SharedState {
|
||||
let (mode, marked_text) = self.neovim.state().await;
|
||||
SharedState {
|
||||
neovim: marked_text,
|
||||
neovim_mode: mode,
|
||||
editor: self.editor_state(),
|
||||
editor_mode: self.mode(),
|
||||
initial: self
|
||||
.last_set_state
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or("N/A".to_string()),
|
||||
recent_keystrokes: self.recent_keystrokes.join(" "),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_binding_matches<const COUNT: usize>(
|
||||
#[must_use]
|
||||
pub async fn simulate_at_each_offset(
|
||||
&mut self,
|
||||
keystrokes: [&str; COUNT],
|
||||
initial_state: &str,
|
||||
) {
|
||||
if let Some(possible_exempted_keystrokes) = self.exemptions.get(initial_state) {
|
||||
match possible_exempted_keystrokes {
|
||||
Some(exempted_keystrokes) => {
|
||||
if exempted_keystrokes.contains(&format!("{keystrokes:?}")) {
|
||||
// This keystroke was exempted for this insertion text
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// All keystrokes for this insertion text are exempted
|
||||
return;
|
||||
}
|
||||
keystrokes: &str,
|
||||
marked_positions: &str,
|
||||
) -> SharedState {
|
||||
let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
|
||||
|
||||
for cursor_offset in cursor_offsets.iter() {
|
||||
let mut marked_text = unmarked_text.clone();
|
||||
marked_text.insert(*cursor_offset, 'ˇ');
|
||||
|
||||
let state = self.simulate(keystrokes, &marked_text).await;
|
||||
if state.neovim != state.editor || state.neovim_mode != state.editor_mode {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
let _state_context = self.set_shared_state(initial_state).await;
|
||||
let _keystroke_context = self.simulate_shared_keystrokes(keystrokes).await;
|
||||
self.assert_state_matches().await;
|
||||
}
|
||||
|
||||
pub async fn assert_binding_matches_all<const COUNT: usize>(
|
||||
&mut self,
|
||||
keystrokes: [&str; COUNT],
|
||||
marked_positions: &str,
|
||||
) {
|
||||
let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
|
||||
|
||||
for cursor_offset in cursor_offsets.iter() {
|
||||
let mut marked_text = unmarked_text.clone();
|
||||
marked_text.insert(*cursor_offset, 'ˇ');
|
||||
|
||||
self.assert_binding_matches(keystrokes, &marked_text).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn each_marked_position(&self, marked_positions: &str) -> Vec<String> {
|
||||
let (unmarked_text, cursor_offsets) = marked_text_offsets(marked_positions);
|
||||
let mut ret = Vec::with_capacity(cursor_offsets.len());
|
||||
|
||||
for cursor_offset in cursor_offsets.iter() {
|
||||
let mut marked_text = unmarked_text.clone();
|
||||
marked_text.insert(*cursor_offset, 'ˇ');
|
||||
ret.push(marked_text)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn assert_neovim_compatible<const COUNT: usize>(
|
||||
&mut self,
|
||||
marked_positions: &str,
|
||||
keystrokes: [&str; COUNT],
|
||||
) {
|
||||
self.set_shared_state(&marked_positions).await;
|
||||
self.simulate_shared_keystrokes(keystrokes).await;
|
||||
self.assert_state_matches().await;
|
||||
}
|
||||
|
||||
pub async fn assert_matches_neovim<const COUNT: usize>(
|
||||
&mut self,
|
||||
marked_positions: &str,
|
||||
keystrokes: [&str; COUNT],
|
||||
result: &str,
|
||||
) {
|
||||
self.set_shared_state(marked_positions).await;
|
||||
self.simulate_shared_keystrokes(keystrokes).await;
|
||||
self.assert_shared_state(result).await;
|
||||
}
|
||||
|
||||
pub async fn assert_binding_matches_all_exempted<const COUNT: usize>(
|
||||
&mut self,
|
||||
keystrokes: [&str; COUNT],
|
||||
marked_positions: &str,
|
||||
feature: ExemptionFeatures,
|
||||
) {
|
||||
if SUPPORTED_FEATURES.contains(&feature) {
|
||||
self.assert_binding_matches_all(keystrokes, marked_positions)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn binding<const COUNT: usize>(
|
||||
self,
|
||||
keystrokes: [&'static str; COUNT],
|
||||
) -> NeovimBackedBindingTestContext<COUNT> {
|
||||
NeovimBackedBindingTestContext::new(keystrokes, self)
|
||||
SharedState::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -438,17 +300,6 @@ impl DerefMut for NeovimBackedTestContext {
|
|||
}
|
||||
}
|
||||
|
||||
// a common mistake in tests is to call set_shared_state when
|
||||
// you mean asswert_shared_state. This notices that and lets
|
||||
// you know.
|
||||
impl Drop for NeovimBackedTestContext {
|
||||
fn drop(&mut self) {
|
||||
if self.is_dirty {
|
||||
panic!("Test context was dropped after set_shared_state before assert_shared_state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::test::NeovimBackedTestContext;
|
||||
|
@ -457,8 +308,8 @@ mod test {
|
|||
#[gpui::test]
|
||||
async fn neovim_backed_test_context_works(cx: &mut TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.assert_state_matches().await;
|
||||
cx.shared_state().await.assert_matches();
|
||||
cx.set_shared_state("This is a tesˇt").await;
|
||||
cx.assert_state_matches().await;
|
||||
cx.shared_state().await.assert_matches();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue