From 4cda183103750342e4c0ad0f8ad6d5a8b8f3ebc8 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Sat, 2 Mar 2024 12:15:31 -0500 Subject: [PATCH] Canonicalize default-config-dir and plugin-path (#11999) # Description This PR makes sure `$nu.default-config-dir` and `$nu.plugin-path` are canonicalized. # User-Facing Changes `$nu.default-config-dir` (and `$nu.plugin-path`) will now give canonical paths, with symlinks and whatnot resolved. # Tests + Formatting I've added a couple of tests to check that even if the config folder and/or any of the config files within are symlinks, the `$nu.*` variables are properly canonicalized. These tests unfortunately only run on Linux and MacOS, because I couldn't figure out how to change the config directory on Windows. Also, given that they involve creating files, I'm not sure if they're excessive, so I could remove one or two of them. # After Submitting --- crates/nu-cli/src/config_files.rs | 9 +- crates/nu-protocol/src/eval_const.rs | 5 +- src/tests/test_config_path.rs | 118 ++++++++++++++++++++++++++- 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/crates/nu-cli/src/config_files.rs b/crates/nu-cli/src/config_files.rs index 5199a1b051..06e20ef248 100644 --- a/crates/nu-cli/src/config_files.rs +++ b/crates/nu-cli/src/config_files.rs @@ -61,17 +61,18 @@ pub fn add_plugin_file( plugin_file: Option>, storage_path: &str, ) { - if let Some(plugin_file) = plugin_file { - let working_set = StateWorkingSet::new(engine_state); - let cwd = working_set.get_cwd(); + let working_set = StateWorkingSet::new(engine_state); + let cwd = working_set.get_cwd(); + if let Some(plugin_file) = plugin_file { if let Ok(path) = canonicalize_with(&plugin_file.item, cwd) { engine_state.plugin_signatures = Some(path) } else { let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span); report_error(&working_set, &e); } - } else if let Some(mut plugin_path) = nu_path::config_dir() { + } else if let Some(plugin_path) = nu_path::config_dir() { + let mut plugin_path = canonicalize_with(&plugin_path, cwd).unwrap_or(plugin_path); // Path to store plugins signatures plugin_path.push(storage_path); plugin_path.push(PLUGIN_FILE); diff --git a/crates/nu-protocol/src/eval_const.rs b/crates/nu-protocol/src/eval_const.rs index 314c3c9465..0f1de11a5c 100644 --- a/crates/nu-protocol/src/eval_const.rs +++ b/crates/nu-protocol/src/eval_const.rs @@ -29,7 +29,7 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result { path.push("nushell"); - Ok(path) + Ok(canonicalize_path(engine_state, &path)) } None => Err(Value::error( ShellError::ConfigDirNotFound { span: Some(span) }, @@ -121,7 +121,8 @@ pub fn create_nu_constant(engine_state: &EngineState, span: Span) -> Result>(p: P) -> String { p.as_ref().display().to_string() @@ -19,8 +24,41 @@ fn adjust_canonicalization>(p: P) -> String { } } -#[test] -fn test_default_config_path() { +/// Make the config directory a symlink that points to a temporary folder. +/// Returns the path to the `nushell` config folder inside, via the symlink. +/// +/// Need to figure out how to change config directory on Windows. +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn setup_fake_config(playground: &mut Playground) -> PathBuf { + #[cfg(target_os = "linux")] + { + let config_dir = "config"; + let config_link = "config_link"; + playground.mkdir(&format!("{config_dir}/nushell")); + playground.symlink(config_dir, config_link); + playground.with_env( + "XDG_CONFIG_HOME", + &playground.cwd().join(config_link).display().to_string(), + ); + Path::new(config_link).join("nushell") + } + + #[cfg(target_os = "macos")] + { + let fake_home = "fake_home"; + let home_link = "home_link"; + let dir_end = "fake-home/Library/Application\\ Support/nushell"; + playground.mkdir(&format!("{fake_home}/{dir_end}")); + playground.symlink(fake_home, home_link); + playground.with_env( + "HOME", + &playground.cwd().join(home_link).display().to_string(), + ); + PathBuf::from(home_link).join(dir_end) + } +} + +fn test_config_path_helper() { let config_dir = nu_path::config_dir().expect("Could not get config directory"); let config_dir_nushell = config_dir.join("nushell"); // Create the config dir folder structure if it does not already exist @@ -69,6 +107,82 @@ fn test_default_config_path() { } } +#[test] +fn test_default_config_path() { + test_config_path_helper(); +} + +/// Make the config folder a symlink to a temporary folder without any config files +/// and see if the config files' paths are properly canonicalized +#[cfg(any(target_os = "linux", target_os = "macos"))] +#[test] +fn test_default_symlinked_config_path_empty() { + Playground::setup("symlinked_empty_config_dir", |_, playground| { + let _ = setup_fake_config(playground); + + test_config_path_helper(); + }); +} + +/// Like [[test_default_symlinked_config_path_empty]], but fill the temporary folder +/// with broken symlinks and see if they're properly canonicalized +#[cfg(any(target_os = "linux", target_os = "macos"))] +#[test] +fn test_default_symlink_config_path_broken_symlink_config_files() { + Playground::setup( + "symlinked_cfg_dir_with_symlinked_cfg_files", + |_, playground| { + let fake_config_dir_nushell = setup_fake_config(playground); + + for config_file in [ + "config.nu", + "env.nu", + "history.txt", + "history.sqlite3", + "login.nu", + "plugin.nu", + ] { + playground.symlink( + format!("fake/{config_file}"), + fake_config_dir_nushell.join(config_file), + ); + } + + test_config_path_helper(); + }, + ); +} + +/// Like [[test_default_symlinked_config_path_empty]], but fill the temporary folder +/// with working symlinks to empty files and see if they're properly canonicalized +#[cfg(any(target_os = "linux", target_os = "macos"))] +#[test] +fn test_default_config_path_symlinked_config_files() { + use std::fs::File; + + Playground::setup( + "symlinked_cfg_dir_with_symlinked_cfg_files", + |_, playground| { + let fake_config_dir_nushell = setup_fake_config(playground); + + for config_file in [ + "config.nu", + "env.nu", + "history.txt", + "history.sqlite3", + "login.nu", + "plugin.nu", + ] { + let empty_file = playground.cwd().join(format!("empty-{config_file}")); + File::create(&empty_file).unwrap(); + playground.symlink(empty_file, fake_config_dir_nushell.join(config_file)); + } + + test_config_path_helper(); + }, + ); +} + #[test] fn test_alternate_config_path() { let config_file = "crates/nu-utils/src/sample_config/default_config.nu";