nushell/tests/plugins/cache_file.rs
Devyn Cairns b576123b0a
Accept filenames in other plugin management commands (#12639)
# Description

This allows the following commands to all accept a filename instead of a
plugin name:

- `plugin use`
- `plugin rm`
- `plugin stop`

Slightly complicated because of the need to also check against
`NU_PLUGIN_DIRS`, but I also fixed some issues with that at the same
time

Requested by @fdncred

# User-Facing Changes

The new commands are updated as described.

# Tests + Formatting

Tests for `NU_PLUGIN_DIRS` handling also made more robust.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting

- [ ] Double check new docs to make sure they describe this capability
2024-04-24 06:28:45 -05:00

475 lines
15 KiB
Rust

use std::{fs::File, path::PathBuf};
use nu_protocol::{PluginCacheFile, PluginCacheItem, PluginCacheItemData};
use nu_test_support::{fs::Stub, nu, nu_with_plugins, playground::Playground};
fn example_plugin_path() -> PathBuf {
nu_test_support::commands::ensure_plugins_built();
let bins_path = nu_test_support::fs::binaries();
nu_path::canonicalize_with(
if cfg!(windows) {
"nu_plugin_example.exe"
} else {
"nu_plugin_example"
},
bins_path,
)
.expect("nu_plugin_example not found")
}
#[test]
fn plugin_add_then_restart_nu() {
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!("
plugin add '{}'
(
^$nu.current-exe
--config $nu.config-path
--env-config $nu.env-path
--plugin-config $nu.plugin-path
--commands 'plugin list | get name | to json --raw'
)
", example_plugin_path().display())
);
assert!(result.status.success());
assert_eq!(r#"["example"]"#, result.out);
}
#[test]
fn plugin_add_in_nu_plugin_dirs_const() {
let example_plugin_path = example_plugin_path();
let dirname = example_plugin_path.parent().expect("no parent");
let filename = example_plugin_path
.file_name()
.expect("no file_name")
.to_str()
.expect("not utf-8");
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!(
r#"
$env.NU_PLUGIN_DIRS = null
const NU_PLUGIN_DIRS = ['{0}']
plugin add '{1}'
(
^$nu.current-exe
--config $nu.config-path
--env-config $nu.env-path
--plugin-config $nu.plugin-path
--commands 'plugin list | get name | to json --raw'
)
"#,
dirname.display(),
filename
)
);
assert!(result.status.success());
assert_eq!(r#"["example"]"#, result.out);
}
#[test]
fn plugin_add_in_nu_plugin_dirs_env() {
let example_plugin_path = example_plugin_path();
let dirname = example_plugin_path.parent().expect("no parent");
let filename = example_plugin_path
.file_name()
.expect("no file_name")
.to_str()
.expect("not utf-8");
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!(
r#"
$env.NU_PLUGIN_DIRS = ['{0}']
plugin add '{1}'
(
^$nu.current-exe
--config $nu.config-path
--env-config $nu.env-path
--plugin-config $nu.plugin-path
--commands 'plugin list | get name | to json --raw'
)
"#,
dirname.display(),
filename
)
);
assert!(result.status.success());
assert_eq!(r#"["example"]"#, result.out);
}
#[test]
fn plugin_add_to_custom_path() {
let example_plugin_path = example_plugin_path();
Playground::setup("plugin add to custom path", |dirs, _playground| {
let result = nu!(
cwd: dirs.test(),
&format!("
plugin add --plugin-config test-plugin-file.msgpackz '{}'
", example_plugin_path.display())
);
assert!(result.status.success());
let contents = PluginCacheFile::read_from(
File::open(dirs.test().join("test-plugin-file.msgpackz"))
.expect("failed to open plugin file"),
None,
)
.expect("failed to read plugin file");
assert_eq!(1, contents.plugins.len());
assert_eq!("example", contents.plugins[0].name);
})
}
#[test]
fn plugin_rm_then_restart_nu() {
let example_plugin_path = example_plugin_path();
Playground::setup("plugin rm from custom path", |dirs, playground| {
playground.with_files(vec![
Stub::FileWithContent("config.nu", ""),
Stub::FileWithContent("env.nu", ""),
]);
let file = File::create(dirs.test().join("test-plugin-file.msgpackz"))
.expect("failed to create file");
let mut contents = PluginCacheFile::new();
contents.upsert_plugin(PluginCacheItem {
name: "example".into(),
filename: example_plugin_path,
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents.upsert_plugin(PluginCacheItem {
name: "foo".into(),
// this doesn't exist, but it should be ok
filename: dirs.test().join("nu_plugin_foo"),
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents
.write_to(file, None)
.expect("failed to write plugin file");
assert_cmd::Command::new(nu_test_support::fs::executable_path())
.current_dir(dirs.test())
.args([
"--no-std-lib",
"--config",
"config.nu",
"--env-config",
"env.nu",
"--plugin-config",
"test-plugin-file.msgpackz",
"--commands",
"plugin rm example",
])
.assert()
.success()
.stderr("");
assert_cmd::Command::new(nu_test_support::fs::executable_path())
.current_dir(dirs.test())
.args([
"--no-std-lib",
"--config",
"config.nu",
"--env-config",
"env.nu",
"--plugin-config",
"test-plugin-file.msgpackz",
"--commands",
"plugin list | get name | to json --raw",
])
.assert()
.success()
.stdout("[\"foo\"]\n");
})
}
#[test]
fn plugin_rm_not_found() {
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
r#"
plugin rm example
"#
);
assert!(!result.status.success());
assert!(result.err.contains("example"));
}
#[test]
fn plugin_rm_from_custom_path() {
let example_plugin_path = example_plugin_path();
Playground::setup("plugin rm from custom path", |dirs, _playground| {
let file = File::create(dirs.test().join("test-plugin-file.msgpackz"))
.expect("failed to create file");
let mut contents = PluginCacheFile::new();
contents.upsert_plugin(PluginCacheItem {
name: "example".into(),
filename: example_plugin_path,
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents.upsert_plugin(PluginCacheItem {
name: "foo".into(),
// this doesn't exist, but it should be ok
filename: dirs.test().join("nu_plugin_foo"),
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents
.write_to(file, None)
.expect("failed to write plugin file");
let result = nu!(
cwd: dirs.test(),
"plugin rm --plugin-config test-plugin-file.msgpackz example",
);
assert!(result.status.success());
assert!(result.err.trim().is_empty());
// Check the contents after running
let contents = PluginCacheFile::read_from(
File::open(dirs.test().join("test-plugin-file.msgpackz")).expect("failed to open file"),
None,
)
.expect("failed to read file");
assert!(!contents.plugins.iter().any(|p| p.name == "example"));
// Shouldn't remove anything else
assert!(contents.plugins.iter().any(|p| p.name == "foo"));
})
}
#[test]
fn plugin_rm_using_filename() {
let example_plugin_path = example_plugin_path();
Playground::setup("plugin rm using filename", |dirs, _playground| {
let file = File::create(dirs.test().join("test-plugin-file.msgpackz"))
.expect("failed to create file");
let mut contents = PluginCacheFile::new();
contents.upsert_plugin(PluginCacheItem {
name: "example".into(),
filename: example_plugin_path.clone(),
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents.upsert_plugin(PluginCacheItem {
name: "foo".into(),
// this doesn't exist, but it should be ok
filename: dirs.test().join("nu_plugin_foo"),
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents
.write_to(file, None)
.expect("failed to write plugin file");
let result = nu!(
cwd: dirs.test(),
&format!(
"plugin rm --plugin-config test-plugin-file.msgpackz '{}'",
example_plugin_path.display()
)
);
assert!(result.status.success());
assert!(result.err.trim().is_empty());
// Check the contents after running
let contents = PluginCacheFile::read_from(
File::open(dirs.test().join("test-plugin-file.msgpackz")).expect("failed to open file"),
None,
)
.expect("failed to read file");
assert!(!contents.plugins.iter().any(|p| p.name == "example"));
// Shouldn't remove anything else
assert!(contents.plugins.iter().any(|p| p.name == "foo"));
})
}
/// Running nu with a test plugin file that fails to parse on one plugin should just cause a warning
/// but the others should be loaded
#[test]
fn warning_on_invalid_plugin_item() {
let example_plugin_path = example_plugin_path();
Playground::setup("warning on invalid plugin item", |dirs, playground| {
playground.with_files(vec![
Stub::FileWithContent("config.nu", ""),
Stub::FileWithContent("env.nu", ""),
]);
let file = File::create(dirs.test().join("test-plugin-file.msgpackz"))
.expect("failed to create file");
let mut contents = PluginCacheFile::new();
contents.upsert_plugin(PluginCacheItem {
name: "example".into(),
filename: example_plugin_path,
shell: None,
data: PluginCacheItemData::Valid { commands: vec![] },
});
contents.upsert_plugin(PluginCacheItem {
name: "badtest".into(),
// this doesn't exist, but it should be ok
filename: dirs.test().join("nu_plugin_badtest"),
shell: None,
data: PluginCacheItemData::Invalid,
});
contents
.write_to(file, None)
.expect("failed to write plugin file");
let result = assert_cmd::Command::new(nu_test_support::fs::executable_path())
.current_dir(dirs.test())
.args([
"--no-std-lib",
"--config",
"config.nu",
"--env-config",
"env.nu",
"--plugin-config",
"test-plugin-file.msgpackz",
"--commands",
"plugin list | get name | to json --raw",
])
.output()
.expect("failed to run nu");
let out = String::from_utf8_lossy(&result.stdout).trim().to_owned();
let err = String::from_utf8_lossy(&result.stderr).trim().to_owned();
println!("=== stdout\n{out}\n=== stderr\n{err}");
// The code should still execute successfully
assert!(result.status.success());
// The "example" plugin should be unaffected
assert_eq!(r#"["example"]"#, out);
// The warning should be in there
assert!(err.contains("cached plugin data"));
assert!(err.contains("badtest"));
})
}
#[test]
fn plugin_use_error_not_found() {
Playground::setup("plugin use error not found", |dirs, playground| {
playground.with_files(vec![
Stub::FileWithContent("config.nu", ""),
Stub::FileWithContent("env.nu", ""),
]);
// Make an empty msgpackz
let file = File::create(dirs.test().join("plugin.msgpackz"))
.expect("failed to open plugin.msgpackz");
PluginCacheFile::default()
.write_to(file, None)
.expect("failed to write empty cache file");
let output = assert_cmd::Command::new(nu_test_support::fs::executable_path())
.current_dir(dirs.test())
.args(["--config", "config.nu"])
.args(["--env-config", "env.nu"])
.args(["--plugin-config", "plugin.msgpackz"])
.args(["--commands", "plugin use custom_values"])
.output()
.expect("failed to run nu");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("Plugin not found"));
})
}
#[test]
fn plugin_add_and_then_use() {
let example_plugin_path = example_plugin_path();
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!(r#"
plugin add '{}'
(
^$nu.current-exe
--config $nu.config-path
--env-config $nu.env-path
--plugin-config $nu.plugin-path
--commands 'plugin use example; plugin list | get name | to json --raw'
)
"#, example_plugin_path.display())
);
assert!(result.status.success());
assert_eq!(r#"["example"]"#, result.out);
}
#[test]
fn plugin_add_and_then_use_by_filename() {
let example_plugin_path = example_plugin_path();
let result = nu_with_plugins!(
cwd: ".",
plugins: [],
&format!(r#"
plugin add '{0}'
(
^$nu.current-exe
--config $nu.config-path
--env-config $nu.env-path
--plugin-config $nu.plugin-path
--commands 'plugin use '{0}'; plugin list | get name | to json --raw'
)
"#, example_plugin_path.display())
);
assert!(result.status.success());
assert_eq!(r#"["example"]"#, result.out);
}
#[test]
fn plugin_add_then_use_with_custom_path() {
let example_plugin_path = example_plugin_path();
Playground::setup("plugin add to custom path", |dirs, _playground| {
let result_add = nu!(
cwd: dirs.test(),
&format!("
plugin add --plugin-config test-plugin-file.msgpackz '{}'
", example_plugin_path.display())
);
assert!(result_add.status.success());
let result_use = nu!(
cwd: dirs.test(),
r#"
plugin use --plugin-config test-plugin-file.msgpackz example
plugin list | get name | to json --raw
"#
);
assert!(result_use.status.success());
assert_eq!(r#"["example"]"#, result_use.out);
})
}