nushell/tests/plugins/cache_file.rs
Devyn Cairns 1f4131532d
Deprecate register and add plugin use (#12607)
# Description

Adds a new keyword, `plugin use`. Unlike `register`, this merely loads
the signatures from the plugin cache file. The file is configurable with
the `--plugin-config` option either to `nu` or to `plugin use` itself,
just like the other `plugin` family of commands. At the REPL, one might
do this to replace `register`:

```nushell
> plugin add ~/.cargo/bin/nu_plugin_foo
> plugin use foo
```

This will not work in a script, because `plugin use` is a keyword and
`plugin add` does not evaluate at parse time (intentionally). This means
we no longer run random binaries during parse.

The `--plugins` option has been added to allow running `nu` with certain
plugins in one step. This is used especially for the `nu_with_plugins!`
test macro, but I'd imagine is generally useful. The only weird quirk is
that it has to be a list, and we don't really do this for any of our
other CLI args at the moment.

`register` now prints a deprecation parse warning.

This should fix #11923, as we now have a complete alternative to
`register`.

# User-Facing Changes

- Add `plugin use` command
- Deprecate `register`
- Add `--plugins` option to `nu` to replace a common use of `register`

# Tests + Formatting

I think I've tested it thoroughly enough and every existing test passes.
Testing nu CLI options and alternate config files is a little hairy and
I wish there were some more generic helpers for this, so this will go on
my TODO list for refactoring.

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

# After Submitting

- [ ] Update plugins sections of book
- [ ] Release notes
2024-04-23 06:37:50 -05:00

334 lines
11 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_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"));
})
}
/// 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_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);
})
}