use kitest::println;
#[macro_export]
macro_rules! nu {
(
@options [ $($options:tt)* ]
cwd: $value:expr,
$($rest:tt)*
) => {
nu!(@options [ $($options)* cwd => $crate::fs::in_directory($value) ; ] $($rest)*)
};
(
@options [ $($options:tt)* ]
$field:ident : $value:expr,
$($rest:tt)*
) => {
nu!(@options [ $($options)* $field => $value.into() ; ] $($rest)*)
};
(
@options [ $($options:tt)* ]
$path:expr
$(, $part:expr)*
$(,)*
) => {{
let opts = nu!(@nu_opts $($options)*);
let path = $path;
nu!(@main opts, path)
}};
(@nu_opts $( $field:ident => $value:expr ; )*) => {
$crate::macros::NuOpts{
$(
$field: Some($value),
)*
..Default::default()
}
};
(@main $opts:expr, $path:expr) => {{
$crate::macros::nu_run_test($opts, $path, false)
}};
($($token:tt)*) => {{
nu!(@options [ ] $($token)*)
}};
}
#[macro_export]
macro_rules! nu_with_std {
(
@options [ $($options:tt)* ]
cwd: $value:expr,
$($rest:tt)*
) => {
nu_with_std!(@options [ $($options)* cwd => $crate::fs::in_directory($value) ; ] $($rest)*)
};
(
@options [ $($options:tt)* ]
$field:ident : $value:expr,
$($rest:tt)*
) => {
nu_with_std!(@options [ $($options)* $field => $value.into() ; ] $($rest)*)
};
(
@options [ $($options:tt)* ]
$path:expr
$(, $part:expr)*
$(,)*
) => {{
let opts = nu_with_std!(@nu_opts $($options)*);
let path = nu_with_std!(@format_path $path, $($part),*);
nu_with_std!(@main opts, path)
}};
(@nu_opts $( $field:ident => $value:expr ; )*) => {
$crate::macros::NuOpts{
$(
$field: Some($value),
)*
..Default::default()
}
};
(@format_path $path:expr $(,)?) => {
$path
};
(@format_path $path:expr, $($part:expr),* $(,)?) => {{
format!($path, $( $part ),*)
}};
(@main $opts:expr, $path:expr) => {{
$crate::macros::nu_run_test($opts, $path, true)
}};
($($token:tt)*) => {{
nu_with_std!(@options [ ] $($token)*)
}};
}
#[macro_export]
macro_rules! nu_with_plugins {
(cwd: $cwd:expr, plugins: [$(($plugin_name:expr)),*$(,)?], $command:expr) => {{
nu_with_plugins!(
cwd: $cwd,
envs: Vec::<(&str, &str)>::new(),
plugins: [$(($plugin_name)),*],
$command
)
}};
(cwd: $cwd:expr, plugin: ($plugin_name:expr), $command:expr) => {{
nu_with_plugins!(
cwd: $cwd,
envs: Vec::<(&str, &str)>::new(),
plugin: ($plugin_name),
$command
)
}};
(
cwd: $cwd:expr,
envs: $envs:expr,
plugins: [$(($plugin_name:expr)),*$(,)?],
$command:expr
) => {{
$crate::macros::nu_with_plugin_run_test($cwd, $envs, &[$($plugin_name),*], $command)
}};
(cwd: $cwd:expr, envs: $envs:expr, plugin: ($plugin_name:expr), $command:expr) => {{
$crate::macros::nu_with_plugin_run_test($cwd, $envs, &[$plugin_name], $command)
}};
}
use crate::Outcome;
use nu_path::{AbsolutePath, AbsolutePathBuf, Path, PathBuf};
use nu_utils::consts::NATIVE_PATH_ENV_VAR;
use std::{
ffi::OsStr,
process::{Command, Stdio},
};
use tempfile::tempdir;
#[derive(Default)]
pub struct NuOpts {
pub cwd: Option<AbsolutePathBuf>,
pub locale: Option<String>,
pub envs: Option<Vec<(String, String)>>,
pub experimental: Option<Vec<String>>,
pub collapse_output: Option<bool>,
pub env_config: Option<PathBuf>,
}
pub fn nu_run_test(opts: NuOpts, commands: impl AsRef<str>, with_std: bool) -> Outcome {
let test_bins = crate::fs::binaries()
.canonicalize()
.expect("Could not canonicalize dummy binaries path");
let mut paths = crate::shell_os_paths();
paths.insert(0, test_bins.into());
let commands = commands.as_ref();
let paths_joined = match std::env::join_paths(paths) {
Ok(all) => all,
Err(_) => panic!("Couldn't join paths for PATH var."),
};
let target_cwd = opts.cwd.unwrap_or_else(crate::fs::root);
let locale = opts.locale.unwrap_or("en_US.UTF-8".to_string());
let executable_path = crate::fs::executable_path();
let mut command = setup_command(&executable_path, &target_cwd);
command
.env(nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR, locale)
.env(NATIVE_PATH_ENV_VAR, paths_joined);
if let Some(envs) = opts.envs {
command.envs(envs);
}
match opts.env_config {
Some(path) => command.arg("--env-config").arg(path),
None => command.arg("--no-config-file"),
};
if let Some(experimental_opts) = opts.experimental {
let opts = format!("[{}]", experimental_opts.join(","));
command.arg(format!("--experimental-options={opts}"));
}
if !with_std {
command.arg("--no-std-lib");
}
command.args(["--error-style", "plain", "--commands", commands]);
command.stdout(Stdio::piped()).stderr(Stdio::piped());
let process = match command.spawn() {
Ok(child) => child,
Err(why) => panic!("Can't run test {:?} {}", crate::fs::executable_path(), why),
};
let output = process
.wait_with_output()
.expect("couldn't read from stdout/stderr");
let out = String::from_utf8_lossy(&output.stdout);
let err = String::from_utf8_lossy(&output.stderr);
let out = if opts.collapse_output.unwrap_or(true) {
collapse_output(&out)
} else {
out.into_owned()
};
println!("=== stderr\n{err}");
Outcome::new(out, err.into_owned(), output.status)
}
pub fn nu_with_plugin_run_test<E, K, V>(
cwd: impl AsRef<Path>,
envs: E,
plugins: &[&str],
command: &str,
) -> Outcome
where
E: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
let test_bins = crate::fs::binaries();
let test_bins = nu_path::canonicalize_with(&test_bins, ".").unwrap_or_else(|e| {
panic!(
"Couldn't canonicalize dummy binaries path {}: {:?}",
test_bins.display(),
e
)
});
let temp = tempdir().expect("couldn't create a temporary directory");
let [temp_config_file, temp_env_config_file] = ["config.nu", "env.nu"].map(|name| {
let temp_file = temp.path().join(name);
std::fs::File::create(&temp_file).expect("couldn't create temporary config file");
temp_file
});
let temp_plugin_file = temp.path().join("plugin.msgpackz");
crate::commands::ensure_plugins_built();
let plugin_paths: Vec<std::path::PathBuf> = plugins
.iter()
.map(|plugin_name| {
let plugin = with_exe(plugin_name);
nu_path::canonicalize_with(&plugin, &test_bins)
.unwrap_or_else(|_| panic!("failed to canonicalize plugin {} path", &plugin))
})
.collect();
let target_cwd = crate::fs::in_directory(&cwd);
let mut executable_path = crate::fs::executable_path();
if !executable_path.exists() {
executable_path = crate::fs::installed_nu_path();
}
let mut cmd = setup_command(&executable_path, &target_cwd);
cmd.envs(envs)
.arg("--commands")
.arg(command)
.args(["--error-style", "plain"])
.arg("--config")
.arg(temp_config_file)
.arg("--env-config")
.arg(temp_env_config_file)
.arg("--plugin-config")
.arg(temp_plugin_file);
if !plugin_paths.is_empty() {
cmd.arg("--plugins");
for path in &plugin_paths {
cmd.arg(path);
}
}
let process = match cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() {
Ok(child) => child,
Err(why) => panic!("Can't run test {why}"),
};
let output = process
.wait_with_output()
.expect("couldn't read from stdout/stderr");
let out = collapse_output(&String::from_utf8_lossy(&output.stdout));
let err = String::from_utf8_lossy(&output.stderr);
println!("=== stderr\n{err}");
Outcome::new(out, err.into_owned(), output.status)
}
fn with_exe(name: &str) -> String {
#[cfg(windows)]
{
name.to_string() + ".exe"
}
#[cfg(not(windows))]
{
name.to_string()
}
}
fn collapse_output(out: &str) -> String {
let out = out.lines().collect::<Vec<_>>().join("\n");
let out = out.replace("\r\n", "");
out.replace('\n', "")
}
fn setup_command(executable_path: &AbsolutePath, target_cwd: &AbsolutePath) -> Command {
let mut command = Command::new(executable_path);
command
.env_clear()
.current_dir(target_cwd)
.env_remove("FILE_PWD")
.env("PWD", target_cwd);
let envs: std::collections::HashMap<String, String> = std::env::vars()
.filter(|(n, _)| {
n.starts_with("System") || n == "NUSHELL_CARGO_PROFILE" || n == "PATHEXT" || n == "TMP"
|| n == "TEMP"
|| n == "USERPROFILE"
|| n == "TMPDIR"
|| n.starts_with("CARGO_")
|| n.starts_with("RUSTUP_")
})
.collect();
#[cfg(windows)]
let mut envs = envs;
#[cfg(windows)]
if let Some(pathext) = envs.get_mut("PATHEXT")
&& !pathext.to_uppercase().contains(".PS1")
{
pathext.push_str(";.PS1");
}
command.envs(envs);
command
}