diff --git a/Cargo.toml b/Cargo.toml index 254c764ad..f82522fbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,12 @@ keywords = ["git", "editor", "tool", "rebase", "cli"] categories = ["command-line-interface", "command-line-utilities", "text-editors"] readme = "README.md" include = [ - "**/*.rs", - "/Cargo.toml", - "/CHANGELOG.md", - "/LICENSE", - "/readme", - "/README.md" + "**/*.rs", + "/Cargo.toml", + "/CHANGELOG.md", + "/LICENSE", + "/readme", + "/README.md" ] edition = "2021" @@ -78,20 +78,20 @@ Full feature terminal based sequence editor for git interactive rebase.""" section = "utility" priority = "optional" assets = [ - ["target/release/interactive-rebase-tool", "usr/bin/interactive-rebase-tool", "755"], - ["README.md", "usr/share/doc/interactive-rebase-tool/", "644"], - ["readme/**/*.md", "usr/share/doc/interactive-rebase-tool/readme/", "644"], - ["CHANGELOG.md", "usr/share/doc/interactive-rebase-tool/", "644"], - ["src/interactive-rebase-tool.1", "usr/share/man/man1/interactive-rebase-tool.1", "644"] + ["target/release/interactive-rebase-tool", "usr/bin/interactive-rebase-tool", "755"], + ["README.md", "usr/share/doc/interactive-rebase-tool/", "644"], + ["readme/**/*.md", "usr/share/doc/interactive-rebase-tool/readme/", "644"], + ["CHANGELOG.md", "usr/share/doc/interactive-rebase-tool/", "644"], + ["src/interactive-rebase-tool.1", "usr/share/man/man1/interactive-rebase-tool.1", "644"] ] [package.metadata.generate-rpm] assets = [ - { source = "target/release/interactive-rebase-tool", dest = "/usr/bin/interactive-rebase-tool", mode = "755" }, - { source = "README.md", dest = "/usr/share/doc/interactive-rebase-tool/", mode = "644" }, - { source = "readme/*.md", dest = "/usr/share/doc/interactive-rebase-tool/readme/", mode = "644" }, - { source = "CHANGELOG.md", dest = "/usr/share/doc/interactive-rebase-tool/", mode = "644" }, - { source = "src/interactive-rebase-tool.1", dest = "/usr/share/man/man1/interactive-rebase-tool.1", mode = "644" }, + { source = "target/release/interactive-rebase-tool", dest = "/usr/bin/interactive-rebase-tool", mode = "755" }, + { source = "README.md", dest = "/usr/share/doc/interactive-rebase-tool/", mode = "644" }, + { source = "readme/*.md", dest = "/usr/share/doc/interactive-rebase-tool/readme/", mode = "644" }, + { source = "CHANGELOG.md", dest = "/usr/share/doc/interactive-rebase-tool/", mode = "644" }, + { source = "src/interactive-rebase-tool.1", dest = "/usr/share/man/man1/interactive-rebase-tool.1", mode = "644" }, ] [lints.rust] @@ -103,7 +103,7 @@ nonstandard_style = { level = "warn", priority = -2 } rust_2018_compatibility = { level = "warn", priority = -2 } rust_2018_idioms = { level = "warn", priority = -2 } rust_2021_compatibility = { level = "warn", priority = -2 } -# rust_2024_compatibility = { level = "warn", priority = -2 } - requires some significant changes +rust_2024_compatibility = { level = "warn", priority = -2 } unused = { level = "warn", priority = -2 } unknown_lints = { level = "warn", priority = -1 } diff --git a/src/application.rs b/src/application.rs index 60190a450..82aeab826 100644 --- a/src/application.rs +++ b/src/application.rs @@ -201,7 +201,7 @@ mod tests { create_config, create_event_reader, mocks, - set_git_directory, + with_git_directory, DefaultTestModule, TestModuleProvider, }, @@ -245,31 +245,31 @@ mod tests { } #[test] - #[serial_test::serial] fn load_repository_failure() { - _ = set_git_directory("fixtures/not-a-repository"); - let event_provider = create_event_reader(|| Ok(None)); - let application: Result>, Exit> = - Application::new(&args(&["todofile"]), event_provider, create_mocked_crossterm()); - let exit = application_error!(application); - assert_eq!(exit.get_status(), &ExitStatus::StateError); - assert!( - exit.get_message() - .as_ref() - .unwrap() - .contains("Unable to load Git repository: ") - ); + with_git_directory("fixtures/not-a-repository", |_| { + let event_provider = create_event_reader(|| Ok(None)); + let application: Result>, Exit> = + Application::new(&args(&["todofile"]), event_provider, create_mocked_crossterm()); + let exit = application_error!(application); + assert_eq!(exit.get_status(), &ExitStatus::StateError); + assert!( + exit.get_message() + .as_ref() + .unwrap() + .contains("Unable to load Git repository: ") + ); + }); } #[test] - #[serial_test::serial] fn load_config_failure() { - _ = set_git_directory("fixtures/invalid-config"); - let event_provider = create_event_reader(|| Ok(None)); - let application: Result>, Exit> = - Application::new(&args(&["rebase-todo"]), event_provider, create_mocked_crossterm()); - let exit = application_error!(application); - assert_eq!(exit.get_status(), &ExitStatus::ConfigError); + with_git_directory("fixtures/invalid-config", |_| { + let event_provider = create_event_reader(|| Ok(None)); + let application: Result>, Exit> = + Application::new(&args(&["rebase-todo"]), event_provider, create_mocked_crossterm()); + let exit = application_error!(application); + assert_eq!(exit.get_status(), &ExitStatus::ConfigError); + }); } #[test] @@ -303,50 +303,50 @@ mod tests { } #[test] - #[serial_test::serial] fn load_todo_file_load_error() { - _ = set_git_directory("fixtures/simple"); - let event_provider = create_event_reader(|| Ok(None)); - let application: Result>, Exit> = - Application::new(&args(&["does-not-exist"]), event_provider, create_mocked_crossterm()); - let exit = application_error!(application); - assert_eq!(exit.get_status(), &ExitStatus::FileReadError); + with_git_directory("fixtures/simple", |_| { + let event_provider = create_event_reader(|| Ok(None)); + let application: Result>, Exit> = + Application::new(&args(&["does-not-exist"]), event_provider, create_mocked_crossterm()); + let exit = application_error!(application); + assert_eq!(exit.get_status(), &ExitStatus::FileReadError); + }); } #[test] - #[serial_test::serial] fn load_todo_file_noop() { - let git_dir = set_git_directory("fixtures/simple"); - let rebase_todo = format!("{git_dir}/rebase-todo-noop"); - let event_provider = create_event_reader(|| Ok(None)); - let application: Result>, Exit> = Application::new( - &args(&[rebase_todo.as_str()]), - event_provider, - create_mocked_crossterm(), - ); - let exit = application_error!(application); - assert_eq!(exit.get_status(), &ExitStatus::Good); + with_git_directory("fixtures/simple", |git_dir| { + let rebase_todo = format!("{git_dir}/rebase-todo-noop"); + let event_provider = create_event_reader(|| Ok(None)); + let application: Result>, Exit> = Application::new( + &args(&[rebase_todo.as_str()]), + event_provider, + create_mocked_crossterm(), + ); + let exit = application_error!(application); + assert_eq!(exit.get_status(), &ExitStatus::Good); + }); } #[test] - #[serial_test::serial] fn load_todo_file_empty() { - let git_dir = set_git_directory("fixtures/simple"); - let rebase_todo = format!("{git_dir}/rebase-todo-empty"); - let event_provider = create_event_reader(|| Ok(None)); - let application: Result>, Exit> = Application::new( - &args(&[rebase_todo.as_str()]), - event_provider, - create_mocked_crossterm(), - ); - let exit = application_error!(application); - assert_eq!(exit.get_status(), &ExitStatus::Good); - assert!( - exit.get_message() - .as_ref() - .unwrap() - .contains("An empty rebase was provided, nothing to edit") - ); + with_git_directory("fixtures/simple", |git_dir| { + let rebase_todo = format!("{git_dir}/rebase-todo-empty"); + let event_provider = create_event_reader(|| Ok(None)); + let application: Result>, Exit> = Application::new( + &args(&[rebase_todo.as_str()]), + event_provider, + create_mocked_crossterm(), + ); + let exit = application_error!(application); + assert_eq!(exit.get_status(), &ExitStatus::Good); + assert!( + exit.get_message() + .as_ref() + .unwrap() + .contains("An empty rebase was provided, nothing to edit") + ); + }); } #[test] @@ -363,22 +363,21 @@ mod tests { } #[test] - #[serial_test::serial] fn run_until_finished_success() { - let git_dir = set_git_directory("fixtures/simple"); - let rebase_todo = format!("{git_dir}/rebase-todo"); - let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W')))))); - let mut application: Application = Application::new( - &args(&[rebase_todo.as_str()]), - event_provider, - create_mocked_crossterm(), - ) - .unwrap(); - assert_ok!(application.run_until_finished()); + with_git_directory("fixtures/simple", |git_dir| { + let rebase_todo = format!("{git_dir}/rebase-todo"); + let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W')))))); + let mut application: Application = Application::new( + &args(&[rebase_todo.as_str()]), + event_provider, + create_mocked_crossterm(), + ) + .unwrap(); + assert_ok!(application.run_until_finished()); + }); } #[test] - #[serial_test::serial] fn run_join_error() { struct FailingThread; impl Threadable for FailingThread { @@ -391,67 +390,68 @@ mod tests { } } - let git_dir = set_git_directory("fixtures/simple"); - let rebase_todo = format!("{git_dir}/rebase-todo"); - let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W')))))); - let mut application: Application = Application::new( - &args(&[rebase_todo.as_str()]), - event_provider, - create_mocked_crossterm(), - ) - .unwrap(); + with_git_directory("fixtures/simple", |git_dir| { + let rebase_todo = format!("{git_dir}/rebase-todo"); + let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W')))))); + let mut application: Application = Application::new( + &args(&[rebase_todo.as_str()]), + event_provider, + create_mocked_crossterm(), + ) + .unwrap(); - application.threads = Some(vec![Box::new(FailingThread {})]); + application.threads = Some(vec![Box::new(FailingThread {})]); - let exit = application.run_until_finished().unwrap_err(); - assert_eq!(exit.get_status(), &ExitStatus::StateError); - assert!( - exit.get_message() - .as_ref() - .unwrap() - .starts_with("Failed to join runtime:") - ); + let exit = application.run_until_finished().unwrap_err(); + assert_eq!(exit.get_status(), &ExitStatus::StateError); + assert!( + exit.get_message() + .as_ref() + .unwrap() + .starts_with("Failed to join runtime:") + ); + }); } #[test] - #[serial_test::serial] fn run_until_finished_kill() { - let git_dir = set_git_directory("fixtures/simple"); - let rebase_todo = format!("{git_dir}/rebase-todo"); - let event_provider = create_event_reader(|| { - Ok(Some(Event::Key(KeyEvent::new( - KeyCode::Char('c'), - KeyModifiers::CONTROL, - )))) + with_git_directory("fixtures/simple", |git_dir| { + let rebase_todo = format!("{git_dir}/rebase-todo"); + let event_provider = create_event_reader(|| { + Ok(Some(Event::Key(KeyEvent::new( + KeyCode::Char('c'), + KeyModifiers::CONTROL, + )))) + }); + let mut application: Application = Application::new( + &args(&[rebase_todo.as_str()]), + event_provider, + create_mocked_crossterm(), + ) + .unwrap(); + let exit = application.run_until_finished().unwrap_err(); + assert_eq!(exit.get_status(), &ExitStatus::Kill); }); - let mut application: Application = Application::new( - &args(&[rebase_todo.as_str()]), - event_provider, - create_mocked_crossterm(), - ) - .unwrap(); - let exit = application.run_until_finished().unwrap_err(); - assert_eq!(exit.get_status(), &ExitStatus::Kill); } #[test] - #[serial_test::serial] fn run_error_on_second_attempt() { - let git_dir = set_git_directory("fixtures/simple"); - let rebase_todo = format!("{git_dir}/rebase-todo"); - let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W')))))); - let mut application: Application = Application::new( - &args(&[rebase_todo.as_str()]), - event_provider, - create_mocked_crossterm(), - ) - .unwrap(); - assert_ok!(application.run_until_finished()); - let exit = application.run_until_finished().unwrap_err(); - assert_eq!(exit.get_status(), &ExitStatus::StateError); - assert_eq!( - exit.get_message().as_ref().unwrap(), - "Attempt made to run application a second time" - ); + with_git_directory("fixtures/simple", |git_dir| { + let rebase_todo = format!("{git_dir}/rebase-todo"); + let event_provider = create_event_reader(|| Ok(Some(Event::Key(KeyEvent::from(KeyCode::Char('W')))))); + let mut application: Application = Application::new( + &args(&[rebase_todo.as_str()]), + event_provider, + create_mocked_crossterm(), + ) + .unwrap(); + assert_ok!(application.run_until_finished()); + let exit = application.run_until_finished().unwrap_err(); + assert_eq!(exit.get_status(), &ExitStatus::StateError); + assert_eq!( + exit.get_message().as_ref().unwrap(), + "Attempt made to run application a second time" + ); + }); } } diff --git a/src/config/git_config.rs b/src/config/git_config.rs index 61fa9da05..fe8bb2d3d 100644 --- a/src/config/git_config.rs +++ b/src/config/git_config.rs @@ -79,15 +79,13 @@ impl TryFrom<&Config> for GitConfig { #[cfg(test)] mod tests { - use std::env::{remove_var, set_var}; - use claims::{assert_err_eq, assert_ok}; use rstest::rstest; use super::*; use crate::{ config::ConfigErrorCause, - test_helpers::{invalid_utf, with_git_config}, + test_helpers::{invalid_utf, with_env_var, with_git_config, EnvVarAction}, }; macro_rules! config_test { @@ -153,42 +151,55 @@ mod tests { } #[test] - #[serial_test::serial] fn git_editor_default_no_env() { - remove_var("VISUAL"); - remove_var("EDITOR"); - let config = GitConfig::new_with_config(None).unwrap(); - assert_eq!(config.editor, "vi"); + with_env_var( + &[EnvVarAction::Remove("VISUAL"), EnvVarAction::Remove("EDITOR")], + || { + let config = GitConfig::new_with_config(None).unwrap(); + assert_eq!(config.editor, "vi"); + }, + ); } #[test] - #[serial_test::serial] fn git_editor_default_visual_env() { - remove_var("EDITOR"); - set_var("VISUAL", "visual-editor"); - let config = GitConfig::new_with_config(None).unwrap(); - assert_eq!(config.editor, "visual-editor"); + with_env_var( + &[ + EnvVarAction::Remove("EDITOR"), + EnvVarAction::Set("VISUAL", String::from("visual-editor")), + ], + || { + let config = GitConfig::new_with_config(None).unwrap(); + assert_eq!(config.editor, "visual-editor"); + }, + ); } #[test] - #[serial_test::serial] fn git_editor_default_editor_env() { - remove_var("VISUAL"); - set_var("EDITOR", "editor"); - - let config = GitConfig::new_with_config(None).unwrap(); - assert_eq!(config.editor, "editor"); + with_env_var( + &[ + EnvVarAction::Remove("VISUAL"), + EnvVarAction::Set("EDITOR", String::from("editor")), + ], + || { + let config = GitConfig::new_with_config(None).unwrap(); + assert_eq!(config.editor, "editor"); + }, + ); } #[test] - #[serial_test::serial] fn git_editor() { - remove_var("VISUAL"); - remove_var("EDITOR"); - with_git_config(&["[core]", "editor = custom"], |git_config| { - let config = GitConfig::new_with_config(Some(&git_config)).unwrap(); - assert_eq!(config.editor, "custom"); - }); + with_env_var( + &[EnvVarAction::Remove("VISUAL"), EnvVarAction::Remove("EDITOR")], + || { + with_git_config(&["[core]", "editor = custom"], |git_config| { + let config = GitConfig::new_with_config(Some(&git_config)).unwrap(); + assert_eq!(config.editor, "custom"); + }); + }, + ); } #[test] @@ -222,16 +233,18 @@ mod tests { } #[test] - #[serial_test::serial] fn git_editor_invalid() { - remove_var("VISUAL"); - remove_var("EDITOR"); - with_git_config( - &["[core]", format!("editor = {}", invalid_utf()).as_str()], - |git_config| { - assert_err_eq!( - GitConfig::new_with_config(Some(&git_config)), - ConfigError::new_read_error("core.editor", ConfigErrorCause::InvalidUtf), + with_env_var( + &[EnvVarAction::Remove("VISUAL"), EnvVarAction::Remove("EDITOR")], + || { + with_git_config( + &["[core]", format!("editor = {}", invalid_utf()).as_str()], + |git_config| { + assert_err_eq!( + GitConfig::new_with_config(Some(&git_config)), + ConfigError::new_read_error("core.editor", ConfigErrorCause::InvalidUtf), + ); + }, ); }, ); diff --git a/src/display/utils.rs b/src/display/utils.rs index c613014e1..e54b2dc01 100644 --- a/src/display/utils.rs +++ b/src/display/utils.rs @@ -198,179 +198,346 @@ fn find_color(color_mode: ColorMode, color: Color) -> CrosstermColor { #[cfg(test)] mod tests { - use std::env::{remove_var, set_var}; - use rstest::rstest; - use serial_test::serial; use super::*; - - fn clear_env() { - remove_var("NO_COLOR"); - remove_var("COLORTERM"); - remove_var("TERM"); - remove_var("VTE_VERSION"); - remove_var("WT_SESSION"); - } + use crate::test_helpers::{with_env_var, EnvVarAction}; #[test] - #[serial] fn detect_color_mode_no_env_2_colors() { - clear_env(); - assert_eq!(detect_color_mode(2), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(2), ColorMode::TwoTone); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_8_colors() { - clear_env(); - assert_eq!(detect_color_mode(8), ColorMode::ThreeBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(8), ColorMode::ThreeBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_less_8_colors() { - clear_env(); - assert_eq!(detect_color_mode(7), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(7), ColorMode::TwoTone); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_16_colors() { - clear_env(); - assert_eq!(detect_color_mode(16), ColorMode::FourBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(16), ColorMode::FourBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_less_16_colors() { - clear_env(); - assert_eq!(detect_color_mode(15), ColorMode::ThreeBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(15), ColorMode::ThreeBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_256_colors() { - clear_env(); - assert_eq!(detect_color_mode(256), ColorMode::EightBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(256), ColorMode::EightBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_less_256_colors() { - clear_env(); - assert_eq!(detect_color_mode(255), ColorMode::FourBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(255), ColorMode::FourBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_env_more_256_colors() { - clear_env(); - assert_eq!(detect_color_mode(257), ColorMode::EightBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(257), ColorMode::EightBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_term_env_no_256() { - clear_env(); - set_var("TERM", "XTERM"); - assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Set("TERM", String::from("XTERM")), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + }, + ); } #[test] - #[serial] fn detect_color_mode_term_env_with_256() { - clear_env(); - set_var("TERM", "XTERM-256"); - assert_eq!(detect_color_mode(0), ColorMode::EightBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Set("TERM", String::from("XTERM-256")), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::EightBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_vte_version_0_36_00() { - clear_env(); - set_var("VTE_VERSION", "3600"); - assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Set("VTE_VERSION", String::from("3600")), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + }, + ); } #[test] - #[serial] fn detect_color_mode_vte_version_greater_0_36_00() { - clear_env(); - set_var("VTE_VERSION", "3601"); - assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Set("VTE_VERSION", String::from("3601")), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + }, + ); } #[test] - #[serial] fn detect_color_mode_vte_version_less_0_36_00() { - clear_env(); - set_var("VTE_VERSION", "1"); - assert_eq!(detect_color_mode(0), ColorMode::EightBit); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Set("VTE_VERSION", String::from("1")), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::EightBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_vte_version_0() { - clear_env(); - set_var("VTE_VERSION", "0"); - assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Set("VTE_VERSION", String::from("0")), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + }, + ); } + #[test] - #[serial] fn detect_color_mode_vte_version_invalid() { - clear_env(); - set_var("VTE_VERSION", "invalid"); - assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Set("VTE_VERSION", String::from("invalid")), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + }, + ); } #[test] - #[serial] fn detect_color_mode_colorterm_env_is_truecolor() { - clear_env(); - set_var("COLORTERM", "truecolor"); - assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Set("COLORTERM", String::from("truecolor")), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_color_with_value() { - clear_env(); - set_var("NO_COLOR", "true"); - assert_eq!(detect_color_mode(16), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Set("NO_COLOR", String::from("true")), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(16), ColorMode::TwoTone); + }, + ); } #[test] - #[serial] fn detect_color_mode_no_color_without_value() { - clear_env(); - set_var("NO_COLOR", ""); - assert_eq!(detect_color_mode(16), ColorMode::FourBit); + with_env_var( + &[ + EnvVarAction::Set("NO_COLOR", String::new()), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(16), ColorMode::FourBit); + }, + ); } #[test] - #[serial] fn detect_color_mode_colorterm_env_is_24bit() { - clear_env(); - set_var("COLORTERM", "24bit"); - assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Set("COLORTERM", String::from("24bit")), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + }, + ); } #[test] - #[serial] fn detect_color_mode_colorterm_env_is_other() { - clear_env(); - set_var("COLORTERM", "other"); - assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Set("COLORTERM", String::from("other")), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + EnvVarAction::Remove("WT_SESSION"), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TwoTone); + }, + ); } #[test] - #[serial] fn detect_color_mode_wt_session_env_iterm() { - clear_env(); - // WT_SESSION is generally a GUID of some sort - set_var("WT_SESSION", "32a25081-6745-4b65-909d-e8257bdbe852"); - assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + with_env_var( + &[ + EnvVarAction::Remove("NO_COLOR"), + EnvVarAction::Remove("COLORTERM"), + EnvVarAction::Remove("TERM"), + EnvVarAction::Remove("VTE_VERSION"), + // WT_SESSION is generally a GUID of some sort + EnvVarAction::Set("WT_SESSION", String::from("32a25081-6745-4b65-909d-e8257bdbe852")), + ], + || { + assert_eq!(detect_color_mode(0), ColorMode::TrueColor); + }, + ); } #[test] diff --git a/src/editor.rs b/src/editor.rs index cb8a0d648..de87efcfa 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -28,31 +28,31 @@ mod tests { use std::{ffi::OsString, path::Path}; use super::*; - use crate::test_helpers::set_git_directory; + use crate::test_helpers::with_git_directory; fn args(args: &[&str]) -> Args { Args::try_from(args.iter().map(OsString::from).collect::>()).unwrap() } #[test] - #[serial_test::serial] fn successful_run() { - let path = set_git_directory("fixtures/simple"); - let todo_file = Path::new(path.as_str()).join("rebase-todo-empty"); - assert_eq!( - run(&args(&[todo_file.to_str().unwrap()])).get_status(), - &ExitStatus::Good - ); + with_git_directory("fixtures/simple", |path| { + let todo_file = Path::new(path).join("rebase-todo-empty"); + assert_eq!( + run(&args(&[todo_file.to_str().unwrap()])).get_status(), + &ExitStatus::Good + ); + }); } #[test] - #[serial_test::serial] fn error_on_application_create() { - let path = set_git_directory("fixtures/simple"); - let todo_file = Path::new(path.as_str()).join("does-not-exist"); - assert_eq!( - run(&args(&[todo_file.to_str().unwrap()])).get_status(), - &ExitStatus::FileReadError - ); + with_git_directory("fixtures/simple", |path| { + let todo_file = Path::new(path).join("does-not-exist"); + assert_eq!( + run(&args(&[todo_file.to_str().unwrap()])).get_status(), + &ExitStatus::FileReadError + ); + }); } } diff --git a/src/git/repository.rs b/src/git/repository.rs index 0ed9ddced..0b1c3a715 100644 --- a/src/git/repository.rs +++ b/src/git/repository.rs @@ -195,26 +195,26 @@ mod unix_tests { use git2::{ErrorClass, ErrorCode}; use super::*; - use crate::test_helpers::{create_commit, set_git_directory, with_temp_bare_repository, with_temp_repository}; + use crate::test_helpers::{create_commit, with_git_directory, with_temp_bare_repository, with_temp_repository}; #[test] - #[serial_test::serial] fn open_from_env() { - _ = set_git_directory("fixtures/simple"); - assert_ok!(Repository::open_from_env()); + with_git_directory("fixtures/simple", |_| { + assert_ok!(Repository::open_from_env()); + }); } #[test] - #[serial_test::serial] fn open_from_env_error() { - let path = set_git_directory("fixtures/does-not-exist"); - assert_err_eq!(Repository::open_from_env(), GitError::RepositoryLoad { - kind: RepositoryLoadKind::Environment, - cause: git2::Error::new( - ErrorCode::NotFound, - ErrorClass::Os, - format!("failed to resolve path '{path}': No such file or directory") - ), + with_git_directory("fixtures/does-not-exist", |path| { + assert_err_eq!(Repository::open_from_env(), GitError::RepositoryLoad { + kind: RepositoryLoadKind::Environment, + cause: git2::Error::new( + ErrorCode::NotFound, + ErrorClass::Os, + format!("failed to resolve path '{path}': No such file or directory") + ), + }); }); } diff --git a/src/test_helpers.rs b/src/test_helpers.rs index 3da3610eb..1cbd319eb 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -9,11 +9,12 @@ mod create_invalid_utf; mod create_test_keybindings; mod create_test_module_handler; pub(crate) mod mocks; -mod set_git_directory; mod shared; pub(crate) mod testers; +mod with_env_var; mod with_event_handler; mod with_git_config; +mod with_git_directory; mod with_search; mod with_temp_bare_repository; mod with_temp_repository; @@ -30,10 +31,11 @@ pub(crate) use self::{ create_invalid_utf::invalid_utf, create_test_keybindings::create_test_keybindings, create_test_module_handler::create_test_module_handler, - set_git_directory::set_git_directory, shared::TestModuleProvider, + with_env_var::{with_env_var, EnvVarAction}, with_event_handler::{with_event_handler, EventHandlerTestContext}, with_git_config::with_git_config, + with_git_directory::with_git_directory, with_search::{with_search, SearchTestContext}, with_temp_bare_repository::with_temp_bare_repository, with_temp_repository::with_temp_repository, diff --git a/src/test_helpers/set_git_directory.rs b/src/test_helpers/set_git_directory.rs deleted file mode 100644 index 1f2666be6..000000000 --- a/src/test_helpers/set_git_directory.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::{ - env::set_var, - path::{Path, PathBuf}, -}; - -pub(crate) fn set_git_directory(repo: &str) -> String { - let path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("test") - .join(repo) - .canonicalize() - .unwrap_or(PathBuf::from("does-not-exist")); - set_var("GIT_DIR", path.to_str().unwrap()); - String::from(path.to_str().unwrap()) -} diff --git a/src/test_helpers/with_env_var.rs b/src/test_helpers/with_env_var.rs new file mode 100644 index 000000000..bd8efed58 --- /dev/null +++ b/src/test_helpers/with_env_var.rs @@ -0,0 +1,59 @@ +use std::env::{remove_var, set_var, var}; + +use lazy_static::lazy_static; +use parking_lot::Mutex; + +lazy_static! { + static ref ENV_CHANGE_LOCK: Mutex<()> = Mutex::new(()); +} + +#[derive(Debug, Clone)] +pub(crate) enum EnvVarAction<'var> { + Remove(&'var str), + Set(&'var str, String), +} + +// This wraps the unsafe modification on environment variables in a lock, that ensures that only one test thread is +// trying to modify environment variables at a time. This does not provide any guarantee that a value was not changed +// outside the scope of tests using this function. So in that regard, this could be considered unsafe, however within +// the confines of the tests for this project, this is safe enough. +// +// The wrapper will attempt to restore all values back to their previous value, cleaning up any changes made. +#[allow(unsafe_code, unused_unsafe)] // unused unsafe until Rust 2024 +pub(crate) fn with_env_var(actions: &[EnvVarAction<'_>], callback: C) +where C: FnOnce() { + let lock = ENV_CHANGE_LOCK.lock(); + let mut undo_actions = vec![]; + + for action in actions { + let name = match action { + EnvVarAction::Set(name, _) | EnvVarAction::Remove(name) => *name, + }; + if let Ok(v) = var(name) { + undo_actions.push(EnvVarAction::Set(name, v)); + } + else { + undo_actions.push(EnvVarAction::Remove(name)); + } + match action { + EnvVarAction::Remove(name) => unsafe { + remove_var(*name); + }, + EnvVarAction::Set(name, value) => unsafe { + set_var(*name, value.as_str()); + }, + } + } + callback(); + for action in undo_actions { + match action { + EnvVarAction::Remove(name) => unsafe { + remove_var(name); + }, + EnvVarAction::Set(name, value) => unsafe { + set_var(name, value); + }, + } + } + drop(lock); +} diff --git a/src/test_helpers/with_git_directory.rs b/src/test_helpers/with_git_directory.rs new file mode 100644 index 000000000..5cd115875 --- /dev/null +++ b/src/test_helpers/with_git_directory.rs @@ -0,0 +1,16 @@ +use std::path::{Path, PathBuf}; + +use crate::test_helpers::{with_env_var, EnvVarAction}; + +pub(crate) fn with_git_directory(repo: &str, callback: C) +where C: FnOnce(&str) { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("test") + .join(repo) + .canonicalize() + .unwrap_or(PathBuf::from("does-not-exist")); + with_env_var( + &[EnvVarAction::Set("GIT_DIR", String::from(path.to_str().unwrap()))], + || callback(path.to_str().unwrap()), + ); +} diff --git a/src/tests.rs b/src/tests.rs index b05407636..abd3de80f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,7 +1,7 @@ use std::path::Path; use super::*; -use crate::{module::ExitStatus, test_helpers::set_git_directory}; +use crate::{module::ExitStatus, test_helpers::with_git_directory}; fn args(args: &[&str]) -> Vec { args.iter().map(OsString::from).collect::>() @@ -42,14 +42,14 @@ fn successful_run_license() { } #[test] -#[serial_test::serial] fn successful_run_editor() { - let path = set_git_directory("fixtures/simple"); - let todo_file = Path::new(path.as_str()).join("rebase-todo-empty"); - assert_eq!( - run(args(&[todo_file.to_str().unwrap()])).get_status(), - &ExitStatus::Good - ); + with_git_directory("fixtures/simple", |path| { + let todo_file = Path::new(path).join("rebase-todo-empty"); + assert_eq!( + run(args(&[todo_file.to_str().unwrap()])).get_status(), + &ExitStatus::Good + ); + }); } #[cfg(unix)]