From 8d8d389536d1f948f25a38c33f278a5e2f8d1b28 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 30 Apr 2022 09:22:20 -0400 Subject: rename top level module to satisfy cargo fmt --- helix-term/tests/test/auto_indent.rs | 26 ++++++ helix-term/tests/test/auto_pairs.rs | 26 ++++++ helix-term/tests/test/commands.rs | 101 +++++++++++++++++++++ helix-term/tests/test/helpers.rs | 169 +++++++++++++++++++++++++++++++++++ helix-term/tests/test/movement.rs | 135 ++++++++++++++++++++++++++++ helix-term/tests/test/write.rs | 153 +++++++++++++++++++++++++++++++ 6 files changed, 610 insertions(+) create mode 100644 helix-term/tests/test/auto_indent.rs create mode 100644 helix-term/tests/test/auto_pairs.rs create mode 100644 helix-term/tests/test/commands.rs create mode 100644 helix-term/tests/test/helpers.rs create mode 100644 helix-term/tests/test/movement.rs create mode 100644 helix-term/tests/test/write.rs (limited to 'helix-term/tests/test') diff --git a/helix-term/tests/test/auto_indent.rs b/helix-term/tests/test/auto_indent.rs new file mode 100644 index 00000000..8933cb6a --- /dev/null +++ b/helix-term/tests/test/auto_indent.rs @@ -0,0 +1,26 @@ +use super::*; + +#[tokio::test] +async fn auto_indent_c() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args { + files: vec![(PathBuf::from("foo.c"), Position::default())], + ..Default::default() + }, + Config::default(), + // switches to append mode? + ( + helpers::platform_line("void foo() {#[|}]#").as_ref(), + "i", + helpers::platform_line(indoc! {"\ + void foo() { + #[|\n]#\ + } + "}) + .as_ref(), + ), + ) + .await?; + + Ok(()) +} diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs new file mode 100644 index 00000000..52fee55e --- /dev/null +++ b/helix-term/tests/test/auto_pairs.rs @@ -0,0 +1,26 @@ +use super::*; + +#[tokio::test] +async fn auto_pairs_basic() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i(", "(#[|)]#\n"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config { + editor: helix_view::editor::Config { + auto_pairs: AutoPairConfig::Enable(false), + ..Default::default() + }, + ..Default::default() + }, + ("#[\n|]#", "i(", "(#[|\n]#"), + ) + .await?; + + Ok(()) +} diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs new file mode 100644 index 00000000..1ff7cc90 --- /dev/null +++ b/helix-term/tests/test/commands.rs @@ -0,0 +1,101 @@ +use std::{ + io::{Read, Write}, + ops::RangeInclusive, +}; + +use helix_core::diagnostic::Severity; +use helix_term::application::Application; + +use super::*; + +#[tokio::test] +async fn test_write_quit_fail() -> anyhow::Result<()> { + let file = helpers::new_readonly_tempfile()?; + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some("ihello:wq"), + Some(&|app| { + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + }), + ) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn test_buffer_close() -> anyhow::Result<()> { + test_key_sequences( + &mut Application::new(Args::default(), Config::default())?, + vec![ + ( + None, + Some(&|app| { + assert_eq!(1, app.editor.documents().count()); + assert!(!app.editor.is_err()); + }), + ), + ( + Some("ihello:new"), + Some(&|app| { + assert_eq!(2, app.editor.documents().count()); + assert!(!app.editor.is_err()); + }), + ), + ( + Some(":bufferclose"), + Some(&|app| { + assert_eq!(1, app.editor.documents().count()); + assert!(!app.editor.is_err()); + }), + ), + ], + ) + .await?; + + // verify if writes are queued up, it finishes them before closing the buffer + let mut file = tempfile::NamedTempFile::new()?; + let mut command = String::new(); + const RANGE: RangeInclusive = 1..=10; + + for i in RANGE { + let cmd = format!("%c{}:w", i); + command.push_str(&cmd); + } + + command.push_str(":bufferclose"); + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some(&command), + Some(&|app| { + assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status()); + + let doc = app.editor.document_by_path(file.path()); + assert!(doc.is_none(), "found doc: {:?}", doc); + }), + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!(RANGE.end().to_string(), file_content); + + Ok(()) +} diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs new file mode 100644 index 00000000..3fe1934f --- /dev/null +++ b/helix-term/tests/test/helpers.rs @@ -0,0 +1,169 @@ +use std::{io::Write, time::Duration}; + +use anyhow::bail; +use crossterm::event::{Event, KeyEvent}; +use helix_core::{test, Selection, Transaction}; +use helix_term::{application::Application, args::Args, config::Config}; +use helix_view::{doc, input::parse_macro}; +use tempfile::NamedTempFile; +use tokio_stream::wrappers::UnboundedReceiverStream; + +#[derive(Clone, Debug)] +pub struct TestCase { + pub in_text: String, + pub in_selection: Selection, + pub in_keys: String, + pub out_text: String, + pub out_selection: Selection, +} + +impl> From<(S, S, S)> for TestCase { + fn from((input, keys, output): (S, S, S)) -> Self { + let (in_text, in_selection) = test::print(&input.into()); + let (out_text, out_selection) = test::print(&output.into()); + + TestCase { + in_text, + in_selection, + in_keys: keys.into(), + out_text, + out_selection, + } + } +} + +#[inline] +pub async fn test_key_sequence( + app: &mut Application, + in_keys: Option<&str>, + test_fn: Option<&dyn Fn(&Application)>, +) -> anyhow::Result<()> { + test_key_sequences(app, vec![(in_keys, test_fn)]).await +} + +pub async fn test_key_sequences( + app: &mut Application, + inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, +) -> anyhow::Result<()> { + const TIMEOUT: Duration = Duration::from_millis(500); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let mut rx_stream = UnboundedReceiverStream::new(rx); + + for (in_keys, test_fn) in inputs { + if let Some(in_keys) = in_keys { + for key_event in parse_macro(&in_keys)?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + } + + if !app.event_loop_until_idle(&mut rx_stream).await { + bail!("application exited before test function could run"); + } + + if let Some(test) = test_fn { + test(app); + }; + } + + for key_event in parse_macro(":q!")?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + + let event_loop = app.event_loop(&mut rx_stream); + tokio::time::timeout(TIMEOUT, event_loop).await?; + app.close().await?; + + Ok(()) +} + +pub async fn test_key_sequence_with_input_text>( + app: Option, + test_case: T, + test_fn: &dyn Fn(&Application), +) -> anyhow::Result<()> { + let test_case = test_case.into(); + let mut app = match app { + Some(app) => app, + None => Application::new(Args::default(), Config::default())?, + }; + + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + + // replace the initial text with the input text + doc.apply( + &Transaction::change_by_selection(&doc.text(), &sel, |_| { + (0, doc.text().len_chars(), Some((&test_case.in_text).into())) + }) + .with_selection(test_case.in_selection.clone()), + view.id, + ); + + test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn)).await +} + +/// Use this for very simple test cases where there is one input +/// document, selection, and sequence of key presses, and you just +/// want to verify the resulting document and selection. +pub async fn test_key_sequence_text_result>( + args: Args, + config: Config, + test_case: T, +) -> anyhow::Result<()> { + let test_case = test_case.into(); + let app = Application::new(args, config)?; + + test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); + + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + }) + .await +} + +pub fn temp_file_with_contents>( + content: S, +) -> anyhow::Result { + let mut temp_file = tempfile::NamedTempFile::new()?; + + temp_file + .as_file_mut() + .write_all(content.as_ref().as_bytes())?; + + temp_file.flush()?; + temp_file.as_file_mut().sync_all()?; + Ok(temp_file) +} + +/// Replaces all LF chars with the system's appropriate line feed +/// character, and if one doesn't exist already, appends the system's +/// appropriate line ending to the end of a string. +pub fn platform_line(input: &str) -> String { + let line_end = helix_core::DEFAULT_LINE_ENDING.as_str(); + + // we can assume that the source files in this code base will always + // be LF, so indoc strings will always insert LF + let mut output = input.replace("\n", line_end); + + if !output.ends_with(line_end) { + output.push_str(line_end); + } + + output +} + +/// Creates a new temporary file that is set to read only. Useful for +/// testing write failures. +pub fn new_readonly_tempfile() -> anyhow::Result { + let mut file = tempfile::NamedTempFile::new()?; + let metadata = file.as_file().metadata()?; + let mut perms = metadata.permissions(); + perms.set_readonly(true); + file.as_file_mut().set_permissions(perms)?; + Ok(file) +} diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs new file mode 100644 index 00000000..e0bfc3bf --- /dev/null +++ b/helix-term/tests/test/movement.rs @@ -0,0 +1,135 @@ +use helix_term::application::Application; + +use super::*; + +#[tokio::test] +async fn insert_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 0), + in_keys: "i".into(), + out_text: String::new(), + out_selection: Selection::single(0, 0), + }, + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i", "#[|\n]#"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "i", "#[|\n]#"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[\n|]#", "ii", "#[|\n]#"), + ) + .await?; + + Ok(()) +} + +/// Range direction is preserved when escaping insert mode to normal +#[tokio::test] +async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { + test_key_sequence_text_result( + Args::default(), + Config::default(), + ("#[f|]#oo\n", "vll", "#[|foo]#\n"), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "vll", + indoc! {"\ + #[|foo]# + #(|bar)#" + }, + ), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[fo|]#o + #(ba|)#r" + }, + ), + ) + .await?; + + test_key_sequence_text_result( + Args::default(), + Config::default(), + ( + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + "a", + indoc! {"\ + #[f|]#oo + #(b|)#ar" + }, + ), + ) + .await?; + + Ok(()) +} + +/// Ensure the very initial cursor in an opened file is the width of +/// the first grapheme +#[tokio::test] +async fn cursor_position_newly_opened_file() -> anyhow::Result<()> { + let test = |content: &str, expected_sel: Selection| -> anyhow::Result<()> { + let file = helpers::temp_file_with_contents(content)?; + + let mut app = Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?; + + let (view, doc) = helix_view::current!(app.editor); + let sel = doc.selection(view.id).clone(); + assert_eq!(expected_sel, sel); + + Ok(()) + }; + + test("foo", Selection::single(0, 1))?; + test("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ foo", Selection::single(0, 7))?; + test("", Selection::single(0, 0))?; + + Ok(()) +} diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs new file mode 100644 index 00000000..f3abbd91 --- /dev/null +++ b/helix-term/tests/test/write.rs @@ -0,0 +1,153 @@ +use std::{ + io::{Read, Write}, + ops::RangeInclusive, +}; + +use helix_core::diagnostic::Severity; +use helix_term::application::Application; +use helix_view::doc; + +use super::*; + +#[tokio::test] +async fn test_write() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some("ii can eat glass, it will not hurt me:w"), + None, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!( + helpers::platform_line("i can eat glass, it will not hurt me"), + file_content + ); + + Ok(()) +} + +#[tokio::test] +async fn test_write_concurrent() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + let mut command = String::new(); + const RANGE: RangeInclusive = 1..=5000; + + for i in RANGE { + let cmd = format!("%c{}:w", i); + command.push_str(&cmd); + } + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some(&command), + None, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!(RANGE.end().to_string(), file_content); + + Ok(()) +} + +#[tokio::test] +async fn test_write_fail_mod_flag() -> anyhow::Result<()> { + let file = helpers::new_readonly_tempfile()?; + + test_key_sequences( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + vec![ + ( + None, + Some(&|app| { + let doc = doc!(app.editor); + assert!(!doc.is_modified()); + }), + ), + ( + Some("ihello"), + Some(&|app| { + let doc = doc!(app.editor); + assert!(doc.is_modified()); + }), + ), + ( + Some(":w"), + Some(&|app| { + assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); + + let doc = doc!(app.editor); + assert!(doc.is_modified()); + }), + ), + ], + ) + .await?; + + Ok(()) +} + +#[tokio::test] +async fn test_write_fail_new_path() -> anyhow::Result<()> { + let file = helpers::new_readonly_tempfile()?; + + test_key_sequences( + &mut Application::new(Args::default(), Config::default())?, + vec![ + ( + None, + Some(&|app| { + let doc = doc!(app.editor); + assert_ne!( + Some(&Severity::Error), + app.editor.get_status().map(|status| status.1) + ); + assert_eq!(None, doc.path()); + }), + ), + ( + Some(&format!(":w {}", file.path().to_string_lossy())), + Some(&|app| { + let doc = doc!(app.editor); + assert_eq!( + Some(&Severity::Error), + app.editor.get_status().map(|status| status.1) + ); + assert_eq!(None, doc.path()); + }), + ), + ], + ) + .await?; + + Ok(()) +} -- cgit v1.2.3-70-g09d2 From 374724f5ac23b4b99022b4de58bcb17a1a3f99d5 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sat, 30 Apr 2022 22:34:52 -0400 Subject: ignore failing write path tests until fixes are merged --- helix-term/tests/test/commands.rs | 6 ++++-- helix-term/tests/test/write.rs | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'helix-term/tests/test') diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 1ff7cc90..cea31e25 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -9,6 +9,7 @@ use helix_term::application::Application; use super::*; #[tokio::test] +#[ignore] async fn test_write_quit_fail() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?; @@ -31,7 +32,8 @@ async fn test_write_quit_fail() -> anyhow::Result<()> { } #[tokio::test] -async fn test_buffer_close() -> anyhow::Result<()> { +#[ignore] +async fn test_buffer_close_concurrent() -> anyhow::Result<()> { test_key_sequences( &mut Application::new(Args::default(), Config::default())?, vec![ @@ -63,7 +65,7 @@ async fn test_buffer_close() -> anyhow::Result<()> { // verify if writes are queued up, it finishes them before closing the buffer let mut file = tempfile::NamedTempFile::new()?; let mut command = String::new(); - const RANGE: RangeInclusive = 1..=10; + const RANGE: RangeInclusive = 1..=1000; for i in RANGE { let cmd = format!("%c{}:w", i); diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs index f3abbd91..3d724af5 100644 --- a/helix-term/tests/test/write.rs +++ b/helix-term/tests/test/write.rs @@ -40,6 +40,7 @@ async fn test_write() -> anyhow::Result<()> { } #[tokio::test] +#[ignore] async fn test_write_concurrent() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; let mut command = String::new(); @@ -74,6 +75,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> { } #[tokio::test] +#[ignore] async fn test_write_fail_mod_flag() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?; @@ -117,6 +119,7 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> { } #[tokio::test] +#[ignore] async fn test_write_fail_new_path() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?; -- cgit v1.2.3-70-g09d2 From 7c0bca186cdacf070355c1a4ab82121d6a4d2e27 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Sun, 22 May 2022 13:29:52 -0400 Subject: rename test helpers --- helix-term/tests/integration.rs | 8 +-- helix-term/tests/test/auto_indent.rs | 2 +- helix-term/tests/test/auto_pairs.rs | 9 +-- helix-term/tests/test/helpers.rs | 13 +++-- helix-term/tests/test/movement.rs | 103 +++++++++++------------------------ 5 files changed, 45 insertions(+), 90 deletions(-) (limited to 'helix-term/tests/test') diff --git a/helix-term/tests/integration.rs b/helix-term/tests/integration.rs index 376bc88b..11bc4e4c 100644 --- a/helix-term/tests/integration.rs +++ b/helix-term/tests/integration.rs @@ -13,13 +13,7 @@ mod test { #[tokio::test] async fn hello_world() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "ihello world", "hello world#[|\n]#"), - ) - .await?; - + test(("#[\n|]#", "ihello world", "hello world#[|\n]#")).await?; Ok(()) } diff --git a/helix-term/tests/test/auto_indent.rs b/helix-term/tests/test/auto_indent.rs index 8933cb6a..2f638893 100644 --- a/helix-term/tests/test/auto_indent.rs +++ b/helix-term/tests/test/auto_indent.rs @@ -2,7 +2,7 @@ use super::*; #[tokio::test] async fn auto_indent_c() -> anyhow::Result<()> { - test_key_sequence_text_result( + test_with_config( Args { files: vec![(PathBuf::from("foo.c"), Position::default())], ..Default::default() diff --git a/helix-term/tests/test/auto_pairs.rs b/helix-term/tests/test/auto_pairs.rs index 52fee55e..ec47a5b4 100644 --- a/helix-term/tests/test/auto_pairs.rs +++ b/helix-term/tests/test/auto_pairs.rs @@ -2,14 +2,9 @@ use super::*; #[tokio::test] async fn auto_pairs_basic() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i(", "(#[|)]#\n"), - ) - .await?; + test(("#[\n|]#", "i(", "(#[|)]#\n")).await?; - test_key_sequence_text_result( + test_with_config( Args::default(), Config { editor: helix_view::editor::Config { diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 3fe1934f..2bebe31b 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -41,6 +41,7 @@ pub async fn test_key_sequence( test_key_sequences(app, vec![(in_keys, test_fn)]).await } +#[allow(clippy::type_complexity)] pub async fn test_key_sequences( app: &mut Application, inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, @@ -51,7 +52,7 @@ pub async fn test_key_sequences( for (in_keys, test_fn) in inputs { if let Some(in_keys) = in_keys { - for key_event in parse_macro(&in_keys)?.into_iter() { + for key_event in parse_macro(in_keys)?.into_iter() { tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; } } @@ -92,7 +93,7 @@ pub async fn test_key_sequence_with_input_text>( // replace the initial text with the input text doc.apply( - &Transaction::change_by_selection(&doc.text(), &sel, |_| { + &Transaction::change_by_selection(doc.text(), &sel, |_| { (0, doc.text().len_chars(), Some((&test_case.in_text).into())) }) .with_selection(test_case.in_selection.clone()), @@ -105,7 +106,7 @@ pub async fn test_key_sequence_with_input_text>( /// Use this for very simple test cases where there is one input /// document, selection, and sequence of key presses, and you just /// want to verify the resulting document and selection. -pub async fn test_key_sequence_text_result>( +pub async fn test_with_config>( args: Args, config: Config, test_case: T, @@ -126,6 +127,10 @@ pub async fn test_key_sequence_text_result>( .await } +pub async fn test>(test_case: T) -> anyhow::Result<()> { + test_with_config(Args::default(), Config::default(), test_case).await +} + pub fn temp_file_with_contents>( content: S, ) -> anyhow::Result { @@ -148,7 +153,7 @@ pub fn platform_line(input: &str) -> String { // we can assume that the source files in this code base will always // be LF, so indoc strings will always insert LF - let mut output = input.replace("\n", line_end); + let mut output = input.replace('\n', line_end); if !output.ends_with(line_end) { output.push_str(line_end); diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index e0bfc3bf..5fb2ce25 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -4,39 +4,18 @@ use super::*; #[tokio::test] async fn insert_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - TestCase { - in_text: String::new(), - in_selection: Selection::single(0, 0), - in_keys: "i".into(), - out_text: String::new(), - out_selection: Selection::single(0, 0), - }, - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "i", "#[|\n]#"), - ) + test(TestCase { + in_text: String::new(), + in_selection: Selection::single(0, 0), + in_keys: "i".into(), + out_text: String::new(), + out_selection: Selection::single(0, 0), + }) .await?; - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[\n|]#", "ii", "#[|\n]#"), - ) - .await?; + test(("#[\n|]#", "i", "#[|\n]#")).await?; + test(("#[\n|]#", "i", "#[|\n]#")).await?; + test(("#[\n|]#", "ii", "#[|\n]#")).await?; Ok(()) } @@ -44,62 +23,44 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> { /// Range direction is preserved when escaping insert mode to normal #[tokio::test] async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { - test_key_sequence_text_result( - Args::default(), - Config::default(), - ("#[f|]#oo\n", "vll", "#[|foo]#\n"), - ) - .await?; - - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ + test(("#[f|]#oo\n", "vll", "#[|foo]#\n")).await?; + test(( + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - "vll", - indoc! {"\ + }, + "vll", + indoc! {"\ #[|foo]# #(|bar)#" - }, - ), - ) + }, + )) .await?; - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ + test(( + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - "a", - indoc! {"\ + }, + "a", + indoc! {"\ #[fo|]#o #(ba|)#r" - }, - ), - ) + }, + )) .await?; - test_key_sequence_text_result( - Args::default(), - Config::default(), - ( - indoc! {"\ + test(( + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - "a", - indoc! {"\ + }, + "a", + indoc! {"\ #[f|]#oo #(b|)#ar" - }, - ), - ) + }, + )) .await?; Ok(()) -- cgit v1.2.3-70-g09d2 From fac36bc5eab804e823ddef01e50d1e36495c7967 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Mon, 23 May 2022 00:24:18 -0400 Subject: add test for write-quit happy path --- helix-term/tests/test/commands.rs | 3 ++ helix-term/tests/test/helpers.rs | 63 ++++++++++++++++++++++++++++----------- helix-term/tests/test/write.rs | 37 +++++++++++++++++++++++ 3 files changed, 85 insertions(+), 18 deletions(-) (limited to 'helix-term/tests/test') diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index cea31e25..27b4da58 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -25,6 +25,7 @@ async fn test_write_quit_fail() -> anyhow::Result<()> { Some(&|app| { assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); }), + false, ) .await?; @@ -59,6 +60,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> { }), ), ], + false, ) .await?; @@ -89,6 +91,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> { let doc = app.editor.document_by_path(file.path()); assert!(doc.is_none(), "found doc: {:?}", doc); }), + false, ) .await?; diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 2bebe31b..894fb674 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -37,41 +37,56 @@ pub async fn test_key_sequence( app: &mut Application, in_keys: Option<&str>, test_fn: Option<&dyn Fn(&Application)>, + should_exit: bool, ) -> anyhow::Result<()> { - test_key_sequences(app, vec![(in_keys, test_fn)]).await + test_key_sequences(app, vec![(in_keys, test_fn)], should_exit).await } #[allow(clippy::type_complexity)] pub async fn test_key_sequences( app: &mut Application, inputs: Vec<(Option<&str>, Option<&dyn Fn(&Application)>)>, + should_exit: bool, ) -> anyhow::Result<()> { const TIMEOUT: Duration = Duration::from_millis(500); let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let mut rx_stream = UnboundedReceiverStream::new(rx); + let num_inputs = inputs.len(); - for (in_keys, test_fn) in inputs { + for (i, (in_keys, test_fn)) in inputs.into_iter().enumerate() { if let Some(in_keys) = in_keys { for key_event in parse_macro(in_keys)?.into_iter() { tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; } } - if !app.event_loop_until_idle(&mut rx_stream).await { + let app_exited = !app.event_loop_until_idle(&mut rx_stream).await; + + // the app should not exit from any test until the last one + if i < num_inputs - 1 && app_exited { bail!("application exited before test function could run"); } + // verify if it exited on the last iteration if it should have and + // the inverse + if i == num_inputs - 1 && app_exited != should_exit { + bail!("expected app to exit: {} != {}", app_exited, should_exit); + } + if let Some(test) = test_fn { test(app); }; } - for key_event in parse_macro(":q!")?.into_iter() { - tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + if !should_exit { + for key_event in parse_macro(":q!")?.into_iter() { + tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?; + } + + let event_loop = app.event_loop(&mut rx_stream); + tokio::time::timeout(TIMEOUT, event_loop).await?; } - let event_loop = app.event_loop(&mut rx_stream); - tokio::time::timeout(TIMEOUT, event_loop).await?; app.close().await?; Ok(()) @@ -81,6 +96,7 @@ pub async fn test_key_sequence_with_input_text>( app: Option, test_case: T, test_fn: &dyn Fn(&Application), + should_exit: bool, ) -> anyhow::Result<()> { let test_case = test_case.into(); let mut app = match app { @@ -100,7 +116,13 @@ pub async fn test_key_sequence_with_input_text>( view.id, ); - test_key_sequence(&mut app, Some(&test_case.in_keys), Some(test_fn)).await + test_key_sequence( + &mut app, + Some(&test_case.in_keys), + Some(test_fn), + should_exit, + ) + .await } /// Use this for very simple test cases where there is one input @@ -114,16 +136,21 @@ pub async fn test_with_config>( let test_case = test_case.into(); let app = Application::new(args, config)?; - test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| { - let doc = doc!(app.editor); - assert_eq!(&test_case.out_text, doc.text()); - - let mut selections: Vec<_> = doc.selections().values().cloned().collect(); - assert_eq!(1, selections.len()); - - let sel = selections.pop().unwrap(); - assert_eq!(test_case.out_selection, sel); - }) + test_key_sequence_with_input_text( + Some(app), + test_case.clone(), + &|app| { + let doc = doc!(app.editor); + assert_eq!(&test_case.out_text, doc.text()); + + let mut selections: Vec<_> = doc.selections().values().cloned().collect(); + assert_eq!(1, selections.len()); + + let sel = selections.pop().unwrap(); + assert_eq!(test_case.out_selection, sel); + }, + false, + ) .await } diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs index 3d724af5..d22b3125 100644 --- a/helix-term/tests/test/write.rs +++ b/helix-term/tests/test/write.rs @@ -23,6 +23,7 @@ async fn test_write() -> anyhow::Result<()> { )?, Some("ii can eat glass, it will not hurt me:w"), None, + false, ) .await?; @@ -31,6 +32,39 @@ async fn test_write() -> anyhow::Result<()> { let mut file_content = String::new(); file.as_file_mut().read_to_string(&mut file_content)?; + + assert_eq!( + helpers::platform_line("i can eat glass, it will not hurt me"), + file_content + ); + + Ok(()) +} + +#[tokio::test] +async fn test_write_quit() -> anyhow::Result<()> { + let mut file = tempfile::NamedTempFile::new()?; + + test_key_sequence( + &mut Application::new( + Args { + files: vec![(file.path().to_path_buf(), Position::default())], + ..Default::default() + }, + Config::default(), + )?, + Some("ii can eat glass, it will not hurt me:wq"), + None, + true, + ) + .await?; + + file.as_file_mut().flush()?; + file.as_file_mut().sync_all()?; + + let mut file_content = String::new(); + file.as_file_mut().read_to_string(&mut file_content)?; + assert_eq!( helpers::platform_line("i can eat glass, it will not hurt me"), file_content @@ -61,6 +95,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> { )?, Some(&command), None, + false, ) .await?; @@ -112,6 +147,7 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> { }), ), ], + false, ) .await?; @@ -149,6 +185,7 @@ async fn test_write_fail_new_path() -> anyhow::Result<()> { }), ), ], + false, ) .await?; -- cgit v1.2.3-70-g09d2 From 41bf1d581137855596e00ad7702e8827325714b0 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Thu, 31 Mar 2022 10:58:50 -0400 Subject: fix(command): write-quit: do not quit if write fails During write-quit, if the file fails to be written for any reason, helix will still quit without saving the changes. This fixes this behavior by introducing fallibility to the asynchronous job queues. This will also benefit all contexts which may depend on these job queues. Fixes #1575 --- helix-term/src/application.rs | 2 +- helix-term/src/commands/typed.rs | 2 ++ helix-term/src/job.rs | 20 ++++++++++++++++---- helix-term/tests/test/commands.rs | 1 - 4 files changed, 19 insertions(+), 6 deletions(-) (limited to 'helix-term/tests/test') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 15b44a85..2790c9a4 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -814,7 +814,7 @@ impl Application { } pub async fn close(&mut self) -> anyhow::Result<()> { - self.jobs.finish().await; + self.jobs.finish().await?; if self.editor.close_language_servers(None).await.is_err() { log::error!("Timed out waiting for language servers to shutdown"); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 3c88b0ce..5b48ca48 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -233,6 +233,7 @@ fn write_impl( doc.detect_language(cx.editor.syn_loader.clone()); let _ = cx.editor.refresh_language_server(id); } + Ok(()) } @@ -422,6 +423,7 @@ fn write_quit( event: PromptEvent, ) -> anyhow::Result<()> { write_impl(cx, args.first(), false)?; + helix_lsp::block_on(cx.jobs.finish())?; quit(cx, &[], event) } diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs index d21099f7..e5147992 100644 --- a/helix-term/src/job.rs +++ b/helix-term/src/job.rs @@ -2,7 +2,7 @@ use helix_view::Editor; use crate::compositor::Compositor; -use futures_util::future::{self, BoxFuture, Future, FutureExt}; +use futures_util::future::{BoxFuture, Future, FutureExt}; use futures_util::stream::{FuturesUnordered, StreamExt}; pub type Callback = Box; @@ -93,9 +93,21 @@ impl Jobs { } /// Blocks until all the jobs that need to be waited on are done. - pub async fn finish(&mut self) { - let wait_futures = std::mem::take(&mut self.wait_futures); + pub async fn finish(&mut self) -> anyhow::Result<()> { log::debug!("waiting on jobs..."); - wait_futures.for_each(|_| future::ready(())).await + let mut wait_futures = std::mem::take(&mut self.wait_futures); + while let (Some(job), tail) = wait_futures.into_future().await { + match job { + Ok(_) => { + wait_futures = tail; + } + Err(e) => { + self.wait_futures = tail; + return Err(e); + } + } + } + + Ok(()) } } diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 27b4da58..01f13c5c 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -9,7 +9,6 @@ use helix_term::application::Application; use super::*; #[tokio::test] -#[ignore] async fn test_write_quit_fail() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?; -- cgit v1.2.3-70-g09d2 From 665286c199b344c0bd65772156b5e460ff11d768 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Fri, 10 Jun 2022 23:35:34 -0400 Subject: factor new Application with file arg to function --- helix-term/src/application.rs | 4 +--- helix-term/tests/test/commands.rs | 16 ++-------------- helix-term/tests/test/helpers.rs | 14 +++++++++++++- helix-term/tests/test/movement.rs | 11 +---------- helix-term/tests/test/write.rs | 32 ++++---------------------------- 5 files changed, 21 insertions(+), 56 deletions(-) (limited to 'helix-term/tests/test') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 2790c9a4..48e9c275 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -239,11 +239,9 @@ impl Application { self.last_render = Instant::now(); loop { - if self.editor.should_close() { + if !self.event_loop_until_idle(input_stream).await { break; } - - self.event_loop_until_idle(input_stream).await; } } diff --git a/helix-term/tests/test/commands.rs b/helix-term/tests/test/commands.rs index 01f13c5c..0cd79bc7 100644 --- a/helix-term/tests/test/commands.rs +++ b/helix-term/tests/test/commands.rs @@ -13,13 +13,7 @@ async fn test_write_quit_fail() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?; test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, + &mut helpers::app_with_file(file.path())?, Some("ihello:wq"), Some(&|app| { assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1); @@ -76,13 +70,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> { command.push_str(":bufferclose"); test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, + &mut helpers::app_with_file(file.path())?, Some(&command), Some(&|app| { assert!(!app.editor.is_err(), "error: {:?}", app.editor.get_status()); diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 894fb674..8f2501e6 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -1,4 +1,4 @@ -use std::{io::Write, time::Duration}; +use std::{io::Write, path::PathBuf, time::Duration}; use anyhow::bail; use crossterm::event::{Event, KeyEvent}; @@ -199,3 +199,15 @@ pub fn new_readonly_tempfile() -> anyhow::Result { file.as_file_mut().set_permissions(perms)?; Ok(file) } + +/// Creates a new Application with default config that opens the given file +/// path +pub fn app_with_file>(path: P) -> anyhow::Result { + Application::new( + Args { + files: vec![(path.into(), helix_core::Position::default())], + ..Default::default() + }, + Config::default(), + ) +} diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs index 5fb2ce25..088685df 100644 --- a/helix-term/tests/test/movement.rs +++ b/helix-term/tests/test/movement.rs @@ -1,5 +1,3 @@ -use helix_term::application::Application; - use super::*; #[tokio::test] @@ -72,14 +70,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> { async fn cursor_position_newly_opened_file() -> anyhow::Result<()> { let test = |content: &str, expected_sel: Selection| -> anyhow::Result<()> { let file = helpers::temp_file_with_contents(content)?; - - let mut app = Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?; + let mut app = helpers::app_with_file(file.path())?; let (view, doc) = helix_view::current!(app.editor); let sel = doc.selection(view.id).clone(); diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs index d22b3125..39efa2ce 100644 --- a/helix-term/tests/test/write.rs +++ b/helix-term/tests/test/write.rs @@ -14,13 +14,7 @@ async fn test_write() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, + &mut helpers::app_with_file(file.path())?, Some("ii can eat glass, it will not hurt me:w"), None, false, @@ -46,13 +40,7 @@ async fn test_write_quit() -> anyhow::Result<()> { let mut file = tempfile::NamedTempFile::new()?; test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, + &mut helpers::app_with_file(file.path())?, Some("ii can eat glass, it will not hurt me:wq"), None, true, @@ -86,13 +74,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> { } test_key_sequence( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, + &mut helpers::app_with_file(file.path())?, Some(&command), None, false, @@ -115,13 +97,7 @@ async fn test_write_fail_mod_flag() -> anyhow::Result<()> { let file = helpers::new_readonly_tempfile()?; test_key_sequences( - &mut Application::new( - Args { - files: vec![(file.path().to_path_buf(), Position::default())], - ..Default::default() - }, - Config::default(), - )?, + &mut helpers::app_with_file(file.path())?, vec![ ( None, -- cgit v1.2.3-70-g09d2 From 5f7c247430998fabceb55d4689118dd75e2bdfb1 Mon Sep 17 00:00:00 2001 From: Skyler Hawthorne Date: Mon, 13 Jun 2022 22:18:17 -0400 Subject: replace phrase in tests --- helix-term/tests/test/write.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'helix-term/tests/test') diff --git a/helix-term/tests/test/write.rs b/helix-term/tests/test/write.rs index 39efa2ce..8869d881 100644 --- a/helix-term/tests/test/write.rs +++ b/helix-term/tests/test/write.rs @@ -15,7 +15,7 @@ async fn test_write() -> anyhow::Result<()> { test_key_sequence( &mut helpers::app_with_file(file.path())?, - Some("ii can eat glass, it will not hurt me:w"), + Some("ithe gostak distims the doshes:w"), None, false, ) @@ -28,7 +28,7 @@ async fn test_write() -> anyhow::Result<()> { file.as_file_mut().read_to_string(&mut file_content)?; assert_eq!( - helpers::platform_line("i can eat glass, it will not hurt me"), + helpers::platform_line("the gostak distims the doshes"), file_content ); @@ -41,7 +41,7 @@ async fn test_write_quit() -> anyhow::Result<()> { test_key_sequence( &mut helpers::app_with_file(file.path())?, - Some("ii can eat glass, it will not hurt me:wq"), + Some("ithe gostak distims the doshes:wq"), None, true, ) @@ -54,7 +54,7 @@ async fn test_write_quit() -> anyhow::Result<()> { file.as_file_mut().read_to_string(&mut file_content)?; assert_eq!( - helpers::platform_line("i can eat glass, it will not hurt me"), + helpers::platform_line("the gostak distims the doshes"), file_content ); -- cgit v1.2.3-70-g09d2