diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 87daad63..dfc2872d 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -125,12 +125,6 @@ jobs: profile: minimal override: true - # Install dotnet at a fixed version - - name: Setup | DotNet - uses: actions/setup-dotnet@v1 - with: - dotnet-version: "2.2.402" - # Install Mercurial (pre-installed on Linux and windows) - name: Setup | Mercurial (macos) if: matrix.os == 'macOS-latest' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dd470e8f..86388528 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,6 +24,52 @@ The project begins in [`main.rs`](src/main.rs), where the appropriate `print::` Any styling that is applied to a module is inherited by its segments. Module prefixes and suffixes by default don't have any styling applied to them. +## Environment Variables and external commands + +We have custom functions to be able to test our modules better. Here we show you how. + +### Environment Variables + +To get an environment variable we have special function to allow for mocking of vars. Here's a quick example: + +```rust +use super::{Context, Module, RootModuleConfig}; + +use crate::configs::php::PhpConfig; +use crate::formatter::StringFormatter; +use crate::utils; + + +pub fn module<'a>(context: &'a Context) -> Option> { + // Here `my_env_var` will be either the contents of the var or the function + // will exit if the variable is not set. + let my_env_var = context.get_env("MY_VAR")?; + + // Then you can happily use the value +} +``` + +## External commands + +To run a external command (e.g. to get the version of a tool) and to allow for mocking use the `utils::exec_cmd` function. Here's a quick example: + +```rust +use super::{Context, Module, RootModuleConfig}; + +use crate::configs::php::PhpConfig; +use crate::formatter::StringFormatter; +use crate::utils; + + +pub fn module<'a>(context: &'a Context) -> Option> { + // Here `my_env_var` will be either the stdout of the called command or the function + // will exit if the called program was not installed or could not be run. + let output = utils::exec_cmd("my_command", &["first_arg", "second_arg"])?.stdout; + + // Then you can happily use the output +} +``` + ## Logging Debug logging in starship is done with [pretty_env_logger](https://crates.io/crates/pretty_env_logger). @@ -56,37 +102,81 @@ rustup component add rustfmt cargo fmt ``` - ## Testing Testing is critical to making sure starship works as intended on systems big and small. Starship interfaces with many applications and system APIs when generating the prompt, so there's a lot of room for bugs to slip in. -Unit tests and a subset of integration tests can be run with `cargo test`. -The full integration test suite is run on GitHub as part of our GitHub Actions continuous integration. +Unit tests are written using the built-in Rust testing library in the same file as the implementation, as is traditionally done in Rust codebases. These tests can be run with `cargo test` and are run on GitHub as part of our GitHub Actions continuous integration to ensure consistend behavior. -### Unit Testing +All tests that test the rendered output of a module should use `ModuleRenderer`. For Example: -Unit tests are written using the built-in Rust testing library in the same file as the implementation, as is traditionally done in Rust codebases. These tests can be run with `cargo test`. +```rust +use super::{Context, Module, RootModuleConfig}; + +use crate::configs::php::PhpConfig; +use crate::formatter::StringFormatter; +use crate::utils; + + +pub fn module<'a>(context: &'a Context) -> Option> { + /* This is where your module code goes */ +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::fs::File; + use std::io; + + + #[test] + fn should_render() -> io::Result<()> { + // Here you setup the testing environment + let tempdir = tempfile::tempdir()?; + // Create some file needed to render the module + File::create(dir.path().join("YOUR_FILE"))?.sync_all()?; + + // The output of the module + let actual = ModuleRenderer::new("YOUR_MODULE_NAME") + // For a custom path + .path(&tempdir.path()) + // For a custom config + .config(toml::toml!{ + [YOUR_MODULE_NAME] + val = 1 + }) + // For env mocking + .env("KEY","VALUE") + // Run the module and collect the output + .collect(); + + // The value that should be rendered by the module. + let expected = Some(format!("{} ",Color::Black.paint("THIS SHOULD BE RENDERED"))); + + // Assert that the actual and expected values are the same + assert_eq!(actual, expected); + + // Close the tempdir + tempdir.close() + } +} +``` + +If a module depends on output of another program, then that output should be added to the match statement in [`utils.rs`](src/utils.rs). The match has to be exactly the same as the call to `utils::exec_cmd()`, including positional arguments and flags. The array of arguments are joined by a `" "`, so `utils::exec_cmd("program", &["arg", "more_args"])` would match with the `program arg more_args` match statement. + +If the program cannot be mocked (e.g. It performs some filesystem operations, either writing or reading files) then it has to added to the project's GitHub Actions workflow file([`.github/workflows/workflow.yml`](.github/workflows/workflow.yml)) and the test has to be marked with an `#[ignored]`. This ensures that anyone can run the test suite locally without needing to pre-configure their environment. The `#[ignored]` attribute is bypassed during CI runs in GitHub Actions. Unit tests should be fully isolated, only testing a given function's expected output given a specific input, and should be reproducible on any machine. Unit tests should not expect the computer running them to be in any particular state. This includes having any applications pre-installed, having any environment variables set, etc. The previous point should be emphasized: even seemingly innocuous ideas like "if we can see the directory, we can read it" or "nobody will have their home directory be a git repo" have bitten us in the past. Having even a single test fail can completely break installation on some platforms, so be careful with tests! -### Integration Testing - -Integration tests are located in the [`tests/`](tests) directory and are also written using the built-in Rust testing library. - -Integration tests should test full modules or the entire prompt. All integration tests that expect the testing environment to have pre-existing state or tests that make permanent changes to the filesystem should have the `#[ignore]` attribute added to them. All tests that don't depend on any preexisting state will be run alongside the unit tests with `cargo test`. - -For tests that depend on having preexisting state, whatever needed state will have to be added to the project's GitHub Actions workflow file([`.github/workflows/workflow.yml`](.github/workflows/workflow.yml)). - ### Test Programming Guidelines Any tests that depend on File I/O should use [`sync_all()`](https://doc.rust-lang.org/std/fs/struct.File.html#method.sync_all) when creating files or after writing to files. -Any tests that use `tempfile::tempdir` should take care to call `dir.close()` after usage to ensure the lifecycle of the directory can be reasoned about. - -Any tests that use `create_fixture_repo()` should remove the returned directory after usage with `remove_dir_all::remove_dir_all()`. +Any tests that use `tempfile::tempdir` should take care to call `dir.close()` after usage to ensure the lifecycle of the directory can be reasoned about. This includes `fixture_repo()` as it returns a TempDir that should be closed. ## Running the Documentation Website Locally @@ -98,17 +188,20 @@ After cloning the project, you can do the following to run the VuePress website 1. `cd` into the `/docs` directory. 2. Install the project dependencies: -``` -$ npm install -``` + + ```sh + npm install + ``` + 3. Start the project in development mode: -``` -$ npm run dev -``` -Once setup is complete, you can refer to VuePress documentation on the actual implementation here: https://vuepress.vuejs.org/guide/. + ```sh + npm run dev + ``` -### Git/GitHub workflow +Once setup is complete, you can refer to VuePress documentation on the actual implementation here: . + +## Git/GitHub workflow This is our preferred process for opening a PR on GitHub: diff --git a/Cargo.lock b/Cargo.lock index 03bb1918..86f00fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1104,7 +1104,6 @@ dependencies = [ "quick-xml", "rayon", "regex", - "remove_dir_all", "serde_json", "starship_module_config_derive", "sysinfo", diff --git a/Cargo.toml b/Cargo.toml index 34f26c65..e2555deb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ tls-vendored = ["native-tls/vendored"] clap = "2.33.1" ansi_term = "0.12.1" dirs-next = "1.0.1" -git2 = { version = "0.13.8", default-features = false, features = [] } +git2 = { version = "0.13.8", default-features = false } toml = { version = "0.5.6", features = ["preserve_order"] } serde_json = "1.0.57" rayon = "1.3.1" @@ -64,17 +64,19 @@ attohttpc = { version = "0.15.0", optional = true, default-features = false, fea native-tls = { version = "0.2", optional = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser", "securitybaseapi", "processthreadsapi", "handleapi", "impl-default"]} +winapi = { version = "0.3", features = [ + "winuser", + "securitybaseapi", + "processthreadsapi", + "handleapi", + "impl-default", +] } [target.'cfg(not(windows))'.dependencies] nix = "0.18.0" [dev-dependencies] tempfile = "3.1.0" -# More realiable than std::fs version on Windows -# For removing temporary directories manually when needed -# This is what tempfile uses to delete temporary directories -remove_dir_all = "0.5.3" [profile.release] codegen-units = 1 diff --git a/src/bug_report.rs b/src/bug_report.rs index 6735f244..d9104ab1 100644 --- a/src/bug_report.rs +++ b/src/bug_report.rs @@ -1,5 +1,6 @@ use crate::utils::exec_cmd; +use clap::crate_version; use std::fs; use std::path::PathBuf; @@ -254,5 +255,6 @@ mod tests { let config_path = get_config_path("bash"); assert_eq!("/test/home/.bashrc", config_path.unwrap().to_str().unwrap()); + env::remove_var("HOME"); } } diff --git a/src/context.rs b/src/context.rs index 9076120c..40b547a7 100644 --- a/src/context.rs +++ b/src/context.rs @@ -7,6 +7,7 @@ use git2::{ErrorCode::UnbornBranch, Repository, RepositoryState}; use once_cell::sync::OnceCell; use std::collections::{HashMap, HashSet}; use std::env; +use std::ffi::OsString; use std::fs; use std::path::{Path, PathBuf}; use std::string::String; @@ -33,6 +34,9 @@ pub struct Context<'a> { /// The shell the user is assumed to be running pub shell: Shell, + + /// A HashMap of environment variable mocks + pub env: HashMap<&'a str, String>, } impl<'a> Context<'a> { @@ -82,11 +86,30 @@ impl<'a> Context<'a> { dir_contents: OnceCell::new(), repo: OnceCell::new(), shell, + env: HashMap::new(), + } + } + + // Retrives a environment variable from the os or from a table if in testing mode + pub fn get_env>(&self, key: K) -> Option { + if cfg!(test) { + self.env.get(key.as_ref()).map(|val| val.to_string()) + } else { + env::var(key.as_ref()).ok() + } + } + + // Retrives a environment variable from the os or from a table if in testing mode (os version) + pub fn get_env_os>(&self, key: K) -> Option { + if cfg!(test) { + self.env.get(key.as_ref()).map(OsString::from) + } else { + env::var_os(key.as_ref()) } } /// Convert a `~` in a path to the home directory - fn expand_tilde(dir: PathBuf) -> PathBuf { + pub fn expand_tilde(dir: PathBuf) -> PathBuf { if dir.starts_with("~") { let without_home = dir.strip_prefix("~").unwrap(); return dirs_next::home_dir().unwrap().join(without_home); @@ -165,7 +188,7 @@ impl<'a> Context<'a> { } fn get_shell() -> Shell { - let shell = std::env::var("STARSHIP_SHELL").unwrap_or_default(); + let shell = env::var("STARSHIP_SHELL").unwrap_or_default(); match shell.as_str() { "bash" => Shell::Bash, "fish" => Shell::Fish, @@ -310,7 +333,7 @@ impl<'a> ScanDir<'a> { self } - /// based on the current Pathbuf check to see + /// based on the current PathBuf check to see /// if any of this criteria match or exist and returning a boolean pub fn is_match(&self) -> bool { self.dir_contents.has_any_extension(self.extensions) diff --git a/src/formatter/parser.rs b/src/formatter/parser.rs index 6fc2595f..929a037f 100644 --- a/src/formatter/parser.rs +++ b/src/formatter/parser.rs @@ -1,4 +1,5 @@ use pest::{error::Error, iterators::Pair, Parser}; +use pest_derive::*; use super::model::*; diff --git a/src/lib.rs b/src/lib.rs index 7621ecd4..0b98721b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate pest_derive; - // Lib is present to allow for benchmarking pub mod config; pub mod configs; @@ -11,3 +8,6 @@ pub mod modules; pub mod print; pub mod segment; mod utils; + +#[cfg(test)] +mod test; diff --git a/src/main.rs b/src/main.rs index a91b9e10..27876b9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,7 @@ +use clap::{crate_authors, crate_version}; use std::io; use std::time::SystemTime; -#[macro_use] -extern crate clap; -#[macro_use] -extern crate pest_derive; - mod bug_report; mod config; mod configs; @@ -19,6 +15,9 @@ mod print; mod segment; mod utils; +#[cfg(test)] +mod test; + use crate::module::ALL_MODULES; use clap::{App, AppSettings, Arg, Shell, SubCommand}; diff --git a/src/modules/aws.rs b/src/modules/aws.rs index 1248642d..b404a2a2 100644 --- a/src/modules/aws.rs +++ b/src/modules/aws.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::env; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; @@ -13,9 +12,9 @@ use crate::formatter::StringFormatter; type Profile = String; type Region = String; -fn get_aws_region_from_config(aws_profile: Option<&str>) -> Option { - let config_location = env::var("AWS_CONFIG_FILE") - .ok() +fn get_aws_region_from_config(context: &Context, aws_profile: Option<&str>) -> Option { + let config_location = context + .get_env("AWS_CONFIG_FILE") .and_then(|path| PathBuf::from_str(&path).ok()) .or_else(|| { let mut home = dirs_next::home_dir()?; @@ -47,19 +46,22 @@ fn get_aws_region_from_config(aws_profile: Option<&str>) -> Option { Some(region.to_string()) } -fn get_aws_profile_and_region() -> (Option, Option) { +fn get_aws_profile_and_region(context: &Context) -> (Option, Option) { match ( - env::var("AWS_VAULT") - .or_else(|_| env::var("AWS_PROFILE")) - .ok(), - env::var("AWS_DEFAULT_REGION") - .or_else(|_| env::var("AWS_REGION")) - .ok(), + context + .get_env("AWS_VAULT") + .or_else(|| context.get_env("AWS_PROFILE")), + context + .get_env("AWS_DEFAULT_REGION") + .or_else(|| context.get_env("AWS_REGION")), ) { (Some(p), Some(r)) => (Some(p), Some(r)), (None, Some(r)) => (None, Some(r)), - (Some(ref p), None) => (Some(p.to_owned()), get_aws_region_from_config(Some(p))), - (None, None) => (None, get_aws_region_from_config(None)), + (Some(ref p), None) => ( + Some(p.to_owned()), + get_aws_region_from_config(context, Some(p)), + ), + (None, None) => (None, get_aws_region_from_config(context, None)), } } @@ -74,7 +76,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("aws"); let config: AwsConfig = AwsConfig::try_load(module.config); - let (aws_profile, aws_region) = get_aws_profile_and_region(); + let (aws_profile, aws_region) = get_aws_profile_and_region(context); if aws_profile.is_none() && aws_region.is_none() { return None; } @@ -113,3 +115,278 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::fs::File; + use std::io::{self, Write}; + + #[test] + fn no_region_set() -> io::Result<()> { + let actual = ModuleRenderer::new("aws").collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn region_set() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_REGION", "ap-northeast-2") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ (ap-northeast-2)") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn region_set_with_alias() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_REGION", "ap-southeast-2") + .config(toml::toml! { + [aws.region_aliases] + ap-southeast-2 = "au" + }) + .collect(); + let expected = Some(format!("on {} ", Color::Yellow.bold().paint("☁️ (au)"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn default_region_set() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_REGION", "ap-northeast-2") + .env("AWS_DEFAULT_REGION", "ap-northeast-1") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ (ap-northeast-1)") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn profile_set() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_PROFILE", "astronauts") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn profile_set_from_aws_vault() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_VAULT", "astronauts-vault") + .env("AWS_PROFILE", "astronauts-profile") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts-vault") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn profile_and_region_set() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_PROFILE", "astronauts") + .env("AWS_REGION", "ap-northeast-2") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts(ap-northeast-2)") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn default_profile_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let config_path = dir.path().join("config"); + let mut file = File::create(&config_path)?; + + file.write_all( + "[default] +region = us-east-1 + +[profile astronauts] +region = us-east-2 +" + .as_bytes(), + )?; + + let actual = ModuleRenderer::new("aws") + .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ (us-east-1)") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn profile_and_config_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let config_path = dir.path().join("config"); + let mut file = File::create(&config_path)?; + + file.write_all( + "[default] +region = us-east-1 + +[profile astronauts] +region = us-east-2 +" + .as_bytes(), + )?; + + let actual = ModuleRenderer::new("aws") + .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) + .env("AWS_PROFILE", "astronauts") + .config(toml::toml! { + [aws] + }) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts(us-east-2)") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn profile_and_region_set_with_display_all() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_PROFILE", "astronauts") + .env("AWS_REGION", "ap-northeast-1") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts(ap-northeast-1)") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn profile_set_with_display_all() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_PROFILE", "astronauts") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn region_set_with_display_all() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_REGION", "ap-northeast-1") + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ (ap-northeast-1)") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn profile_and_region_set_with_display_region() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_PROFILE", "astronauts") + .env("AWS_DEFAULT_REGION", "ap-northeast-1") + .config(toml::toml! { + [aws] + format = "on [$symbol$region]($style) " + }) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ ap-northeast-1") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn profile_and_region_set_with_display_profile() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_PROFILE", "astronauts") + .env("AWS_REGION", "ap-northeast-1") + .config(toml::toml! { + [aws] + format = "on [$symbol$profile]($style) " + }) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Yellow.bold().paint("☁️ astronauts") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn region_set_with_display_profile() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .env("AWS_REGION", "ap-northeast-1") + .config(toml::toml! { + [aws] + format = "on [$symbol$profile]($style) " + }) + .collect(); + let expected = Some(format!("on {} ", Color::Yellow.bold().paint("☁️ "))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn region_not_set_with_display_region() -> io::Result<()> { + let actual = ModuleRenderer::new("aws") + .config(toml::toml! { + [aws] + format = "on [$symbol$region]($style) " + }) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } +} diff --git a/src/modules/character.rs b/src/modules/character.rs index fc435195..4f471406 100644 --- a/src/modules/character.rs +++ b/src/modules/character.rs @@ -68,3 +68,142 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod test { + use crate::context::Shell; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; + + #[test] + fn success_status() -> io::Result<()> { + let expected = Some(format!("{} ", Color::Green.bold().paint("❯"))); + + // Status code 0 + let actual = ModuleRenderer::new("character").status(0).collect(); + assert_eq!(expected, actual); + + // No status code + let actual = ModuleRenderer::new("character").collect(); + assert_eq!(expected, actual); + + Ok(()) + } + + #[test] + fn failure_status() -> io::Result<()> { + let expected = Some(format!("{} ", Color::Red.bold().paint("❯"))); + + let exit_values = [1, 54321, -5000]; + + for status in &exit_values { + let actual = ModuleRenderer::new("character").status(*status).collect(); + assert_eq!(expected, actual); + } + + Ok(()) + } + + #[test] + fn custom_symbol() -> io::Result<()> { + let expected_fail = Some(format!("{} ", Color::Red.bold().paint("✖"))); + let expected_success = Some(format!("{} ", Color::Green.bold().paint("➜"))); + + let exit_values = [1, 54321, -5000]; + + // Test failure values + for status in &exit_values { + let actual = ModuleRenderer::new("character") + .config(toml::toml! { + [character] + success_symbol = "[➜](bold green)" + error_symbol = "[✖](bold red)" + }) + .status(*status) + .collect(); + assert_eq!(expected_fail, actual); + } + + // Test success + let actual = ModuleRenderer::new("character") + .config(toml::toml! { + [character] + success_symbol = "[➜](bold green)" + error_symbol = "[✖](bold red)" + }) + .status(0) + .collect(); + assert_eq!(expected_success, actual); + + Ok(()) + } + + #[test] + fn zsh_keymap() -> io::Result<()> { + let expected_vicmd = Some(format!("{} ", Color::Green.bold().paint("❮"))); + let expected_specified = Some(format!("{} ", Color::Green.bold().paint("V"))); + let expected_other = Some(format!("{} ", Color::Green.bold().paint("❯"))); + + // zle keymap is vicmd + let actual = ModuleRenderer::new("character") + .shell(Shell::Zsh) + .keymap("vicmd") + .collect(); + assert_eq!(expected_vicmd, actual); + + // specified vicmd character + let actual = ModuleRenderer::new("character") + .config(toml::toml! { + [character] + vicmd_symbol = "[V](bold green)" + }) + .shell(Shell::Zsh) + .keymap("vicmd") + .collect(); + assert_eq!(expected_specified, actual); + + // zle keymap is other + let actual = ModuleRenderer::new("character") + .shell(Shell::Zsh) + .keymap("visual") + .collect(); + assert_eq!(expected_other, actual); + + Ok(()) + } + + #[test] + fn fish_keymap() -> io::Result<()> { + let expected_vicmd = Some(format!("{} ", Color::Green.bold().paint("❮"))); + let expected_specified = Some(format!("{} ", Color::Green.bold().paint("V"))); + let expected_other = Some(format!("{} ", Color::Green.bold().paint("❯"))); + + // fish keymap is default + let actual = ModuleRenderer::new("character") + .shell(Shell::Fish) + .keymap("default") + .collect(); + assert_eq!(expected_vicmd, actual); + + // specified vicmd character + let actual = ModuleRenderer::new("character") + .config(toml::toml! { + [character] + vicmd_symbol = "[V](bold green)" + }) + .shell(Shell::Fish) + .keymap("default") + .collect(); + assert_eq!(expected_specified, actual); + + // fish keymap is other + let actual = ModuleRenderer::new("character") + .shell(Shell::Fish) + .keymap("visual") + .collect(); + assert_eq!(expected_other, actual); + + Ok(()) + } +} diff --git a/src/modules/cmake.rs b/src/modules/cmake.rs index 90c09988..ec078ce5 100644 --- a/src/modules/cmake.rs +++ b/src/modules/cmake.rs @@ -58,7 +58,7 @@ fn format_cmake_version(cmake_version: &str) -> Option { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -66,7 +66,7 @@ mod tests { #[test] fn folder_without_cmake_lists() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("cmake", dir.path(), None); + let actual = ModuleRenderer::new("cmake").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -76,7 +76,7 @@ mod tests { fn folder_with_cmake_lists() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("CMakeLists.txt"))?.sync_all()?; - let actual = render_module("cmake", dir.path(), None); + let actual = ModuleRenderer::new("cmake").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Blue.bold().paint("🛆 v3.17.3"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/cmd_duration.rs b/src/modules/cmd_duration.rs index 3a10f8f1..fe1f22b6 100644 --- a/src/modules/cmd_duration.rs +++ b/src/modules/cmd_duration.rs @@ -89,6 +89,9 @@ fn render_time_component((component, suffix): (&u128, &&str)) -> String { #[cfg(test)] mod tests { use super::*; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; #[test] fn test_500ms() { @@ -110,4 +113,86 @@ mod tests { fn test_1d() { assert_eq!(render_time(86_400_000 as u128, true), "1d") } + + #[test] + fn config_blank_duration_1s() -> io::Result<()> { + let actual = ModuleRenderer::new("cmd_duration") + .cmd_duration(1000) + .collect(); + + let expected = None; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_blank_duration_5s() -> io::Result<()> { + let actual = ModuleRenderer::new("cmd_duration") + .cmd_duration(5000) + .collect(); + + let expected = Some(format!("took {} ", Color::Yellow.bold().paint("5s"))); + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_5s_duration_3s() -> io::Result<()> { + let actual = ModuleRenderer::new("cmd_duration") + .config(toml::toml! { + [cmd_duration] + min_time = 5000 + }) + .cmd_duration(3000) + .collect(); + + let expected = None; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_5s_duration_10s() -> io::Result<()> { + let actual = ModuleRenderer::new("cmd_duration") + .config(toml::toml! { + [cmd_duration] + min_time = 5000 + }) + .cmd_duration(10000) + .collect(); + + let expected = Some(format!("took {} ", Color::Yellow.bold().paint("10s"))); + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_1s_duration_prefix_underwent() -> io::Result<()> { + let actual = ModuleRenderer::new("cmd_duration") + .config(toml::toml! { + [cmd_duration] + format = "underwent [$duration]($style) " + }) + .cmd_duration(1000) + .collect(); + + let expected = None; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_5s_duration_prefix_underwent() -> io::Result<()> { + let actual = ModuleRenderer::new("cmd_duration") + .config(toml::toml! { + [cmd_duration] + format = "underwent [$duration]($style) " + }) + .cmd_duration(5000) + .collect(); + + let expected = Some(format!("underwent {} ", Color::Yellow.bold().paint("5s"))); + assert_eq!(expected, actual); + Ok(()) + } } diff --git a/src/modules/conda.rs b/src/modules/conda.rs index 4d564176..36762c4c 100644 --- a/src/modules/conda.rs +++ b/src/modules/conda.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module, RootModuleConfig}; use super::utils::directory::truncate; @@ -11,7 +9,9 @@ use crate::formatter::StringFormatter; /// Will display the Conda environment iff `$CONDA_DEFAULT_ENV` is set. pub fn module<'a>(context: &'a Context) -> Option> { // Reference implementation: https://github.com/denysdovhan/spaceship-prompt/blob/master/sections/conda.zsh - let conda_env = env::var("CONDA_DEFAULT_ENV").unwrap_or_else(|_| "".into()); + let conda_env = context + .get_env("CONDA_DEFAULT_ENV") + .unwrap_or_else(|| "".into()); if conda_env.trim().is_empty() { return None; } @@ -52,3 +52,63 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; + + #[test] + fn not_in_env() -> io::Result<()> { + let actual = ModuleRenderer::new("conda").collect(); + + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn ignore_base() -> io::Result<()> { + let actual = ModuleRenderer::new("conda") + .env("CONDA_DEFAULT_ENV", "base") + .config(toml::toml! { + [conda] + ignore_base = true + }) + .collect(); + + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn env_set() -> io::Result<()> { + let actual = ModuleRenderer::new("conda") + .env("CONDA_DEFAULT_ENV", "astronauts") + .collect(); + + let expected = Some(format!( + "via {} ", + Color::Green.bold().paint("🅒 astronauts") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn truncate() -> io::Result<()> { + let actual = ModuleRenderer::new("conda") + .env("CONDA_DEFAULT_ENV", "/some/really/long/and/really/annoying/path/that/shouldnt/be/displayed/fully/conda/my_env") + .collect(); + + let expected = Some(format!("via {} ", Color::Green.bold().paint("🅒 my_env"))); + + assert_eq!(expected, actual); + Ok(()) + } +} diff --git a/src/modules/crystal.rs b/src/modules/crystal.rs index af5f5f8b..888cf83f 100644 --- a/src/modules/crystal.rs +++ b/src/modules/crystal.rs @@ -68,7 +68,7 @@ fn format_crystal_version(crystal_version: &str) -> Option { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -76,7 +76,7 @@ mod tests { #[test] fn folder_without_crystal_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("crystal", dir.path(), None); + let actual = ModuleRenderer::new("crystal").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -88,7 +88,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("shard.yml"))?.sync_all()?; - let actual = render_module("crystal", dir.path(), None); + let actual = ModuleRenderer::new("crystal").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.bold().paint("🔮 v0.35.1"))); assert_eq!(expected, actual); @@ -100,7 +100,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.cr"))?.sync_all()?; - let actual = render_module("crystal", dir.path(), None); + let actual = ModuleRenderer::new("crystal").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.bold().paint("🔮 v0.35.1"))); assert_eq!(expected, actual); diff --git a/src/modules/dart.rs b/src/modules/dart.rs index ecf7b47c..a2269030 100644 --- a/src/modules/dart.rs +++ b/src/modules/dart.rs @@ -68,7 +68,7 @@ fn parse_dart_version(dart_version: &str) -> Option { #[cfg(test)] mod tests { use super::parse_dart_version; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::{self, File}; use std::io; @@ -82,7 +82,7 @@ mod tests { #[test] fn folder_without_dart_file() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("dart", dir.path(), None); + let actual = ModuleRenderer::new("dart").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -93,7 +93,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.dart"))?.sync_all()?; - let actual = render_module("dart", dir.path(), None); + let actual = ModuleRenderer::new("dart").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Blue.bold().paint("🎯 v2.8.4"))); assert_eq!(expected, actual); dir.close() @@ -104,7 +104,7 @@ mod tests { let dir = tempfile::tempdir()?; fs::create_dir_all(dir.path().join(".dart_tool"))?; - let actual = render_module("dart", dir.path(), None); + let actual = ModuleRenderer::new("dart").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Blue.bold().paint("🎯 v2.8.4"))); assert_eq!(expected, actual); dir.close() @@ -115,7 +115,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("pubspec.yaml"))?.sync_all()?; - let actual = render_module("dart", dir.path(), None); + let actual = ModuleRenderer::new("dart").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Blue.bold().paint("🎯 v2.8.4"))); assert_eq!(expected, actual); dir.close() @@ -126,7 +126,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("pubspec.yml"))?.sync_all()?; - let actual = render_module("dart", dir.path(), None); + let actual = ModuleRenderer::new("dart").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Blue.bold().paint("🎯 v2.8.4"))); assert_eq!(expected, actual); dir.close() @@ -137,7 +137,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("pubspec.lock"))?.sync_all()?; - let actual = render_module("dart", dir.path(), None); + let actual = ModuleRenderer::new("dart").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Blue.bold().paint("🎯 v2.8.4"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/directory.rs b/src/modules/directory.rs index f0e1f4b4..a80fc035 100644 --- a/src/modules/directory.rs +++ b/src/modules/directory.rs @@ -18,10 +18,12 @@ use crate::formatter::StringFormatter; /// Creates a module with the current directory /// /// Will perform path contraction, substitution, and truncation. +/// /// **Contraction** -/// - Paths beginning with the home directory or with a git repo right -/// inside the home directory will be contracted to `~` -/// - Paths containing a git repo will contract to begin at the repo root +/// +/// - Paths beginning with the home directory or with a git repo right inside +/// the home directory will be contracted to `~` +/// - Paths containing a git repo will contract to begin at the repo root /// /// **Substitution** /// Paths will undergo user-provided substitutions of substrings @@ -37,10 +39,10 @@ pub fn module<'a>(context: &'a Context) -> Option> { // Using environment PWD is the standard approach for determining logical path // If this is None for any reason, we fall back to reading the os-provided path let physical_current_dir = if config.use_logical_path { - match std::env::var("PWD") { - Ok(x) => Some(PathBuf::from(x)), - Err(e) => { - log::debug!("Error getting PWD environment variable: {}", e); + match context.get_env("PWD") { + Some(x) => Some(PathBuf::from(x)), + None => { + log::debug!("Error getting PWD environment variable!"); None } } @@ -269,6 +271,17 @@ fn to_fish_style(pwd_dir_length: usize, dir_string: String, truncated_dir_string #[cfg(test)] mod tests { use super::*; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use dirs_next::home_dir; + #[cfg(not(target_os = "windows"))] + use std::os::unix::fs::symlink; + #[cfg(target_os = "windows")] + use std::os::windows::fs::symlink_dir as symlink; + use std::path::Path; + use std::process::Command; + use std::{fs, io}; + use tempfile::TempDir; #[test] fn contract_home_directory() { @@ -355,7 +368,7 @@ mod tests { #[test] fn fish_style_with_no_contracted_path() { - // `truncatation_length = 2` + // `truncation_length = 2` let path = "/absolute/Path/not/in_a/repo/but_nested"; let output = to_fish_style(1, path.to_string(), "repo/but_nested"); assert_eq!(output, "/a/P/n/i/"); @@ -363,7 +376,7 @@ mod tests { #[test] fn fish_style_with_pwd_dir_len_no_contracted_path() { - // `truncatation_length = 2` + // `truncation_length = 2` let path = "/absolute/Path/not/in_a/repo/but_nested"; let output = to_fish_style(2, path.to_string(), "repo/but_nested"); assert_eq!(output, "/ab/Pa/no/in/"); @@ -382,4 +395,777 @@ mod tests { let output = to_fish_style(1, path.to_string(), "目录"); assert_eq!(output, "~/s/t/目/a̐/"); } + + fn init_repo(path: &Path) -> io::Result<()> { + Command::new("git") + .args(&["init"]) + .current_dir(path) + .output() + .map(|_| ()) + } + + fn make_known_tempdir(root: &Path) -> io::Result<(TempDir, String)> { + fs::create_dir_all(root)?; + let dir = TempDir::new_in(root)?; + let path = dir + .path() + .file_name() + .unwrap() + .to_string_lossy() + .to_string(); + Ok((dir, path)) + } + + #[cfg(not(target_os = "windows"))] + mod linux { + use super::*; + use std::sync::atomic::{AtomicBool, Ordering}; + + // As tests are run in parallel we have to keep a lock on which of the + // two tests are currently running as they both modify `HOME` which can + // override the other value resulting in inconsistent runs which is why + // we only run one of these tests at once. + static LOCK: AtomicBool = AtomicBool::new(false); + + #[test] + #[ignore] + fn symlinked_subdirectory_git_repo_out_of_tree() -> io::Result<()> { + while LOCK.load(Ordering::Relaxed) {} + LOCK.store(true, Ordering::Relaxed); + let tmp_dir = TempDir::new_in(home_dir().unwrap().as_path())?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let src_dir = repo_dir.join("src/meters/fuel-gauge"); + let symlink_dir = tmp_dir.path().join("fuel-gauge"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir)?; + symlink(&src_dir, &symlink_dir)?; + + // We can't mock `HOME` since dirs-next uses it which does not care about our mocking + let previous_home = home_dir().unwrap(); + + std::env::set_var("HOME", tmp_dir.path()); + + let actual = ModuleRenderer::new("directory").path(symlink_dir).collect(); + let expected = Some(format!("{} ", Color::Cyan.bold().paint("~/fuel-gauge"))); + + std::env::set_var("HOME", previous_home.as_path()); + + assert_eq!(expected, actual); + + LOCK.store(false, Ordering::Relaxed); + + tmp_dir.close() + } + + #[test] + #[ignore] + fn git_repo_in_home_directory_truncate_to_repo_true() -> io::Result<()> { + while LOCK.load(Ordering::Relaxed) {} + LOCK.store(true, Ordering::Relaxed); + let tmp_dir = TempDir::new_in(home_dir().unwrap().as_path())?; + let dir = tmp_dir.path().join("src/fuel-gauge"); + fs::create_dir_all(&dir)?; + init_repo(&tmp_dir.path())?; + + // We can't mock `HOME` since dirs-next uses it which does not care about our mocking + let previous_home = home_dir().unwrap(); + + std::env::set_var("HOME", tmp_dir.path()); + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // `truncate_to_repo = true` should attempt to display the truncated path + truncate_to_repo = true + truncation_length = 5 + }) + .path(dir) + .collect(); + let expected = Some(format!("{} ", Color::Cyan.bold().paint("~/src/fuel-gauge"))); + + std::env::set_var("HOME", previous_home.as_path()); + + assert_eq!(expected, actual); + + LOCK.store(false, Ordering::Relaxed); + + tmp_dir.close() + } + + #[test] + fn directory_in_root() -> io::Result<()> { + let actual = ModuleRenderer::new("directory").path("/etc").collect(); + let expected = Some(format!( + "{}{} ", + Color::Cyan.bold().paint("/etc"), + Color::Red.normal().paint("🔒") + )); + + assert_eq!(expected, actual); + Ok(()) + } + } + + #[test] + fn home_directory() -> io::Result<()> { + let actual = ModuleRenderer::new("directory") + .path(home_dir().unwrap()) + .config(toml::toml! { // Necessary if homedir is a git repo + [directory] + truncate_to_repo = false + }) + .collect(); + let expected = Some(format!("{} ", Color::Cyan.bold().paint("~"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn substituted_truncated_path() -> io::Result<()> { + let actual = ModuleRenderer::new("directory") + .path("/some/long/network/path/workspace/a/b/c/dev") + .config(toml::toml! { + [directory] + truncation_length = 4 + [directory.substitutions] + "/some/long/network/path" = "/some/net" + "a/b/c" = "d" + }) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("net/workspace/d/dev") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn strange_substitution() -> io::Result<()> { + let strange_sub = "/\\/;,!"; + let actual = ModuleRenderer::new("directory") + .path("/foo/bar/regular/path") + .config(toml::toml! { + [directory] + truncation_length = 0 + fish_style_pwd_dir_length = 2 // Overridden by substitutions + [directory.substitutions] + "regular" = strange_sub + }) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint(format!("/foo/bar/{}/path", strange_sub)) + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn directory_in_home() -> io::Result<()> { + let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?; + let dir = tmp_dir.path().join("starship"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory").path(dir).collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!("~/{}/starship", name)) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn truncated_directory_in_home() -> io::Result<()> { + let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?; + let dir = tmp_dir.path().join("engine/schematics"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory").path(dir).collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint(format!("{}/engine/schematics", name)) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn fish_directory_in_home() -> io::Result<()> { + let (tmp_dir, name) = make_known_tempdir(home_dir().unwrap().as_path())?; + let dir = tmp_dir.path().join("starship/schematics"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + truncation_length = 1 + fish_style_pwd_dir_length = 2 + }) + .path(&dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint(format!("~/{}/st/schematics", name.split_at(3).0)) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn root_directory() -> io::Result<()> { + let actual = ModuleRenderer::new("directory").path("/").collect(); + #[cfg(not(target_os = "windows"))] + let expected = Some(format!( + "{}{} ", + Color::Cyan.bold().paint("/"), + Color::Red.normal().paint("🔒") + )); + #[cfg(target_os = "windows")] + let expected = Some(format!("{} ", Color::Cyan.bold().paint("/"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn truncated_directory_in_root() -> io::Result<()> { + let (tmp_dir, name) = make_known_tempdir(Path::new("/tmp"))?; + let dir = tmp_dir.path().join("thrusters/rocket"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory").path(dir).collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint(format!("{}/thrusters/rocket", name)) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn truncated_directory_config_large() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let dir = tmp_dir.path().join("thrusters/rocket"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + truncation_length = 100 + }) + .path(&dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint(truncate(dir.to_slash_lossy(), 100)) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn fish_style_directory_config_large() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let dir = tmp_dir.path().join("thrusters/rocket"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + truncation_length = 1 + fish_style_pwd_dir_length = 100 + }) + .path(&dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint(to_fish_style(100, dir.to_slash_lossy(), "")) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn truncated_directory_config_small() -> io::Result<()> { + let (tmp_dir, name) = make_known_tempdir(Path::new("/tmp"))?; + let dir = tmp_dir.path().join("rocket"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + truncation_length = 2 + }) + .path(dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!("{}/rocket", name)) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + fn fish_directory_config_small() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let dir = tmp_dir.path().join("thrusters/rocket"); + fs::create_dir_all(&dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + truncation_length = 2 + fish_style_pwd_dir_length = 1 + }) + .path(&dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!( + "{}/thrusters/rocket", + to_fish_style(1, dir.to_slash_lossy(), "/thrusters/rocket") + )) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn git_repo_root() -> io::Result<()> { + let tmp_dir = TempDir::new()?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + fs::create_dir(&repo_dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory").path(repo_dir).collect(); + let expected = Some(format!("{} ", Color::Cyan.bold().paint("rocket-controls"))); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn directory_in_git_repo() -> io::Result<()> { + let tmp_dir = TempDir::new()?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + let dir = repo_dir.join("src"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory").path(dir).collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("rocket-controls/src") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn truncated_directory_in_git_repo() -> io::Result<()> { + let tmp_dir = TempDir::new()?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory").path(dir).collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("src/meters/fuel-gauge") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> { + let tmp_dir = TempDir::new()?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // Don't truncate the path at all. + truncation_length = 5 + truncate_to_repo = false + }) + .path(dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint("above-repo/rocket-controls/src/meters/fuel-gauge") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn fish_path_directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // Don't truncate the path at all. + truncation_length = 5 + truncate_to_repo = false + fish_style_pwd_dir_length = 1 + }) + .path(dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!( + "{}/above-repo/rocket-controls/src/meters/fuel-gauge", + to_fish_style(1, tmp_dir.path().to_slash_lossy(), "") + )) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn fish_path_directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // `truncate_to_repo = true` should display the truncated path + truncation_length = 5 + truncate_to_repo = true + fish_style_pwd_dir_length = 1 + }) + .path(dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!( + "{}/rocket-controls/src/meters/fuel-gauge", + to_fish_style(1, tmp_dir.path().join("above-repo").to_slash_lossy(), "") + )) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let dir = repo_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // `truncate_to_repo = true` should display the truncated path + truncation_length = 5 + truncate_to_repo = true + }) + .path(dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint("rocket-controls/src/meters/fuel-gauge") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn symlinked_git_repo_root() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + let symlink_dir = tmp_dir.path().join("rocket-controls-symlink"); + fs::create_dir(&repo_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory").path(symlink_dir).collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("rocket-controls-symlink") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn directory_in_symlinked_git_repo() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + let src_dir = repo_dir.join("src"); + let symlink_dir = tmp_dir.path().join("rocket-controls-symlink"); + let symlink_src_dir = symlink_dir.join("src"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory") + .path(symlink_src_dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("rocket-controls-symlink/src") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn truncated_directory_in_symlinked_git_repo() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + let src_dir = repo_dir.join("src/meters/fuel-gauge"); + let symlink_dir = tmp_dir.path().join("rocket-controls-symlink"); + let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory") + .path(symlink_src_dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("src/meters/fuel-gauge") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn directory_in_symlinked_git_repo_truncate_to_repo_false() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let src_dir = repo_dir.join("src/meters/fuel-gauge"); + let symlink_dir = tmp_dir + .path() + .join("above-repo") + .join("rocket-controls-symlink"); + let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // Don't truncate the path at all. + truncation_length = 5 + truncate_to_repo = false + }) + .path(symlink_src_dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint("above-repo/rocket-controls-symlink/src/meters/fuel-gauge") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn fish_path_directory_in_symlinked_git_repo_truncate_to_repo_false() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let src_dir = repo_dir.join("src/meters/fuel-gauge"); + let symlink_dir = tmp_dir + .path() + .join("above-repo") + .join("rocket-controls-symlink"); + let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // Don't truncate the path at all. + truncation_length = 5 + truncate_to_repo = false + fish_style_pwd_dir_length = 1 + }) + .path(symlink_src_dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!( + "{}/above-repo/rocket-controls-symlink/src/meters/fuel-gauge", + to_fish_style(1, tmp_dir.path().to_slash_lossy(), "") + )) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn fish_path_directory_in_symlinked_git_repo_truncate_to_repo_true() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let src_dir = repo_dir.join("src/meters/fuel-gauge"); + let symlink_dir = tmp_dir + .path() + .join("above-repo") + .join("rocket-controls-symlink"); + let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // `truncate_to_repo = true` should display the truncated path + truncation_length = 5 + truncate_to_repo = true + fish_style_pwd_dir_length = 1 + }) + .path(symlink_src_dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint(format!( + "{}/rocket-controls-symlink/src/meters/fuel-gauge", + to_fish_style(1, tmp_dir.path().join("above-repo").to_slash_lossy(), "") + )) + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn directory_in_symlinked_git_repo_truncate_to_repo_true() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); + let src_dir = repo_dir.join("src/meters/fuel-gauge"); + let symlink_dir = tmp_dir + .path() + .join("above-repo") + .join("rocket-controls-symlink"); + let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); + fs::create_dir_all(&src_dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&repo_dir, &symlink_dir)?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // `truncate_to_repo = true` should display the truncated path + truncation_length = 5 + truncate_to_repo = true + }) + .path(symlink_src_dir) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan + .bold() + .paint("rocket-controls-symlink/src/meters/fuel-gauge") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } + + #[test] + #[ignore] + fn symlinked_directory_in_git_repo() -> io::Result<()> { + let (tmp_dir, _) = make_known_tempdir(Path::new("/tmp"))?; + let repo_dir = tmp_dir.path().join("rocket-controls"); + let dir = repo_dir.join("src"); + fs::create_dir_all(&dir)?; + init_repo(&repo_dir).unwrap(); + symlink(&dir, repo_dir.join("src/loop"))?; + + let actual = ModuleRenderer::new("directory") + .config(toml::toml! { + [directory] + // `truncate_to_repo = true` should display the truncated path + truncation_length = 5 + truncate_to_repo = true + }) + .path(repo_dir.join("src/loop/loop")) + .collect(); + let expected = Some(format!( + "{} ", + Color::Cyan.bold().paint("rocket-controls/src/loop/loop") + )); + + assert_eq!(expected, actual); + tmp_dir.close() + } } diff --git a/src/modules/docker_context.rs b/src/modules/docker_context.rs index fcc14395..ae56f19f 100644 --- a/src/modules/docker_context.rs +++ b/src/modules/docker_context.rs @@ -1,4 +1,3 @@ -use std::env; use std::path::PathBuf; use super::{Context, Module, RootModuleConfig}; @@ -27,7 +26,8 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; } let docker_config = PathBuf::from( - &env::var_os("DOCKER_CONFIG") + &context + .get_env_os("DOCKER_CONFIG") .unwrap_or(dirs_next::home_dir()?.join(".docker").into_os_string()), ) .join("config.json"); diff --git a/src/modules/dotnet.rs b/src/modules/dotnet.rs index c2b5f4d6..0e0ad52b 100644 --- a/src/modules/dotnet.rs +++ b/src/modules/dotnet.rs @@ -3,7 +3,7 @@ use quick_xml::Reader; use std::ffi::OsStr; use std::iter::Iterator; use std::ops::Deref; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str; use super::{Context, Module, RootModuleConfig}; @@ -89,12 +89,12 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } -fn find_current_tfm<'a>(files: &[DotNetFile<'a>]) -> Option { +fn find_current_tfm(files: &[DotNetFile]) -> Option { let get_file_of_type = |t: FileType| files.iter().find(|f| f.file_type == t); let relevant_file = get_file_of_type(FileType::ProjectFile)?; - get_tfm_from_project_file(relevant_file.path) + get_tfm_from_project_file(relevant_file.path.as_path()) } fn get_tfm_from_project_file(path: &Path) -> Option { @@ -135,8 +135,8 @@ fn get_tfm_from_project_file(path: &Path) -> Option { None } -fn estimate_dotnet_version<'a>( - files: &[DotNetFile<'a>], +fn estimate_dotnet_version( + files: &[DotNetFile], current_dir: &Path, repo_root: Option<&Path>, ) -> Option { @@ -149,9 +149,8 @@ fn estimate_dotnet_version<'a>( .or_else(|| files.iter().next())?; match relevant_file.file_type { - FileType::GlobalJson => { - get_pinned_sdk_version_from_file(relevant_file.path).or_else(get_latest_sdk_from_cli) - } + FileType::GlobalJson => get_pinned_sdk_version_from_file(relevant_file.path.as_path()) + .or_else(get_latest_sdk_from_cli), FileType::SolutionFile => { // With this heuristic, we'll assume that a "global.json" won't // be found in any directory above the solution file. @@ -251,13 +250,13 @@ fn get_pinned_sdk_version(json: &str) -> Option { } } -fn get_local_dotnet_files<'a>(context: &'a Context) -> Result>, std::io::Error> { +fn get_local_dotnet_files<'a>(context: &'a Context) -> Result, std::io::Error> { Ok(context .dir_contents()? .files() .filter_map(|p| { get_dotnet_file_type(p).map(|t| DotNetFile { - path: p.as_ref(), + path: context.current_dir.join(p), file_type: t, }) }) @@ -331,8 +330,8 @@ fn get_latest_sdk_from_cli() -> Option { } } -struct DotNetFile<'a> { - path: &'a Path, +struct DotNetFile { + path: PathBuf, file_type: FileType, } @@ -354,9 +353,266 @@ impl Deref for Version { } } -#[test] -fn should_parse_version_from_global_json() { - let json_text = r#" +#[cfg(test)] +mod tests { + use super::*; + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::fs::{self, OpenOptions}; + use std::io::{self, Write}; + use std::process::Command; + use tempfile::{self, TempDir}; + + #[test] + fn shows_nothing_in_directory_with_zero_relevant_files() -> io::Result<()> { + let workspace = create_workspace(false)?; + expect_output(&workspace.path(), None)?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_directory_build_props_file() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "Directory.Build.props", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_directory_build_targets_file() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "Directory.Build.targets", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_packages_props_file() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "Packages.props", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_solution() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "solution.sln", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_csproj() -> io::Result<()> { + let workspace = create_workspace(false)?; + let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0"); + touch_path(&workspace, "project.csproj", Some(&csproj))?; + expect_output( + &workspace.path(), + Some(format!( + "{} ", + Color::Blue.bold().paint("•NET v3.1.103 🎯 netstandard2.0") + )), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_fsproj() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "project.fsproj", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_xproj() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "project.xproj", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_latest_in_directory_with_project_json() -> io::Result<()> { + let workspace = create_workspace(false)?; + touch_path(&workspace, "project.json", None)?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v3.1.103"))), + )?; + workspace.close() + } + + #[test] + fn shows_pinned_in_directory_with_global_json() -> io::Result<()> { + let workspace = create_workspace(false)?; + let global_json = make_pinned_sdk_json("1.2.3"); + touch_path(&workspace, "global.json", Some(&global_json))?; + expect_output( + &workspace.path(), + Some(format!("{} ", Color::Blue.bold().paint("•NET v1.2.3"))), + )?; + workspace.close() + } + + #[test] + fn shows_pinned_in_project_below_root_with_global_json() -> io::Result<()> { + let workspace = create_workspace(false)?; + let global_json = make_pinned_sdk_json("1.2.3"); + let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0"); + touch_path(&workspace, "global.json", Some(&global_json))?; + touch_path(&workspace, "project/project.csproj", Some(&csproj))?; + expect_output( + &workspace.path().join("project"), + Some(format!( + "{} ", + Color::Blue.bold().paint("•NET v1.2.3 🎯 netstandard2.0") + )), + )?; + workspace.close() + } + + #[test] + fn shows_pinned_in_deeply_nested_project_within_repository() -> io::Result<()> { + let workspace = create_workspace(true)?; + let global_json = make_pinned_sdk_json("1.2.3"); + let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0"); + touch_path(&workspace, "global.json", Some(&global_json))?; + touch_path( + &workspace, + "deep/path/to/project/project.csproj", + Some(&csproj), + )?; + expect_output( + &workspace.path().join("deep/path/to/project"), + Some(format!( + "{} ", + Color::Blue.bold().paint("•NET v1.2.3 🎯 netstandard2.0") + )), + )?; + workspace.close() + } + + #[test] + fn shows_single_tfm() -> io::Result<()> { + let workspace = create_workspace(false)?; + let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0"); + touch_path(&workspace, "project.csproj", Some(&csproj))?; + expect_output( + workspace.path(), + Some(format!( + "{} ", + Color::Blue.bold().paint("•NET v3.1.103 🎯 netstandard2.0") + )), + )?; + workspace.close() + } + + #[test] + fn shows_multiple_tfms() -> io::Result<()> { + let workspace = create_workspace(false)?; + let csproj = make_csproj_with_tfm("TargetFrameworks", "netstandard2.0;net461"); + touch_path(&workspace, "project.csproj", Some(&csproj))?; + expect_output( + workspace.path(), + Some(format!( + "{} ", + Color::Blue + .bold() + .paint("•NET v3.1.103 🎯 netstandard2.0;net461") + )), + )?; + workspace.close() + } + + fn create_workspace(is_repo: bool) -> io::Result { + let repo_dir = tempfile::tempdir()?; + + if is_repo { + Command::new("git") + .args(&["init", "--quiet"]) + .current_dir(repo_dir.path()) + .output()?; + } + + Ok(repo_dir) + } + + fn touch_path( + workspace: &TempDir, + relative_path: &str, + contents: Option<&str>, + ) -> io::Result<()> { + let path = workspace.path().join(relative_path); + + fs::create_dir_all( + path.parent() + .expect("Expected relative_path to be a file in a directory"), + )?; + + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&path)?; + write!(file, "{}", contents.unwrap_or(""))?; + file.sync_data() + } + + fn make_pinned_sdk_json(version: &str) -> String { + let json_text = r#" + { + "sdk": { + "version": "INSERT_VERSION" + } + } + "#; + json_text.replace("INSERT_VERSION", version) + } + + fn make_csproj_with_tfm(tfm_element: &str, tfm: &str) -> String { + let json_text = r#" + + + TFM_VALUE + + + "#; + json_text + .replace("TFM_ELEMENT", tfm_element) + .replace("TFM_VALUE", tfm) + } + + fn expect_output(dir: &Path, expected: Option) -> io::Result<()> { + let actual = ModuleRenderer::new("dotnet").path(dir).collect(); + + assert_eq!(actual, expected); + + Ok(()) + } + + #[test] + fn should_parse_version_from_global_json() { + let json_text = r#" { "sdk": { "version": "1.2.3" @@ -364,14 +620,15 @@ fn should_parse_version_from_global_json() { } "#; - let version = get_pinned_sdk_version(json_text).unwrap(); - assert_eq!("v1.2.3", version.0); -} + let version = get_pinned_sdk_version(json_text).unwrap(); + assert_eq!("v1.2.3", version.0); + } -#[test] -fn should_ignore_empty_global_json() { - let json_text = "{}"; + #[test] + fn should_ignore_empty_global_json() { + let json_text = "{}"; - let version = get_pinned_sdk_version(json_text); - assert!(version.is_none()); + let version = get_pinned_sdk_version(json_text); + assert!(version.is_none()); + } } diff --git a/src/modules/elixir.rs b/src/modules/elixir.rs index df7f5db9..921e97af 100644 --- a/src/modules/elixir.rs +++ b/src/modules/elixir.rs @@ -73,7 +73,7 @@ fn parse_elixir_version(version: &str) -> Option<(String, String)> { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -97,7 +97,7 @@ Elixir 1.10 (compiled with Erlang/OTP 22) let dir = tempfile::tempdir()?; let expected = None; - let output = render_module("elixir", dir.path(), None); + let output = ModuleRenderer::new("elixir").path(dir.path()).collect(); assert_eq!(output, expected); @@ -113,7 +113,7 @@ Elixir 1.10 (compiled with Erlang/OTP 22) "via {} ", Color::Purple.bold().paint("💧 1.10 (OTP 22)") )); - let output = render_module("elixir", dir.path(), None); + let output = ModuleRenderer::new("elixir").path(dir.path()).collect(); assert_eq!(output, expected); diff --git a/src/modules/elm.rs b/src/modules/elm.rs index 6ccdf6ee..a7da023b 100644 --- a/src/modules/elm.rs +++ b/src/modules/elm.rs @@ -60,7 +60,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::{self, File}; use std::io; @@ -68,7 +68,7 @@ mod tests { #[test] fn folder_without_elm() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("elm", dir.path(), None); + let actual = ModuleRenderer::new("elm").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -78,7 +78,7 @@ mod tests { fn folder_with_elm_json() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("elm.json"))?.sync_all()?; - let actual = render_module("elm", dir.path(), None); + let actual = ModuleRenderer::new("elm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🌳 v0.19.1"))); assert_eq!(expected, actual); dir.close() @@ -88,7 +88,7 @@ mod tests { fn folder_with_elm_package_json() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("elm-package.json"))?.sync_all()?; - let actual = render_module("elm", dir.path(), None); + let actual = ModuleRenderer::new("elm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🌳 v0.19.1"))); assert_eq!(expected, actual); dir.close() @@ -98,7 +98,7 @@ mod tests { fn folder_with_elm_version() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join(".elm-version"))?.sync_all()?; - let actual = render_module("elm", dir.path(), None); + let actual = ModuleRenderer::new("elm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🌳 v0.19.1"))); assert_eq!(expected, actual); dir.close() @@ -109,7 +109,7 @@ mod tests { let dir = tempfile::tempdir()?; let elmstuff = dir.path().join("elm-stuff"); fs::create_dir_all(&elmstuff)?; - let actual = render_module("elm", dir.path(), None); + let actual = ModuleRenderer::new("elm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🌳 v0.19.1"))); assert_eq!(expected, actual); dir.close() @@ -119,7 +119,7 @@ mod tests { fn folder_with_elm_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.elm"))?.sync_all()?; - let actual = render_module("elm", dir.path(), None); + let actual = ModuleRenderer::new("elm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🌳 v0.19.1"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/env_var.rs b/src/modules/env_var.rs index 6b687ba5..47af5074 100644 --- a/src/modules/env_var.rs +++ b/src/modules/env_var.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module}; use crate::config::RootModuleConfig; @@ -16,7 +14,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("env_var"); let config: EnvVarConfig = EnvVarConfig::try_load(module.config); - let env_value = get_env_value(config.variable?, config.default)?; + let env_value = get_env_value(context, config.variable?, config.default)?; let parsed = StringFormatter::new(config.format).and_then(|formatter| { formatter .map_meta(|var, _| match var { @@ -45,12 +43,153 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } -fn get_env_value(name: &str, default: Option<&str>) -> Option { - match env::var_os(name) { - Some(os_value) => match os_value.into_string() { - Ok(value) => Some(value), - Err(_error) => None, - }, +fn get_env_value(context: &Context, name: &str, default: Option<&str>) -> Option { + match context.get_env(name) { + Some(value) => Some(value), None => default.map(|value| value.to_owned()), } } + +#[cfg(test)] +mod test { + use crate::test::ModuleRenderer; + use ansi_term::{Color, Style}; + use std::io; + + const TEST_VAR_VALUE: &str = "astronauts"; + + #[test] + fn empty_config() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + }) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn defined_variable() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "TEST_VAR" + }) + .env("TEST_VAR", TEST_VAR_VALUE) + .collect(); + let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn undefined_variable() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "TEST_VAR" + }) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn default_has_no_effect() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "TEST_VAR" + default = "N/A" + }) + .env("TEST_VAR", TEST_VAR_VALUE) + .collect(); + let expected = Some(format!("with {} ", style().paint(TEST_VAR_VALUE))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn default_takes_effect() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "UNDEFINED_TEST_VAR" + default = "N/A" + }) + .collect(); + let expected = Some(format!("with {} ", style().paint("N/A"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn symbol() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "TEST_VAR" + format = "with [■ $env_value](black bold dimmed) " + }) + .env("TEST_VAR", TEST_VAR_VALUE) + .collect(); + let expected = Some(format!( + "with {} ", + style().paint(format!("■ {}", TEST_VAR_VALUE)) + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn prefix() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "TEST_VAR" + format = "with [_$env_value](black bold dimmed) " + }) + .env("TEST_VAR", TEST_VAR_VALUE) + .collect(); + let expected = Some(format!( + "with {} ", + style().paint(format!("_{}", TEST_VAR_VALUE)) + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn suffix() -> io::Result<()> { + let actual = ModuleRenderer::new("env_var") + .config(toml::toml! { + [env_var] + variable = "TEST_VAR" + format = "with [${env_value}_](black bold dimmed) " + }) + .env("TEST_VAR", TEST_VAR_VALUE) + .collect(); + let expected = Some(format!( + "with {} ", + style().paint(format!("{}_", TEST_VAR_VALUE)) + )); + + assert_eq!(expected, actual); + Ok(()) + } + + fn style() -> Style { + // default style + Color::Black.bold().dimmed() + } +} diff --git a/src/modules/erlang.rs b/src/modules/erlang.rs index 60617c33..dd633c4b 100644 --- a/src/modules/erlang.rs +++ b/src/modules/erlang.rs @@ -67,7 +67,7 @@ fn get_erlang_version() -> Option { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -77,7 +77,7 @@ mod tests { let dir = tempfile::tempdir()?; let expected = None; - let output = render_module("erlang", dir.path(), None); + let output = ModuleRenderer::new("erlang").path(dir.path()).collect(); assert_eq!(output, expected); @@ -90,7 +90,7 @@ mod tests { File::create(dir.path().join("rebar.config"))?.sync_all()?; let expected = Some(format!("via {} ", Color::Red.bold().paint("🖧 22.1.3"))); - let output = render_module("erlang", dir.path(), None); + let output = ModuleRenderer::new("erlang").path(dir.path()).collect(); assert_eq!(output, expected); diff --git a/src/modules/gcloud.rs b/src/modules/gcloud.rs index c5101e60..19064bb8 100644 --- a/src/modules/gcloud.rs +++ b/src/modules/gcloud.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::env; use std::fs::File; use std::io::{BufRead, BufReader, Error, ErrorKind}; use std::path::PathBuf; @@ -68,16 +67,16 @@ fn get_active_config(config_root: &PathBuf) -> Option { } } -fn get_current_config_path() -> Option { - let config_dir = get_config_dir()?; +fn get_current_config_path(context: &Context) -> Option { + let config_dir = get_config_dir(context)?; let active_config = get_active_config(&config_dir)?; let current_config = config_dir.join(format!("configurations/config_{}", active_config)); Some(current_config) } -fn get_config_dir() -> Option { - let config_dir = env::var("CLOUDSDK_CONFIG") - .ok() +fn get_config_dir(context: &Context) -> Option { + let config_dir = context + .get_env("CLOUDSDK_CONFIG") .and_then(|path| PathBuf::from_str(&path).ok()) .or_else(|| { let mut home = dirs_next::home_dir()?; @@ -98,11 +97,11 @@ pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("gcloud"); let config: GcloudConfig = GcloudConfig::try_load(module.config); - let config_path = get_current_config_path()?; + let config_path = get_current_config_path(context)?; let gcloud_account = get_gcloud_account_from_config(&config_path); let gcloud_project = get_gcloud_project_from_config(&config_path); let gcloud_region = get_gcloud_region_from_config(&config_path); - let config_dir = get_config_dir()?; + let config_dir = get_config_dir(context)?; let gcloud_active: Option = get_active_config(&config_dir); if gcloud_account.is_none() @@ -149,3 +148,172 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod tests { + use std::fs::{create_dir, File}; + use std::io::{self, Write}; + + use ansi_term::Color; + + use crate::test::ModuleRenderer; + + #[test] + fn account_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let active_config_path = dir.path().join("active_config"); + let mut active_config_file = File::create(&active_config_path)?; + active_config_file.write_all(b"default")?; + + create_dir(dir.path().join("configurations"))?; + let config_default_path = dir.path().join("configurations/config_default"); + let mut config_default_file = File::create(&config_default_path)?; + config_default_file.write_all( + b"[core] +account = foo@example.com +", + )?; + + let actual = ModuleRenderer::new("gcloud") + .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy()) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Blue.bold().paint("☁️ foo@example.com") + )); + + assert_eq!(actual, expected); + dir.close() + } + + #[test] + fn account_and_region_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let active_config_path = dir.path().join("active_config"); + let mut active_config_file = File::create(&active_config_path)?; + active_config_file.write_all(b"default")?; + + create_dir(dir.path().join("configurations"))?; + let config_default_path = dir.path().join("configurations/config_default"); + let mut config_default_file = File::create(&config_default_path)?; + config_default_file.write_all( + b"[core] +account = foo@example.com + +[compute] +region = us-central1 +", + )?; + + let actual = ModuleRenderer::new("gcloud") + .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy()) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Blue.bold().paint("☁️ foo@example.com(us-central1)") + )); + + assert_eq!(actual, expected); + dir.close() + } + + #[test] + fn account_and_region_set_with_alias() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let active_config_path = dir.path().join("active_config"); + let mut active_config_file = File::create(&active_config_path)?; + active_config_file.write_all(b"default")?; + + create_dir(dir.path().join("configurations"))?; + let config_default_path = dir.path().join("configurations/config_default"); + let mut config_default_file = File::create(&config_default_path)?; + config_default_file.write_all( + b"[core] +account = foo@example.com + +[compute] +region = us-central1 +", + )?; + + let actual = ModuleRenderer::new("gcloud") + .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy()) + .config(toml::toml! { + [gcloud.region_aliases] + us-central1 = "uc1" + }) + .collect(); + let expected = Some(format!( + "on {} ", + Color::Blue.bold().paint("☁️ foo@example.com(uc1)") + )); + + assert_eq!(actual, expected); + dir.close() + } + + #[test] + fn active_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let active_config_path = dir.path().join("active_config"); + let mut active_config_file = File::create(&active_config_path)?; + active_config_file.write_all(b"default1")?; + + let actual = ModuleRenderer::new("gcloud") + .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy()) + .config(toml::toml! { + [gcloud] + format = "on [$symbol$active]($style) " + }) + .collect(); + let expected = Some(format!("on {} ", Color::Blue.bold().paint("☁️ default1"))); + + assert_eq!(actual, expected); + dir.close() + } + + #[test] + fn project_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let active_config_path = dir.path().join("active_config"); + let mut active_config_file = File::create(&active_config_path)?; + active_config_file.write_all(b"default")?; + + create_dir(dir.path().join("configurations"))?; + let config_default_path = dir.path().join("configurations/config_default"); + let mut config_default_file = File::create(&config_default_path)?; + config_default_file.write_all( + b"[core] +project = abc +", + )?; + + let actual = ModuleRenderer::new("gcloud") + .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy()) + .config(toml::toml! { + [gcloud] + format = "on [$symbol$project]($style) " + }) + .collect(); + let expected = Some(format!("on {} ", Color::Blue.bold().paint("☁️ abc"))); + + assert_eq!(actual, expected); + dir.close() + } + + #[test] + fn region_not_set_with_display_region() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let actual = ModuleRenderer::new("gcloud") + .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy()) + .config(toml::toml! { + [gcloud] + format = "on [$symbol$region]($style) " + }) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + dir.close() + } +} diff --git a/src/modules/git_branch.rs b/src/modules/git_branch.rs index 6ef58e5f..bcebd83e 100644 --- a/src/modules/git_branch.rs +++ b/src/modules/git_branch.rs @@ -29,7 +29,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { let repo = context.get_repo().ok()?; let branch_name = repo.branch.as_ref()?; - let mut graphemes = get_graphemes(&branch_name); + let mut graphemes: Vec<&str> = branch_name.graphemes(true).collect(); let trunc_len = len.min(graphemes.len()); if trunc_len < graphemes.len() { @@ -72,6 +72,295 @@ fn get_first_grapheme(text: &str) -> &str { .unwrap_or("") } -fn get_graphemes(text: &str) -> Vec<&str> { - UnicodeSegmentation::graphemes(text, true).collect() +#[cfg(test)] +mod tests { + use ansi_term::Color; + use std::io; + use std::process::Command; + + use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer}; + + #[test] + fn show_nothing_on_empty_dir() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("git_branch") + .path(repo_dir.path()) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn test_changed_truncation_symbol() -> io::Result<()> { + test_truncate_length_with_config( + "1337_hello_world", + 15, + "1337_hello_worl", + "%", + "truncation_symbol = \"%\"", + ) + } + + #[test] + fn test_no_truncation_symbol() -> io::Result<()> { + test_truncate_length_with_config( + "1337_hello_world", + 15, + "1337_hello_worl", + "", + "truncation_symbol = \"\"", + ) + } + + #[test] + fn test_multi_char_truncation_symbol() -> io::Result<()> { + test_truncate_length_with_config( + "1337_hello_world", + 15, + "1337_hello_worl", + "a", + "truncation_symbol = \"apple\"", + ) + } + + #[test] + fn test_ascii_boundary_below() -> io::Result<()> { + test_truncate_length("1337_hello_world", 15, "1337_hello_worl", "…") + } + + #[test] + fn test_ascii_boundary_on() -> io::Result<()> { + test_truncate_length("1337_hello_world", 16, "1337_hello_world", "") + } + + #[test] + fn test_ascii_boundary_above() -> io::Result<()> { + test_truncate_length("1337_hello_world", 17, "1337_hello_world", "") + } + + #[test] + fn test_one() -> io::Result<()> { + test_truncate_length("1337_hello_world", 1, "1", "…") + } + + #[test] + fn test_zero() -> io::Result<()> { + test_truncate_length("1337_hello_world", 0, "1337_hello_world", "") + } + + #[test] + fn test_negative() -> io::Result<()> { + test_truncate_length("1337_hello_world", -1, "1337_hello_world", "") + } + + #[test] + fn test_hindi_truncation() -> io::Result<()> { + test_truncate_length("नमस्ते", 3, "नमस्", "…") + } + + #[test] + fn test_hindi_truncation2() -> io::Result<()> { + test_truncate_length("नमस्त", 3, "नमस्", "…") + } + + #[test] + fn test_japanese_truncation() -> io::Result<()> { + test_truncate_length("がんばってね", 4, "がんばっ", "…") + } + + #[test] + fn test_format_no_branch() -> io::Result<()> { + test_format("1337_hello_world", "no_branch", "", "no_branch") + } + + #[test] + fn test_format_just_branch_name() -> io::Result<()> { + test_format("1337_hello_world", "$branch", "", "1337_hello_world") + } + + #[test] + fn test_format_just_branch_name_color() -> io::Result<()> { + test_format( + "1337_hello_world", + "[$branch](bold blue)", + "", + Color::Blue.bold().paint("1337_hello_world").to_string(), + ) + } + + #[test] + fn test_format_mixed_colors() -> io::Result<()> { + test_format( + "1337_hello_world", + "branch: [$branch](bold blue) [THE COLORS](red) ", + "", + format!( + "branch: {} {} ", + Color::Blue.bold().paint("1337_hello_world").to_string(), + Color::Red.paint("THE COLORS").to_string() + ), + ) + } + + #[test] + fn test_format_symbol_style() -> io::Result<()> { + test_format( + "1337_hello_world", + "$symbol[$branch]($style)", + r#" + symbol = "git: " + style = "green" + "#, + format!( + "git: {}", + Color::Green.paint("1337_hello_world").to_string(), + ), + ) + } + + #[test] + fn test_works_with_unborn_default_branch() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + Command::new("git") + .args(&["init"]) + .current_dir(&repo_dir) + .output()?; + + Command::new("git") + .args(&["symbolic-ref", "HEAD", "refs/heads/main"]) + .current_dir(&repo_dir) + .output()?; + + let actual = ModuleRenderer::new("git_branch") + .path(&repo_dir.path()) + .collect(); + + let expected = Some(format!( + "on {} ", + Color::Purple.bold().paint(format!("\u{e0a0} {}", "main")), + )); + + assert_eq!(expected, actual); + repo_dir.close() + } + + // This test is not possible until we switch to `git status --porcelain` + // where we can mock the env for the specific git process. This is because + // git2 does not care about our mocking and when we set the real `GIT_DIR` + // variable it will interfere with the other tests. + // #[test] + // fn test_git_dir_env_variable() -> io::Result<()> {let repo_dir = + // tempfile::tempdir()?; + + // Command::new("git") + // .args(&["init"]) + // .current_dir(&repo_dir) + // .output()?; + + // // git2 does not care about our mocking + // std::env::set_var("GIT_DIR", repo_dir.path().join(".git")); + + // let actual = ModuleRenderer::new("git_branch").collect(); + + // std::env::remove_var("GIT_DIR"); + + // let expected = Some(format!( + // "on {} ", + // Color::Purple.bold().paint(format!("\u{e0a0} {}", "master")), + // )); + + // assert_eq!(expected, actual); + // repo_dir.close() + // } + + fn test_truncate_length( + branch_name: &str, + truncate_length: i64, + expected_name: &str, + truncation_symbol: &str, + ) -> io::Result<()> { + test_truncate_length_with_config( + branch_name, + truncate_length, + expected_name, + truncation_symbol, + "", + ) + } + + fn test_truncate_length_with_config( + branch_name: &str, + truncate_length: i64, + expected_name: &str, + truncation_symbol: &str, + config_options: &str, + ) -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + Command::new("git") + .args(&["checkout", "-b", branch_name]) + .current_dir(repo_dir.path()) + .output()?; + + let actual = ModuleRenderer::new("git_branch") + .config( + toml::from_str(&format!( + " + [git_branch] + truncation_length = {} + {} + ", + truncate_length, config_options + )) + .unwrap(), + ) + .path(repo_dir.path()) + .collect(); + + let expected = Some(format!( + "on {} ", + Color::Purple + .bold() + .paint(format!("\u{e0a0} {}{}", expected_name, truncation_symbol)), + )); + + assert_eq!(expected, actual); + repo_dir.close() + } + + fn test_format>( + branch_name: &str, + format: &str, + config_options: &str, + expected: T, + ) -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + Command::new("git") + .args(&["checkout", "-b", branch_name]) + .current_dir(repo_dir.path()) + .output()?; + + let actual = ModuleRenderer::new("git_branch") + .config( + toml::from_str(&format!( + r#" + [git_branch] + format = "{}" + {} + "#, + format, config_options + )) + .unwrap(), + ) + .path(repo_dir.path()) + .collect(); + + assert_eq!(Some(expected.into()), actual); + repo_dir.close() + } } diff --git a/src/modules/git_commit.rs b/src/modules/git_commit.rs index 865b3909..ae80ee97 100644 --- a/src/modules/git_commit.rs +++ b/src/modules/git_commit.rs @@ -62,3 +62,138 @@ pub fn id_to_hex_abbrev(bytes: &[u8], len: usize) -> String { .take(len) .collect() } + +#[cfg(test)] +mod tests { + use ansi_term::Color; + use std::process::Command; + use std::{io, str}; + + use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer}; + + #[test] + fn show_nothing_on_empty_dir() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("git_commit") + .path(&repo_dir.path()) + .collect(); + + let expected = None; + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn test_render_commit_hash() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + let mut git_output = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .current_dir(&repo_dir.path()) + .output()? + .stdout; + git_output.truncate(7); + let expected_hash = str::from_utf8(&git_output).unwrap(); + + let actual = ModuleRenderer::new("git_commit") + .config(toml::toml! { + [git_commit] + only_detached = false + }) + .path(&repo_dir.path()) + .collect(); + + let expected = Some(format!( + "{} ", + Color::Green + .bold() + .paint(format!("({})", expected_hash)) + .to_string() + )); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn test_render_commit_hash_len_override() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + let mut git_output = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .current_dir(&repo_dir.path()) + .output()? + .stdout; + git_output.truncate(14); + let expected_hash = str::from_utf8(&git_output).unwrap(); + + let actual = ModuleRenderer::new("git_commit") + .config(toml::toml! { + [git_commit] + only_detached = false + commit_hash_length = 14 + }) + .path(&repo_dir.path()) + .collect(); + + let expected = Some(format!( + "{} ", + Color::Green + .bold() + .paint(format!("({})", expected_hash)) + .to_string() + )); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn test_render_commit_hash_only_detached_on_branch() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + let actual = ModuleRenderer::new("git_commit") + .path(&repo_dir.path()) + .collect(); + + let expected = None; + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn test_render_commit_hash_only_detached_on_detached() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + Command::new("git") + .args(&["checkout", "@~1"]) + .current_dir(&repo_dir.path()) + .output()?; + + let mut git_output = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .current_dir(&repo_dir.path()) + .output()? + .stdout; + git_output.truncate(7); + let expected_hash = str::from_utf8(&git_output).unwrap(); + + let actual = ModuleRenderer::new("git_commit") + .path(&repo_dir.path()) + .collect(); + + let expected = Some(format!( + "{} ", + Color::Green + .bold() + .paint(format!("({})", expected_hash)) + .to_string() + )); + + assert_eq!(expected, actual); + repo_dir.close() + } +} diff --git a/src/modules/git_state.rs b/src/modules/git_state.rs index afdf9eef..9ef4dfe8 100644 --- a/src/modules/git_state.rs +++ b/src/modules/git_state.rs @@ -153,3 +153,195 @@ struct StateDescription<'a> { current: Option, total: Option, } + +#[cfg(test)] +mod tests { + use ansi_term::Color; + use std::ffi::OsStr; + use std::fs::OpenOptions; + use std::io::{self, Error, ErrorKind, Write}; + use std::path::Path; + use std::process::{Command, Stdio}; + + use crate::test::ModuleRenderer; + + #[test] + fn show_nothing_on_empty_dir() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("git_state") + .path(repo_dir.path()) + .collect(); + + let expected = None; + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_rebasing() -> io::Result<()> { + let repo_dir = create_repo_with_conflict()?; + let path = repo_dir.path(); + + run_git_cmd(&["rebase", "other-branch"], Some(path), false)?; + + let actual = ModuleRenderer::new("git_state").path(path).collect(); + + let expected = Some(format!("{} ", Color::Yellow.bold().paint("(REBASING 1/1)"))); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_merging() -> io::Result<()> { + let repo_dir = create_repo_with_conflict()?; + let path = repo_dir.path(); + + run_git_cmd(&["merge", "other-branch"], Some(path), false)?; + + let actual = ModuleRenderer::new("git_state").path(path).collect(); + + let expected = Some(format!("{} ", Color::Yellow.bold().paint("(MERGING)"))); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_cherry_picking() -> io::Result<()> { + let repo_dir = create_repo_with_conflict()?; + let path = repo_dir.path(); + + run_git_cmd(&["cherry-pick", "other-branch"], Some(path), false)?; + + let actual = ModuleRenderer::new("git_state").path(path).collect(); + + let expected = Some(format!( + "{} ", + Color::Yellow.bold().paint("(CHERRY-PICKING)") + )); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_bisecting() -> io::Result<()> { + let repo_dir = create_repo_with_conflict()?; + let path = repo_dir.path(); + + run_git_cmd(&["bisect", "start"], Some(path), false)?; + + let actual = ModuleRenderer::new("git_state").path(path).collect(); + + let expected = Some(format!("{} ", Color::Yellow.bold().paint("(BISECTING)"))); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_reverting() -> io::Result<()> { + let repo_dir = create_repo_with_conflict()?; + let path = repo_dir.path(); + + run_git_cmd(&["revert", "--no-commit", "HEAD~1"], Some(path), false)?; + + let actual = ModuleRenderer::new("git_state").path(path).collect(); + + let expected = Some(format!("{} ", Color::Yellow.bold().paint("(REVERTING)"))); + + assert_eq!(expected, actual); + repo_dir.close() + } + + fn run_git_cmd(args: A, dir: Option<&Path>, should_succeed: bool) -> io::Result<()> + where + A: IntoIterator, + S: AsRef, + { + let mut command = Command::new("git"); + command + .args(args) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()); + + if let Some(dir) = dir { + command.current_dir(dir); + } + + let status = command.status()?; + + if should_succeed && !status.success() { + Err(Error::from(ErrorKind::Other)) + } else { + Ok(()) + } + } + + fn create_repo_with_conflict() -> io::Result { + let repo_dir = tempfile::tempdir()?; + let path = repo_dir.path(); + let conflicted_file = repo_dir.path().join("the_file"); + + let write_file = |text: &str| { + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&conflicted_file)?; + write!(file, "{}", text) + }; + + // Initialize a new git repo + run_git_cmd( + &[ + "init", + "--quiet", + path.to_str().expect("Path was not UTF-8"), + ], + None, + true, + )?; + + // Set local author info + run_git_cmd( + &["config", "--local", "user.email", "starship@example.com"], + Some(path), + true, + )?; + run_git_cmd( + &["config", "--local", "user.name", "starship"], + Some(path), + true, + )?; + + // Write a file on master and commit it + write_file("Version A")?; + run_git_cmd(&["add", "the_file"], Some(path), true)?; + run_git_cmd(&["commit", "--message", "Commit A"], Some(path), true)?; + + // Switch to another branch, and commit a change to the file + run_git_cmd(&["checkout", "-b", "other-branch"], Some(path), true)?; + write_file("Version B")?; + run_git_cmd( + &["commit", "--all", "--message", "Commit B"], + Some(path), + true, + )?; + + // Switch back to master, and commit a third change to the file + run_git_cmd(&["checkout", "master"], Some(path), true)?; + write_file("Version C")?; + run_git_cmd( + &["commit", "--all", "--message", "Commit C"], + Some(path), + true, + )?; + + Ok(repo_dir) + } +} diff --git a/src/modules/git_status.rs b/src/modules/git_status.rs index 1cbf0da7..5e3ecb8a 100644 --- a/src/modules/git_status.rs +++ b/src/modules/git_status.rs @@ -371,3 +371,599 @@ fn format_count(format_str: &str, config_path: &str, count: usize) -> Option None, }) } + +#[cfg(test)] +mod tests { + use ansi_term::{ANSIStrings, Color}; + use std::fs::{self, File}; + use std::io; + use std::path::Path; + use std::process::Command; + + use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer}; + + /// Right after the calls to git the filesystem state may not have finished + /// updating yet causing some of the tests to fail. These barriers are placed + /// after each call to git. + /// This barrier is windows-specific though other operating systems may need it + /// in the future. + #[cfg(not(windows))] + fn barrier() {} + #[cfg(windows)] + fn barrier() { + std::thread::sleep(std::time::Duration::from_millis(500)); + } + + fn format_output(symbols: &str) -> Option { + Some(format!( + "{} ", + Color::Red.bold().paint(format!("[{}]", symbols)) + )) + } + + #[test] + fn show_nothing_on_empty_dir() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("git_status") + .path(repo_dir.path()) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_behind() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + behind(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(repo_dir.path()) + .collect(); + let expected = format_output("⇣"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_behind_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + behind(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + behind = "⇣$count" + }) + .path(repo_dir.path()) + .collect(); + let expected = format_output("⇣1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_ahead() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + File::create(repo_dir.path().join("readme.md"))?.sync_all()?; + ahead(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("⇡"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_ahead_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + File::create(repo_dir.path().join("readme.md"))?.sync_all()?; + ahead(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + ahead="⇡$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("⇡1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_diverged() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + diverge(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("⇕"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_diverged_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + diverge(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + diverged=r"⇕⇡$ahead_count⇣$behind_count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("⇕⇡1⇣1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_conflicted() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_conflict(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("="); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_conflicted_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_conflict(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + conflicted = "=$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("=1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_untracked_file() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_untracked(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("?"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_untracked_file_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_untracked(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + untracked = "?$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("?1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn doesnt_show_untracked_file_if_disabled() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_untracked(&repo_dir.path())?; + + Command::new("git") + .args(&["config", "status.showUntrackedFiles", "no"]) + .current_dir(repo_dir.path()) + .output()?; + barrier(); + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_stashed() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + barrier(); + + create_stash(&repo_dir.path())?; + + Command::new("git") + .args(&["reset", "--hard", "HEAD"]) + .current_dir(repo_dir.path()) + .output()?; + barrier(); + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("$"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_stashed_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + barrier(); + + create_stash(&repo_dir.path())?; + barrier(); + + Command::new("git") + .args(&["reset", "--hard", "HEAD"]) + .current_dir(repo_dir.path()) + .output()?; + barrier(); + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + stashed = r"\$$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("$1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_modified() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_modified(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("!"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_modified_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_modified(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + modified = "!$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("!1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_staged_file() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_staged(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("+"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_staged_file_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_staged(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + staged = "+[$count](green)" + }) + .path(&repo_dir.path()) + .collect(); + let expected = Some(format!( + "{} ", + ANSIStrings(&[ + Color::Red.bold().paint("[+"), + Color::Green.paint("1"), + Color::Red.bold().paint("]"), + ]) + )); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_renamed_file() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_renamed(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("»"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_renamed_file_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_renamed(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + renamed = "»$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("»1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_deleted_file() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_deleted(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .collect(); + let expected = format_output("✘"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn shows_deleted_file_with_count() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + + create_deleted(&repo_dir.path())?; + + let actual = ModuleRenderer::new("git_status") + .config(toml::toml! { + [git_status] + deleted = "✘$count" + }) + .path(&repo_dir.path()) + .collect(); + let expected = format_output("✘1"); + + assert_eq!(expected, actual); + repo_dir.close() + } + + // Whenever a file is manually renamed, git itself ('git status') does not treat such file as renamed, + // but as untracked instead. The following test checks if manually deleted and manually renamed + // files are tracked by git_status module in the same way 'git status' does. + #[test] + #[ignore] + fn ignore_manually_renamed() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::GIT)?; + File::create(repo_dir.path().join("a"))?.sync_all()?; + File::create(repo_dir.path().join("b"))?.sync_all()?; + Command::new("git") + .args(&["add", "--all"]) + .current_dir(&repo_dir.path()) + .output()?; + Command::new("git") + .args(&["commit", "-m", "add new files"]) + .current_dir(&repo_dir.path()) + .output()?; + + fs::remove_file(repo_dir.path().join("a"))?; + fs::rename(repo_dir.path().join("b"), repo_dir.path().join("c"))?; + barrier(); + + let actual = ModuleRenderer::new("git_status") + .path(&repo_dir.path()) + .config(toml::toml! { + [git_status] + ahead = "A" + deleted = "D" + untracked = "U" + renamed = "R" + }) + .collect(); + let expected = format_output("DUA"); + + assert_eq!(actual, expected); + + repo_dir.close() + } + + fn ahead(repo_dir: &Path) -> io::Result<()> { + File::create(repo_dir.join("readme.md"))?.sync_all()?; + + Command::new("git") + .args(&["commit", "-am", "Update readme"]) + .current_dir(&repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn behind(repo_dir: &Path) -> io::Result<()> { + Command::new("git") + .args(&["reset", "--hard", "HEAD^"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn diverge(repo_dir: &Path) -> io::Result<()> { + Command::new("git") + .args(&["reset", "--hard", "HEAD^"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + fs::write(repo_dir.join("Cargo.toml"), " ")?; + + Command::new("git") + .args(&["commit", "-am", "Update readme"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn create_conflict(repo_dir: &Path) -> io::Result<()> { + Command::new("git") + .args(&["reset", "--hard", "HEAD^"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + fs::write(repo_dir.join("readme.md"), "# goodbye")?; + + Command::new("git") + .args(&["add", "."]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Command::new("git") + .args(&["commit", "-m", "Change readme"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Command::new("git") + .args(&["pull", "--rebase"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn create_stash(repo_dir: &Path) -> io::Result<()> { + File::create(repo_dir.join("readme.md"))?.sync_all()?; + barrier(); + + Command::new("git") + .args(&["stash", "--all"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn create_untracked(repo_dir: &Path) -> io::Result<()> { + File::create(repo_dir.join("license"))?.sync_all()?; + + Ok(()) + } + + fn create_modified(repo_dir: &Path) -> io::Result<()> { + File::create(repo_dir.join("readme.md"))?.sync_all()?; + + Ok(()) + } + + fn create_staged(repo_dir: &Path) -> io::Result<()> { + File::create(repo_dir.join("license"))?.sync_all()?; + + Command::new("git") + .args(&["add", "."]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn create_renamed(repo_dir: &Path) -> io::Result<()> { + Command::new("git") + .args(&["mv", "readme.md", "readme.md.bak"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Command::new("git") + .args(&["add", "-A"]) + .current_dir(repo_dir) + .output()?; + barrier(); + + Ok(()) + } + + fn create_deleted(repo_dir: &Path) -> io::Result<()> { + fs::remove_file(repo_dir.join("readme.md"))?; + + Ok(()) + } +} diff --git a/src/modules/golang.rs b/src/modules/golang.rs index 96d03f8b..80ff1b79 100644 --- a/src/modules/golang.rs +++ b/src/modules/golang.rs @@ -86,7 +86,7 @@ fn format_go_version(go_stdout: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::{self, File}; use std::io; @@ -95,7 +95,7 @@ mod tests { fn folder_without_go_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -107,7 +107,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.go"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); @@ -119,7 +119,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("go.mod"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); @@ -131,7 +131,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("go.sum"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); @@ -144,7 +144,7 @@ mod tests { let godeps = dir.path().join("Godeps"); fs::create_dir_all(&godeps)?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); @@ -156,7 +156,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("glide.yaml"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); @@ -168,7 +168,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Gopkg.yml"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); @@ -179,7 +179,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Gopkg.lock"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); dir.close() @@ -189,7 +189,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join(".go-version"))?.sync_all()?; - let actual = render_module("golang", dir.path(), None); + let actual = ModuleRenderer::new("golang").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Cyan.bold().paint("🐹 v1.12.1"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/helm.rs b/src/modules/helm.rs index d14c8757..06cd85da 100644 --- a/src/modules/helm.rs +++ b/src/modules/helm.rs @@ -77,7 +77,7 @@ fn format_helm_version(helm_stdout: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -86,7 +86,7 @@ mod tests { fn folder_without_helm_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("helm", dir.path(), None); + let actual = ModuleRenderer::new("helm").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -98,7 +98,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("helmfile.yaml"))?.sync_all()?; - let actual = render_module("helm", dir.path(), None); + let actual = ModuleRenderer::new("helm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::White.bold().paint("⎈ v3.1.1"))); assert_eq!(expected, actual); @@ -110,7 +110,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Chart.yaml"))?.sync_all()?; - let actual = render_module("helm", dir.path(), None); + let actual = ModuleRenderer::new("helm").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::White.bold().paint("⎈ v3.1.1"))); assert_eq!(expected, actual); diff --git a/src/modules/hg_branch.rs b/src/modules/hg_branch.rs index 72b4bf60..7b301945 100644 --- a/src/modules/hg_branch.rs +++ b/src/modules/hg_branch.rs @@ -92,3 +92,228 @@ fn get_graphemes(text: &str, length: usize) -> String { fn graphemes_len(text: &str) -> usize { UnicodeSegmentation::graphemes(&text[..], true).count() } + +#[cfg(test)] +mod tests { + use ansi_term::{Color, Style}; + use std::fs; + use std::io; + use std::path::Path; + use std::process::Command; + + use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer}; + + enum Expect<'a> { + BranchName(&'a str), + Empty, + NoTruncation, + Symbol(&'a str), + Style(Style), + TruncationSymbol(&'a str), + } + + #[test] + fn show_nothing_on_empty_dir() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("hg_branch") + .path(repo_dir.path()) + .collect(); + + let expected = None; + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + #[ignore] + fn test_hg_get_branch_fails() -> io::Result<()> { + let tempdir = tempfile::tempdir()?; + + // Create a fake corrupted mercurial repo. + let hgdir = tempdir.path().join(".hg"); + fs::create_dir(&hgdir)?; + fs::write(&hgdir.join("requires"), "fake-corrupted-repo")?; + + expect_hg_branch_with_config( + tempdir.path(), + None, + &[Expect::BranchName(&"default"), Expect::NoTruncation], + )?; + tempdir.close() + } + + #[test] + #[ignore] + fn test_hg_get_branch_autodisabled() -> io::Result<()> { + let tempdir = tempfile::tempdir()?; + expect_hg_branch_with_config(tempdir.path(), None, &[Expect::Empty])?; + tempdir.close() + } + + #[test] + #[ignore] + fn test_hg_bookmark() -> io::Result<()> { + let tempdir = fixture_repo(FixtureProvider::HG)?; + let repo_dir = tempdir.path(); + run_hg(&["bookmark", "bookmark-101"], &repo_dir)?; + expect_hg_branch_with_config( + &repo_dir, + None, + &[Expect::BranchName(&"bookmark-101"), Expect::NoTruncation], + )?; + tempdir.close() + } + + #[test] + #[ignore] + fn test_default_truncation_symbol() -> io::Result<()> { + let tempdir = fixture_repo(FixtureProvider::HG)?; + let repo_dir = tempdir.path(); + run_hg(&["branch", "-f", "branch-name-101"], &repo_dir)?; + run_hg( + &[ + "commit", + "-m", + "empty commit 101", + "-u", + "fake user ", + ], + &repo_dir, + )?; + expect_hg_branch_with_config( + &repo_dir, + Some(toml::toml! { + [hg_branch] + truncation_length = 14 + }), + &[Expect::BranchName(&"branch-name-10")], + )?; + tempdir.close() + } + + #[test] + #[ignore] + fn test_configured_symbols() -> io::Result<()> { + let tempdir = fixture_repo(FixtureProvider::HG)?; + let repo_dir = tempdir.path(); + run_hg(&["branch", "-f", "branch-name-121"], &repo_dir)?; + run_hg( + &[ + "commit", + "-m", + "empty commit 121", + "-u", + "fake user ", + ], + &repo_dir, + )?; + expect_hg_branch_with_config( + &repo_dir, + Some(toml::toml! { + [hg_branch] + symbol = "B " + truncation_length = 14 + truncation_symbol = "%" + }), + &[ + Expect::BranchName(&"branch-name-12"), + Expect::Symbol(&"B"), + Expect::TruncationSymbol(&"%"), + ], + )?; + tempdir.close() + } + + #[test] + #[ignore] + fn test_configured_style() -> io::Result<()> { + let tempdir = fixture_repo(FixtureProvider::HG)?; + let repo_dir = tempdir.path(); + run_hg(&["branch", "-f", "branch-name-131"], &repo_dir)?; + run_hg( + &[ + "commit", + "-m", + "empty commit 131", + "-u", + "fake user ", + ], + &repo_dir, + )?; + + expect_hg_branch_with_config( + &repo_dir, + Some(toml::toml! { + [hg_branch] + style = "underline blue" + }), + &[ + Expect::BranchName(&"branch-name-131"), + Expect::Style(Color::Blue.underline()), + Expect::TruncationSymbol(&""), + ], + )?; + tempdir.close() + } + + fn expect_hg_branch_with_config( + repo_dir: &Path, + config: Option, + expectations: &[Expect], + ) -> io::Result<()> { + let actual = ModuleRenderer::new("hg_branch") + .path(repo_dir.to_str().unwrap()) + .config(config.unwrap_or_else(|| { + toml::toml! { + [hg_branch] + } + })) + .collect(); + + let mut expect_branch_name = "default"; + let mut expect_style = Color::Purple.bold(); + let mut expect_symbol = "\u{e0a0}"; + let mut expect_truncation_symbol = "…"; + + for expect in expectations { + match expect { + Expect::Empty => { + assert_eq!(None, actual); + return Ok(()); + } + Expect::Symbol(symbol) => { + expect_symbol = symbol; + } + Expect::TruncationSymbol(truncation_symbol) => { + expect_truncation_symbol = truncation_symbol; + } + Expect::NoTruncation => { + expect_truncation_symbol = ""; + } + Expect::BranchName(branch_name) => { + expect_branch_name = branch_name; + } + Expect::Style(style) => expect_style = *style, + } + } + + let expected = Some(format!( + "on {} ", + expect_style.paint(format!( + "{} {}{}", + expect_symbol, expect_branch_name, expect_truncation_symbol + )), + )); + assert_eq!(expected, actual); + Ok(()) + } + + fn run_hg(args: &[&str], repo_dir: &Path) -> io::Result<()> { + Command::new("hg") + .args(args) + .current_dir(&repo_dir) + .output()?; + Ok(()) + } +} diff --git a/src/modules/hostname.rs b/src/modules/hostname.rs index 2528e867..c9948220 100644 --- a/src/modules/hostname.rs +++ b/src/modules/hostname.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module}; use std::ffi::OsString; @@ -16,7 +14,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("hostname"); let config: HostnameConfig = HostnameConfig::try_load(module.config); - let ssh_connection = env::var("SSH_CONNECTION").ok(); + let ssh_connection = context.get_env("SSH_CONNECTION"); if config.ssh_only && ssh_connection.is_none() { return None; } @@ -66,3 +64,108 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::{Color, Style}; + use std::io; + + macro_rules! get_hostname { + () => { + if let Some(hostname) = gethostname::gethostname().into_string().ok() { + hostname + } else { + println!( + "hostname was not tested because gethostname failed! \ + This could be caused by your hostname containing invalid UTF." + ); + return Ok(()); + } + }; + } + + #[test] + fn ssh_only_false() -> io::Result<()> { + let hostname = get_hostname!(); + let actual = ModuleRenderer::new("hostname") + .config(toml::toml! { + [hostname] + ssh_only = false + trim_at = "" + }) + .collect(); + let expected = Some(format!("on {} ", style().paint(hostname))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn no_ssh() -> io::Result<()> { + let actual = ModuleRenderer::new("hostname") + .config(toml::toml! { + [hostname] + ssh_only = true + }) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn ssh() -> io::Result<()> { + let hostname = get_hostname!(); + let actual = ModuleRenderer::new("hostname") + .config(toml::toml! { + [hostname] + ssh_only = true + trim_at = "" + }) + .env("SSH_CONNECTION", "something") + .collect(); + let expected = Some(format!("on {} ", style().paint(hostname))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn no_trim_at() -> io::Result<()> { + let hostname = get_hostname!(); + let actual = ModuleRenderer::new("hostname") + .config(toml::toml! { + [hostname] + ssh_only = false + trim_at = "" + }) + .collect(); + let expected = Some(format!("on {} ", style().paint(hostname))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn trim_at() -> io::Result<()> { + let hostname = get_hostname!(); + let (remainder, trim_at) = hostname.split_at(1); + let actual = ModuleRenderer::new("hostname") + .config(toml::toml! { + [hostname] + ssh_only = false + trim_at = trim_at + }) + .collect(); + let expected = Some(format!("on {} ", style().paint(remainder))); + + assert_eq!(expected, actual); + Ok(()) + } + + fn style() -> Style { + Color::Green.bold().dimmed() + } +} diff --git a/src/modules/java.rs b/src/modules/java.rs index 4298108b..e06182e3 100644 --- a/src/modules/java.rs +++ b/src/modules/java.rs @@ -24,7 +24,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; } - let java_version = get_java_version()?; + let java_version = get_java_version(context)?; let mut module = context.new_module("java"); let config: JavaConfig = JavaConfig::try_load(module.config); @@ -57,10 +57,10 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } -fn get_java_version() -> Option { - let java_command = match std::env::var("JAVA_HOME") { - Ok(java_home) => format!("{}/bin/java", java_home), - Err(_) => String::from("java"), +fn get_java_version(context: &Context) -> Option { + let java_command = match context.get_env("JAVA_HOME") { + Some(java_home) => format!("{}/bin/java", java_home), + None => String::from("java"), }; let output = utils::exec_cmd(&java_command.as_str(), &["-Xinternalversion"])?; @@ -84,7 +84,7 @@ fn parse_java_version(java_version: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -156,7 +156,7 @@ mod tests { #[test] fn folder_without_java_file() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -166,7 +166,7 @@ mod tests { fn folder_with_java_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("Main.java"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -176,7 +176,7 @@ mod tests { fn folder_with_class_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("Main.class"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -186,7 +186,7 @@ mod tests { fn folder_with_gradle_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("build.gradle"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -196,7 +196,7 @@ mod tests { fn folder_with_jar_archive() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("test.jar"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -206,7 +206,7 @@ mod tests { fn folder_with_pom_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("pom.xml"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -216,7 +216,7 @@ mod tests { fn folder_with_gradle_kotlin_build_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("build.gradle.kts"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -226,7 +226,7 @@ mod tests { fn folder_with_sbt_build_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("build.gradle.kts"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() @@ -236,7 +236,7 @@ mod tests { fn folder_with_java_version_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join(".java-version"))?.sync_all()?; - let actual = render_module("java", dir.path(), None); + let actual = ModuleRenderer::new("java").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.dimmed().paint("☕ v13.0.2"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/jobs.rs b/src/modules/jobs.rs index b390b419..65a6ccc2 100644 --- a/src/modules/jobs.rs +++ b/src/modules/jobs.rs @@ -52,3 +52,67 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod test { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; + + #[test] + fn config_blank_job_0() -> io::Result<()> { + let actual = ModuleRenderer::new("jobs").jobs(0).collect(); + + let expected = None; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_blank_job_1() -> io::Result<()> { + let actual = ModuleRenderer::new("jobs").jobs(1).collect(); + + let expected = Some(format!("{} ", Color::Blue.bold().paint("✦"))); + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_blank_job_2() -> io::Result<()> { + let actual = ModuleRenderer::new("jobs").jobs(2).collect(); + + let expected = Some(format!("{} ", Color::Blue.bold().paint("✦2"))); + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_2_job_2() -> io::Result<()> { + let actual = ModuleRenderer::new("jobs") + .config(toml::toml! { + [jobs] + threshold = 2 + }) + .jobs(2) + .collect(); + + let expected = Some(format!("{} ", Color::Blue.bold().paint("✦"))); + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_2_job_3() -> io::Result<()> { + let actual = ModuleRenderer::new("jobs") + .config(toml::toml! { + [jobs] + threshold = 2 + }) + .jobs(3) + .collect(); + + let expected = Some(format!("{} ", Color::Blue.bold().paint("✦3"))); + assert_eq!(expected, actual); + Ok(()) + } +} diff --git a/src/modules/julia.rs b/src/modules/julia.rs index 7913f5af..11bfe744 100644 --- a/src/modules/julia.rs +++ b/src/modules/julia.rs @@ -72,7 +72,7 @@ fn format_julia_version(julia_stdout: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -81,7 +81,7 @@ mod tests { fn folder_without_julia_file() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("julia", dir.path(), None); + let actual = ModuleRenderer::new("julia").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -93,7 +93,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("hello.jl"))?.sync_all()?; - let actual = render_module("julia", dir.path(), None); + let actual = ModuleRenderer::new("julia").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Purple.bold().paint("ஃ v1.4.0"))); assert_eq!(expected, actual); @@ -105,7 +105,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Project.toml"))?.sync_all()?; - let actual = render_module("julia", dir.path(), None); + let actual = ModuleRenderer::new("julia").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Purple.bold().paint("ஃ v1.4.0"))); assert_eq!(expected, actual); @@ -117,7 +117,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Manifest.toml"))?.sync_all()?; - let actual = render_module("julia", dir.path(), None); + let actual = ModuleRenderer::new("julia").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Purple.bold().paint("ஃ v1.4.0"))); assert_eq!(expected, actual); diff --git a/src/modules/kubernetes.rs b/src/modules/kubernetes.rs index 7a770bfc..a10a33aa 100644 --- a/src/modules/kubernetes.rs +++ b/src/modules/kubernetes.rs @@ -42,11 +42,11 @@ fn parse_kubectl_file(filename: &path::PathBuf) -> Option<(String, String)> { } pub fn module<'a>(context: &'a Context) -> Option> { - let kube_cfg = match env::var("KUBECONFIG") { - Ok(paths) => env::split_paths(&paths) + let kube_cfg = match context.get_env("KUBECONFIG") { + Some(paths) => env::split_paths(&paths) .filter_map(|filename| parse_kubectl_file(&filename)) .next(), - Err(_) => { + None => { let filename = dirs_next::home_dir()?.join(".kube").join("config"); parse_kubectl_file(&filename) } diff --git a/src/modules/nim.rs b/src/modules/nim.rs index 742abc48..0b0d7cca 100644 --- a/src/modules/nim.rs +++ b/src/modules/nim.rs @@ -68,7 +68,7 @@ fn parse_nim_version(version_cmd_output: &str) -> Option<&str> { #[cfg(test)] mod tests { use super::parse_nim_version; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -100,7 +100,7 @@ mod tests { fn folder_without_nim() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("nim.txt"))?.sync_all()?; - let actual = render_module("nim", dir.path(), None); + let actual = ModuleRenderer::new("nim").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -110,7 +110,7 @@ mod tests { fn folder_with_nimble_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.nimble"))?.sync_all()?; - let actual = render_module("nim", dir.path(), None); + let actual = ModuleRenderer::new("nim").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("👑 v1.2.0"))); assert_eq!(expected, actual); dir.close() @@ -120,7 +120,7 @@ mod tests { fn folder_with_nim_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.nim"))?.sync_all()?; - let actual = render_module("nim", dir.path(), None); + let actual = ModuleRenderer::new("nim").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("👑 v1.2.0"))); assert_eq!(expected, actual); dir.close() @@ -130,7 +130,7 @@ mod tests { fn folder_with_nims_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.nims"))?.sync_all()?; - let actual = render_module("nim", dir.path(), None); + let actual = ModuleRenderer::new("nim").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("👑 v1.2.0"))); assert_eq!(expected, actual); dir.close() @@ -140,7 +140,7 @@ mod tests { fn folder_with_cfg_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("cfg.nim"))?.sync_all()?; - let actual = render_module("nim", dir.path(), None); + let actual = ModuleRenderer::new("nim").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("👑 v1.2.0"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/nix_shell.rs b/src/modules/nix_shell.rs index 78537012..ab5fba08 100644 --- a/src/modules/nix_shell.rs +++ b/src/modules/nix_shell.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module, RootModuleConfig}; use crate::configs::nix_shell::NixShellConfig; @@ -23,8 +21,8 @@ pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("nix_shell"); let config: NixShellConfig = NixShellConfig::try_load(module.config); - let shell_name = env::var("name").ok(); - let shell_type = env::var("IN_NIX_SHELL").ok()?; + let shell_name = context.get_env("name"); + let shell_type = context.get_env("IN_NIX_SHELL")?; let shell_type_format = match shell_type.as_ref() { "impure" => config.impure_msg, "pure" => config.pure_msg, @@ -61,3 +59,82 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; + + #[test] + fn no_env_variables() -> io::Result<()> { + let actual = ModuleRenderer::new("nix_shell").collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn invalid_env_variables() -> io::Result<()> { + let actual = ModuleRenderer::new("nix_shell") + .env("IN_NIX_SHELL", "something_wrong") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn pure_shell() -> io::Result<()> { + let actual = ModuleRenderer::new("nix_shell") + .env("IN_NIX_SHELL", "pure") + .collect(); + let expected = Some(format!("via {} ", Color::Blue.bold().paint("❄️ pure"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn impure_shell() -> io::Result<()> { + let actual = ModuleRenderer::new("nix_shell") + .env("IN_NIX_SHELL", "impure") + .collect(); + let expected = Some(format!("via {} ", Color::Blue.bold().paint("❄️ impure"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn pure_shell_name() -> io::Result<()> { + let actual = ModuleRenderer::new("nix_shell") + .env("IN_NIX_SHELL", "pure") + .env("name", "starship") + .collect(); + let expected = Some(format!( + "via {} ", + Color::Blue.bold().paint("❄️ pure (starship)") + )); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn impure_shell_name() -> io::Result<()> { + let actual = ModuleRenderer::new("nix_shell") + .env("IN_NIX_SHELL", "impure") + .env("name", "starship") + .collect(); + let expected = Some(format!( + "via {} ", + Color::Blue.bold().paint("❄️ impure (starship)") + )); + + assert_eq!(expected, actual); + Ok(()) + } +} diff --git a/src/modules/nodejs.rs b/src/modules/nodejs.rs index 9f3dcfc8..1a3d087b 100644 --- a/src/modules/nodejs.rs +++ b/src/modules/nodejs.rs @@ -61,7 +61,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::{self, File}; use std::io; @@ -69,7 +69,7 @@ mod tests { #[test] fn folder_without_node_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -80,7 +80,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("package.json"))?.sync_all()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() @@ -93,7 +93,7 @@ mod tests { let esy_lock = dir.path().join("esy.lock"); fs::create_dir_all(&esy_lock)?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -104,7 +104,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join(".node-version"))?.sync_all()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() @@ -115,7 +115,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("index.js"))?.sync_all()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() @@ -126,7 +126,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("index.mjs"))?.sync_all()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() @@ -137,7 +137,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("index.cjs"))?.sync_all()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() @@ -148,7 +148,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("index.ts"))?.sync_all()?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() @@ -160,7 +160,7 @@ mod tests { let node_modules = dir.path().join("node_modules"); fs::create_dir_all(&node_modules)?; - let actual = render_module("nodejs", dir.path(), None); + let actual = ModuleRenderer::new("nodejs").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Green.bold().paint("⬢ v12.0.0"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/ocaml.rs b/src/modules/ocaml.rs index 6b827332..523f8bd5 100644 --- a/src/modules/ocaml.rs +++ b/src/modules/ocaml.rs @@ -69,7 +69,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::{self, File}; use std::io; @@ -77,7 +77,7 @@ mod tests { #[test] fn folder_without_ocaml_file() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -88,7 +88,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.opam"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -99,7 +99,7 @@ mod tests { let dir = tempfile::tempdir()?; fs::create_dir_all(dir.path().join("_opam"))?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -114,7 +114,7 @@ mod tests { dir.path().join("package.lock"), "{\"dependencies\": {\"ocaml\": \"4.8.1000\"}}", )?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.08.1"))); assert_eq!(expected, actual); dir.close() @@ -125,7 +125,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("dune"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -136,7 +136,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("dune-project"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -147,7 +147,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("jbuild"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -158,7 +158,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("jbuild-ignore"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -169,7 +169,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join(".merlin"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -180,7 +180,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.ml"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -191,7 +191,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.mli"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -202,7 +202,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.re"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() @@ -213,7 +213,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.rei"))?.sync_all()?; - let actual = render_module("ocaml", dir.path(), None); + let actual = ModuleRenderer::new("ocaml").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐫 v4.10.0"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/package.rs b/src/modules/package.rs index 5c56ac12..9b3dad29 100644 --- a/src/modules/package.rs +++ b/src/modules/package.rs @@ -198,7 +198,7 @@ fn format_version(version: &str) -> String { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -741,12 +741,15 @@ end"; contains: Option<&str>, config: Option, ) -> io::Result<()> { - let starship_config = Some(config.unwrap_or(toml::toml! { + let starship_config = config.unwrap_or(toml::toml! { [package] disabled = false - })); + }); - let actual = render_module("package", project_dir.path(), starship_config); + let actual = ModuleRenderer::new("package") + .path(project_dir.path()) + .config(starship_config) + .collect(); let text = String::from(contains.unwrap_or("")); let expected = Some(format!( "is {} ", diff --git a/src/modules/perl.rs b/src/modules/perl.rs index 5130930d..a3b33f1b 100644 --- a/src/modules/perl.rs +++ b/src/modules/perl.rs @@ -64,7 +64,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -73,7 +73,7 @@ mod tests { fn folder_without_perl_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -85,7 +85,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Makefile.PL"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -100,7 +100,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Build.PL"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -115,7 +115,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("cpanfile"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -130,7 +130,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("cpanfile.snapshot"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -145,7 +145,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("META.json"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -160,7 +160,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("META.yml"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -175,7 +175,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join(".perl-version"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -190,7 +190,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.pl"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -205,7 +205,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.pm"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -220,7 +220,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.pod"))?.sync_all()?; - let actual = render_module("perl", dir.path(), None); + let actual = ModuleRenderer::new("perl").path(dir.path()).collect(); let expected = Some(format!( "via {} ", diff --git a/src/modules/php.rs b/src/modules/php.rs index f827494c..ae4bdf06 100644 --- a/src/modules/php.rs +++ b/src/modules/php.rs @@ -72,7 +72,7 @@ fn format_php_version(php_version: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -87,7 +87,7 @@ mod tests { fn folder_without_php_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("php", dir.path(), None); + let actual = ModuleRenderer::new("php").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -99,7 +99,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("composer.json"))?.sync_all()?; - let actual = render_module("php", dir.path(), None); + let actual = ModuleRenderer::new("php").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -114,7 +114,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join(".php-version"))?.sync_all()?; - let actual = render_module("php", dir.path(), None); + let actual = ModuleRenderer::new("php").path(dir.path()).collect(); let expected = Some(format!( "via {} ", @@ -129,7 +129,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.php"))?.sync_all()?; - let actual = render_module("php", dir.path(), None); + let actual = ModuleRenderer::new("php").path(dir.path()).collect(); let expected = Some(format!( "via {} ", diff --git a/src/modules/purescript.rs b/src/modules/purescript.rs index 16531df3..49dabc58 100644 --- a/src/modules/purescript.rs +++ b/src/modules/purescript.rs @@ -55,7 +55,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -63,7 +63,7 @@ mod tests { #[test] fn folder_without_purescript_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("purescript", dir.path(), None); + let actual = ModuleRenderer::new("purescript").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -74,7 +74,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Main.purs"))?.sync_all()?; - let actual = render_module("purescript", dir.path(), None); + let actual = ModuleRenderer::new("purescript").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::White.bold().paint("<=> v0.13.5"))); assert_eq!(expected, actual); dir.close() @@ -85,7 +85,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("spago.dhall"))?.sync_all()?; - let actual = render_module("purescript", dir.path(), None); + let actual = ModuleRenderer::new("purescript").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::White.bold().paint("<=> v0.13.5"))); assert_eq!(expected, actual); dir.close() diff --git a/src/modules/python.rs b/src/modules/python.rs index b993638f..8eb175ac 100644 --- a/src/modules/python.rs +++ b/src/modules/python.rs @@ -1,4 +1,3 @@ -use std::env; use std::path::Path; use super::{Context, Module, RootModuleConfig}; @@ -36,7 +35,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { } }; - let is_venv = env::var("VIRTUAL_ENV").ok().is_some(); + let is_venv = context.get_env("VIRTUAL_ENV").is_some(); if !is_py_project && !is_venv { return None; @@ -48,7 +47,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { let version = get_python_version(&config.python_binary)?; format_python_version(&version) }; - let virtual_env = get_python_virtual_env(); + let virtual_env = get_python_virtual_env(context); let parsed = StringFormatter::new(config.format).and_then(|formatter| { formatter @@ -102,8 +101,8 @@ fn format_python_version(python_stdout: &str) -> String { ) } -fn get_python_virtual_env() -> Option { - env::var("VIRTUAL_ENV").ok().and_then(|venv| { +fn get_python_virtual_env(context: &Context) -> Option { + context.get_env("VIRTUAL_ENV").and_then(|venv| { Path::new(&venv) .file_name() .map(|filename| String::from(filename.to_str().unwrap_or(""))) @@ -113,7 +112,7 @@ fn get_python_virtual_env() -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -133,7 +132,7 @@ mod tests { #[test] fn folder_without_python_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("python", dir.path(), None); + let actual = ModuleRenderer::new("python").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -230,7 +229,10 @@ mod tests { [python] scan_for_pyfiles = false }; - let actual = render_module("python", dir.path(), Some(config)); + let actual = ModuleRenderer::new("python") + .path(dir.path()) + .config(config) + .collect(); assert_eq!(expected, actual); dir.close() } @@ -258,19 +260,68 @@ mod tests { dir.close() } + #[test] + fn with_virtual_env() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.py"))?.sync_all()?; + let actual = ModuleRenderer::new("python") + .path(dir.path()) + .env("VIRTUAL_ENV", "/foo/bar/my_venv") + .collect(); + + let expected = Some(format!( + "via {} ", + Color::Yellow.bold().paint("🐍 v2.7.17 (my_venv)") + )); + + assert_eq!(actual, expected); + dir.close() + } + + #[test] + fn with_active_venv() -> io::Result<()> { + let dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("python") + .path(dir.path()) + .env("VIRTUAL_ENV", "/foo/bar/my_venv") + .collect(); + + let expected = Some(format!( + "via {} ", + Color::Yellow.bold().paint("🐍 v2.7.17 (my_venv)") + )); + + assert_eq!(actual, expected); + dir.close() + } + fn check_python2_renders(dir: &tempfile::TempDir, starship_config: Option) { - let actual = render_module("python", dir.path(), starship_config); + let config = starship_config.unwrap_or(toml::toml! { + [python] + python_binary = "python" + }); + + let actual = ModuleRenderer::new("python") + .path(dir.path()) + .config(config) + .collect(); + let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐍 v2.7.17"))); assert_eq!(expected, actual); } fn check_python3_renders(dir: &tempfile::TempDir, starship_config: Option) { - let config = Some(starship_config.unwrap_or(toml::toml! { + let config = starship_config.unwrap_or(toml::toml! { [python] python_binary = "python3" - })); + }); + + let actual = ModuleRenderer::new("python") + .path(dir.path()) + .config(config) + .collect(); - let actual = render_module("python", dir.path(), config); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("🐍 v3.8.0"))); assert_eq!(expected, actual); } diff --git a/src/modules/ruby.rs b/src/modules/ruby.rs index 38f8de7f..477c4952 100644 --- a/src/modules/ruby.rs +++ b/src/modules/ruby.rs @@ -72,7 +72,7 @@ fn format_ruby_version(ruby_version: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -81,7 +81,7 @@ mod tests { fn folder_without_ruby_files() -> io::Result<()> { let dir = tempfile::tempdir()?; - let actual = render_module("ruby", dir.path(), None); + let actual = ModuleRenderer::new("ruby").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); @@ -93,7 +93,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("Gemfile"))?.sync_all()?; - let actual = render_module("ruby", dir.path(), None); + let actual = ModuleRenderer::new("ruby").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.bold().paint("💎 v2.5.1"))); assert_eq!(expected, actual); @@ -105,7 +105,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join(".ruby-version"))?.sync_all()?; - let actual = render_module("ruby", dir.path(), None); + let actual = ModuleRenderer::new("ruby").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.bold().paint("💎 v2.5.1"))); assert_eq!(expected, actual); @@ -117,7 +117,7 @@ mod tests { let dir = tempfile::tempdir()?; File::create(dir.path().join("any.rb"))?.sync_all()?; - let actual = render_module("ruby", dir.path(), None); + let actual = ModuleRenderer::new("ruby").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Red.bold().paint("💎 v2.5.1"))); assert_eq!(expected, actual); diff --git a/src/modules/rust.rs b/src/modules/rust.rs index db0822ed..c72be0ec 100644 --- a/src/modules/rust.rs +++ b/src/modules/rust.rs @@ -1,6 +1,6 @@ +use std::fs; use std::path::Path; use std::process::{Command, Output}; -use std::{env, fs}; use super::{Context, Module, RootModuleConfig}; @@ -72,7 +72,7 @@ fn get_module_version(context: &Context) -> Option { // - `rustup show` // - `rustup show active-toolchain` // - `rustup which` - let module_version = if let Some(toolchain) = env_rustup_toolchain() + let module_version = if let Some(toolchain) = env_rustup_toolchain(context) .or_else(|| execute_rustup_override_list(&context.current_dir)) .or_else(|| find_rust_toolchain_file(&context)) { @@ -93,8 +93,8 @@ fn get_module_version(context: &Context) -> Option { Some(module_version) } -fn env_rustup_toolchain() -> Option { - let val = env::var("RUSTUP_TOOLCHAIN").ok()?; +fn env_rustup_toolchain(context: &Context) -> Option { + let val = context.get_env("RUSTUP_TOOLCHAIN")?; Some(val.trim().to_owned()) } diff --git a/src/modules/shlvl.rs b/src/modules/shlvl.rs index 19fb735a..2118c801 100644 --- a/src/modules/shlvl.rs +++ b/src/modules/shlvl.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module}; use crate::config::RootModuleConfig; @@ -9,7 +7,7 @@ use crate::formatter::StringFormatter; const SHLVL_ENV_VAR: &str = "SHLVL"; pub fn module<'a>(context: &'a Context) -> Option> { - let shlvl = get_shlvl_value()?; + let shlvl = context.get_env(SHLVL_ENV_VAR)?.parse::().ok()?; let mut module = context.new_module("shlvl"); let config: ShLvlConfig = ShLvlConfig::try_load(module.config); @@ -47,7 +45,155 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } +#[cfg(test)] +mod tests { + use ansi_term::{Color, Style}; + use std::io; -fn get_shlvl_value() -> Option { - env::var(SHLVL_ENV_VAR).ok()?.parse::().ok() + use crate::test::ModuleRenderer; + + use super::SHLVL_ENV_VAR; + + fn style() -> Style { + // default style + Color::Yellow.bold() + } + + #[test] + fn empty_config() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + }) + .env(SHLVL_ENV_VAR, "2") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn enabled() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .collect(); + let expected = Some(format!("{} ", style().paint("↕️ 2"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn no_level() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + disabled = false + }) + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn enabled_config_level_1() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + disabled = false + }) + .env(SHLVL_ENV_VAR, "1") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn lower_threshold() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + threshold = 1 + disabled = false + }) + .env(SHLVL_ENV_VAR, "1") + .collect(); + let expected = Some(format!("{} ", style().paint("↕️ 1"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn higher_threshold() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + threshold = 3 + disabled = false + }) + .env(SHLVL_ENV_VAR, "1") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn custom_style() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + style = "Red Underline" + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .collect(); + let expected = Some(format!("{} ", Color::Red.underline().paint("↕️ 2"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn custom_symbol() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + symbol = "shlvl is " + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .collect(); + let expected = Some(format!("{} ", style().paint("shlvl is 2"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn formatting() -> io::Result<()> { + let actual = ModuleRenderer::new("shlvl") + .config(toml::toml! { + [shlvl] + format = "$symbol going down [$shlvl]($style) GOING UP " + disabled = false + }) + .env(SHLVL_ENV_VAR, "2") + .collect(); + let expected = Some(format!("↕️ going down {} GOING UP ", style().paint("2"))); + + assert_eq!(expected, actual); + Ok(()) + } } diff --git a/src/modules/singularity.rs b/src/modules/singularity.rs index ac14ac86..c408ddb2 100644 --- a/src/modules/singularity.rs +++ b/src/modules/singularity.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module, RootModuleConfig}; use crate::configs::singularity::SingularityConfig; @@ -9,7 +7,7 @@ use crate::formatter::StringFormatter; /// /// Will display the Singularity image if `$SINGULARITY_NAME` is set. pub fn module<'a>(context: &'a Context) -> Option> { - let singularity_env = env::var("SINGULARITY_NAME").ok(); + let singularity_env = context.get_env("SINGULARITY_NAME"); singularity_env.as_ref()?; let mut module = context.new_module("singularity"); @@ -42,3 +40,33 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; + + #[test] + fn no_env_set() -> io::Result<()> { + let actual = ModuleRenderer::new("singularity").collect(); + + let expected = None; + assert_eq!(expected, actual); + Ok(()) + } + #[test] + fn env_set() -> io::Result<()> { + let actual = ModuleRenderer::new("singularity") + .env("SINGULARITY_NAME", "centos.img") + .collect(); + + let expected = Some(format!( + "{} ", + Color::Blue.bold().dimmed().paint("[centos.img]") + )); + + assert_eq!(expected, actual); + Ok(()) + } +} diff --git a/src/modules/swift.rs b/src/modules/swift.rs index b83bce1d..32431179 100644 --- a/src/modules/swift.rs +++ b/src/modules/swift.rs @@ -65,7 +65,7 @@ fn parse_swift_version(swift_version: &str) -> Option { #[cfg(test)] mod tests { use super::parse_swift_version; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -80,7 +80,7 @@ mod tests { fn folder_without_swift_files() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("swift.txt"))?.sync_all()?; - let actual = render_module("swift", dir.path(), None); + let actual = ModuleRenderer::new("swift").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -90,7 +90,7 @@ mod tests { fn folder_with_package_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("Package.swift"))?.sync_all()?; - let actual = render_module("swift", dir.path(), None); + let actual = ModuleRenderer::new("swift").path(dir.path()).collect(); let expected = Some(format!( "via {} ", Color::Fixed(202).bold().paint("🐦 v5.2.2") @@ -103,7 +103,7 @@ mod tests { fn folder_with_swift_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.swift"))?.sync_all()?; - let actual = render_module("swift", dir.path(), None); + let actual = ModuleRenderer::new("swift").path(dir.path()).collect(); let expected = Some(format!( "via {} ", Color::Fixed(202).bold().paint("🐦 v5.2.2") diff --git a/src/modules/terraform.rs b/src/modules/terraform.rs index d708e563..e12aa3a9 100644 --- a/src/modules/terraform.rs +++ b/src/modules/terraform.rs @@ -4,7 +4,6 @@ use crate::configs::terraform::TerraformConfig; use crate::formatter::StringFormatter; use crate::utils; -use std::env; use std::io; use std::path::PathBuf; @@ -42,7 +41,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { &utils::exec_cmd("terraform", &["version"])?.stdout.as_str(), ) .map(Ok), - "workspace" => get_terraform_workspace(&context.current_dir).map(Ok), + "workspace" => get_terraform_workspace(context).map(Ok), _ => None, }) .parse(None) @@ -60,17 +59,17 @@ pub fn module<'a>(context: &'a Context) -> Option> { } // Determines the currently selected workspace (see https://github.com/hashicorp/terraform/blob/master/command/meta.go for the original implementation) -fn get_terraform_workspace(cwd: &PathBuf) -> Option { +fn get_terraform_workspace(context: &Context) -> Option { // Workspace can be explicitly overwritten by an env var - let workspace_override = env::var("TF_WORKSPACE"); - if workspace_override.is_ok() { - return workspace_override.ok(); + let workspace_override = context.get_env("TF_WORKSPACE"); + if workspace_override.is_some() { + return workspace_override; } // Data directory containing current workspace can be overwritten by an env var - let datadir = match env::var("TF_DATA_DIR") { - Ok(s) => PathBuf::from(s), - Err(_) => cwd.join(".terraform"), + let datadir = match context.get_env("TF_DATA_DIR") { + Some(s) => PathBuf::from(s), + None => context.current_dir.join(".terraform"), }; match utils::read_file(datadir.join("environment")) { Err(ref e) if e.kind() == io::ErrorKind::NotFound => Some("default".to_string()), @@ -97,10 +96,10 @@ fn format_terraform_version(version: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::{self, File}; - use std::io::Write; + use std::io::{self, Write}; #[test] fn test_format_terraform_version_release() { @@ -149,21 +148,20 @@ is 0.12.14. You can update by downloading from www.terraform.io/downloads.html let tf_dir = dir.path().join(".terraform"); fs::create_dir(&tf_dir)?; - let actual = render_module( - "terraform", - dir.path(), - Some(toml::toml! { + let actual = ModuleRenderer::new("terraform") + .path(dir.path()) + .config(toml::toml! { [terraform] format = "via [$symbol$version$workspace]($style) " - }), - ); + }) + .collect(); let expected = Some(format!( "via {} ", Color::Fixed(105).bold().paint("💠 v0.12.14 default") )); assert_eq!(expected, actual); - Ok(()) + dir.close() } #[test] @@ -175,20 +173,122 @@ is 0.12.14. You can update by downloading from www.terraform.io/downloads.html file.write_all(b"development")?; file.sync_all()?; - let actual = render_module( - "terraform", - dir.path(), - Some(toml::toml! { + let actual = ModuleRenderer::new("terraform") + .path(dir.path()) + .config(toml::toml! { [terraform] format = "via [$symbol$version$workspace]($style) " - }), - ); + }) + .collect(); let expected = Some(format!( "via {} ", Color::Fixed(105).bold().paint("💠 v0.12.14 development") )); assert_eq!(expected, actual); - Ok(()) + dir.close() + } + + #[test] + fn folder_without_dotterraform() -> io::Result<()> { + let dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("terraform").path(dir.path()).collect(); + let expected = None; + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_tf_file() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.tf"))?; + + let actual = ModuleRenderer::new("terraform").path(dir.path()).collect(); + let expected = Some(format!( + "via {} ", + Color::Fixed(105).bold().paint("💠 default") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_workspace_override() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.tf"))?; + + let actual = ModuleRenderer::new("terraform") + .path(dir.path()) + .env("TF_WORKSPACE", "development") + .collect(); + let expected = Some(format!( + "via {} ", + Color::Fixed(105).bold().paint("💠 development") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_datadir_override() -> io::Result<()> { + let dir = tempfile::tempdir()?; + File::create(dir.path().join("main.tf"))?; + + let datadir = tempfile::tempdir()?; + let mut file = File::create(datadir.path().join("environment"))?; + file.write_all(b"development")?; + file.sync_all()?; + + let actual = ModuleRenderer::new("terraform") + .path(dir.path()) + .env("TF_DATA_DIR", datadir.path().to_str().unwrap()) + .collect(); + let expected = Some(format!( + "via {} ", + Color::Fixed(105).bold().paint("💠 development") + )); + + assert_eq!(expected, actual); + dir.close()?; + datadir.close() + } + + #[test] + fn folder_with_dotterraform_no_environment() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let tf_dir = dir.path().join(".terraform"); + fs::create_dir(&tf_dir)?; + + let actual = ModuleRenderer::new("terraform").path(dir.path()).collect(); + let expected = Some(format!( + "via {} ", + Color::Fixed(105).bold().paint("💠 default") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn folder_with_dotterraform_with_environment() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let tf_dir = dir.path().join(".terraform"); + fs::create_dir(&tf_dir)?; + let mut file = File::create(tf_dir.join("environment"))?; + file.write_all(b"development")?; + file.sync_all()?; + + let actual = ModuleRenderer::new("terraform").path(dir.path()).collect(); + let expected = Some(format!( + "via {} ", + Color::Fixed(105).bold().paint("💠 development") + )); + + assert_eq!(expected, actual); + dir.close() } } diff --git a/src/modules/time.rs b/src/modules/time.rs index 3bd7ac15..f0228b68 100644 --- a/src/modules/time.rs +++ b/src/modules/time.rs @@ -151,7 +151,9 @@ tests become extra important */ #[cfg(test)] mod tests { use super::*; + use crate::test::ModuleRenderer; use chrono::offset::TimeZone; + use std::io; const FMT_12: &str = "%r"; const FMT_24: &str = "%T"; @@ -464,4 +466,50 @@ mod tests { assert_eq!(is_inside_time_range(time_now2, time_start, time_end), false); assert_eq!(is_inside_time_range(time_now3, time_start, time_end), true); } + + #[test] + fn config_enabled() -> io::Result<()> { + let actual = ModuleRenderer::new("time") + .config(toml::toml! { + [time] + disabled = false + }) + .collect(); + + // We can't test what it actually is...but we can assert that it is something + assert!(actual.is_some()); + Ok(()) + } + + #[test] + fn config_blank() -> io::Result<()> { + let actual = ModuleRenderer::new("time").collect(); + + let expected = None; + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn config_check_prefix_and_suffix() -> io::Result<()> { + let actual = ModuleRenderer::new("time") + .config(toml::toml! { + [time] + disabled = false + format = "at [\\[$time\\]]($style) " + time_format = "%T" + }) + .collect() + .unwrap(); + + // This is the prefix with "at ", the color code, then the prefix char [ + let col_prefix = format!("at {}{}[", '\u{1b}', "[1;33m"); + + // This is the suffix with suffix char ']', then color codes, then a space + let col_suffix = format!("]{}{} ", '\u{1b}', "[0m"); + + assert!(actual.starts_with(&col_prefix)); + assert!(actual.ends_with(&col_suffix)); + Ok(()) + } } diff --git a/src/modules/username.rs b/src/modules/username.rs index 831fbe3d..ca05c3ae 100644 --- a/src/modules/username.rs +++ b/src/modules/username.rs @@ -1,5 +1,3 @@ -use std::env; - use super::{Context, Module, RootModuleConfig}; use crate::configs::username::UsernameConfig; @@ -13,9 +11,9 @@ use crate::utils; /// - The current user is root (UID = 0) /// - The user is currently connected as an SSH session (`$SSH_CONNECTION`) pub fn module<'a>(context: &'a Context) -> Option> { - let user = env::var("USER").ok(); - let logname = env::var("LOGNAME").ok(); - let ssh_connection = env::var("SSH_CONNECTION").ok(); + let user = context.get_env("USER"); + let logname = context.get_env("LOGNAME"); + let ssh_connection = context.get_env("SSH_CONNECTION"); const ROOT_UID: Option = Some(0); let user_uid = get_uid(); @@ -64,3 +62,85 @@ fn get_uid() -> Option { .parse::() .ok() } + +#[cfg(test)] +mod tests { + use crate::test::ModuleRenderer; + use ansi_term::Color; + use std::io; + + // TODO: Add tests for if root user (UID == 0) + // Requires mocking + + #[test] + fn no_env_variables() -> io::Result<()> { + let actual = ModuleRenderer::new("username").collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn logname_equals_user() -> io::Result<()> { + let actual = ModuleRenderer::new("username") + .env("LOGNAME", "astronaut") + .env("USER", "astronaut") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn ssh_wo_username() -> io::Result<()> { + // SSH connection w/o username + let actual = ModuleRenderer::new("username") + .env("SSH_CONNECTION", "192.168.223.17 36673 192.168.223.229 22") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn current_user_not_logname() -> io::Result<()> { + let actual = ModuleRenderer::new("username") + .env("LOGNAME", "astronaut") + .env("USER", "cosmonaut") + .collect(); + let expected = Some(format!("via {} ", Color::Yellow.bold().paint("cosmonaut"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn ssh_connection() -> io::Result<()> { + let actual = ModuleRenderer::new("username") + .env("USER", "astronaut") + .env("SSH_CONNECTION", "192.168.223.17 36673 192.168.223.229 22") + .collect(); + let expected = Some(format!("via {} ", Color::Yellow.bold().paint("astronaut"))); + + assert_eq!(expected, actual); + Ok(()) + } + + #[test] + fn show_always() -> io::Result<()> { + let actual = ModuleRenderer::new("username") + .env("USER", "astronaut") + .config(toml::toml! { + [username] + show_always = true + }) + .collect(); + let expected = Some(format!("via {} ", Color::Yellow.bold().paint("astronaut"))); + + assert_eq!(expected, actual); + Ok(()) + } +} diff --git a/src/modules/utils/mod.rs b/src/modules/utils/mod.rs index 89b12415..51f46e51 100644 --- a/src/modules/utils/mod.rs +++ b/src/modules/utils/mod.rs @@ -5,6 +5,3 @@ pub mod directory_win; #[cfg(not(target_os = "windows"))] pub mod directory_nix; - -#[cfg(test)] -pub mod test; diff --git a/src/modules/utils/test.rs b/src/modules/utils/test.rs deleted file mode 100644 index cdb8fc7b..00000000 --- a/src/modules/utils/test.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::config::StarshipConfig; -use crate::context::{Context, Shell}; -use std::path::Path; - -/// Render a specific starship module by name -pub fn render_module( - module_name: &str, - path: &Path, - config: Option, -) -> Option { - let mut context = Context::new_with_dir(clap::ArgMatches::default(), path); - context.config = StarshipConfig { config }; - context.shell = Shell::Unknown; - - crate::print::get_module(module_name, context) -} diff --git a/src/modules/zig.rs b/src/modules/zig.rs index c4bc4a07..b4f711f9 100644 --- a/src/modules/zig.rs +++ b/src/modules/zig.rs @@ -57,7 +57,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { #[cfg(test)] mod tests { - use crate::modules::utils::test::render_module; + use crate::test::ModuleRenderer; use ansi_term::Color; use std::fs::File; use std::io; @@ -66,7 +66,7 @@ mod tests { fn folder_without_zig() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("zig.txt"))?.sync_all()?; - let actual = render_module("zig", dir.path(), None); + let actual = ModuleRenderer::new("zig").path(dir.path()).collect(); let expected = None; assert_eq!(expected, actual); dir.close() @@ -76,7 +76,7 @@ mod tests { fn folder_with_zig_file() -> io::Result<()> { let dir = tempfile::tempdir()?; File::create(dir.path().join("main.zig"))?.sync_all()?; - let actual = render_module("zig", dir.path(), None); + let actual = ModuleRenderer::new("zig").path(dir.path()).collect(); let expected = Some(format!("via {} ", Color::Yellow.bold().paint("↯ v0.6.0"))); assert_eq!(expected, actual); dir.close() diff --git a/tests/fixtures/rocket.bundle b/src/test/fixtures/git-repo.bundle similarity index 100% rename from tests/fixtures/rocket.bundle rename to src/test/fixtures/git-repo.bundle diff --git a/tests/fixtures/hg-repo.bundle b/src/test/fixtures/hg-repo.bundle similarity index 100% rename from tests/fixtures/hg-repo.bundle rename to src/test/fixtures/hg-repo.bundle diff --git a/src/test/mod.rs b/src/test/mod.rs new file mode 100644 index 00000000..71402a0a --- /dev/null +++ b/src/test/mod.rs @@ -0,0 +1,138 @@ +use crate::config::StarshipConfig; +use crate::context::{Context, Shell}; +use once_cell::sync::Lazy; +use std::io; +use std::path::PathBuf; +use std::process::Command; +use tempfile::TempDir; + +static FIXTURE_DIR: Lazy = + Lazy::new(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/test/fixtures/")); + +static GIT_FIXTURE: Lazy = Lazy::new(|| FIXTURE_DIR.join("git-repo.bundle")); +static HG_FIXTURE: Lazy = Lazy::new(|| FIXTURE_DIR.join("hg-repo.bundle")); + +/// Render a specific starship module by name +pub struct ModuleRenderer<'a> { + name: &'a str, + context: Context<'a>, +} + +impl<'a> ModuleRenderer<'a> { + /// Creates a new ModuleRenderer + pub fn new(name: &'a str) -> Self { + let mut context = Context::new_with_dir(clap::ArgMatches::default(), PathBuf::new()); + context.shell = Shell::Unknown; + context.config = StarshipConfig { config: None }; + + Self { name, context } + } + + pub fn path(mut self, path: T) -> Self + where + T: Into, + { + self.context.current_dir = path.into(); + self + } + + /// Sets the config of the underlying context + pub fn config(mut self, config: toml::Value) -> Self { + self.context.config = StarshipConfig { + config: Some(config), + }; + self + } + + /// Adds the variable to the env_mocks of the underlying context + pub fn env>(mut self, key: &'a str, val: V) -> Self { + self.context.env.insert(key, val.into()); + self + } + + pub fn shell(mut self, shell: Shell) -> Self { + self.context.shell = shell; + self + } + + pub fn jobs(mut self, jobs: u64) -> Self { + self.context.properties.insert("jobs", jobs.to_string()); + self + } + + pub fn cmd_duration(mut self, duration: u64) -> Self { + self.context + .properties + .insert("cmd_duration", duration.to_string()); + self + } + + pub fn keymap(mut self, keymap: T) -> Self + where + T: Into, + { + self.context.properties.insert("keymap", keymap.into()); + self + } + + pub fn status(mut self, status: i32) -> Self { + self.context + .properties + .insert("status_code", status.to_string()); + self + } + + /// Renders the module returning its output + pub fn collect(self) -> Option { + crate::print::get_module(self.name, self.context) + } +} + +pub enum FixtureProvider { + GIT, + HG, +} + +pub fn fixture_repo(provider: FixtureProvider) -> io::Result { + match provider { + FixtureProvider::GIT => { + let path = tempfile::tempdir()?; + + Command::new("git") + .current_dir(path.path()) + .args(&["clone", "-b", "master"]) + .arg(GIT_FIXTURE.as_os_str()) + .arg(&path.path()) + .output()?; + + Command::new("git") + .args(&["config", "--local", "user.email", "starship@example.com"]) + .current_dir(&path.path()) + .output()?; + + Command::new("git") + .args(&["config", "--local", "user.name", "starship"]) + .current_dir(&path.path()) + .output()?; + + Command::new("git") + .args(&["reset", "--hard", "HEAD"]) + .current_dir(&path.path()) + .output()?; + + Ok(path) + } + FixtureProvider::HG => { + let path = tempfile::tempdir()?; + + Command::new("hg") + .current_dir(path.path()) + .arg("clone") + .arg(HG_FIXTURE.as_os_str()) + .arg(&path.path()) + .output()?; + + Ok(path) + } + } +} diff --git a/src/utils.rs b/src/utils.rs index ecdd4dc7..9d480b4e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -26,7 +26,7 @@ impl PartialEq for CommandOutput { } } -/// Execute a command and return the output on stdout and stderr if sucessful +/// Execute a command and return the output on stdout and stderr if successful #[cfg(not(test))] pub fn exec_cmd(cmd: &str, args: &[&str]) -> Option { internal_exec_cmd(&cmd, &args) @@ -158,6 +158,14 @@ CMake suite maintained and supported by Kitware (kitware.com/cmake).\n", ), stderr: String::default(), }), + "dotnet --version" => Some(CommandOutput { + stdout: String::from("3.1.103"), + stderr: String::default(), + }), + "dotnet --list-sdks" => Some(CommandOutput { + stdout: String::from("3.1.103 [/usr/share/dotnet/sdk]"), + stderr: String::default(), + }), "terraform version" => Some(CommandOutput { stdout: String::from("Terraform v0.12.14\n"), stderr: String::default(), diff --git a/tests/fixtures/empty_config.toml b/tests/fixtures/empty_config.toml deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/testsuite/aws.rs b/tests/testsuite/aws.rs deleted file mode 100644 index bfaf3acb..00000000 --- a/tests/testsuite/aws.rs +++ /dev/null @@ -1,250 +0,0 @@ -use std::fs::File; -use std::io::{self, Write}; - -use ansi_term::Color; - -use crate::common::{self, TestCommand}; - -#[test] -#[ignore] -fn no_region_set() -> io::Result<()> { - let output = common::render_module("aws") - .env("PATH", env!("PATH")) - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn region_set() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_REGION", "ap-northeast-2") - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (ap-northeast-2)")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn region_set_with_alias() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_REGION", "ap-southeast-2") - .use_config(toml::toml! { - [aws.region_aliases] - ap-southeast-2 = "au" - }) - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (au)")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn default_region_set() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_REGION", "ap-northeast-2") - .env("AWS_DEFAULT_REGION", "ap-northeast-1") - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (ap-northeast-1)")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn profile_set() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_PROFILE", "astronauts") - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn profile_set_from_aws_vault() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_VAULT", "astronauts-vault") - .env("AWS_PROFILE", "astronauts-profile") - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts-vault")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn profile_and_region_set() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_PROFILE", "astronauts") - .env("AWS_REGION", "ap-northeast-2") - .output()?; - let expected = format!( - "on {} ", - Color::Yellow.bold().paint("☁️ astronauts(ap-northeast-2)") - ); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn default_profile_set() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let config_path = dir.path().join("config"); - let mut file = File::create(&config_path)?; - - file.write_all( - "[default] -region = us-east-1 - -[profile astronauts] -region = us-east-2 -" - .as_bytes(), - )?; - - let output = common::render_module("aws") - .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (us-east-1)")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -fn profile_and_config_set() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let config_path = dir.path().join("config"); - let mut file = File::create(&config_path)?; - - file.write_all( - "[default] -region = us-east-1 - -[profile astronauts] -region = us-east-2 -" - .as_bytes(), - )?; - - let output = common::render_module("aws") - .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) - .env("AWS_PROFILE", "astronauts") - .use_config(toml::toml! { - [aws] - }) - .output()?; - let expected = format!( - "on {} ", - Color::Yellow.bold().paint("☁️ astronauts(us-east-2)") - ); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -fn profile_and_region_set_with_display_all() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_PROFILE", "astronauts") - .env("AWS_REGION", "ap-northeast-1") - .output()?; - let expected = format!( - "on {} ", - Color::Yellow.bold().paint("☁️ astronauts(ap-northeast-1)") - ); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn profile_set_with_display_all() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_PROFILE", "astronauts") - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn region_set_with_display_all() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_REGION", "ap-northeast-1") - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ (ap-northeast-1)")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn profile_and_region_set_with_display_region() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_PROFILE", "astronauts") - .env("AWS_DEFAULT_REGION", "ap-northeast-1") - .use_config(toml::toml! { - [aws] - format = "on [$symbol$region]($style) " - }) - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ap-northeast-1")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn profile_and_region_set_with_display_profile() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_PROFILE", "astronauts") - .env("AWS_REGION", "ap-northeast-1") - .use_config(toml::toml! { - [aws] - format = "on [$symbol$profile]($style) " - }) - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ astronauts")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn region_set_with_display_profile() -> io::Result<()> { - let output = common::render_module("aws") - .env("AWS_REGION", "ap-northeast-1") - .use_config(toml::toml! { - [aws] - format = "on [$symbol$profile]($style) " - }) - .output()?; - let expected = format!("on {} ", Color::Yellow.bold().paint("☁️ ")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn region_not_set_with_display_region() -> io::Result<()> { - let output = common::render_module("aws") - .use_config(toml::toml! { - [aws] - format = "on [$symbol$region]($style) " - }) - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/character.rs b/tests/testsuite/character.rs deleted file mode 100644 index 6735ab95..00000000 --- a/tests/testsuite/character.rs +++ /dev/null @@ -1,151 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common::{self, TestCommand}; - -#[test] -fn success_status() -> io::Result<()> { - let expected = format!("{} ", Color::Green.bold().paint("❯")); - - // Status code 0 - let output = common::render_module("character") - .arg("--status=0") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - - // No status code - let output = common::render_module("character").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - - Ok(()) -} - -#[test] -fn failure_status() -> io::Result<()> { - let expected = format!("{} ", Color::Red.bold().paint("❯")); - - let exit_values = ["1", "54321", "-5000"]; - - for status in exit_values.iter() { - let arg = format!("--status={}", status); - let output = common::render_module("character").arg(arg).output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - } - - Ok(()) -} - -#[test] -fn custom_symbol() -> io::Result<()> { - let expected_fail = format!("{} ", Color::Red.bold().paint("✖")); - let expected_success = format!("{} ", Color::Green.bold().paint("➜")); - - let exit_values = ["1", "54321", "-5000"]; - - // Test failure values - for status in exit_values.iter() { - let arg = format!("--status={}", status); - let output = common::render_module("character") - .use_config(toml::toml! { - [character] - success_symbol = "[➜](bold green)" - error_symbol = "[✖](bold red)" - - }) - .arg(arg) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_fail, actual); - } - - // Test success - let output = common::render_module("character") - .use_config(toml::toml! { - [character] - success_symbol = "[➜](bold green)" - error_symbol = "[✖](bold red)" - }) - .arg("--status=0") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_success, actual); - - Ok(()) -} - -#[test] -fn zsh_keymap() -> io::Result<()> { - let expected_vicmd = format!("{} ", Color::Green.bold().paint("❮")); - let expected_specified = format!("{} ", Color::Green.bold().paint("V")); - let expected_other = format!("{} ", Color::Green.bold().paint("❯")); - - // zle keymap is vicmd - let output = common::render_module("character") - .env("STARSHIP_SHELL", "zsh") - .arg("--keymap=vicmd") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_vicmd, actual); - - // specified vicmd character - let output = common::render_module("character") - .use_config(toml::toml! { - [character] - vicmd_symbol = "[V](bold green)" - }) - .env("STARSHIP_SHELL", "zsh") - .arg("--keymap=vicmd") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_specified, actual); - - // zle keymap is other - let output = common::render_module("character") - .env("STARSHIP_SHELL", "zsh") - .arg("--keymap=visual") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_other, actual); - - Ok(()) -} - -#[test] -fn fish_keymap() -> io::Result<()> { - let expected_vicmd = format!("{} ", Color::Green.bold().paint("❮")); - let expected_specified = format!("{} ", Color::Green.bold().paint("V")); - let expected_other = format!("{} ", Color::Green.bold().paint("❯")); - - // fish keymap is default - let output = common::render_module("character") - .env("STARSHIP_SHELL", "fish") - .arg("--keymap=default") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_vicmd, actual); - - // specified vicmd character - let output = common::render_module("character") - .use_config(toml::toml! { - [character] - vicmd_symbol = "[V](bold green)" - }) - .env("STARSHIP_SHELL", "fish") - .arg("--keymap=default") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_specified, actual); - - // fish keymap is other - let output = common::render_module("character") - .env("STARSHIP_SHELL", "fish") - .arg("--keymap=visual") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected_other, actual); - - Ok(()) -} diff --git a/tests/testsuite/cmd_duration.rs b/tests/testsuite/cmd_duration.rs deleted file mode 100644 index 61e92c6d..00000000 --- a/tests/testsuite/cmd_duration.rs +++ /dev/null @@ -1,92 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common::{self, TestCommand}; - -#[test] -fn config_blank_duration_1s() -> io::Result<()> { - let output = common::render_module("cmd_duration") - .arg("--cmd-duration=1000") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_blank_duration_5s() -> io::Result<()> { - let output = common::render_module("cmd_duration") - .arg("--cmd-duration=5000") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("took {} ", Color::Yellow.bold().paint("5s")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_5s_duration_3s() -> io::Result<()> { - let output = common::render_module("cmd_duration") - .use_config(toml::toml! { - [cmd_duration] - min_time = 5000 - }) - .arg("--cmd-duration=3000") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_5s_duration_10s() -> io::Result<()> { - let output = common::render_module("cmd_duration") - .use_config(toml::toml! { - [cmd_duration] - min_time = 5000 - }) - .arg("--cmd-duration=10000") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("took {} ", Color::Yellow.bold().paint("10s")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_1s_duration_prefix_underwent() -> io::Result<()> { - let output = common::render_module("cmd_duration") - .use_config(toml::toml! { - [cmd_duration] - format = "underwent [$duration]($style) " - }) - .arg("--cmd-duration=1000") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_5s_duration_prefix_underwent() -> io::Result<()> { - let output = common::render_module("cmd_duration") - .use_config(toml::toml! { - [cmd_duration] - format = "underwent [$duration]($style) " - }) - .arg("--cmd-duration=5000") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("underwent {} ", Color::Yellow.bold().paint("5s")); - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/common.rs b/tests/testsuite/common.rs deleted file mode 100644 index 3ad2271e..00000000 --- a/tests/testsuite/common.rs +++ /dev/null @@ -1,105 +0,0 @@ -use once_cell::sync::Lazy; -use remove_dir_all::remove_dir_all; -use std::io::prelude::*; -use std::io::{Error, ErrorKind}; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::{env, fs, io, process}; - -static MANIFEST_DIR: Lazy<&'static Path> = Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR"))); -static EMPTY_CONFIG: Lazy = Lazy::new(|| MANIFEST_DIR.join("empty_config.toml")); - -#[cfg(windows)] -const EXE_PATH: &str = "./target/debug/starship.exe"; - -#[cfg(not(windows))] -const EXE_PATH: &str = "./target/debug/starship"; - -/// Render the full starship prompt -pub fn _render_prompt() -> process::Command { - let mut command = process::Command::new(EXE_PATH); - - command - .arg("prompt") - .env_clear() - .env("PATH", env!("PATH")) // Provide the $PATH variable so that external programs are runnable - .env("STARSHIP_CONFIG", EMPTY_CONFIG.as_os_str()); - - command -} - -/// Render a specific starship module by name -pub fn render_module(module_name: &str) -> process::Command { - let binary = fs::canonicalize(EXE_PATH).unwrap(); - let mut command = process::Command::new(binary); - - command - .arg("module") - .arg(module_name) - .env_clear() - .env("PATH", env!("PATH")) // Provide the $PATH variable so that external programs are runnable - .env("STARSHIP_CONFIG", EMPTY_CONFIG.as_os_str()); - - command -} - -/// Create a repo from the fixture to be used in git module tests -/// Please delete the returned directory manually after usage with `remove_dir_all::remove_dir_all` -pub fn create_fixture_repo() -> io::Result { - let fixture_repo_path = tempfile::tempdir()?.into_path(); - let repo_path = tempfile::tempdir()?.into_path(); - let fixture_path = env::current_dir()?.join("tests/fixtures/rocket.bundle"); - - let fixture_repo_dir = path_str(&fixture_repo_path)?; - let repo_dir = path_str(&repo_path)?; - - Command::new("git") - .args(&["clone", "-b", "master"]) - .args(&[&fixture_path, &repo_path]) - .output()?; - - git2::Repository::clone(&fixture_repo_dir, &repo_dir).ok(); - remove_dir_all(fixture_repo_path)?; - - Command::new("git") - .args(&["config", "--local", "user.email", "starship@example.com"]) - .current_dir(&repo_path) - .output()?; - - Command::new("git") - .args(&["config", "--local", "user.name", "starship"]) - .current_dir(&repo_path) - .output()?; - - Command::new("git") - .args(&["reset", "--hard", "HEAD"]) - .current_dir(&repo_path) - .output()?; - - Ok(repo_path) -} - -fn path_str(repo_dir: &PathBuf) -> io::Result { - repo_dir - .to_str() - .ok_or_else(|| Error::from(ErrorKind::Other)) - .map(|i| i.replace("\\", "/")) -} - -/// Extends `std::process::Command` with methods for testing -pub trait TestCommand { - fn use_config(&mut self, toml: toml::value::Value) -> &mut process::Command; -} - -impl TestCommand for process::Command { - /// Create a configuration file with the provided TOML and use it - fn use_config(&mut self, toml: toml::value::Value) -> &mut process::Command { - // Create a persistent config file in a tempdir - let (mut config_file, config_path) = - tempfile::NamedTempFile::new().unwrap().keep().unwrap(); - write!(config_file, "{}", toml.to_string()).unwrap(); - - // Set that newly-created file as the config for the prompt instance - self.env("STARSHIP_CONFIG", config_path) - } -} diff --git a/tests/testsuite/conda.rs b/tests/testsuite/conda.rs deleted file mode 100644 index 985b634a..00000000 --- a/tests/testsuite/conda.rs +++ /dev/null @@ -1,58 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common::{self, TestCommand}; - -#[test] -fn not_in_env() -> io::Result<()> { - let output = common::render_module("conda").output()?; - - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn ignore_base() -> io::Result<()> { - let output = common::render_module("conda") - .env("CONDA_DEFAULT_ENV", "base") - .use_config(toml::toml! { - [conda] - ignore_base = true - }) - .output()?; - - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn env_set() -> io::Result<()> { - let output = common::render_module("conda") - .env("CONDA_DEFAULT_ENV", "astronauts") - .output()?; - - let expected = format!("via {} ", Color::Green.bold().paint("🅒 astronauts")); - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn truncate() -> io::Result<()> { - let output = common::render_module("conda") - .env("CONDA_DEFAULT_ENV", "/some/really/long/and/really/annoying/path/that/shouldnt/be/displayed/fully/conda/my_env") - .output()?; - - let expected = format!("via {} ", Color::Green.bold().paint("🅒 my_env")); - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/configuration.rs b/tests/testsuite/configuration.rs deleted file mode 100644 index 84e23b83..00000000 --- a/tests/testsuite/configuration.rs +++ /dev/null @@ -1,20 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common::{self, TestCommand}; - -#[test] -fn char_symbol_configuration() -> io::Result<()> { - let expected = format!("{} ", Color::Green.bold().paint("❯")); - - let output = common::render_module("character") - .use_config(toml::toml! { - [character] - symbol = "❯" - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - - Ok(()) -} diff --git a/tests/testsuite/directory.rs b/tests/testsuite/directory.rs deleted file mode 100755 index 70327642..00000000 --- a/tests/testsuite/directory.rs +++ /dev/null @@ -1,781 +0,0 @@ -use ansi_term::Color; -use dirs_next::home_dir; -use git2::Repository; -use std::fs; -use std::io; -#[cfg(not(target_os = "windows"))] -use std::os::unix::fs::symlink; -#[cfg(target_os = "windows")] -use std::os::windows::fs::symlink_dir as symlink; -use std::path::Path; -use tempfile::TempDir; - -use crate::common::{self, TestCommand}; - -#[test] -fn home_directory() -> io::Result<()> { - let output = common::render_module("directory") - .arg("--path=~") - .use_config(toml::toml! { // Necessary if homedir is a git repo - [directory] - truncate_to_repo = false - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("~")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn substituted_truncated_path() -> io::Result<()> { - let output = common::render_module("directory") - .arg("--path=/some/long/network/path/workspace/a/b/c/dev") - .use_config(toml::toml! { - [directory] - truncation_length = 4 - [directory.substitutions] - "/some/long/network/path" = "/some/net" - "a/b/c" = "d" - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("net/workspace/d/dev")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn strange_substitution() -> io::Result<()> { - let strange_sub = "/\\/;,!"; - let output = common::render_module("directory") - .arg("--path=/foo/bar/regular/path") - .use_config(toml::toml! { - [directory] - truncation_length = 0 - fish_style_pwd_dir_length = 2 // Overridden by substitutions - [directory.substitutions] - "regular" = strange_sub - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint(format!("/foo/bar/{}/path", strange_sub)) - ); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn directory_in_home() -> io::Result<()> { - let dir = home_dir().unwrap().join("starship/engine"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("~/starship/engine")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn truncated_directory_in_home() -> io::Result<()> { - let dir = home_dir().unwrap().join("starship/engine/schematics"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan.bold().paint("starship/engine/schematics") - ); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn fish_directory_in_home() -> io::Result<()> { - let dir = home_dir().unwrap().join("starship/engine/schematics"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - truncation_length = 1 - fish_style_pwd_dir_length = 2 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("~/st/en/schematics")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn root_directory() -> io::Result<()> { - let output = common::render_module("directory") - .arg("--path=/") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - #[cfg(not(target_os = "windows"))] - let expected = format!( - "{}{} ", - Color::Cyan.bold().paint("/"), - Color::Red.normal().paint("🔒") - ); - #[cfg(target_os = "windows")] - let expected = format!("{} ", Color::Cyan.bold().paint("/"),); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[cfg(not(target_os = "windows"))] -fn directory_in_root() -> io::Result<()> { - let output = common::render_module("directory") - .arg("--path=/etc") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{}{} ", - Color::Cyan.bold().paint("/etc"), - Color::Red.normal().paint("🔒") - ); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[cfg(target_os = "windows")] -fn directory_in_root() -> io::Result<()> { - let output = common::render_module("directory") - .arg("--path=C:\\") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("C:")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn truncated_directory_in_root() -> io::Result<()> { - let dir = Path::new("/tmp/starship/thrusters/rocket"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("starship/thrusters/rocket")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn truncated_directory_config_large() -> io::Result<()> { - let dir = Path::new("/tmp/starship/thrusters/rocket"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - truncation_length = 100 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan.bold().paint("/tmp/starship/thrusters/rocket") - ); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn fish_style_directory_config_large() -> io::Result<()> { - let dir = Path::new("/tmp/starship/thrusters/rocket"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - truncation_length = 1 - fish_style_pwd_dir_length = 100 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan.bold().paint("/tmp/starship/thrusters/rocket") - ); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn truncated_directory_config_small() -> io::Result<()> { - let dir = Path::new("/tmp/starship/thrusters/rocket"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - truncation_length = 2 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("thrusters/rocket")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn fish_directory_config_small() -> io::Result<()> { - let dir = Path::new("/tmp/starship/thrusters/rocket"); - fs::create_dir_all(&dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - truncation_length = 2 - fish_style_pwd_dir_length = 1 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("/t/s/thrusters/rocket")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn git_repo_root() -> io::Result<()> { - // TODO: Investigate why git repo related tests fail when the tempdir is within /tmp/... - // Temporarily making the tempdir within $HOME - // #[ignore] can be removed after this TODO is addressed - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - fs::create_dir(&repo_dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .arg("--path") - .arg(repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("rocket-controls")); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn directory_in_git_repo() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - let dir = repo_dir.join("src"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("rocket-controls/src")); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn truncated_directory_in_git_repo() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - let dir = repo_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("src/meters/fuel-gauge")); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let dir = repo_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // Don't truncate the path at all. - truncation_length = 5 - truncate_to_repo = false - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("above-repo/rocket-controls/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn fish_path_directory_in_git_repo_truncate_to_repo_false() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let dir = repo_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // Don't truncate the path at all. - truncation_length = 5 - truncate_to_repo = false - fish_style_pwd_dir_length = 1 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("~/.t/above-repo/rocket-controls/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn fish_path_directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let dir = repo_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // `truncate_to_repo = true` should display the truncated path - truncation_length = 5 - truncate_to_repo = true - fish_style_pwd_dir_length = 1 - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("~/.t/a/rocket-controls/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn directory_in_git_repo_truncate_to_repo_true() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let dir = repo_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // `truncate_to_repo = true` should display the truncated path - truncation_length = 5 - truncate_to_repo = true - }) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("rocket-controls/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -#[cfg(not(target_os = "windows"))] -fn git_repo_in_home_directory_truncate_to_repo_true() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let dir = tmp_dir.path().join("src/meters/fuel-gauge"); - fs::create_dir_all(&dir)?; - Repository::init(&tmp_dir).unwrap(); - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // `truncate_to_repo = true` should attmpt to display the truncated path - truncate_to_repo = true - truncation_length = 5 - }) - // Set home directory to the temp repository - .env("HOME", tmp_dir.path()) - .arg("--path") - .arg(dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("~/src/meters/fuel-gauge")); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn symlinked_git_repo_root() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - let symlink_dir = tmp_dir.path().join("rocket-controls-symlink"); - fs::create_dir(&repo_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .arg("--path") - .arg(symlink_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("rocket-controls-symlink")); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn directory_in_symlinked_git_repo() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - let src_dir = repo_dir.join("src"); - let symlink_dir = tmp_dir.path().join("rocket-controls-symlink"); - let symlink_src_dir = symlink_dir.join("src"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .arg("--path") - .arg(symlink_src_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan.bold().paint("rocket-controls-symlink/src") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn truncated_directory_in_symlinked_git_repo() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - let src_dir = repo_dir.join("src/meters/fuel-gauge"); - let symlink_dir = tmp_dir.path().join("rocket-controls-symlink"); - let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .arg("--path") - .arg(symlink_src_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("src/meters/fuel-gauge")); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn directory_in_symlinked_git_repo_truncate_to_repo_false() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let src_dir = repo_dir.join("src/meters/fuel-gauge"); - let symlink_dir = tmp_dir - .path() - .join("above-repo") - .join("rocket-controls-symlink"); - let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // Don't truncate the path at all. - truncation_length = 5 - truncate_to_repo = false - }) - .arg("--path") - .arg(symlink_src_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("above-repo/rocket-controls-symlink/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn fish_path_directory_in_symlinked_git_repo_truncate_to_repo_false() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let src_dir = repo_dir.join("src/meters/fuel-gauge"); - let symlink_dir = tmp_dir - .path() - .join("above-repo") - .join("rocket-controls-symlink"); - let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // Don't truncate the path at all. - truncation_length = 5 - truncate_to_repo = false - fish_style_pwd_dir_length = 1 - }) - .arg("--path") - .arg(symlink_src_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("~/.t/above-repo/rocket-controls-symlink/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn fish_path_directory_in_symlinked_git_repo_truncate_to_repo_true() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let src_dir = repo_dir.join("src/meters/fuel-gauge"); - let symlink_dir = tmp_dir - .path() - .join("above-repo") - .join("rocket-controls-symlink"); - let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // `truncate_to_repo = true` should display the truncated path - truncation_length = 5 - truncate_to_repo = true - fish_style_pwd_dir_length = 1 - }) - .arg("--path") - .arg(symlink_src_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("~/.t/a/rocket-controls-symlink/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn directory_in_symlinked_git_repo_truncate_to_repo_true() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let src_dir = repo_dir.join("src/meters/fuel-gauge"); - let symlink_dir = tmp_dir - .path() - .join("above-repo") - .join("rocket-controls-symlink"); - let symlink_src_dir = symlink_dir.join("src/meters/fuel-gauge"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&repo_dir, &symlink_dir)?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // `truncate_to_repo = true` should display the truncated path - truncation_length = 5 - truncate_to_repo = true - }) - .arg("--path") - .arg(symlink_src_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan - .bold() - .paint("rocket-controls-symlink/src/meters/fuel-gauge") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -fn symlinked_directory_in_git_repo() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("rocket-controls"); - let dir = repo_dir.join("src"); - fs::create_dir_all(&dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&dir, repo_dir.join("src/loop"))?; - - let output = common::render_module("directory") - .use_config(toml::toml! { - [directory] - // `truncate_to_repo = true` should display the truncated path - truncation_length = 5 - truncate_to_repo = true - }) - .arg("--path") - .arg(repo_dir.join("src/loop/loop")) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "{} ", - Color::Cyan.bold().paint("rocket-controls/src/loop/loop") - ); - assert_eq!(expected, actual); - tmp_dir.close() -} - -#[test] -#[ignore] -#[cfg(not(target_os = "windows"))] -fn symlinked_subdirectory_git_repo_out_of_tree() -> io::Result<()> { - let tmp_dir = TempDir::new_in(home_dir().unwrap())?; - let repo_dir = tmp_dir.path().join("above-repo").join("rocket-controls"); - let src_dir = repo_dir.join("src/meters/fuel-gauge"); - let symlink_dir = tmp_dir.path().join("fuel-gauge"); - fs::create_dir_all(&src_dir)?; - Repository::init(&repo_dir).unwrap(); - symlink(&src_dir, &symlink_dir)?; - - let output = common::render_module("directory") - // Set home directory to the temp repository - .env("HOME", tmp_dir.path()) - .arg("--path") - .arg(symlink_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Cyan.bold().paint("~/fuel-gauge")); - assert_eq!(expected, actual); - tmp_dir.close() -} diff --git a/tests/testsuite/dotnet.rs b/tests/testsuite/dotnet.rs deleted file mode 100644 index 188403a2..00000000 --- a/tests/testsuite/dotnet.rs +++ /dev/null @@ -1,244 +0,0 @@ -use super::common; -use regex::Regex; -use std::fs::{DirBuilder, OpenOptions}; -use std::io::{self, Error, ErrorKind, Write}; -use std::process::{Command, Stdio}; -use tempfile::{self, TempDir}; - -const DOTNET_OUTPUT_PATTERN: &str = "•NET v\\d+?\\.\\d+?\\.\\d?"; -const DOTNET_PINNED_VERSION: &str = "1.2.3"; -const DOTNET_PINNED_VERSION_OUTPUT_PATTERN: &str = "•NET v1\\.2\\.3"; -const DOTNET_TFM_PATTERN: &str = r"🎯 .+"; -const DOTNET_TFM_PINNED_VERSION: &str = r"netstandard2.0"; - -#[test] -#[ignore] -fn shows_nothing_in_directory_with_zero_relevant_files() -> io::Result<()> { - let workspace = create_workspace(false)?; - expect_output(&workspace, ".", None)?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_directory_build_props_file() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "Directory.Build.props", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_directory_build_targets_file() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "Directory.Build.targets", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_packages_props_file() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "Packages.props", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_solution() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "solution.sln", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_csproj() -> io::Result<()> { - let workspace = create_workspace(false)?; - let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0"); - touch_path(&workspace, "project.csproj", Some(&csproj))?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - expect_output(&workspace, ".", Some(DOTNET_TFM_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_fsproj() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "project.fsproj", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_xproj() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "project.xproj", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_latest_in_directory_with_project_json() -> io::Result<()> { - let workspace = create_workspace(false)?; - touch_path(&workspace, "project.json", None)?; - expect_output(&workspace, ".", Some(DOTNET_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_pinned_in_directory_with_global_json() -> io::Result<()> { - let workspace = create_workspace(false)?; - let global_json = make_pinned_sdk_json(DOTNET_PINNED_VERSION); - touch_path(&workspace, "global.json", Some(&global_json))?; - expect_output(&workspace, ".", Some(DOTNET_PINNED_VERSION_OUTPUT_PATTERN))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_pinned_in_project_below_root_with_global_json() -> io::Result<()> { - let workspace = create_workspace(false)?; - let global_json = make_pinned_sdk_json(DOTNET_PINNED_VERSION); - let csproj = make_csproj_with_tfm("TargetFramework", DOTNET_TFM_PINNED_VERSION); - touch_path(&workspace, "global.json", Some(&global_json))?; - touch_path(&workspace, "project/project.csproj", Some(&csproj))?; - expect_output( - &workspace, - "project", - Some(DOTNET_PINNED_VERSION_OUTPUT_PATTERN), - )?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_pinned_in_deeply_nested_project_within_repository() -> io::Result<()> { - let workspace = create_workspace(true)?; - let global_json = make_pinned_sdk_json("1.2.3"); - let csproj = make_csproj_with_tfm("TargetFramework", DOTNET_TFM_PINNED_VERSION); - touch_path(&workspace, "global.json", Some(&global_json))?; - touch_path( - &workspace, - "deep/path/to/project/project.csproj", - Some(&csproj), - )?; - expect_output( - &workspace, - "deep/path/to/project", - Some(DOTNET_PINNED_VERSION_OUTPUT_PATTERN), - )?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_single_tfm() -> io::Result<()> { - let workspace = create_workspace(false)?; - let csproj = make_csproj_with_tfm("TargetFramework", "netstandard2.0"); - touch_path(&workspace, "project.csproj", Some(&csproj))?; - expect_output(&workspace, ".", Some("•NET v2.2.402"))?; - expect_output(&workspace, ".", Some("🎯 netstandard2.0"))?; - workspace.close() -} - -#[test] -#[ignore] -fn shows_multiple_tfms() -> io::Result<()> { - let workspace = create_workspace(false)?; - let csproj = make_csproj_with_tfm("TargetFrameworks", "netstandard2.0;net461"); - touch_path(&workspace, "project.csproj", Some(&csproj))?; - expect_output(&workspace, ".", Some("•NET v2.2.402"))?; - expect_output(&workspace, ".", Some("🎯 netstandard2.0;net461"))?; - workspace.close() -} - -fn create_workspace(is_repo: bool) -> io::Result { - let repo_dir = tempfile::tempdir()?; - - if is_repo { - let mut command = Command::new("git"); - command - .args(&["init", "--quiet"]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .stdin(Stdio::null()) - .current_dir(repo_dir.path()); - - if !command.status()?.success() { - return Err(Error::from(ErrorKind::Other)); - } - } - - Ok(repo_dir) -} - -fn touch_path(workspace: &TempDir, relative_path: &str, contents: Option<&str>) -> io::Result<()> { - let path = workspace.path().join(relative_path); - - DirBuilder::new().recursive(true).create( - path.parent() - .expect("Expected relative_path to be a file in a directory"), - )?; - - let mut file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&path)?; - write!(file, "{}", contents.unwrap_or(""))?; - file.sync_data() -} - -fn make_pinned_sdk_json(version: &str) -> String { - let json_text = r#" - { - "sdk": { - "version": "INSERT_VERSION" - } - } - "#; - json_text.replace("INSERT_VERSION", version) -} - -fn make_csproj_with_tfm(tfm_element: &str, tfm: &str) -> String { - let json_text = r#" - - - TFM_VALUE - - - "#; - json_text - .replace("TFM_ELEMENT", tfm_element) - .replace("TFM_VALUE", tfm) -} - -fn expect_output(workspace: &TempDir, run_from: &str, pattern: Option<&str>) -> io::Result<()> { - let run_path = workspace.path().join(run_from); - let output = common::render_module("dotnet") - .current_dir(run_path) - .output()?; - let text = String::from_utf8(output.stdout).unwrap(); - - // This can be helpful for debugging - eprintln!("The dotnet module showed: {}", text); - - match pattern { - Some(pattern) => { - let re = Regex::new(pattern).unwrap(); - assert!(re.is_match(&text)); - } - None => assert!(text.is_empty()), - } - - Ok(()) -} diff --git a/tests/testsuite/env_var.rs b/tests/testsuite/env_var.rs deleted file mode 100644 index e388459d..00000000 --- a/tests/testsuite/env_var.rs +++ /dev/null @@ -1,141 +0,0 @@ -use ansi_term::{Color, Style}; -use std::io; - -use crate::common; -use crate::common::TestCommand; - -const TEST_VAR_VALUE: &str = "astronauts"; - -#[test] -fn empty_config() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - }) - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn defined_variable() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "TEST_VAR" - }) - .env("TEST_VAR", TEST_VAR_VALUE) - .output()?; - let expected = format!("with {} ", style().paint(TEST_VAR_VALUE)); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn undefined_variable() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "TEST_VAR" - }) - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn default_has_no_effect() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "TEST_VAR" - default = "N/A" - }) - .env("TEST_VAR", TEST_VAR_VALUE) - .output()?; - let expected = format!("with {} ", style().paint(TEST_VAR_VALUE)); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn default_takes_effect() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "UNDEFINED_TEST_VAR" - default = "N/A" - }) - .output()?; - let expected = format!("with {} ", style().paint("N/A")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn symbol() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "TEST_VAR" - format = "with [■ $env_value](black bold dimmed) " - }) - .env("TEST_VAR", TEST_VAR_VALUE) - .output()?; - let expected = format!("with {} ", style().paint(format!("■ {}", TEST_VAR_VALUE))); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn prefix() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "TEST_VAR" - format = "with [_$env_value](black bold dimmed) " - }) - .env("TEST_VAR", TEST_VAR_VALUE) - .output()?; - let expected = format!("with {} ", style().paint(format!("_{}", TEST_VAR_VALUE))); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn suffix() -> io::Result<()> { - let output = common::render_module("env_var") - .env_clear() - .use_config(toml::toml! { - [env_var] - variable = "TEST_VAR" - format = "with [${env_value}_](black bold dimmed) " - }) - .env("TEST_VAR", TEST_VAR_VALUE) - .output()?; - let expected = format!("with {} ", style().paint(format!("{}_", TEST_VAR_VALUE))); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -fn style() -> Style { - // default style - Color::Black.bold().dimmed() -} diff --git a/tests/testsuite/gcloud.rs b/tests/testsuite/gcloud.rs deleted file mode 100644 index 2405e06d..00000000 --- a/tests/testsuite/gcloud.rs +++ /dev/null @@ -1,159 +0,0 @@ -use std::fs::{create_dir, File}; -use std::io::{self, Write}; - -use ansi_term::Color; - -use crate::common::{self, TestCommand}; - -#[test] -fn account_set() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let active_config_path = dir.path().join("active_config"); - let mut active_config_file = File::create(&active_config_path)?; - active_config_file.write_all(b"default")?; - - create_dir(dir.path().join("configurations"))?; - let config_default_path = dir.path().join("configurations/config_default"); - let mut config_default_file = File::create(&config_default_path)?; - config_default_file.write_all( - b"[core] -account = foo@example.com -", - )?; - - let output = common::render_module("gcloud") - .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy().as_ref()) - .output()?; - let expected = format!("on {} ", Color::Blue.bold().paint("☁️ foo@example.com")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(actual, expected); - dir.close() -} - -#[test] -fn account_and_region_set() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let active_config_path = dir.path().join("active_config"); - let mut active_config_file = File::create(&active_config_path)?; - active_config_file.write_all(b"default")?; - - create_dir(dir.path().join("configurations"))?; - let config_default_path = dir.path().join("configurations/config_default"); - let mut config_default_file = File::create(&config_default_path)?; - config_default_file.write_all( - b"[core] -account = foo@example.com - -[compute] -region = us-central1 -", - )?; - - let output = common::render_module("gcloud") - .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy().as_ref()) - .output()?; - let expected = format!( - "on {} ", - Color::Blue.bold().paint("☁️ foo@example.com(us-central1)") - ); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(actual, expected); - dir.close() -} - -#[test] -fn account_and_region_set_with_alias() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let active_config_path = dir.path().join("active_config"); - let mut active_config_file = File::create(&active_config_path)?; - active_config_file.write_all(b"default")?; - - create_dir(dir.path().join("configurations"))?; - let config_default_path = dir.path().join("configurations/config_default"); - let mut config_default_file = File::create(&config_default_path)?; - config_default_file.write_all( - b"[core] -account = foo@example.com - -[compute] -region = us-central1 -", - )?; - - let output = common::render_module("gcloud") - .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy().as_ref()) - .use_config(toml::toml! { - [gcloud.region_aliases] - us-central1 = "uc1" - }) - .output()?; - let expected = format!("on {} ", Color::Blue.bold().paint("☁️ foo@example.com(uc1)")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(actual, expected); - dir.close() -} - -#[test] -fn active_set() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let active_config_path = dir.path().join("active_config"); - let mut active_config_file = File::create(&active_config_path)?; - active_config_file.write_all(b"default1")?; - - let output = common::render_module("gcloud") - .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy().as_ref()) - .use_config(toml::toml! { - [gcloud] - format = "on [$symbol$active]($style) " - }) - .output()?; - let expected = format!("on {} ", Color::Blue.bold().paint("☁️ default1")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(actual, expected); - dir.close() -} - -#[test] -fn project_set() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let active_config_path = dir.path().join("active_config"); - let mut active_config_file = File::create(&active_config_path)?; - active_config_file.write_all(b"default")?; - - create_dir(dir.path().join("configurations"))?; - let config_default_path = dir.path().join("configurations/config_default"); - let mut config_default_file = File::create(&config_default_path)?; - config_default_file.write_all( - b"[core] -project = abc -", - )?; - - let output = common::render_module("gcloud") - .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy().as_ref()) - .use_config(toml::toml! { - [gcloud] - format = "on [$symbol$project]($style) " - }) - .output()?; - let expected = format!("on {} ", Color::Blue.bold().paint("☁️ abc")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(actual, expected); - dir.close() -} - -#[test] -fn region_not_set_with_display_region() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let output = common::render_module("gcloud") - .env("CLOUDSDK_CONFIG", dir.path().to_string_lossy().as_ref()) - .use_config(toml::toml! { - [gcloud] - format = "on [$symbol$region]($style) " - }) - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - dir.close() -} diff --git a/tests/testsuite/git_branch.rs b/tests/testsuite/git_branch.rs deleted file mode 100644 index 5b5a33a0..00000000 --- a/tests/testsuite/git_branch.rs +++ /dev/null @@ -1,292 +0,0 @@ -use ansi_term::Color; -use remove_dir_all::remove_dir_all; -use std::io; -use std::path::Path; -use std::process::Command; - -use crate::common::{self, TestCommand}; - -#[test] -fn show_nothing_on_empty_dir() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?; - - let output = common::render_module("git_branch") - .arg("--path") - .arg(repo_dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - repo_dir.close() -} - -#[test] -fn test_changed_truncation_symbol() -> io::Result<()> { - test_truncate_length_with_config( - "1337_hello_world", - 15, - "1337_hello_worl", - "%", - "truncation_symbol = \"%\"", - ) -} - -#[test] -fn test_no_truncation_symbol() -> io::Result<()> { - test_truncate_length_with_config( - "1337_hello_world", - 15, - "1337_hello_worl", - "", - "truncation_symbol = \"\"", - ) -} - -#[test] -fn test_multi_char_truncation_symbol() -> io::Result<()> { - test_truncate_length_with_config( - "1337_hello_world", - 15, - "1337_hello_worl", - "a", - "truncation_symbol = \"apple\"", - ) -} - -#[test] -fn test_ascii_boundary_below() -> io::Result<()> { - test_truncate_length("1337_hello_world", 15, "1337_hello_worl", "…") -} - -#[test] -fn test_ascii_boundary_on() -> io::Result<()> { - test_truncate_length("1337_hello_world", 16, "1337_hello_world", "") -} - -#[test] -fn test_ascii_boundary_above() -> io::Result<()> { - test_truncate_length("1337_hello_world", 17, "1337_hello_world", "") -} - -#[test] -fn test_one() -> io::Result<()> { - test_truncate_length("1337_hello_world", 1, "1", "…") -} - -#[test] -fn test_zero() -> io::Result<()> { - test_truncate_length("1337_hello_world", 0, "1337_hello_world", "") -} - -#[test] -fn test_negative() -> io::Result<()> { - test_truncate_length("1337_hello_world", -1, "1337_hello_world", "") -} - -#[test] -fn test_hindi_truncation() -> io::Result<()> { - test_truncate_length("नमस्ते", 3, "नमस्", "…") -} - -#[test] -fn test_hindi_truncation2() -> io::Result<()> { - test_truncate_length("नमस्त", 3, "नमस्", "…") -} - -#[test] -fn test_japanese_truncation() -> io::Result<()> { - test_truncate_length("がんばってね", 4, "がんばっ", "…") -} - -#[test] -fn test_format_no_branch() -> io::Result<()> { - test_format("1337_hello_world", "no_branch", "", "no_branch") -} - -#[test] -fn test_format_just_branch_name() -> io::Result<()> { - test_format("1337_hello_world", "$branch", "", "1337_hello_world") -} - -#[test] -fn test_format_just_branch_name_color() -> io::Result<()> { - test_format( - "1337_hello_world", - "[$branch](bold blue)", - "", - Color::Blue.bold().paint("1337_hello_world").to_string(), - ) -} - -#[test] -fn test_format_mixed_colors() -> io::Result<()> { - test_format( - "1337_hello_world", - "branch: [$branch](bold blue) [THE COLORS](red) ", - "", - format!( - "branch: {} {} ", - Color::Blue.bold().paint("1337_hello_world").to_string(), - Color::Red.paint("THE COLORS").to_string() - ), - ) -} - -#[test] -fn test_format_symbol_style() -> io::Result<()> { - test_format( - "1337_hello_world", - "$symbol[$branch]($style)", - r#" - symbol = "git: " - style = "green" - "#, - format!( - "git: {}", - Color::Green.paint("1337_hello_world").to_string(), - ), - ) -} - -#[test] -fn test_works_with_unborn_default_branch() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?.into_path(); - - Command::new("git") - .args(&["init"]) - .current_dir(&repo_dir) - .output()?; - - Command::new("git") - .args(&["symbolic-ref", "HEAD", "refs/heads/main"]) - .current_dir(&repo_dir) - .output()?; - - let output = common::render_module("git_branch") - .arg("--path") - .arg(&repo_dir) - .output() - .unwrap(); - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "on {} ", - Color::Purple.bold().paint(format!("\u{e0a0} {}", "main")), - ); - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -#[test] -fn test_git_dir_env_variable() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?.into_path(); - - Command::new("git") - .args(&["init"]) - .current_dir(&repo_dir) - .output()?; - - let output = common::render_module("git_branch") - .env("GIT_DIR", Path::new(&repo_dir).join(".git")) - .output() - .unwrap(); - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "on {} ", - Color::Purple.bold().paint(format!("\u{e0a0} {}", "master")), - ); - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -fn test_truncate_length( - branch_name: &str, - truncate_length: i64, - expected_name: &str, - truncation_symbol: &str, -) -> io::Result<()> { - test_truncate_length_with_config( - branch_name, - truncate_length, - expected_name, - truncation_symbol, - "", - ) -} - -fn test_truncate_length_with_config( - branch_name: &str, - truncate_length: i64, - expected_name: &str, - truncation_symbol: &str, - config_options: &str, -) -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - Command::new("git") - .args(&["checkout", "-b", branch_name]) - .current_dir(repo_dir.as_path()) - .output()?; - - let output = common::render_module("git_branch") - .use_config( - toml::from_str(&format!( - " - [git_branch] - truncation_length = {} - {} - ", - truncate_length, config_options - )) - .unwrap(), - ) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!( - "on {} ", - Color::Purple - .bold() - .paint(format!("\u{e0a0} {}{}", expected_name, truncation_symbol)), - ); - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -fn test_format>( - branch_name: &str, - format: &str, - config_options: &str, - expected: T, -) -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - Command::new("git") - .args(&["checkout", "-b", branch_name]) - .current_dir(repo_dir.as_path()) - .output()?; - - let output = common::render_module("git_branch") - .use_config( - toml::from_str(&format!( - r#" - [git_branch] - format = "{}" - {} - "#, - format, config_options - )) - .unwrap(), - ) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!(expected.as_ref(), actual); - remove_dir_all(repo_dir) -} diff --git a/tests/testsuite/git_commit.rs b/tests/testsuite/git_commit.rs deleted file mode 100644 index 6e66921c..00000000 --- a/tests/testsuite/git_commit.rs +++ /dev/null @@ -1,135 +0,0 @@ -use ansi_term::Color; -use remove_dir_all::remove_dir_all; -use std::process::Command; -use std::{io, str}; - -use crate::common::{self, TestCommand}; - -#[test] -fn show_nothing_on_empty_dir() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?; - - let output = common::render_module("git_commit") - .arg("--path") - .arg(repo_dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - repo_dir.close() -} - -#[test] -fn test_render_commit_hash() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - let mut git_output = Command::new("git") - .args(&["rev-parse", "HEAD"]) - .current_dir(repo_dir.as_path()) - .output()? - .stdout; - git_output.truncate(7); - let expected_hash = str::from_utf8(&git_output).unwrap(); - - let output = common::render_module("git_commit") - .use_config(toml::toml! { - [git_commit] - only_detached = false - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - let mut expected = Color::Green - .bold() - .paint(format!("({})", expected_hash)) - .to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -#[test] -fn test_render_commit_hash_len_override() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - let mut git_output = Command::new("git") - .args(&["rev-parse", "HEAD"]) - .current_dir(repo_dir.as_path()) - .output()? - .stdout; - git_output.truncate(14); - let expected_hash = str::from_utf8(&git_output).unwrap(); - - let output = common::render_module("git_commit") - .use_config(toml::toml! { - [git_commit] - only_detached = false - commit_hash_length = 14 - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - let mut expected = Color::Green - .bold() - .paint(format!("({})", expected_hash)) - .to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -#[test] -fn test_render_commit_hash_only_detached_on_branch() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - let output = common::render_module("git_commit") - .arg("--path") - .arg(&repo_dir) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!("", actual); - remove_dir_all(repo_dir) -} - -#[test] -fn test_render_commit_hash_only_detached_on_detached() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - Command::new("git") - .args(&["checkout", "@~1"]) - .current_dir(repo_dir.as_path()) - .output()?; - - let mut git_output = Command::new("git") - .args(&["rev-parse", "HEAD"]) - .current_dir(repo_dir.as_path()) - .output()? - .stdout; - git_output.truncate(7); - let expected_hash = str::from_utf8(&git_output).unwrap(); - - let output = common::render_module("git_commit") - .arg("--path") - .arg(&repo_dir) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expected = Color::Green - .bold() - .paint(format!("({})", expected_hash)) - .to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} diff --git a/tests/testsuite/git_state.rs b/tests/testsuite/git_state.rs deleted file mode 100644 index 241d904c..00000000 --- a/tests/testsuite/git_state.rs +++ /dev/null @@ -1,217 +0,0 @@ -use super::common; -use ansi_term::Color; -use std::ffi::OsStr; -use std::fs::OpenOptions; -use std::io::{self, Error, ErrorKind, Write}; -use std::process::{Command, Stdio}; - -#[test] -fn show_nothing_on_empty_dir() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?; - - let output = common::render_module("git_state") - .arg("--path") - .arg(repo_dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - repo_dir.close() -} - -#[test] -fn shows_rebasing() -> io::Result<()> { - let repo_dir = create_repo_with_conflict()?; - let path = path_str(&repo_dir)?; - - run_git_cmd(&["rebase", "other-branch"], Some(path), false)?; - - let output = common::render_module("git_state") - .arg("--path") - .arg(&path) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expected = Color::Yellow.bold().paint("(REBASING 1/1)").to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - - Ok(()) -} - -#[test] -fn shows_merging() -> io::Result<()> { - let repo_dir = create_repo_with_conflict()?; - let path = path_str(&repo_dir)?; - - run_git_cmd(&["merge", "other-branch"], Some(path), false)?; - - let output = common::render_module("git_state") - .arg("--path") - .arg(&path) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expected = Color::Yellow.bold().paint("(MERGING)").to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - - Ok(()) -} - -#[test] -fn shows_cherry_picking() -> io::Result<()> { - let repo_dir = create_repo_with_conflict()?; - let path = path_str(&repo_dir)?; - - run_git_cmd(&["cherry-pick", "other-branch"], Some(path), false)?; - - let output = common::render_module("git_state") - .arg("--path") - .arg(&path) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expected = Color::Yellow.bold().paint("(CHERRY-PICKING)").to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - - Ok(()) -} - -#[test] -fn shows_bisecting() -> io::Result<()> { - let repo_dir = create_repo_with_conflict()?; - let path = path_str(&repo_dir)?; - - run_git_cmd(&["bisect", "start"], Some(path), false)?; - - let output = common::render_module("git_state") - .arg("--path") - .arg(&path) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expected = Color::Yellow.bold().paint("(BISECTING)").to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - - Ok(()) -} - -#[test] -fn shows_reverting() -> io::Result<()> { - let repo_dir = create_repo_with_conflict()?; - let path = path_str(&repo_dir)?; - - run_git_cmd(&["revert", "--no-commit", "HEAD~1"], Some(path), false)?; - - let output = common::render_module("git_state") - .arg("--path") - .arg(&path) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expected = Color::Yellow.bold().paint("(REVERTING)").to_string(); - expected.push(' '); - - assert_eq!(expected, actual); - Ok(()) -} - -fn run_git_cmd(args: A, dir: Option<&str>, expect_ok: bool) -> io::Result<()> -where - A: IntoIterator, - S: AsRef, -{ - let mut command = Command::new("git"); - command - .args(args) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .stdin(Stdio::null()); - - if let Some(dir) = dir { - command.current_dir(dir); - } - - let status = command.status()?; - - if expect_ok && !status.success() { - Err(Error::from(ErrorKind::Other)) - } else { - Ok(()) - } -} - -fn create_repo_with_conflict() -> io::Result { - let repo_dir = tempfile::tempdir()?; - let path = path_str(&repo_dir)?; - let conflicted_file = repo_dir.path().join("the_file"); - - let write_file = |text: &str| { - let mut file = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(&conflicted_file)?; - write!(file, "{}", text) - }; - - // Initialize a new git repo - run_git_cmd(&["init", "--quiet", path], None, true)?; - - // Set local author info - run_git_cmd( - &["config", "--local", "user.email", "starship@example.com"], - Some(path), - true, - )?; - run_git_cmd( - &["config", "--local", "user.name", "starship"], - Some(path), - true, - )?; - - // Write a file on master and commit it - write_file("Version A")?; - run_git_cmd(&["add", "the_file"], Some(path), true)?; - run_git_cmd(&["commit", "--message", "Commit A"], Some(path), true)?; - - // Switch to another branch, and commit a change to the file - run_git_cmd(&["checkout", "-b", "other-branch"], Some(path), true)?; - write_file("Version B")?; - run_git_cmd( - &["commit", "--all", "--message", "Commit B"], - Some(path), - true, - )?; - - // Switch back to master, and commit a third change to the file - run_git_cmd(&["checkout", "master"], Some(path), true)?; - write_file("Version C")?; - run_git_cmd( - &["commit", "--all", "--message", "Commit C"], - Some(path), - true, - )?; - - Ok(repo_dir) -} - -fn path_str(repo_dir: &tempfile::TempDir) -> io::Result<&str> { - repo_dir - .path() - .to_str() - .ok_or_else(|| Error::from(ErrorKind::Other)) -} diff --git a/tests/testsuite/git_status.rs b/tests/testsuite/git_status.rs deleted file mode 100644 index ed6d85c0..00000000 --- a/tests/testsuite/git_status.rs +++ /dev/null @@ -1,680 +0,0 @@ -use ansi_term::{ANSIStrings, Color}; -use remove_dir_all::remove_dir_all; -use std::fs::{self, File}; -use std::io; -use std::path::PathBuf; -use std::process::Command; - -use crate::common::{self, TestCommand}; - -/// Right after the calls to git the filesystem state may not have finished -/// updating yet causing some of the tests to fail. These barriers are placed -/// after each call to git. -/// This barrier is windows-specific though other operating systems may need it -/// in the future. -#[cfg(not(windows))] -fn barrier() {} -#[cfg(windows)] -fn barrier() { - std::thread::sleep(std::time::Duration::from_millis(500)); -} - -fn format_output(symbols: &str) -> String { - format!("{} ", Color::Red.bold().paint(format!("[{}]", symbols))) -} - -#[test] -fn show_nothing_on_empty_dir() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(repo_dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - repo_dir.close() -} - -#[test] -#[ignore] -fn shows_behind() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - behind(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("⇣"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_behind_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - behind(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - behind = "⇣$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("⇣1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_ahead() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - File::create(repo_dir.join("readme.md"))?.sync_all()?; - ahead(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("⇡"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_ahead_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - File::create(repo_dir.join("readme.md"))?.sync_all()?; - ahead(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - ahead="⇡$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("⇡1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_diverged() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - diverge(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("⇕"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_diverged_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - diverge(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - diverged=r"⇕⇡$ahead_count⇣$behind_count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("⇕⇡1⇣1"); - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_conflicted() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_conflict(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("="); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_conflicted_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_conflict(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - conflicted = "=$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("=1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_untracked_file() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_untracked(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("?"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_untracked_file_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_untracked(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - untracked = "?$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("?1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn doesnt_show_untracked_file_if_disabled() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_untracked(&repo_dir)?; - - Command::new("git") - .args(&["config", "status.showUntrackedFiles", "no"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = ""; - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_stashed() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - barrier(); - - create_stash(&repo_dir)?; - - Command::new("git") - .args(&["reset", "--hard", "HEAD"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("$"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -fn shows_stashed_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - barrier(); - - create_stash(&repo_dir)?; - barrier(); - - Command::new("git") - .args(&["reset", "--hard", "HEAD"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - stashed = r"\$$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("$1"); - - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_modified() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_modified(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("!"); - - assert_eq!(expected, actual); - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_modified_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_modified(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - modified = "!$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("!1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_staged_file() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_staged(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("+"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_staged_file_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_staged(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - staged = "+[$count](green)" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!( - "{} ", - ANSIStrings(&[ - Color::Red.bold().paint("[+"), - Color::Green.paint("1"), - Color::Red.bold().paint("]"), - ]) - ); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_renamed_file() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_renamed(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("»"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_renamed_file_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_renamed(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - renamed = "»$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("»1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_deleted_file() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_deleted(&repo_dir)?; - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("✘"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -#[test] -#[ignore] -fn shows_deleted_file_with_count() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - - create_deleted(&repo_dir)?; - - let output = common::render_module("git_status") - .use_config(toml::toml! { - [git_status] - deleted = "✘$count" - }) - .arg("--path") - .arg(&repo_dir) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format_output("✘1"); - - assert_eq!(expected, actual); - - remove_dir_all(repo_dir) -} - -// Whenever a file is manually renamed, git itself ('git status') does not treat such file as renamed, -// but as untracked instead. The following test checks if manually deleted and manually renamed -// files are tracked by git_status module in the same way 'git status' does. -#[test] -#[ignore] -fn ignore_manually_renamed() -> io::Result<()> { - let repo_dir = common::create_fixture_repo()?; - File::create(repo_dir.join("a"))?.sync_all()?; - File::create(repo_dir.join("b"))?.sync_all()?; - Command::new("git") - .args(&["add", "--all"]) - .current_dir(&repo_dir) - .output()?; - Command::new("git") - .args(&["commit", "-m", "add new files"]) - .current_dir(&repo_dir) - .output()?; - - fs::remove_file(repo_dir.join("a"))?; - fs::rename(repo_dir.join("b"), repo_dir.join("c"))?; - barrier(); - - let output = common::render_module("git_status") - .arg("--path") - .arg(&repo_dir) - .env_clear() - .use_config(toml::toml! { - [git_status] - prefix = "" - suffix = "" - style = "" - ahead = "A" - deleted = "D" - untracked = "U" - renamed = "R" - }) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - assert!(actual.contains('A')); - assert!(actual.contains('D')); - assert!(actual.contains('U')); - assert!(!actual.contains('R')); - - remove_dir_all(repo_dir) -} - -fn ahead(repo_dir: &PathBuf) -> io::Result<()> { - File::create(repo_dir.join("readme.md"))?.sync_all()?; - - Command::new("git") - .args(&["commit", "-am", "Update readme"]) - .current_dir(&repo_dir) - .output()?; - barrier(); - - Ok(()) -} - -fn behind(repo_dir: &PathBuf) -> io::Result<()> { - Command::new("git") - .args(&["reset", "--hard", "HEAD^"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Ok(()) -} - -fn diverge(repo_dir: &PathBuf) -> io::Result<()> { - Command::new("git") - .args(&["reset", "--hard", "HEAD^"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - fs::write(repo_dir.join("Cargo.toml"), " ")?; - - Command::new("git") - .args(&["commit", "-am", "Update readme"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Ok(()) -} - -fn create_conflict(repo_dir: &PathBuf) -> io::Result<()> { - Command::new("git") - .args(&["reset", "--hard", "HEAD^"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - fs::write(repo_dir.join("readme.md"), "# goodbye")?; - - Command::new("git") - .args(&["add", "."]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Command::new("git") - .args(&["commit", "-m", "Change readme"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Command::new("git") - .args(&["pull", "--rebase"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Ok(()) -} - -fn create_stash(repo_dir: &PathBuf) -> io::Result<()> { - File::create(repo_dir.join("readme.md"))?.sync_all()?; - barrier(); - - Command::new("git") - .args(&["stash", "--all"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Ok(()) -} - -fn create_untracked(repo_dir: &PathBuf) -> io::Result<()> { - File::create(repo_dir.join("license"))?.sync_all()?; - - Ok(()) -} - -fn create_modified(repo_dir: &PathBuf) -> io::Result<()> { - File::create(repo_dir.join("readme.md"))?.sync_all()?; - - Ok(()) -} - -fn create_staged(repo_dir: &PathBuf) -> io::Result<()> { - File::create(repo_dir.join("license"))?.sync_all()?; - - Command::new("git") - .args(&["add", "."]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Ok(()) -} - -fn create_renamed(repo_dir: &PathBuf) -> io::Result<()> { - Command::new("git") - .args(&["mv", "readme.md", "readme.md.bak"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Command::new("git") - .args(&["add", "-A"]) - .current_dir(repo_dir.as_path()) - .output()?; - barrier(); - - Ok(()) -} - -fn create_deleted(repo_dir: &PathBuf) -> io::Result<()> { - fs::remove_file(repo_dir.join("readme.md"))?; - - Ok(()) -} diff --git a/tests/testsuite/hg_branch.rs b/tests/testsuite/hg_branch.rs deleted file mode 100644 index 8e0928f9..00000000 --- a/tests/testsuite/hg_branch.rs +++ /dev/null @@ -1,239 +0,0 @@ -use ansi_term::{Color, Style}; -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::{env, io}; - -use crate::common::{self, TestCommand}; - -enum Expect<'a> { - BranchName(&'a str), - Empty, - NoTruncation, - Symbol(&'a str), - Style(Style), - TruncationSymbol(&'a str), -} - -#[test] -fn show_nothing_on_empty_dir() -> io::Result<()> { - let repo_dir = tempfile::tempdir()?; - - let output = common::render_module("hg_branch") - .arg("--path") - .arg(repo_dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - repo_dir.close() -} - -#[test] -#[ignore] -fn test_hg_get_branch_fails() -> io::Result<()> { - let tempdir = tempfile::tempdir()?; - - // Create a fake corrupted mercurial repo. - let hgdir = tempdir.path().join(".hg"); - fs::create_dir(&hgdir)?; - fs::write(&hgdir.join("requires"), "fake-corrupted-repo")?; - - expect_hg_branch_with_config( - tempdir.path(), - "", - &[Expect::BranchName(&"default"), Expect::NoTruncation], - )?; - tempdir.close() -} - -#[test] -#[ignore] -fn test_hg_get_branch_autodisabled() -> io::Result<()> { - let tempdir = tempfile::tempdir()?; - expect_hg_branch_with_config(tempdir.path(), "", &[Expect::Empty])?; - tempdir.close() -} - -#[test] -#[ignore] -fn test_hg_bookmark() -> io::Result<()> { - let tempdir = tempfile::tempdir()?; - let repo_dir = create_fixture_hgrepo(&tempdir)?; - run_hg(&["bookmark", "bookmark-101"], &repo_dir)?; - expect_hg_branch_with_config( - &repo_dir, - "", - &[Expect::BranchName(&"bookmark-101"), Expect::NoTruncation], - )?; - tempdir.close() -} - -#[test] -#[ignore] -fn test_default_truncation_symbol() -> io::Result<()> { - let tempdir = tempfile::tempdir()?; - let repo_dir = create_fixture_hgrepo(&tempdir)?; - run_hg(&["branch", "-f", "branch-name-101"], &repo_dir)?; - run_hg( - &[ - "commit", - "-m", - "empty commit 101", - "-u", - "fake user ", - ], - &repo_dir, - )?; - expect_hg_branch_with_config( - &repo_dir, - "truncation_length = 14", - &[Expect::BranchName(&"branch-name-10")], - )?; - tempdir.close() -} - -#[test] -#[ignore] -fn test_configured_symbols() -> io::Result<()> { - let tempdir = tempfile::tempdir()?; - let repo_dir = create_fixture_hgrepo(&tempdir)?; - run_hg(&["branch", "-f", "branch-name-121"], &repo_dir)?; - run_hg( - &[ - "commit", - "-m", - "empty commit 121", - "-u", - "fake user ", - ], - &repo_dir, - )?; - expect_hg_branch_with_config( - &repo_dir, - r#" - symbol = "B " - truncation_length = 14 - truncation_symbol = "%" - "#, - &[ - Expect::BranchName(&"branch-name-12"), - Expect::Symbol(&"B"), - Expect::TruncationSymbol(&"%"), - ], - )?; - tempdir.close() -} - -#[test] -#[ignore] -fn test_configured_style() -> io::Result<()> { - let tempdir = tempfile::tempdir()?; - let repo_dir = create_fixture_hgrepo(&tempdir)?; - run_hg(&["branch", "-f", "branch-name-131"], &repo_dir)?; - run_hg( - &[ - "commit", - "-m", - "empty commit 131", - "-u", - "fake user ", - ], - &repo_dir, - )?; - - expect_hg_branch_with_config( - &repo_dir, - r#" - style = "underline blue" - "#, - &[ - Expect::BranchName(&"branch-name-131"), - Expect::Style(Color::Blue.underline()), - Expect::TruncationSymbol(&""), - ], - )?; - tempdir.close() -} - -fn expect_hg_branch_with_config( - repo_dir: &Path, - config_options: &str, - expectations: &[Expect], -) -> io::Result<()> { - let output = common::render_module("hg_branch") - .use_config(toml::from_str(&format!( - r#" - [hg_branch] - {} - "#, - config_options - ))?) - .arg("--path") - .arg(repo_dir.to_str().unwrap()) - .output()?; - - let actual = String::from_utf8(output.stdout).unwrap(); - - let mut expect_branch_name = "default"; - let mut expect_style = Color::Purple.bold(); - let mut expect_symbol = "\u{e0a0}"; - let mut expect_truncation_symbol = "…"; - - for expect in expectations { - match expect { - Expect::Empty => { - assert_eq!("", actual); - return Ok(()); - } - Expect::Symbol(symbol) => { - expect_symbol = symbol; - } - Expect::TruncationSymbol(truncation_symbol) => { - expect_truncation_symbol = truncation_symbol; - } - Expect::NoTruncation => { - expect_truncation_symbol = ""; - } - Expect::BranchName(branch_name) => { - expect_branch_name = branch_name; - } - Expect::Style(style) => expect_style = *style, - } - } - - let expected = format!( - "on {} ", - expect_style.paint(format!( - "{} {}{}", - expect_symbol, expect_branch_name, expect_truncation_symbol - )), - ); - assert_eq!(expected, actual); - Ok(()) -} - -pub fn create_fixture_hgrepo(tempdir: &tempfile::TempDir) -> io::Result { - let repo_path = tempdir.path().join("hg-repo"); - let fixture_path = env::current_dir()?.join("tests/fixtures/hg-repo.bundle"); - - run_hg( - &[ - "clone", - fixture_path.to_str().unwrap(), - repo_path.to_str().unwrap(), - ], - &tempdir.path(), - )?; - - Ok(repo_path) -} - -fn run_hg(args: &[&str], repo_dir: &Path) -> io::Result<()> { - Command::new("hg") - .args(args) - .current_dir(&repo_dir) - .output()?; - Ok(()) -} diff --git a/tests/testsuite/hostname.rs b/tests/testsuite/hostname.rs deleted file mode 100644 index 0e0338b3..00000000 --- a/tests/testsuite/hostname.rs +++ /dev/null @@ -1,120 +0,0 @@ -use ansi_term::{Color, Style}; -use std::io; - -use crate::common; -use crate::common::TestCommand; - -#[test] -fn ssh_only_false() -> io::Result<()> { - let hostname = match get_hostname() { - Some(h) => h, - None => return hostname_not_tested(), - }; - let output = common::render_module("hostname") - .env_clear() - .use_config(toml::toml! { - [hostname] - ssh_only = false - trim_at = "" - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!("on {} ", style().paint(hostname)); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn no_ssh() -> io::Result<()> { - let output = common::render_module("hostname") - .env_clear() - .use_config(toml::toml! { - [hostname] - ssh_only = true - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!("", actual); - Ok(()) -} - -#[test] -fn ssh() -> io::Result<()> { - let hostname = match get_hostname() { - Some(h) => h, - None => return hostname_not_tested(), - }; - let output = common::render_module("hostname") - .env_clear() - .use_config(toml::toml! { - [hostname] - ssh_only = true - trim_at = "" - }) - .env("SSH_CONNECTION", "something") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!("on {} ", style().paint(hostname)); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn no_trim_at() -> io::Result<()> { - let hostname = match get_hostname() { - Some(h) => h, - None => return hostname_not_tested(), - }; - let output = common::render_module("hostname") - .env_clear() - .use_config(toml::toml! { - [hostname] - ssh_only = false - trim_at = "" - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!("on {} ", style().paint(hostname)); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn trim_at() -> io::Result<()> { - let hostname = match get_hostname() { - Some(h) => h, - None => return hostname_not_tested(), - }; - let (remainder, trim_at) = hostname.split_at(1); - let output = common::render_module("hostname") - .env_clear() - .use_config(toml::toml! { - [hostname] - ssh_only = false - trim_at = trim_at - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - let expected = format!("on {} ", style().paint(remainder)); - assert_eq!(expected, actual); - Ok(()) -} - -fn get_hostname() -> Option { - match gethostname::gethostname().into_string() { - Ok(hostname) => Some(hostname), - Err(_) => None, - } -} - -fn style() -> Style { - Color::Green.bold().dimmed() -} - -fn hostname_not_tested() -> io::Result<()> { - println!( - "hostname was not tested because gethostname failed! \ - This could be caused by your hostname containing invalid UTF." - ); - Ok(()) -} diff --git a/tests/testsuite/jobs.rs b/tests/testsuite/jobs.rs deleted file mode 100644 index cfdd1e9d..00000000 --- a/tests/testsuite/jobs.rs +++ /dev/null @@ -1,66 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common::{self, TestCommand}; - -#[test] -fn config_blank_job_0() -> io::Result<()> { - let output = common::render_module("jobs").arg("--jobs=0").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_blank_job_1() -> io::Result<()> { - let output = common::render_module("jobs").arg("--jobs=1").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Blue.bold().paint("✦")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_blank_job_2() -> io::Result<()> { - let output = common::render_module("jobs").arg("--jobs=2").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Blue.bold().paint("✦2")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_2_job_2() -> io::Result<()> { - let output = common::render_module("jobs") - .use_config(toml::toml! { - [jobs] - threshold = 2 - }) - .arg("--jobs=2") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Blue.bold().paint("✦")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_2_job_3() -> io::Result<()> { - let output = common::render_module("jobs") - .use_config(toml::toml! { - [jobs] - threshold = 2 - }) - .arg("--jobs=3") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("{} ", Color::Blue.bold().paint("✦3")); - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs deleted file mode 100644 index 558a867e..00000000 --- a/tests/testsuite/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -mod aws; -mod character; -mod cmd_duration; -mod common; -mod conda; -mod configuration; -mod directory; -mod dotnet; -mod env_var; -mod gcloud; -mod git_branch; -mod git_commit; -mod git_state; -mod git_status; -mod hg_branch; -mod hostname; -mod jobs; -mod modules; -mod nix_shell; -mod python; -mod shlvl; -mod singularity; -mod terraform; -mod time; -mod username; diff --git a/tests/testsuite/modules.rs b/tests/testsuite/modules.rs deleted file mode 100644 index 0ffcb5ca..00000000 --- a/tests/testsuite/modules.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::io; - -use crate::common; - -#[test] -fn unknown_module_name() -> io::Result<()> { - let unknown_module_name = "some_random_name"; - let output = common::render_module(unknown_module_name).output()?; - let actual_stdout = String::from_utf8(output.stdout).unwrap(); - let actual_stderr = String::from_utf8(output.stderr).unwrap(); - let expected_stdout = ""; - let expected_stderr = format!( - "Error: Unknown module {}. Use starship module --list to list out all supported modules.\n", - unknown_module_name - ); - assert_eq!(expected_stdout, actual_stdout); - assert_eq!(expected_stderr, actual_stderr); - Ok(()) -} - -#[test] -fn known_module_name() -> io::Result<()> { - let output = common::render_module("line_break").output()?; - let actual_stdout = String::from_utf8(output.stdout).unwrap(); - let actual_stderr = String::from_utf8(output.stderr).unwrap(); - let expected_stdout = "\n"; - let expected_stderr = ""; - assert_eq!(expected_stdout, actual_stdout); - assert_eq!(expected_stderr, actual_stderr); - Ok(()) -} diff --git a/tests/testsuite/nix_shell.rs b/tests/testsuite/nix_shell.rs deleted file mode 100644 index f5743e48..00000000 --- a/tests/testsuite/nix_shell.rs +++ /dev/null @@ -1,72 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common; - -#[test] -fn no_env_variables() -> io::Result<()> { - let output = common::render_module("nix_shell").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!("", actual); - Ok(()) -} - -#[test] -fn invalid_env_variables() -> io::Result<()> { - let output = common::render_module("nix_shell") - .env("IN_NIX_SHELL", "something_wrong") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!("", actual); - Ok(()) -} - -#[test] -fn pure_shell() -> io::Result<()> { - let output = common::render_module("nix_shell") - .env("IN_NIX_SHELL", "pure") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Blue.bold().paint("❄️ pure")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn impure_shell() -> io::Result<()> { - let output = common::render_module("nix_shell") - .env("IN_NIX_SHELL", "impure") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Blue.bold().paint("❄️ impure")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn pure_shell_name() -> io::Result<()> { - let output = common::render_module("nix_shell") - .env("IN_NIX_SHELL", "pure") - .env("name", "starship") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Blue.bold().paint("❄️ pure (starship)")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn impure_shell_name() -> io::Result<()> { - let output = common::render_module("nix_shell") - .env("IN_NIX_SHELL", "impure") - .env("name", "starship") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Blue.bold().paint("❄️ impure (starship)")); - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/python.rs b/tests/testsuite/python.rs deleted file mode 100644 index d799f7f9..00000000 --- a/tests/testsuite/python.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::fs::File; -use std::io; - -use crate::common; - -// TODO - These tests should be moved into the python module when we have sorted out mocking of env -// vars. - -#[test] -fn with_virtual_env() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("main.py"))?.sync_all()?; - let output = common::render_module("python") - .env("VIRTUAL_ENV", "/foo/bar/my_venv") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - assert!(actual.contains("my_venv")); - dir.close() -} - -#[test] -fn with_active_venv() -> io::Result<()> { - let dir = tempfile::tempdir()?; - - let output = common::render_module("python") - .env("VIRTUAL_ENV", "/foo/bar/my_venv") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - assert!(actual.contains("my_venv")); - dir.close() -} diff --git a/tests/testsuite/shlvl.rs b/tests/testsuite/shlvl.rs deleted file mode 100644 index 5a5d299e..00000000 --- a/tests/testsuite/shlvl.rs +++ /dev/null @@ -1,159 +0,0 @@ -use ansi_term::{Color, Style}; -use std::io; - -use crate::common; -use crate::common::TestCommand; - -const SHLVL_ENV_VAR: &str = "SHLVL"; - -fn style() -> Style { - // default style - Color::Yellow.bold() -} - -#[test] -fn empty_config() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - }) - .env(SHLVL_ENV_VAR, "2") - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn enabled() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - disabled = false - }) - .env(SHLVL_ENV_VAR, "2") - .output()?; - let expected = format!("{} ", style().paint("↕️ 2")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn no_level() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - disabled = false - }) - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn enabled_config_level_1() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - disabled = false - }) - .env(SHLVL_ENV_VAR, "1") - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn lower_threshold() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - threshold = 1 - disabled = false - }) - .env(SHLVL_ENV_VAR, "1") - .output()?; - let expected = format!("{} ", style().paint("↕️ 1")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn higher_threshold() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - threshold = 3 - disabled = false - }) - .env(SHLVL_ENV_VAR, "1") - .output()?; - let expected = ""; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn custom_style() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - style = "Red Underline" - disabled = false - }) - .env(SHLVL_ENV_VAR, "2") - .output()?; - let expected = format!("{} ", Color::Red.underline().paint("↕️ 2")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn custom_symbol() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - symbol = "shlvl is " - disabled = false - }) - .env(SHLVL_ENV_VAR, "2") - .output()?; - let expected = format!("{} ", style().paint("shlvl is 2")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn formatting() -> io::Result<()> { - let output = common::render_module("shlvl") - .env_clear() - .use_config(toml::toml! { - [shlvl] - format = "$symbol going down [$shlvl]($style) GOING UP " - disabled = false - }) - .env(SHLVL_ENV_VAR, "2") - .output()?; - let expected = format!("↕️ going down {} GOING UP ", style().paint("2")); - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/singularity.rs b/tests/testsuite/singularity.rs deleted file mode 100644 index 84ae45e6..00000000 --- a/tests/testsuite/singularity.rs +++ /dev/null @@ -1,27 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common; - -#[test] -fn no_env_set() -> io::Result<()> { - let output = common::render_module("singularity").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} -#[test] -fn env_set() -> io::Result<()> { - let output = common::render_module("singularity") - .env_clear() - .env("SINGULARITY_NAME", "centos.img") - .output()?; - - let expected = format!("{} ", Color::Blue.bold().dimmed().paint("[centos.img]")); - let actual = String::from_utf8(output.stdout).unwrap(); - - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/terraform.rs b/tests/testsuite/terraform.rs deleted file mode 100644 index e13b9aec..00000000 --- a/tests/testsuite/terraform.rs +++ /dev/null @@ -1,118 +0,0 @@ -use ansi_term::Color; -use std::fs::{self, File}; -use std::io::{self, Write}; - -use crate::common; - -#[test] -fn folder_without_dotterraform() -> io::Result<()> { - let dir = tempfile::tempdir()?; - - let output = common::render_module("terraform") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_tf_file() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("main.tf"))?; - - let output = common::render_module("terraform") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Fixed(105).bold().paint("💠 default")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_workspace_override() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("main.tf"))?; - - let output = common::render_module("terraform") - .arg("--path") - .arg(dir.path()) - .env("TF_WORKSPACE", "development") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Fixed(105).bold().paint("💠 development")); - assert_eq!(expected, actual); - dir.close() -} - -#[test] -#[ignore] -fn folder_with_datadir_override() -> io::Result<()> { - let dir = tempfile::tempdir()?; - File::create(dir.path().join("main.tf"))?; - - let datadir = tempfile::tempdir()?; - let mut file = File::create(datadir.path().join("environment"))?; - file.write_all(b"development")?; - file.sync_all()?; - - let output = common::render_module("terraform") - .arg("--path") - .arg(dir.path()) - .env("TF_DATA_DIR", datadir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Fixed(105).bold().paint("💠 development")); - assert_eq!(expected, actual); - dir.close()?; - datadir.close() -} - -#[test] -#[ignore] -fn folder_with_dotterraform_no_environment() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let tf_dir = dir.path().join(".terraform"); - fs::create_dir(&tf_dir)?; - - let output = common::render_module("terraform") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Fixed(105).bold().paint("💠 default")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -#[ignore] -fn folder_with_dotterraform_with_environment() -> io::Result<()> { - let dir = tempfile::tempdir()?; - let tf_dir = dir.path().join(".terraform"); - fs::create_dir(&tf_dir)?; - let mut file = File::create(tf_dir.join("environment"))?; - file.write_all(b"development")?; - file.sync_all()?; - - let output = common::render_module("terraform") - .arg("--path") - .arg(dir.path()) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Fixed(105).bold().paint("💠 development")); - assert_eq!(expected, actual); - Ok(()) -} diff --git a/tests/testsuite/time.rs b/tests/testsuite/time.rs deleted file mode 100644 index 35b9ed0f..00000000 --- a/tests/testsuite/time.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::io; - -use crate::common::{self, TestCommand}; - -/* Note: tests in this crate cannot rely on the actual time displayed by -the module, since that is dependent on the time inside the test environment, -which we cannot control. - -However, we *can* test certain things here, such as the fact that the module -should not display when disabled, should display *something* when enabled, -and should have the correct prefixes and suffixes in a given config */ - -#[test] -fn config_enabled() -> io::Result<()> { - let output = common::render_module("time") - .use_config(toml::toml! { - [time] - disabled = false - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - // We can't test what it actually is...but we can assert it's not blank - assert!(!actual.is_empty()); - Ok(()) -} - -#[test] -fn config_blank() -> io::Result<()> { - let output = common::render_module("time").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = ""; - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn config_check_prefix_and_suffix() -> io::Result<()> { - let output = common::render_module("time") - .use_config(toml::toml! { - [time] - disabled = false - format = "at [\\[$time\\]]($style) " - time_format = "%T" - }) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - // This is the prefix with "at ", the color code, then the prefix char [ - let col_prefix = format!("at {}{}[", '\u{1b}', "[1;33m"); - - // This is the suffix with suffix char ']', then color codes, then a space - let col_suffix = format!("]{}{} ", '\u{1b}', "[0m"); - - assert!(actual.starts_with(&col_prefix)); - assert!(actual.ends_with(&col_suffix)); - Ok(()) -} diff --git a/tests/testsuite/username.rs b/tests/testsuite/username.rs deleted file mode 100644 index 08f4e668..00000000 --- a/tests/testsuite/username.rs +++ /dev/null @@ -1,79 +0,0 @@ -use ansi_term::Color; -use std::io; - -use crate::common::{self, TestCommand}; - -// TODO: Add tests for if root user (UID == 0) -// Requires mocking - -#[test] -fn no_env_variables() -> io::Result<()> { - let output = common::render_module("username").output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!("", actual); - Ok(()) -} - -#[test] -fn logname_equals_user() -> io::Result<()> { - let output = common::render_module("username") - .env("LOGNAME", "astronaut") - .env("USER", "astronaut") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!("", actual); - Ok(()) -} - -#[test] -fn ssh_wo_username() -> io::Result<()> { - // SSH connection w/o username - let output = common::render_module("username") - .env("SSH_CONNECTION", "192.168.223.17 36673 192.168.223.229 22") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - assert_eq!("", actual); - Ok(()) -} - -#[test] -fn current_user_not_logname() -> io::Result<()> { - let output = common::render_module("username") - .env("LOGNAME", "astronaut") - .env("USER", "cosmonaut") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("cosmonaut")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn ssh_connection() -> io::Result<()> { - let output = common::render_module("username") - .env("USER", "astronaut") - .env("SSH_CONNECTION", "192.168.223.17 36673 192.168.223.229 22") - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("astronaut")); - assert_eq!(expected, actual); - Ok(()) -} - -#[test] -fn show_always() -> io::Result<()> { - let output = common::render_module("username") - .env("USER", "astronaut") - .use_config(toml::toml! { - [username] - show_always = true}) - .output()?; - let actual = String::from_utf8(output.stdout).unwrap(); - - let expected = format!("via {} ", Color::Yellow.bold().paint("astronaut")); - - assert_eq!(expected, actual); - Ok(()) -}