From 84be3c9ae7a1b6a86f3a4ccc58e48eab46466b22 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Thu, 6 Jul 2023 15:22:44 +0200 Subject: Move test helper functions into their own helper module --- tests/cli.rs | 239 +++++++--------------------------------- tests/cli_test_utils/context.rs | 114 +++++++++++++++++++ tests/cli_test_utils/git.rs | 41 +++++++ tests/cli_test_utils/mod.rs | 5 + 4 files changed, 200 insertions(+), 199 deletions(-) create mode 100644 tests/cli_test_utils/context.rs create mode 100644 tests/cli_test_utils/git.rs create mode 100644 tests/cli_test_utils/mod.rs (limited to 'tests') diff --git a/tests/cli.rs b/tests/cli.rs index dbe99b2..b8ada66 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -1,94 +1,22 @@ +mod cli_test_utils; + use anyhow::Result; -use assert_cmd::{cargo::cargo_bin, Command}; -use rexpect::session::{spawn_command, PtySession}; -use shackle_shell::user_info::{get_user_groups, get_username}; -use std::path::{Path, PathBuf}; -use tempfile::TempDir; +use cli_test_utils::*; const REPO_NAME: &str = "my-repository"; const REPO_NAME_2: &str = "my-other-repository"; - -struct TestContext { - p: PtySession, - workdir: TempDir, -} - -impl TestContext { - fn personal_repo_dir(&self, repo_name: &str) -> PathBuf { - let username = get_username().unwrap(); - self.workdir - .as_ref() - .join("git") - .join(username) - .join(&format!("{}.git", repo_name)) - } - - fn group_repo_dir(&self, group: &str, repo_name: &str) -> PathBuf { - self.workdir - .as_ref() - .join("git") - .join(group) - .join(&format!("{}.git", repo_name)) - } - - fn expect_prompt(&mut self) -> Result<()> { - self.p.exp_string("> ")?; - Ok(()) - } - - fn expect_successful_init_message(&mut self, repo_path: &str) -> Result<()> { - self.p - .exp_string(&format!("Successfully created \"{repo_path}\""))?; - self.expect_prompt() - } -} - -fn personal_repo_path(repo_name: &str) -> String { - let username = get_username().unwrap(); - format!("git/{username}/{repo_name}.git") -} - -fn arbitrary_user_group() -> String { - get_user_groups().into_iter().next().unwrap() -} - -fn group_repo_path(group: &str, repo_name: &str) -> String { - format!("git/{group}/{repo_name}.git") -} - -fn spawn_interactive_process() -> Result { - let workdir = tempfile::tempdir()?; - - let path = cargo_bin(env!("CARGO_PKG_NAME")); - let mut command = std::process::Command::new(&path); - command.current_dir(&workdir); - let p = spawn_command(command, Some(3000))?; - let mut c = TestContext { p, workdir }; - c.expect_prompt()?; - Ok(c) -} - -fn run_batch_command(batch_command: &str) -> Result { - let workdir = tempfile::tempdir()?; - - let path = cargo_bin(env!("CARGO_PKG_NAME")); - let mut command = std::process::Command::new(&path); - command.current_dir(&workdir); - command.args(["-c", batch_command]); - let p = spawn_command(command, Some(3000))?; - - Ok(TestContext { p, workdir }) -} +const DEFAULT_DESCRIPTION: &str = + "Unnamed repository; edit this file 'description' to name the repository."; #[test] fn shows_a_prompt() -> Result<()> { - spawn_interactive_process()?; + TestContext::new_interactive()?; Ok(()) } #[test] fn does_nothing_after_receiving_whitespace_input() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line("")?; c.expect_prompt()?; c.p.send_line(" ")?; @@ -98,7 +26,7 @@ fn does_nothing_after_receiving_whitespace_input() -> Result<()> { #[test] fn quits_when_eof_is_sent() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_control('d')?; c.p.exp_eof()?; Ok(()) @@ -106,7 +34,7 @@ fn quits_when_eof_is_sent() -> Result<()> { #[test] fn quits_when_exit_command_is_sent() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line("exit")?; c.p.exp_eof()?; Ok(()) @@ -114,7 +42,7 @@ fn quits_when_exit_command_is_sent() -> Result<()> { #[test] fn reports_error_with_unsupported_shell_commands() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line("ls")?; c.p.exp_string("error: unrecognized subcommand 'ls'")?; c.expect_prompt()?; @@ -123,55 +51,16 @@ fn reports_error_with_unsupported_shell_commands() -> Result<()> { #[test] fn reports_error_with_nonsense_input() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(" asd fg ")?; c.p.exp_string("error: unrecognized subcommand 'asd'")?; c.expect_prompt()?; Ok(()) } -fn verify_repo_exists(repo_dir: &Path) { - Command::new("git") - .arg("rev-list") - .arg("--all") - .current_dir(repo_dir) - .assert() - .success() - .stdout(""); -} - -fn verify_repo_does_not_exist(repo_dir: &Path) { - assert!(!repo_dir.exists()); -} - -fn verify_current_branch(repo_dir: &Path, expected_ref: &str) { - Command::new("git") - .arg("symbolic-ref") - .arg("HEAD") - .current_dir(repo_dir) - .assert() - .success() - .stdout(format!("{expected_ref}\n")); -} - -fn verify_repo_config_value(repo_dir: &Path, config_key: &str, config_value: Option<&str>) { - let assert = Command::new("git") - .args(["config", "--local", config_key]) - .current_dir(repo_dir) - .assert(); - match config_value { - Some(value) => { - assert.success().stdout(format!("{value}\n")); - } - None => { - assert.failure().code(1); - } - } -} - #[test] fn can_init_a_new_git_repo() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(&format!("init {}", REPO_NAME))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; @@ -185,7 +74,7 @@ fn can_init_a_new_git_repo() -> Result<()> { #[test] fn can_init_a_new_shared_git_repo() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let group = arbitrary_user_group(); c.p.send_line(&format!("init --group {} {}", group, REPO_NAME))?; c.expect_successful_init_message(&group_repo_path(&group, REPO_NAME))?; @@ -199,7 +88,7 @@ fn can_init_a_new_shared_git_repo() -> Result<()> { #[test] fn does_not_init_shared_repo_if_the_user_isnt_in_the_group() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let group = "not-a-real-group"; c.p.send_line(&format!("init --group {} {}", group, REPO_NAME))?; c.p.exp_string("Unknown group")?; @@ -209,7 +98,7 @@ fn does_not_init_shared_repo_if_the_user_isnt_in_the_group() -> Result<()> { #[test] fn runs_a_single_command_and_exit_with_cli_flag() -> Result<()> { - let mut c = run_batch_command(&format!("init {}", REPO_NAME))?; + let mut c = TestContext::new_batch(&format!("init {}", REPO_NAME))?; c.p.exp_string(&format!( "Successfully created \"{}\"", personal_repo_path(REPO_NAME) @@ -220,7 +109,7 @@ fn runs_a_single_command_and_exit_with_cli_flag() -> Result<()> { #[test] fn allows_quotes_arguments() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(&format!("\"init\" '{REPO_NAME}'"))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; Ok(()) @@ -228,7 +117,7 @@ fn allows_quotes_arguments() -> Result<()> { #[test] fn errors_with_an_open_double_quote() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line("\"init 'another-new-repo'")?; c.p.exp_string("Incomplete input")?; Ok(()) @@ -236,7 +125,7 @@ fn errors_with_an_open_double_quote() -> Result<()> { #[test] fn errors_with_an_open_single_quote() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line("'init 'another-new-repo'")?; c.p.exp_string("Incomplete input")?; Ok(()) @@ -245,76 +134,34 @@ fn errors_with_an_open_single_quote() -> Result<()> { #[test] fn allows_single_quotes_and_spaces_inside_double_quotes() -> Result<()> { let repo_name = "shukkie's new repo"; - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(&format!("init \"{repo_name}\""))?; c.expect_successful_init_message(&personal_repo_path(repo_name))?; Ok(()) } -const DEFAULT_DESCRIPTION: &str = - "Unnamed repository; edit this file 'description' to name the repository."; - -fn expect_list_table(c: &mut TestContext, repos: &[(&str, &str)]) -> Result<()> { - c.p.send_line("list")?; - c.p.exp_regex(r"\+-+\+-+\+")?; - c.p.exp_regex(r"\| path +\| description +\|")?; - c.p.exp_regex(r"\+=+\+")?; - for (path, description) in repos { - c.p.exp_string("| ")?; - c.p.exp_string(path)?; - c.p.exp_regex(r" +\| ")?; - c.p.exp_string(&description)?; - c.p.exp_regex(r" +\|")?; - } - c.p.exp_regex(r"\+-+\+-+\+")?; - c.expect_prompt()?; - Ok(()) -} - -fn expect_list_table_verbose(c: &mut TestContext, repos: &[(&str, &str)]) -> Result<()> { - c.p.send_line("list --verbose")?; - c.p.exp_regex(r"\+-+\+-+\+-+\+")?; - c.p.exp_regex(r"\| path +\| description +\| size +\|")?; - c.p.exp_regex(r"\+=+\+")?; - for (path, description) in repos { - c.p.exp_string("| ")?; - c.p.exp_string(path)?; - c.p.exp_regex(r" +\| ")?; - c.p.exp_string(&description)?; - c.p.exp_regex(r" +\|")?; - c.p.exp_regex(r"\d+ (MiB|KiB|B)")?; - c.p.exp_regex(r" +\|")?; - } - c.p.exp_regex(r"\+-+\+-+\+-+\+")?; - c.expect_prompt()?; - Ok(()) -} - #[test] fn list_can_print_an_empty_list() -> Result<()> { - let mut c = spawn_interactive_process()?; - expect_list_table(&mut c, &[])?; + let mut c = TestContext::new_interactive()?; + c.expect_list_table(&[])?; Ok(()) } #[test] fn list_can_print_a_list_of_personal_repos_with_descriptions() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(&format!("init {}", REPO_NAME))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; - expect_list_table( - &mut c, - &[(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION)], - )?; + c.expect_list_table(&[(&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION)])?; Ok(()) } #[test] fn list_can_print_a_list_of_all_repos_with_descriptions() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(&format!("init {}", REPO_NAME))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; @@ -322,20 +169,17 @@ fn list_can_print_a_list_of_all_repos_with_descriptions() -> Result<()> { c.p.send_line(&format!("init --group {} {}", group, REPO_NAME_2))?; c.expect_successful_init_message(&group_repo_path(&group, REPO_NAME_2))?; - expect_list_table( - &mut c, - &[ - (&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION), - (&group_repo_path(&group, REPO_NAME_2), DEFAULT_DESCRIPTION), - ], - )?; + c.expect_list_table(&[ + (&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION), + (&group_repo_path(&group, REPO_NAME_2), DEFAULT_DESCRIPTION), + ])?; Ok(()) } #[test] fn list_can_print_a_verbose_list_of_all_repos() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; c.p.send_line(&format!("init {}", REPO_NAME))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; @@ -343,32 +187,29 @@ fn list_can_print_a_verbose_list_of_all_repos() -> Result<()> { c.p.send_line(&format!("init --group {} {}", group, REPO_NAME_2))?; c.expect_successful_init_message(&group_repo_path(&group, REPO_NAME_2))?; - expect_list_table_verbose( - &mut c, - &[ - (&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION), - (&group_repo_path(&group, REPO_NAME_2), DEFAULT_DESCRIPTION), - ], - )?; + c.expect_list_table_verbose(&[ + (&personal_repo_path(REPO_NAME), DEFAULT_DESCRIPTION), + (&group_repo_path(&group, REPO_NAME_2), DEFAULT_DESCRIPTION), + ])?; Ok(()) } #[test] fn can_set_the_description_on_a_repo_during_init() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let description = "A cool repo that does cool things"; c.p.send_line(&format!("init --description \"{description}\" {REPO_NAME}"))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; - expect_list_table(&mut c, &[(&personal_repo_path(REPO_NAME), description)])?; + c.expect_list_table(&[(&personal_repo_path(REPO_NAME), description)])?; Ok(()) } #[test] fn can_change_the_description_on_a_repo() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let description = "A cool repo that does cool things"; let repo_path = personal_repo_path(REPO_NAME); c.p.send_line(&format!("init {REPO_NAME}"))?; @@ -378,14 +219,14 @@ fn can_change_the_description_on_a_repo() -> Result<()> { ))?; c.p.exp_string("Successfully updated description")?; c.expect_prompt()?; - expect_list_table(&mut c, &[(&repo_path, description)])?; + c.expect_list_table(&[(&repo_path, description)])?; Ok(()) } #[test] fn can_set_the_main_branch_of_a_new_git_repo() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let main_branch = "foobar"; c.p.send_line(&format!("init --branch {} {}", main_branch, REPO_NAME))?; c.expect_successful_init_message(&personal_repo_path(REPO_NAME))?; @@ -398,7 +239,7 @@ fn can_set_the_main_branch_of_a_new_git_repo() -> Result<()> { #[test] fn can_change_the_main_branch_on_a_repo() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let main_branch = "foobar"; let repo_path = personal_repo_path(REPO_NAME); @@ -416,7 +257,7 @@ fn can_change_the_main_branch_on_a_repo() -> Result<()> { #[test] fn can_delete_a_repo() -> Result<()> { - let mut c = spawn_interactive_process()?; + let mut c = TestContext::new_interactive()?; let repo_path = personal_repo_path(REPO_NAME); let repo_dir = c.personal_repo_dir(REPO_NAME); diff --git a/tests/cli_test_utils/context.rs b/tests/cli_test_utils/context.rs new file mode 100644 index 0000000..2517040 --- /dev/null +++ b/tests/cli_test_utils/context.rs @@ -0,0 +1,114 @@ +use anyhow::Result; +use assert_cmd::cargo::cargo_bin; +use rexpect::session::{spawn_command, PtySession}; +use shackle_shell::user_info::{get_user_groups, get_username}; +use std::path::PathBuf; +use tempfile::TempDir; + +pub struct TestContext { + pub p: PtySession, + pub workdir: TempDir, +} + +impl TestContext { + pub fn new_interactive() -> Result { + let workdir = tempfile::tempdir()?; + + let path = cargo_bin(env!("CARGO_PKG_NAME")); + let mut command = std::process::Command::new(&path); + command.current_dir(&workdir); + let p = spawn_command(command, Some(3000))?; + let mut c = TestContext { p, workdir }; + c.expect_prompt()?; + Ok(c) + } + + pub fn new_batch(batch_command: &str) -> Result { + let workdir = tempfile::tempdir()?; + + let path = cargo_bin(env!("CARGO_PKG_NAME")); + let mut command = std::process::Command::new(&path); + command.current_dir(&workdir); + command.args(["-c", batch_command]); + let p = spawn_command(command, Some(3000))?; + + Ok(TestContext { p, workdir }) + } + + pub fn personal_repo_dir(&self, repo_name: &str) -> PathBuf { + let username = get_username().unwrap(); + self.workdir + .as_ref() + .join("git") + .join(username) + .join(&format!("{}.git", repo_name)) + } + + pub fn group_repo_dir(&self, group: &str, repo_name: &str) -> PathBuf { + self.workdir + .as_ref() + .join("git") + .join(group) + .join(&format!("{}.git", repo_name)) + } + + pub fn expect_prompt(&mut self) -> Result<()> { + self.p.exp_string("> ")?; + Ok(()) + } + + pub fn expect_successful_init_message(&mut self, repo_path: &str) -> Result<()> { + self.p + .exp_string(&format!("Successfully created \"{repo_path}\""))?; + self.expect_prompt() + } + + pub fn expect_list_table(&mut self, repos: &[(&str, &str)]) -> Result<()> { + self.p.send_line("list")?; + self.p.exp_regex(r"\+-+\+-+\+")?; + self.p.exp_regex(r"\| path +\| description +\|")?; + self.p.exp_regex(r"\+=+\+")?; + for (path, description) in repos { + self.p.exp_string("| ")?; + self.p.exp_string(path)?; + self.p.exp_regex(r" +\| ")?; + self.p.exp_string(&description)?; + self.p.exp_regex(r" +\|")?; + } + self.p.exp_regex(r"\+-+\+-+\+")?; + self.expect_prompt()?; + Ok(()) + } + + pub fn expect_list_table_verbose(&mut self, repos: &[(&str, &str)]) -> Result<()> { + self.p.send_line("list --verbose")?; + self.p.exp_regex(r"\+-+\+-+\+-+\+")?; + self.p.exp_regex(r"\| path +\| description +\| size +\|")?; + self.p.exp_regex(r"\+=+\+")?; + for (path, description) in repos { + self.p.exp_string("| ")?; + self.p.exp_string(path)?; + self.p.exp_regex(r" +\| ")?; + self.p.exp_string(&description)?; + self.p.exp_regex(r" +\|")?; + self.p.exp_regex(r"\d+ (MiB|KiB|B)")?; + self.p.exp_regex(r" +\|")?; + } + self.p.exp_regex(r"\+-+\+-+\+-+\+")?; + self.expect_prompt()?; + Ok(()) + } +} + +pub fn personal_repo_path(repo_name: &str) -> String { + let username = get_username().unwrap(); + format!("git/{username}/{repo_name}.git") +} + +pub fn arbitrary_user_group() -> String { + get_user_groups().into_iter().next().unwrap() +} + +pub fn group_repo_path(group: &str, repo_name: &str) -> String { + format!("git/{group}/{repo_name}.git") +} diff --git a/tests/cli_test_utils/git.rs b/tests/cli_test_utils/git.rs new file mode 100644 index 0000000..d3f35c4 --- /dev/null +++ b/tests/cli_test_utils/git.rs @@ -0,0 +1,41 @@ +use assert_cmd::Command; +use std::path::Path; + +pub fn verify_repo_exists(repo_dir: &Path) { + Command::new("git") + .arg("rev-list") + .arg("--all") + .current_dir(repo_dir) + .assert() + .success() + .stdout(""); +} + +pub fn verify_repo_does_not_exist(repo_dir: &Path) { + assert!(!repo_dir.exists()); +} + +pub fn verify_current_branch(repo_dir: &Path, expected_ref: &str) { + Command::new("git") + .arg("symbolic-ref") + .arg("HEAD") + .current_dir(repo_dir) + .assert() + .success() + .stdout(format!("{expected_ref}\n")); +} + +pub fn verify_repo_config_value(repo_dir: &Path, config_key: &str, config_value: Option<&str>) { + let assert = Command::new("git") + .args(["config", "--local", config_key]) + .current_dir(repo_dir) + .assert(); + match config_value { + Some(value) => { + assert.success().stdout(format!("{value}\n")); + } + None => { + assert.failure().code(1); + } + } +} diff --git a/tests/cli_test_utils/mod.rs b/tests/cli_test_utils/mod.rs new file mode 100644 index 0000000..7aaf1db --- /dev/null +++ b/tests/cli_test_utils/mod.rs @@ -0,0 +1,5 @@ +pub mod context; +pub mod git; + +pub use context::*; +pub use git::*; -- cgit v1.2.3