on master help sign in
tests for serialized writes
commit07fc80aece221233b4a986b0c5a03e2056cc1307 authorSkyler Hawthorne <skyler@dead10ck.com> dateSat 30 Apr 2022, 20:44 (4 years ago) parentee705dc
8 files changed+147−44
helix-term/tests/integration.rs+2 helix-term/tests/integration/auto_indent.rs+1 helix-term/tests/integration/auto_pairs.rs+2 helix-term/tests/integration/commands.rs+25 helix-term/tests/integration/helpers.rs+44−25 helix-term/tests/integration/movement.rs+8 helix-term/tests/integration/write.rs+60−19 helix-view/src/editor.rs+5
helix-term/tests/integration.rs+2
@@ -17,6 +17,7 @@ mod integration {
1717 Args::default(),
1818 Config::default(),
1919 ("#[\n|]#", "ihello world<esc>", "hello world#[|\n]#"),
20+ None,
2021 )
2122 .await?;
2223
@@ -25,6 +26,7 @@ mod integration {
2526
2627 mod auto_indent;
2728 mod auto_pairs;
29+ mod commands;
2830 mod movement;
2931 mod write;
3032}
helix-term/tests/integration/auto_indent.rs+1
@@ -18,6 +18,7 @@ async fn auto_indent_c() -> anyhow::Result<()> {
1818 }
1919 "},
2020 ),
21+ None,
2122 )
2223 .await?;
2324
helix-term/tests/integration/auto_pairs.rs+2
@@ -6,6 +6,7 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
66 Args::default(),
77 Config::default(),
88 ("#[\n|]#", "i(<esc>", "(#[|)]#\n"),
9+ None,
910 )
1011 .await?;
1112
@@ -19,6 +20,7 @@ async fn auto_pairs_basic() -> anyhow::Result<()> {
1920 ..Default::default()
2021 },
2122 ("#[\n|]#", "i(<esc>", "(#[|\n]#"),
23+ None,
2224 )
2325 .await?;
2426
helix-term/tests/integration/commands.rsnew file+25
@@ -0,0 +1,25 @@
1+use helix_core::diagnostic::Severity;
2+use helix_term::application::Application;
3+
4+use super::*;
5+
6+#[tokio::test]
7+async fn test_write_quit_fail() -> anyhow::Result<()> {
8+ test_key_sequence(
9+ &mut Application::new(
10+ Args {
11+ files: vec![(PathBuf::from("/foo"), Position::default())],
12+ ..Default::default()
13+ },
14+ Config::default(),
15+ )?,
16+ "ihello<esc>:wq<ret>",
17+ Some(&|app| {
18+ assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
19+ }),
20+ None,
21+ )
22+ .await?;
23+
24+ Ok(())
25+}
helix-term/tests/integration/helpers.rs+44−25
@@ -5,7 +5,6 @@ use crossterm::event::{Event, KeyEvent};
55use helix_core::{test, Selection, Transaction};
66use helix_term::{application::Application, args::Args, config::Config};
77use helix_view::{doc, input::parse_macro};
8−use tokio::time::timeout;
98use tokio_stream::wrappers::UnboundedReceiverStream;
109
1110#[derive(Clone, Debug)]
@@ -32,35 +31,48 @@ impl<S: Into<String>> From<(S, S, S)> for TestCase {
3231 }
3332}
3433
34+#[inline]
3535pub async fn test_key_sequence(
3636 app: &mut Application,
3737 in_keys: &str,
3838 test_fn: Option<&dyn Fn(&Application)>,
39+ timeout: Option<Duration>,
3940) -> anyhow::Result<()> {
41+ test_key_sequences(app, vec![(in_keys, test_fn)], timeout).await
42+}
43+
44+pub async fn test_key_sequences(
45+ app: &mut Application,
46+ inputs: Vec<(&str, Option<&dyn Fn(&Application)>)>,
47+ timeout: Option<Duration>,
48+) -> anyhow::Result<()> {
49+ let timeout = timeout.unwrap_or(Duration::from_millis(500));
4050 let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
51+ let mut rx_stream = UnboundedReceiverStream::new(rx);
4152
42− for key_event in parse_macro(&in_keys)?.into_iter() {
43− tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
44− }
53+ for (in_keys, test_fn) in inputs {
54+ for key_event in parse_macro(&in_keys)?.into_iter() {
55+ tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
56+ }
4557
46− let mut rx_stream = UnboundedReceiverStream::new(rx);
47− let event_loop = app.event_loop(&mut rx_stream);
48− let result = timeout(Duration::from_millis(500), event_loop).await;
58+ let event_loop = app.event_loop(&mut rx_stream);
59+ let result = tokio::time::timeout(timeout, event_loop).await;
4960
50− if result.is_ok() {
51− bail!("application exited before test function could run");
52− }
61+ if result.is_ok() {
62+ bail!("application exited before test function could run");
63+ }
5364
54− if let Some(test) = test_fn {
55− test(app);
56− };
65+ if let Some(test) = test_fn {
66+ test(app);
67+ };
68+ }
5769
5870 for key_event in parse_macro("<esc>:q!<ret>")?.into_iter() {
5971 tx.send(Ok(Event::Key(KeyEvent::from(key_event))))?;
6072 }
6173
6274 let event_loop = app.event_loop(&mut rx_stream);
63− timeout(Duration::from_millis(5000), event_loop).await?;
75+ tokio::time::timeout(timeout, event_loop).await?;
6476 app.close().await?;
6577
6678 Ok(())
@@ -70,6 +82,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
7082 app: Option<Application>,
7183 test_case: T,
7284 test_fn: &dyn Fn(&Application),
85+ timeout: Option<Duration>,
7386) -> anyhow::Result<()> {
7487 let test_case = test_case.into();
7588 let mut app =
@@ -87,7 +100,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
87100 view.id,
88101 );
89102
90− test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn)).await
103+ test_key_sequence(&mut app, &test_case.in_keys, Some(test_fn), timeout).await
91104}
92105
93106/// Use this for very simple test cases where there is one input
@@ -97,20 +110,26 @@ pub async fn test_key_sequence_text_result<T: Into<TestCase>>(
97110 args: Args,
98111 config: Config,
99112 test_case: T,
113+ timeout: Option<Duration>,
100114) -> anyhow::Result<()> {
101115 let test_case = test_case.into();
102116 let app = Application::new(args, config).unwrap();
103117
104− test_key_sequence_with_input_text(Some(app), test_case.clone(), &|app| {
105− let doc = doc!(app.editor);
106− assert_eq!(&test_case.out_text, doc.text());
107−
108− let mut selections: Vec<_> = doc.selections().values().cloned().collect();
109− assert_eq!(1, selections.len());
110−
111− let sel = selections.pop().unwrap();
112− assert_eq!(test_case.out_selection, sel);
113− })
118+ test_key_sequence_with_input_text(
119+ Some(app),
120+ test_case.clone(),
121+ &|app| {
122+ let doc = doc!(app.editor);
123+ assert_eq!(&test_case.out_text, doc.text());
124+
125+ let mut selections: Vec<_> = doc.selections().values().cloned().collect();
126+ assert_eq!(1, selections.len());
127+
128+ let sel = selections.pop().unwrap();
129+ assert_eq!(test_case.out_selection, sel);
130+ },
131+ timeout,
132+ )
114133 .await
115134}
116135
helix-term/tests/integration/movement.rs+8
@@ -14,6 +14,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
1414 out_text: String::new(),
1515 out_selection: Selection::single(0, 0),
1616 },
17+ None,
1718 )
1819 .await?;
1920
@@ -21,6 +22,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
2122 Args::default(),
2223 Config::default(),
2324 ("#[\n|]#", "i", "#[|\n]#"),
25+ None,
2426 )
2527 .await?;
2628
@@ -28,6 +30,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
2830 Args::default(),
2931 Config::default(),
3032 ("#[\n|]#", "i<esc>", "#[|\n]#"),
33+ None,
3134 )
3235 .await?;
3336
@@ -35,6 +38,7 @@ async fn insert_mode_cursor_position() -> anyhow::Result<()> {
3538 Args::default(),
3639 Config::default(),
3740 ("#[\n|]#", "i<esc>i", "#[|\n]#"),
41+ None,
3842 )
3943 .await?;
4044
@@ -48,6 +52,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
4852 Args::default(),
4953 Config::default(),
5054 ("#[f|]#oo\n", "vll<A-;><esc>", "#[|foo]#\n"),
55+ None,
5156 )
5257 .await?;
5358
@@ -65,6 +70,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
6570 #(|bar)#"
6671 },
6772 ),
73+ None,
6874 )
6975 .await?;
7076
@@ -82,6 +88,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
8288 #(ba|)#r"
8389 },
8490 ),
91+ None,
8592 )
8693 .await?;
8794
@@ -99,6 +106,7 @@ async fn insert_to_normal_mode_cursor_position() -> anyhow::Result<()> {
99106 #(b|)#ar"
100107 },
101108 ),
109+ None,
102110 )
103111 .await?;
104112
helix-term/tests/integration/write.rs+60−19
@@ -1,9 +1,11 @@
11use std::{
22 io::{Read, Write},
3− ops::RangeInclusive,
3+ time::Duration,
44};
55
6+use helix_core::diagnostic::Severity;
67use helix_term::application::Application;
8+use helix_view::doc;
79
810use super::*;
911
@@ -21,6 +23,7 @@ async fn test_write() -> anyhow::Result<()> {
2123 )?,
2224 "ii can eat glass, it will not hurt me<ret><esc>:w<ret>",
2325 None,
26+ Some(Duration::from_millis(1000)),
2427 )
2528 .await?;
2629
@@ -35,35 +38,73 @@ async fn test_write() -> anyhow::Result<()> {
3538}
3639
3740#[tokio::test]
38−async fn test_write_concurrent() -> anyhow::Result<()> {
39− let mut file = tempfile::NamedTempFile::new().unwrap();
40− let mut command = String::new();
41− const RANGE: RangeInclusive<i32> = 1..=1000;
42−
43− for i in RANGE {
44− let cmd = format!("%c{}<esc>:w<ret>", i);
45− command.push_str(&cmd);
46− }
47−
48− test_key_sequence(
41+async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
42+ test_key_sequences(
4943 &mut Application::new(
5044 Args {
51− files: vec![(file.path().to_path_buf(), Position::default())],
45+ files: vec![(PathBuf::from("/foo"), Position::default())],
5246 ..Default::default()
5347 },
5448 Config::default(),
5549 )?,
56− &command,
50+ vec![
51+ (
52+ "",
53+ Some(&|app| {
54+ let doc = doc!(app.editor);
55+ assert!(!doc.is_modified());
56+ }),
57+ ),
58+ (
59+ "ihello<esc>",
60+ Some(&|app| {
61+ let doc = doc!(app.editor);
62+ assert!(doc.is_modified());
63+ }),
64+ ),
65+ (
66+ ":w<ret>",
67+ Some(&|app| {
68+ assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
69+
70+ let doc = doc!(app.editor);
71+ assert!(doc.is_modified());
72+ }),
73+ ),
74+ ],
5775 None,
5876 )
5977 .await?;
6078
61− file.as_file_mut().flush()?;
62− file.as_file_mut().sync_all()?;
79+ Ok(())
80+}
6381
64− let mut file_content = String::new();
65− file.as_file_mut().read_to_string(&mut file_content)?;
66− assert_eq!(RANGE.end().to_string(), file_content);
82+#[tokio::test]
83+#[ignore]
84+async fn test_write_fail_new_path() -> anyhow::Result<()> {
85+ test_key_sequences(
86+ &mut Application::new(Args::default(), Config::default())?,
87+ vec![
88+ (
89+ "",
90+ Some(&|app| {
91+ let doc = doc!(app.editor);
92+ assert_eq!(None, app.editor.get_status());
93+ assert_eq!(None, doc.path());
94+ }),
95+ ),
96+ (
97+ ":w /foo<ret>",
98+ Some(&|app| {
99+ let doc = doc!(app.editor);
100+ assert_eq!(&Severity::Error, app.editor.get_status().unwrap().1);
101+ assert_eq!(None, doc.path());
102+ }),
103+ ),
104+ ],
105+ Some(Duration::from_millis(1000)),
106+ )
107+ .await?;
67108
68109 Ok(())
69110}
helix-view/src/editor.rs+5
@@ -571,6 +571,11 @@ impl Editor {
571571 self.status_msg = Some((error.into(), Severity::Error));
572572 }
573573
574+ #[inline]
575+ pub fn get_status(&self) -> Option<(&Cow<'static, str>, &Severity)> {
576+ self.status_msg.as_ref().map(|(status, sev)| (status, sev))
577+ }
578+
574579 pub fn set_theme(&mut self, theme: Theme) {
575580 // `ui.selection` is the only scope required to be able to render a theme.
576581 if theme.find_scope_index("ui.selection").is_none() {