From d2213d18fa2956e9990eab891bccc892da16a157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Mon, 15 Mar 2021 02:26:30 -0500 Subject: [PATCH] Playground infraestructure (tests, etc) additions. (#3179) * Playground infraestructure (tests, etc) additions. A few things to note: * Nu can be started with a custom configuration file (`nu --config-file /path/to/sample_config.toml`). Useful for mocking the configuration on test runs. * When given a custom configuration file Nu will save any changes to the file supplied appropiately. * The `$nu.config-path` variable either shows the default configuration file (or the custom one, if given) * We can now run end to end tests with finer grained control (currently, since this is baseline work, standard out) This will allow to check things like exit status, assert the contents with a format, etc) * Remove (for another PR) --- Cargo.lock | 14 ++ Cargo.toml | 1 + crates/nu-cli/src/cli.rs | 103 ++++++++- crates/nu-cli/src/lib.rs | 1 + crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/commands.rs | 2 +- .../nu-command/src/commands/config/clear.rs | 16 +- crates/nu-command/src/commands/config/get.rs | 27 ++- crates/nu-command/src/commands/config/load.rs | 51 ----- crates/nu-command/src/commands/config/mod.rs | 2 - crates/nu-command/src/commands/config/path.rs | 17 +- .../nu-command/src/commands/config/remove.rs | 19 +- crates/nu-command/src/commands/config/set.rs | 39 +++- .../src/commands/config/set_into.rs | 26 ++- .../src/commands/default_context.rs | 1 - crates/nu-command/tests/commands/append.rs | 36 ++- crates/nu-command/tests/commands/autoenv.rs | 1 - .../tests/commands/autoenv_trust.rs | 1 - .../tests/commands/autoenv_untrust.rs | 1 - crates/nu-command/tests/commands/ls.rs | 110 +++++---- crates/nu-command/tests/commands/mod.rs | 3 - crates/nu-data/src/config/nuconfig.rs | 8 +- crates/nu-data/src/config/tests.rs | 8 +- crates/nu-engine/Cargo.toml | 1 + crates/nu-engine/src/evaluate/evaluator.rs | 2 +- crates/nu-engine/src/evaluate/variables.rs | 24 +- crates/nu-engine/tests/evaluate/mod.rs | 1 + crates/nu-engine/tests/evaluate/variables.rs | 34 +++ crates/nu-protocol/src/value/unit.rs | 0 crates/nu-test-support/Cargo.toml | 3 +- crates/nu-test-support/src/playground.rs | 184 +-------------- .../src/playground/director.rs | 141 ++++++++++++ .../src/playground/matchers.rs | 106 +++++++++ .../src/playground/nu_process.rs | 99 ++++++++ crates/nu-test-support/src/playground/play.rs | 216 ++++++++++++++++++ .../nu-test-support/src/playground/tests.rs | 47 ++++ src/main.rs | 44 ++-- tests/fixtures/nuplayground/.gitignore | 2 - tests/fixtures/playground/config/default.toml | 1 + tests/shell/environment/configuration.rs | 142 ++++++++++++ tests/shell/environment/mod.rs | 1 + 41 files changed, 1128 insertions(+), 408 deletions(-) delete mode 100644 crates/nu-command/src/commands/config/load.rs delete mode 100644 crates/nu-command/tests/commands/autoenv.rs delete mode 100644 crates/nu-command/tests/commands/autoenv_trust.rs delete mode 100644 crates/nu-command/tests/commands/autoenv_untrust.rs create mode 100644 crates/nu-engine/tests/evaluate/variables.rs create mode 100644 crates/nu-protocol/src/value/unit.rs create mode 100644 crates/nu-test-support/src/playground/director.rs create mode 100644 crates/nu-test-support/src/playground/matchers.rs create mode 100644 crates/nu-test-support/src/playground/nu_process.rs create mode 100644 crates/nu-test-support/src/playground/play.rs create mode 100644 crates/nu-test-support/src/playground/tests.rs delete mode 100644 tests/fixtures/nuplayground/.gitignore create mode 100644 tests/fixtures/playground/config/default.toml create mode 100644 tests/shell/environment/configuration.rs diff --git a/Cargo.lock b/Cargo.lock index 6ae0dc0db6..2a05adcf06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2097,6 +2097,16 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "hamcrest2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f837c62de05dc9cc71ff6486cd85de8856a330395ae338a04bfcefe5e91075" +dependencies = [ + "num", + "regex 1.4.3", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -3065,6 +3075,7 @@ dependencies = [ "ctrlc", "dunce", "futures 0.3.13", + "hamcrest2", "itertools", "log 0.4.14", "nu-cli", @@ -3246,6 +3257,7 @@ dependencies = [ "futures_codec", "getset", "glob", + "hamcrest2", "htmlescape", "ical", "ichwh", @@ -3362,6 +3374,7 @@ dependencies = [ "futures_codec", "getset", "glob", + "hamcrest2", "indexmap", "itertools", "log 0.4.14", @@ -3522,6 +3535,7 @@ dependencies = [ "dunce", "getset", "glob", + "hamcrest2", "indexmap", "nu-errors", "nu-protocol", diff --git a/Cargo.toml b/Cargo.toml index 6550a387f8..33a84f4f0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ pretty_env_logger = "0.4.0" nu-test-support = { version = "0.28.0", path = "./crates/nu-test-support" } dunce = "1.0.1" serial_test = "0.5.1" +hamcrest2 = "0.3.0" [build-dependencies] diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index c34fb521d2..c139bb9580 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -15,8 +15,10 @@ use crate::line_editor::{ #[allow(unused_imports)] use nu_data::config; -use nu_source::{Tag, Text}; +use nu_data::config::{Conf, NuConfig}; +use nu_source::{AnchorLocation, Tag, Text}; use nu_stream::InputStream; +use std::ffi::{OsStr, OsString}; #[allow(unused_imports)] use std::sync::atomic::Ordering; @@ -33,6 +35,67 @@ use std::error::Error; use std::iter::Iterator; use std::path::PathBuf; +pub struct Options { + pub config: Option, + pub stdin: bool, + pub scripts: Vec, +} + +impl Default for Options { + fn default() -> Self { + Self::new() + } +} + +impl Options { + pub fn new() -> Self { + Self { + config: None, + stdin: false, + scripts: vec![], + } + } +} + +pub struct NuScript { + pub filepath: Option, + pub contents: String, +} + +impl NuScript { + pub fn code<'a>(content: impl Iterator) -> Result { + let text = content + .map(|x| x.to_string()) + .collect::>() + .join("\n"); + + Ok(Self { + filepath: None, + contents: text, + }) + } + + pub fn get_code(&self) -> &str { + &self.contents + } + + pub fn source_file(path: &OsStr) -> Result { + use std::fs::File; + use std::io::Read; + + let path = path.to_os_string(); + let mut file = File::open(&path)?; + let mut buffer = String::new(); + + file.read_to_string(&mut buffer)?; + + Ok(Self { + filepath: Some(path), + contents: buffer, + }) + } +} + pub fn search_paths() -> Vec { use std::env; @@ -62,12 +125,9 @@ pub fn search_paths() -> Vec { search_paths } -pub async fn run_script_file( - file_contents: String, - redirect_stdin: bool, -) -> Result<(), Box> { - let mut syncer = EnvironmentSyncer::new(); +pub async fn run_script_file(options: Options) -> Result<(), Box> { let mut context = create_default_context(false)?; + let mut syncer = create_environment_syncer(&context, &options); let config = syncer.get_config(); context.configure(&config, |_, ctx| { @@ -85,15 +145,38 @@ pub async fn run_script_file( let _ = run_startup_commands(&mut context, &config).await; - run_script_standalone(file_contents, redirect_stdin, &context, true).await?; + let script = options + .scripts + .get(0) + .ok_or_else(|| ShellError::unexpected("Nu source code not available"))?; + + run_script_standalone(script.get_code().to_string(), options.stdin, &context, true).await?; Ok(()) } -/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input. +fn create_environment_syncer(context: &EvaluationContext, options: &Options) -> EnvironmentSyncer { + if let Some(config_file) = &options.config { + let location = Some(AnchorLocation::File( + config_file.to_string_lossy().to_string(), + )); + let tag = Tag::unknown().anchored(location); + + context.scope.add_var( + "config-path", + UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag), + ); + + EnvironmentSyncer::with_config(Box::new(NuConfig::with(Some(config_file.into())))) + } else { + EnvironmentSyncer::new() + } +} + #[cfg(feature = "rustyline-support")] -pub async fn cli(mut context: EvaluationContext) -> Result<(), Box> { - let mut syncer = EnvironmentSyncer::new(); +pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(), Box> { + let mut syncer = create_environment_syncer(&context, &options); + let configuration = syncer.get_config(); let mut rl = default_rustyline_editor_configuration(); diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 8069da2082..809bac120c 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -28,6 +28,7 @@ pub mod types; pub use crate::cli::cli; pub use crate::cli::{parse_and_eval, register_plugins, run_script_file}; +pub use crate::cli::{NuScript, Options}; pub use crate::env::environment_syncer::EnvironmentSyncer; pub use nu_command::commands::default_context::create_default_context; diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 218b445a5a..3f3599a2a1 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -124,6 +124,7 @@ shadow-rs = "0.5" [dev-dependencies] quickcheck = "1.0.3" quickcheck_macros = "1.0.0" +hamcrest2 = "0.3.0" [features] clipboard-cli = ["arboard"] diff --git a/crates/nu-command/src/commands.rs b/crates/nu-command/src/commands.rs index b4e6ea937c..e8f263b0cc 100644 --- a/crates/nu-command/src/commands.rs +++ b/crates/nu-command/src/commands.rs @@ -152,7 +152,7 @@ pub(crate) use char_::Char; pub(crate) use chart::Chart; pub(crate) use compact::Compact; pub(crate) use config::{ - Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto, + Config, ConfigClear, ConfigGet, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto, }; pub(crate) use cp::Cpy; pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone}; diff --git a/crates/nu-command/src/commands/config/clear.rs b/crates/nu-command/src/commands/config/clear.rs index 9d91dfbf95..2885f90df9 100644 --- a/crates/nu-command/src/commands/config/clear.rs +++ b/crates/nu-command/src/commands/config/clear.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; +use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value}; pub struct SubCommand; @@ -35,13 +35,19 @@ impl WholeStreamCommand for SubCommand { pub async fn clear(args: CommandArgs) -> Result { let name_span = args.call_info.name_tag.clone(); - // NOTE: None because we are not loading a new config file, we just want to read from the - // existing config - let mut result = nu_data::config::read(name_span, &None)?; + let path = match args.scope.get_var("config-path") { + Some(Value { + value: UntaggedValue::Primitive(Primitive::FilePath(path)), + .. + }) => Some(path), + _ => nu_data::config::default_path().ok(), + }; + + let mut result = nu_data::config::read(name_span, &path)?; result.clear(); - config::write(&result, &None)?; + config::write(&result, &path)?; Ok(OutputStream::one(ReturnSuccess::value( UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag), diff --git a/crates/nu-command/src/commands/config/get.rs b/crates/nu-command/src/commands/config/get.rs index df07939156..ff432741e9 100644 --- a/crates/nu-command/src/commands/config/get.rs +++ b/crates/nu-command/src/commands/config/get.rs @@ -1,13 +1,15 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_protocol::{ + ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, +}; pub struct SubCommand; #[derive(Deserialize)] -pub struct GetArgs { - path: ColumnPath, +pub struct Arguments { + column_path: ColumnPath, } #[async_trait] @@ -42,14 +44,21 @@ impl WholeStreamCommand for SubCommand { } pub async fn get(args: CommandArgs) -> Result { - let name_tag = args.call_info.name_tag.clone(); - let (GetArgs { path }, _) = args.process().await?; + let name = args.call_info.name_tag.clone(); + let scope = args.scope.clone(); + let (Arguments { column_path }, _) = args.process().await?; - // NOTE: None because we are not loading a new config file, we just want to read from the - // existing config - let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag); + let path = match scope.get_var("config-path") { + Some(Value { + value: UntaggedValue::Primitive(Primitive::FilePath(path)), + .. + }) => Some(path), + _ => nu_data::config::default_path().ok(), + }; - let value = crate::commands::get::get_column_path(&path, &result)?; + let result = UntaggedValue::row(nu_data::config::read(&name, &path)?).into_value(&name); + + let value = crate::commands::get::get_column_path(&column_path, &result)?; Ok(match value { Value { diff --git a/crates/nu-command/src/commands/config/load.rs b/crates/nu-command/src/commands/config/load.rs deleted file mode 100644 index d389ada77b..0000000000 --- a/crates/nu-command/src/commands/config/load.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::prelude::*; -use nu_engine::WholeStreamCommand; -use nu_errors::ShellError; -use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; -use nu_source::Tagged; -use std::path::PathBuf; - -pub struct SubCommand; - -#[derive(Deserialize)] -pub struct LoadArgs { - load: Tagged, -} - -#[async_trait] -impl WholeStreamCommand for SubCommand { - fn name(&self) -> &str { - "config load" - } - - fn signature(&self) -> Signature { - Signature::build("config load").required( - "load", - SyntaxShape::FilePath, - "Path to load the config from", - ) - } - - fn usage(&self) -> &str { - "Loads the config from the path given" - } - - async fn run(&self, args: CommandArgs) -> Result { - set(args).await - } -} - -pub async fn set(args: CommandArgs) -> Result { - let name = args.call_info.name_tag.clone(); - let name_span = args.call_info.name_tag.clone(); - let (LoadArgs { load }, _) = args.process().await?; - - let configuration = load.item().clone(); - - let result = nu_data::config::read(name_span, &Some(configuration))?; - - Ok(futures::stream::iter(vec![ReturnSuccess::value( - UntaggedValue::Row(result.into()).into_value(name), - )]) - .to_output_stream()) -} diff --git a/crates/nu-command/src/commands/config/mod.rs b/crates/nu-command/src/commands/config/mod.rs index 6d57aeed1a..8b5c5757cc 100644 --- a/crates/nu-command/src/commands/config/mod.rs +++ b/crates/nu-command/src/commands/config/mod.rs @@ -1,7 +1,6 @@ pub mod clear; pub mod command; pub mod get; -pub mod load; pub mod path; pub mod remove; pub mod set; @@ -10,7 +9,6 @@ pub mod set_into; pub use clear::SubCommand as ConfigClear; pub use command::Command as Config; pub use get::SubCommand as ConfigGet; -pub use load::SubCommand as ConfigLoad; pub use path::SubCommand as ConfigPath; pub use remove::SubCommand as ConfigRemove; pub use set::SubCommand as ConfigSet; diff --git a/crates/nu-command/src/commands/config/path.rs b/crates/nu-command/src/commands/config/path.rs index 394942a59b..6ae182acdb 100644 --- a/crates/nu-command/src/commands/config/path.rs +++ b/crates/nu-command/src/commands/config/path.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue}; +use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value}; pub struct SubCommand; @@ -33,9 +33,18 @@ impl WholeStreamCommand for SubCommand { } pub async fn path(args: CommandArgs) -> Result { - let path = config::default_path()?; - Ok(OutputStream::one(ReturnSuccess::value( - UntaggedValue::Primitive(Primitive::FilePath(path)).into_value(args.call_info.name_tag), + match args.scope.get_var("config-path") { + Some( + path + @ + Value { + value: UntaggedValue::Primitive(Primitive::FilePath(_)), + .. + }, + ) => path, + _ => UntaggedValue::Primitive(Primitive::FilePath(nu_data::config::default_path()?)) + .into_value(args.call_info.name_tag), + }, ))) } diff --git a/crates/nu-command/src/commands/config/remove.rs b/crates/nu-command/src/commands/config/remove.rs index d37da4b439..da8e1d3c6a 100644 --- a/crates/nu-command/src/commands/config/remove.rs +++ b/crates/nu-command/src/commands/config/remove.rs @@ -1,13 +1,13 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; +use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_source::Tagged; pub struct SubCommand; #[derive(Deserialize)] -pub struct RemoveArgs { +pub struct Arguments { remove: Tagged, } @@ -44,15 +44,24 @@ impl WholeStreamCommand for SubCommand { pub async fn remove(args: CommandArgs) -> Result { let name_span = args.call_info.name_tag.clone(); - let (RemoveArgs { remove }, _) = args.process().await?; + let scope = args.scope.clone(); + let (Arguments { remove }, _) = args.process().await?; - let mut result = nu_data::config::read(name_span, &None)?; + let path = match scope.get_var("config-path") { + Some(Value { + value: UntaggedValue::Primitive(Primitive::FilePath(path)), + .. + }) => Some(path), + _ => nu_data::config::default_path().ok(), + }; + + let mut result = nu_data::config::read(name_span, &path)?; let key = remove.to_string(); if result.contains_key(&key) { result.swap_remove(&key); - config::write(&result, &None)?; + config::write(&result, &path)?; Ok(futures::stream::iter(vec![ReturnSuccess::value( UntaggedValue::Row(result.into()).into_value(remove.tag()), )]) diff --git a/crates/nu-command/src/commands/config/set.rs b/crates/nu-command/src/commands/config/set.rs index 03b1c004a2..abe09865ec 100644 --- a/crates/nu-command/src/commands/config/set.rs +++ b/crates/nu-command/src/commands/config/set.rs @@ -1,13 +1,15 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_protocol::{ + ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value, +}; pub struct SubCommand; #[derive(Deserialize)] -pub struct SetArgs { - path: ColumnPath, +pub struct Arguments { + column_path: ColumnPath, value: Value, } @@ -58,13 +60,26 @@ impl WholeStreamCommand for SubCommand { } pub async fn set(args: CommandArgs) -> Result { - let name_tag = args.call_info.name_tag.clone(); - let (SetArgs { path, mut value }, _) = args.process().await?; + let name = args.call_info.name_tag.clone(); + let scope = args.scope.clone(); + let ( + Arguments { + column_path, + mut value, + }, + _, + ) = args.process().await?; - // NOTE: None because we are not loading a new config file, we just want to read from the - // existing config - let raw_entries = nu_data::config::read(&name_tag, &None)?; - let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag); + let path = match scope.get_var("config-path") { + Some(Value { + value: UntaggedValue::Primitive(Primitive::FilePath(path)), + .. + }) => Some(path), + _ => nu_data::config::default_path().ok(), + }; + + let raw_entries = nu_data::config::read(&name, &path)?; + let configuration = UntaggedValue::row(raw_entries).into_value(&name); if let UntaggedValue::Table(rows) = &value.value { if rows.len() == 1 && rows[0].is_row() { @@ -72,15 +87,15 @@ pub async fn set(args: CommandArgs) -> Result { } } - match configuration.forgiving_insert_data_at_column_path(&path, value) { + match configuration.forgiving_insert_data_at_column_path(&column_path, value) { Ok(Value { value: UntaggedValue::Row(changes), .. }) => { - config::write(&changes.entries, &None)?; + config::write(&changes.entries, &path)?; Ok(OutputStream::one(ReturnSuccess::value( - UntaggedValue::Row(changes).into_value(name_tag), + UntaggedValue::Row(changes).into_value(name), ))) } Ok(_) => Ok(OutputStream::empty()), diff --git a/crates/nu-command/src/commands/config/set_into.rs b/crates/nu-command/src/commands/config/set_into.rs index a9561cd191..9cb6873283 100644 --- a/crates/nu-command/src/commands/config/set_into.rs +++ b/crates/nu-command/src/commands/config/set_into.rs @@ -1,13 +1,13 @@ use crate::prelude::*; use nu_engine::WholeStreamCommand; use nu_errors::ShellError; -use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_source::Tagged; pub struct SubCommand; #[derive(Deserialize)] -pub struct SetIntoArgs { +pub struct Arguments { set_into: Tagged, } @@ -43,17 +43,19 @@ impl WholeStreamCommand for SubCommand { } pub async fn set_into(args: CommandArgs) -> Result { - let name_span = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone(); + let scope = args.scope.clone(); + let (Arguments { set_into: v }, input) = args.process().await?; - let (SetIntoArgs { set_into: v }, input) = args.process().await?; + let path = match scope.get_var("config-path") { + Some(Value { + value: UntaggedValue::Primitive(Primitive::FilePath(path)), + .. + }) => Some(path), + _ => nu_data::config::default_path().ok(), + }; - // NOTE: None because we are not loading a new config file, we just want to read from the - // existing config - let mut result = nu_data::config::read(name_span, &None)?; - - // In the original code, this is set to `Some` if the `load flag is set` - let configuration = None; + let mut result = nu_data::config::read(&name, &path)?; let rows: Vec = input.collect().await; let key = v.to_string(); @@ -70,7 +72,7 @@ pub async fn set_into(args: CommandArgs) -> Result { result.insert(key, value.clone()); - config::write(&result, &configuration)?; + config::write(&result, &path)?; OutputStream::one(ReturnSuccess::value( UntaggedValue::Row(result.into()).into_value(name), @@ -81,7 +83,7 @@ pub async fn set_into(args: CommandArgs) -> Result { result.insert(key, value); - config::write(&result, &configuration)?; + config::write(&result, &path)?; OutputStream::one(ReturnSuccess::value( UntaggedValue::Row(result.into()).into_value(name), diff --git a/crates/nu-command/src/commands/default_context.rs b/crates/nu-command/src/commands/default_context.rs index 1a86aea099..6ed7bb1025 100644 --- a/crates/nu-command/src/commands/default_context.rs +++ b/crates/nu-command/src/commands/default_context.rs @@ -29,7 +29,6 @@ pub fn create_default_context(interactive: bool) -> Result) -> NuConfig { + pub fn with(config_file: Option) -> NuConfig { match &config_file { None => NuConfig::new(), Some(_) => { - let vars = if let Ok(variables) = read(Tag::unknown(), &config_file) { + let source_file = config_file.map(std::path::PathBuf::from); + + let vars = if let Ok(variables) = read(Tag::unknown(), &source_file) { variables } else { IndexMap::default() @@ -59,7 +61,7 @@ impl NuConfig { NuConfig { vars, - modified_at: NuConfig::get_last_modified(&config_file), + modified_at: NuConfig::get_last_modified(&source_file), } } } diff --git a/crates/nu-data/src/config/tests.rs b/crates/nu-data/src/config/tests.rs index c008173240..9a89355375 100644 --- a/crates/nu-data/src/config/tests.rs +++ b/crates/nu-data/src/config/tests.rs @@ -36,11 +36,11 @@ impl Conf for FakeConfig { impl FakeConfig { pub fn new(config_file: &Path) -> FakeConfig { - let config_file = Some(PathBuf::from(config_file)); + let config_file = config_file.to_path_buf(); FakeConfig { - config: NuConfig::with(config_file.clone()), - source_file: config_file, + config: NuConfig::with(Some(config_file.clone().into_os_string())), + source_file: Some(config_file), } } @@ -61,6 +61,6 @@ impl FakeConfig { } pub fn reload(&mut self) { - self.config = NuConfig::with(self.source_file.clone()); + self.config = NuConfig::with(self.source_file.clone().map(|x| x.into_os_string())); } } diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 75d6f3a423..e8b19dffb0 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -51,6 +51,7 @@ users = "0.11.0" [dev-dependencies] nu-test-support = { version = "0.28.0", path = "../nu-test-support" } +hamcrest2 = "0.3.0" [features] rustyline-support = [] diff --git a/crates/nu-engine/src/evaluate/evaluator.rs b/crates/nu-engine/src/evaluate/evaluator.rs index 930b2ea0ad..1c8e7b5adb 100644 --- a/crates/nu-engine/src/evaluate/evaluator.rs +++ b/crates/nu-engine/src/evaluate/evaluator.rs @@ -232,7 +232,7 @@ fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value { fn evaluate_reference(name: &str, ctx: &EvaluationContext, tag: Tag) -> Result { match name { - "$nu" => crate::evaluate::variables::nu(&ctx.scope.get_env_vars(), tag), + "$nu" => crate::evaluate::variables::nu(&ctx.scope, tag), "$true" => Ok(Value { value: UntaggedValue::boolean(true), diff --git a/crates/nu-engine/src/evaluate/variables.rs b/crates/nu-engine/src/evaluate/variables.rs index 2df871d6ed..17a4e83cff 100644 --- a/crates/nu-engine/src/evaluate/variables.rs +++ b/crates/nu-engine/src/evaluate/variables.rs @@ -1,10 +1,11 @@ +use crate::evaluate::scope::Scope; use crate::history_path::history_path; -use indexmap::IndexMap; use nu_errors::ShellError; -use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value}; +use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value}; use nu_source::Tag; -pub fn nu(env: &IndexMap, tag: impl Into) -> Result { +pub fn nu(scope: &Scope, tag: impl Into) -> Result { + let env = &scope.get_env_vars(); let tag = tag.into(); let mut nu_dict = TaggedDictBuilder::new(&tag); @@ -17,7 +18,15 @@ pub fn nu(env: &IndexMap, tag: impl Into) -> Result Some(path), + _ => None, + }; + + let config = nu_data::config::read(&tag, &config_file)?; nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag)); let mut table = vec![]; @@ -40,7 +49,12 @@ pub fn nu(env: &IndexMap, tag: impl Into) -> Result PathBuf { - self.fixtures.join("formats") - } -} - -impl Playground { - pub fn root(&self) -> &Path { - self.root.path() - } - - pub fn back_to_playground(&mut self) -> &mut Self { - self.cwd = PathBuf::from(self.root()).join(self.tests.clone()); - self - } - - pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) { - let root = tempdir().expect("Couldn't create a tempdir"); - let nuplay_dir = root.path().join(topic); - - if PathBuf::from(&nuplay_dir).exists() { - std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory"); - } - - std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); - - let mut playground = Playground { - root, - tests: topic.to_string(), - cwd: nuplay_dir, - }; - - let playground_root = playground.root.path(); - - let fixtures = fs::fixtures(); - let fixtures = dunce::canonicalize(fixtures.clone()).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize fixtures path {}: {:?}", - fixtures.display(), - e - ) - }); - - let test = dunce::canonicalize(playground_root.join(topic)).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize test path {}: {:?}", - playground_root.join(topic).display(), - e - ) - }); - - let root = dunce::canonicalize(playground_root).unwrap_or_else(|e| { - panic!( - "Couldn't canonicalize tests root path {}: {:?}", - playground_root.display(), - e - ) - }); - - let dirs = Dirs { - root, - test, - fixtures, - }; - - block(dirs, &mut playground); - } - - pub fn mkdir(&mut self, directory: &str) -> &mut Self { - self.cwd.push(directory); - std::fs::create_dir_all(&self.cwd).expect("can not create directory"); - self.back_to_playground(); - self - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn symlink(&mut self, from: impl AsRef, to: impl AsRef) -> &mut Self { - let from = self.cwd.join(from); - let to = self.cwd.join(to); - - let create_symlink = { - #[cfg(unix)] - { - std::os::unix::fs::symlink - } - - #[cfg(windows)] - { - if from.is_file() { - std::os::windows::fs::symlink_file - } else if from.is_dir() { - std::os::windows::fs::symlink_dir - } else { - panic!("symlink from must be a file or dir") - } - } - }; - - create_symlink(from, to).expect("can not create symlink"); - self.back_to_playground(); - self - } - - pub fn with_files(&mut self, files: Vec) -> &mut Self { - let endl = fs::line_ending(); - - files - .iter() - .map(|f| { - let mut path = PathBuf::from(&self.cwd); - - let (file_name, contents) = match *f { - Stub::EmptyFile(name) => (name, "fake data".to_string()), - Stub::FileWithContent(name, content) => (name, content.to_string()), - Stub::FileWithContentToBeTrimmed(name, content) => ( - name, - content - .lines() - .skip(1) - .map(|line| line.trim()) - .collect::>() - .join(&endl), - ), - }; - - path.push(file_name); - - std::fs::write(path, contents.as_bytes()).expect("can not create file"); - }) - .for_each(drop); - self.back_to_playground(); - self - } - - pub fn within(&mut self, directory: &str) -> &mut Self { - self.cwd.push(directory); - std::fs::create_dir(&self.cwd).expect("can not create directory"); - self - } - - pub fn glob_vec(pattern: &str) -> Vec { - let glob = glob(pattern); - - glob.expect("invalid pattern") - .map(|path| { - if let Ok(path) = path { - path - } else { - unreachable!() - } - }) - .collect() - } -} +pub use director::Director; +pub use matchers::says; +pub use nu_process::{Executable, NuProcess, NuResult, Outcome}; +pub use play::{Dirs, Playground}; diff --git a/crates/nu-test-support/src/playground/director.rs b/crates/nu-test-support/src/playground/director.rs new file mode 100644 index 0000000000..87757b75da --- /dev/null +++ b/crates/nu-test-support/src/playground/director.rs @@ -0,0 +1,141 @@ +use super::nu_process::*; +use std::ffi::OsString; +use std::fmt; + +#[derive(Default, Debug)] +pub struct Director { + pub cwd: Option, + pub config: Option, + pub pipeline: Option, + pub executable: Option, +} + +impl Director { + pub fn cococo(&self, arg: &str) -> Self { + let mut process = NuProcess::default(); + process.args(&["--testbin", "cococo", arg]); + Director { + config: self.config.clone(), + executable: Some(process), + ..Default::default() + } + } + + pub fn pipeline(&self, commands: &str) -> Self { + let mut director = Director { + pipeline: if commands.is_empty() { + None + } else { + Some(format!( + " + {} + exit", + commands + )) + }, + ..Default::default() + }; + + let mut process = NuProcess::default(); + + if let Some(working_directory) = &self.cwd { + process.cwd(working_directory); + } + + process.arg("--skip-plugins"); + if let Some(config_file) = self.config.as_ref() { + process.args(&[ + "--config-file", + config_file.to_str().expect("failed to convert."), + ]); + } + + director.executable = Some(process); + director + } + + pub fn executable(&self) -> Option<&NuProcess> { + if let Some(binary) = &self.executable { + Some(binary) + } else { + None + } + } +} + +impl Executable for Director { + fn execute(&self) -> NuResult { + use std::io::Write; + use std::process::Stdio; + + match self.executable() { + Some(binary) => { + let mut process = match binary + .construct() + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {}", why.to_string()), + }; + + if let Some(pipeline) = &self.pipeline { + process + .stdin + .as_mut() + .expect("couldn't open stdin") + .write_all(pipeline.as_bytes()) + .expect("couldn't write to stdin"); + } + + process + .wait_with_output() + .map_err(|_| { + let reason = format!( + "could not execute process {} ({})", + binary, "No execution took place" + ); + + NuError { + desc: reason, + exit: None, + output: None, + } + }) + .and_then(|process| { + let out = + Outcome::new(&read_std(&process.stdout), &read_std(&process.stderr)); + + match process.status.success() { + true => Ok(out), + false => Err(NuError { + desc: String::new(), + exit: Some(process.status), + output: Some(out), + }), + } + }) + } + None => Err(NuError { + desc: String::from("err"), + exit: None, + output: None, + }), + } + } +} + +impl fmt::Display for Director { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "director") + } +} + +fn read_std(std: &[u8]) -> Vec { + let out = String::from_utf8_lossy(std); + let out = out.lines().collect::>().join("\n"); + let out = out.replace("\r\n", ""); + out.replace("\n", "").into_bytes() +} diff --git a/crates/nu-test-support/src/playground/matchers.rs b/crates/nu-test-support/src/playground/matchers.rs new file mode 100644 index 0000000000..025f23ddab --- /dev/null +++ b/crates/nu-test-support/src/playground/matchers.rs @@ -0,0 +1,106 @@ +use hamcrest2::core::{MatchResult, Matcher}; +use std::fmt; +use std::str; + +use super::nu_process::Outcome; +use super::{Director, Executable}; + +#[derive(Clone)] +pub struct Play { + stdout_expectation: Option, +} + +impl fmt::Display for Play { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "play") + } +} + +impl fmt::Debug for Play { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "play") + } +} + +pub fn says() -> Play { + Play { + stdout_expectation: None, + } +} + +trait CheckerMatchers { + fn output(&self, actual: &Outcome) -> MatchResult; + fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult; + fn stdout(&self, actual: &Outcome) -> MatchResult; +} + +impl CheckerMatchers for Play { + fn output(&self, actual: &Outcome) -> MatchResult { + self.stdout(actual) + } + + fn stdout(&self, actual: &Outcome) -> MatchResult { + self.std(&actual.out, self.stdout_expectation.as_ref(), "stdout") + } + + fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult { + let out = match expected { + Some(out) => out, + None => return Ok(()), + }; + let actual = match str::from_utf8(actual) { + Err(..) => return Err(format!("{} was not utf8 encoded", description)), + Ok(actual) => actual, + }; + + if actual != *out { + return Err(format!( + "not equal:\n actual: {}\n expected: {}\n\n", + actual, out + )); + } + + Ok(()) + } +} + +impl Matcher for Play { + fn matches(&self, output: Outcome) -> MatchResult { + self.output(&output) + } +} + +impl Matcher for Play { + fn matches(&self, mut director: Director) -> MatchResult { + self.matches(&mut director) + } +} + +impl<'a> Matcher<&'a mut Director> for Play { + fn matches(&self, director: &'a mut Director) -> MatchResult { + if director.executable().is_none() { + return Err(format!("no such process {}", director)); + } + + let res = director.execute(); + + match res { + Ok(out) => self.output(&out), + Err(err) => { + if let Some(out) = &err.output { + return self.output(out); + } + + Err(format!("could not exec process {}: {:?}", director, err)) + } + } + } +} + +impl Play { + #[allow(clippy::clippy::wrong_self_convention)] + pub fn to_stdout(mut self, expected: &str) -> Self { + self.stdout_expectation = Some(expected.to_string()); + self + } +} diff --git a/crates/nu-test-support/src/playground/nu_process.rs b/crates/nu-test-support/src/playground/nu_process.rs new file mode 100644 index 0000000000..fcbd6078d4 --- /dev/null +++ b/crates/nu-test-support/src/playground/nu_process.rs @@ -0,0 +1,99 @@ +use crate::fs::executable_path; +use std::collections::HashMap; +use std::ffi::{OsStr, OsString}; +use std::fmt; +use std::path::Path; +use std::process::{Command, ExitStatus}; + +pub trait Executable { + fn execute(&self) -> NuResult; +} + +#[derive(Clone, Debug)] +pub struct Outcome { + pub out: Vec, + pub err: Vec, +} + +impl Outcome { + pub fn new(out: &[u8], err: &[u8]) -> Outcome { + Outcome { + out: out.to_vec(), + err: err.to_vec(), + } + } +} + +pub type NuResult = Result; + +#[derive(Debug)] +pub struct NuError { + pub desc: String, + pub exit: Option, + pub output: Option, +} + +#[derive(Clone, Debug)] +pub struct NuProcess { + pub arguments: Vec, + pub environment_vars: HashMap>, + pub cwd: Option, +} + +impl fmt::Display for NuProcess { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "`nu")?; + + for arg in &self.arguments { + write!(f, " {}", arg.to_string_lossy())?; + } + + write!(f, "`") + } +} + +impl Default for NuProcess { + fn default() -> Self { + Self { + arguments: vec![], + environment_vars: HashMap::default(), + cwd: None, + } + } +} + +impl NuProcess { + pub fn arg>(&mut self, arg: T) -> &mut Self { + self.arguments.push(arg.as_ref().to_os_string()); + self + } + + pub fn args>(&mut self, arguments: &[T]) -> &mut NuProcess { + self.arguments + .extend(arguments.iter().map(|t| t.as_ref().to_os_string())); + self + } + + pub fn cwd>(&mut self, path: T) -> &mut NuProcess { + self.cwd = Some(path.as_ref().to_os_string()); + self + } + + pub fn get_cwd(&self) -> Option<&Path> { + self.cwd.as_ref().map(Path::new) + } + + pub fn construct(&self) -> Command { + let mut command = Command::new(&executable_path()); + + if let Some(cwd) = self.get_cwd() { + command.current_dir(cwd); + } + + for arg in &self.arguments { + command.arg(arg); + } + + command + } +} diff --git a/crates/nu-test-support/src/playground/play.rs b/crates/nu-test-support/src/playground/play.rs new file mode 100644 index 0000000000..d198ff04fd --- /dev/null +++ b/crates/nu-test-support/src/playground/play.rs @@ -0,0 +1,216 @@ +use super::Director; +use crate::fs; +use crate::fs::Stub; +use getset::Getters; +use glob::glob; +use std::path::{Path, PathBuf}; +use std::str; +use tempfile::{tempdir, TempDir}; + +pub struct Playground<'a> { + root: TempDir, + tests: String, + cwd: PathBuf, + config: PathBuf, + dirs: &'a Dirs, +} + +#[derive(Default, Getters, Clone)] +#[get = "pub"] +pub struct Dirs { + pub root: PathBuf, + pub test: PathBuf, + pub fixtures: PathBuf, +} + +impl Dirs { + pub fn formats(&self) -> PathBuf { + self.fixtures.join("formats") + } +} + +impl<'a> Playground<'a> { + pub fn root(&self) -> &Path { + self.root.path() + } + + pub fn cwd(&self) -> &Path { + &self.cwd + } + + pub fn back_to_playground(&mut self) -> &mut Self { + self.cwd = PathBuf::from(self.root()).join(self.tests.clone()); + self + } + + pub fn play(&mut self) -> &mut Self { + self + } + + pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) { + let root = tempdir().expect("Couldn't create a tempdir"); + let nuplay_dir = root.path().join(topic); + + if PathBuf::from(&nuplay_dir).exists() { + std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory"); + } + + std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); + + let fixtures = fs::fixtures(); + let fixtures = dunce::canonicalize(fixtures.clone()).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize fixtures path {}: {:?}", + fixtures.display(), + e + ) + }); + + let mut playground = Playground { + root, + tests: topic.to_string(), + cwd: nuplay_dir, + config: fixtures.join("playground/config/default.toml"), + dirs: &Dirs::default(), + }; + + let playground_root = playground.root.path(); + + let test = dunce::canonicalize(playground_root.join(topic)).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize test path {}: {:?}", + playground_root.join(topic).display(), + e + ) + }); + + let root = dunce::canonicalize(playground_root).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize tests root path {}: {:?}", + playground_root.display(), + e + ) + }); + + let dirs = Dirs { + root, + test, + fixtures, + }; + + playground.dirs = &dirs; + + block(dirs.clone(), &mut playground); + } + + pub fn with_config(&mut self, source_file: impl AsRef) -> &mut Self { + self.config = source_file.as_ref().to_path_buf(); + self + } + + pub fn get_config(&self) -> &str { + self.config.to_str().expect("could not convert path.") + } + + pub fn build(&mut self) -> Director { + Director { + cwd: Some(self.dirs.test().into()), + config: Some(self.config.clone().into()), + ..Default::default() + } + } + + pub fn cococo(&mut self, arg: &str) -> Director { + self.build().cococo(arg) + } + + pub fn pipeline(&mut self, commands: &str) -> Director { + self.build().pipeline(commands) + } + + pub fn mkdir(&mut self, directory: &str) -> &mut Self { + self.cwd.push(directory); + std::fs::create_dir_all(&self.cwd).expect("can not create directory"); + self.back_to_playground(); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn symlink(&mut self, from: impl AsRef, to: impl AsRef) -> &mut Self { + let from = self.cwd.join(from); + let to = self.cwd.join(to); + + let create_symlink = { + #[cfg(unix)] + { + std::os::unix::fs::symlink + } + + #[cfg(windows)] + { + if from.is_file() { + std::os::windows::fs::symlink_file + } else if from.is_dir() { + std::os::windows::fs::symlink_dir + } else { + panic!("symlink from must be a file or dir") + } + } + }; + + create_symlink(from, to).expect("can not create symlink"); + self.back_to_playground(); + self + } + + pub fn with_files(&mut self, files: Vec) -> &mut Self { + let endl = fs::line_ending(); + + files + .iter() + .map(|f| { + let mut path = PathBuf::from(&self.cwd); + + let (file_name, contents) = match *f { + Stub::EmptyFile(name) => (name, "fake data".to_string()), + Stub::FileWithContent(name, content) => (name, content.to_string()), + Stub::FileWithContentToBeTrimmed(name, content) => ( + name, + content + .lines() + .skip(1) + .map(|line| line.trim()) + .collect::>() + .join(&endl), + ), + }; + + path.push(file_name); + + std::fs::write(path, contents.as_bytes()).expect("can not create file"); + }) + .for_each(drop); + self.back_to_playground(); + self + } + + pub fn within(&mut self, directory: &str) -> &mut Self { + self.cwd.push(directory); + std::fs::create_dir(&self.cwd).expect("can not create directory"); + self + } + + pub fn glob_vec(pattern: &str) -> Vec { + let glob = glob(pattern); + + glob.expect("invalid pattern") + .map(|path| { + if let Ok(path) = path { + path + } else { + unreachable!() + } + }) + .collect() + } +} diff --git a/crates/nu-test-support/src/playground/tests.rs b/crates/nu-test-support/src/playground/tests.rs new file mode 100644 index 0000000000..54fdd2bbe4 --- /dev/null +++ b/crates/nu-test-support/src/playground/tests.rs @@ -0,0 +1,47 @@ +use crate::playground::Playground; +use std::path::{Path, PathBuf}; + +use super::matchers::says; +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +fn path(p: &Path) -> PathBuf { + dunce::canonicalize(p) + .unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e)) +} + +#[test] +fn asserts_standard_out_expectation_from_nu_executable() { + Playground::setup("topic", |_, nu| { + assert_that!(nu.cococo("andres"), says().to_stdout("andres")); + }) +} + +#[test] +fn asserts_standard_out_expectation_from_nu_executable_pipeline_fed() { + Playground::setup("topic", |_, nu| { + assert_that!(nu.pipeline("echo 'andres'"), says().to_stdout("andres")); + }) +} + +#[test] +fn current_working_directory_in_sandbox_directory_created() { + Playground::setup("topic", |dirs, nu| { + let original_cwd = dirs.test(); + nu.within("some_directory_within"); + + assert_eq!(path(&nu.cwd()), original_cwd.join("some_directory_within")); + }) +} + +#[test] +fn current_working_directory_back_to_root_from_anywhere() { + Playground::setup("topic", |dirs, nu| { + let original_cwd = dirs.test(); + + nu.within("some_directory_within"); + nu.back_to_playground(); + + assert_eq!(path(&nu.cwd()), *original_cwd); + }) +} diff --git a/src/main.rs b/src/main.rs index 99f1ae72ca..06eb03524d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,21 @@ use clap::{App, Arg}; use log::LevelFilter; -use nu_cli::create_default_context; +use nu_cli::{create_default_context, NuScript, Options}; use nu_command::utils::test_bins as binaries; use std::error::Error; -use std::fs::File; -use std::io::prelude::*; fn main() -> Result<(), Box> { + let mut options = Options::new(); + let matches = App::new("nushell") .version(clap::crate_version!()) + .arg( + Arg::with_name("config-file") + .long("config-file") + .help("custom configuration source file") + .hidden(true) + .takes_value(true), + ) .arg( Arg::with_name("loglevel") .short("l") @@ -84,6 +91,11 @@ fn main() -> Result<(), Box> { return Ok(()); } + options.config = matches + .value_of("config-file") + .map(std::ffi::OsString::from); + options.stdin = matches.is_present("stdin"); + let loglevel = match matches.value_of("loglevel") { None => LevelFilter::Warn, Some("error") => LevelFilter::Error, @@ -125,28 +137,20 @@ fn main() -> Result<(), Box> { match matches.values_of("commands") { None => {} Some(values) => { - let script_text: String = values - .map(|x| x.to_string()) - .collect::>() - .join("\n"); - futures::executor::block_on(nu_cli::run_script_file( - script_text, - matches.is_present("stdin"), - ))?; + options.scripts = vec![NuScript::code(values)?]; + + futures::executor::block_on(nu_cli::run_script_file(options))?; return Ok(()); } } match matches.value_of("script") { - Some(script) => { - let mut file = File::open(script)?; - let mut buffer = String::new(); - file.read_to_string(&mut buffer)?; + Some(filepath) => { + let filepath = std::ffi::OsString::from(filepath); - futures::executor::block_on(nu_cli::run_script_file( - buffer, - matches.is_present("stdin"), - ))?; + options.scripts = vec![NuScript::source_file(filepath.as_os_str())?]; + + futures::executor::block_on(nu_cli::run_script_file(options))?; return Ok(()); } @@ -159,7 +163,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "rustyline-support")] { - futures::executor::block_on(nu_cli::cli(context))?; + futures::executor::block_on(nu_cli::cli(context, options))?; } #[cfg(not(feature = "rustyline-support"))] diff --git a/tests/fixtures/nuplayground/.gitignore b/tests/fixtures/nuplayground/.gitignore deleted file mode 100644 index 56353ae29e..0000000000 --- a/tests/fixtures/nuplayground/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*_test* -*.txt diff --git a/tests/fixtures/playground/config/default.toml b/tests/fixtures/playground/config/default.toml new file mode 100644 index 0000000000..4b2f5ebcc2 --- /dev/null +++ b/tests/fixtures/playground/config/default.toml @@ -0,0 +1 @@ +skip_welcome_message = true \ No newline at end of file diff --git a/tests/shell/environment/configuration.rs b/tests/shell/environment/configuration.rs new file mode 100644 index 0000000000..1c072c0bfa --- /dev/null +++ b/tests/shell/environment/configuration.rs @@ -0,0 +1,142 @@ +use nu_test_support::fs::{file_contents, Stub::FileWithContent}; +use nu_test_support::fs::{AbsolutePath, DisplayPath}; +use nu_test_support::pipeline as input; +use nu_test_support::playground::{says, Executable, Playground}; + +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +#[test] +fn clears_the_configuration() { + Playground::setup("config_clear_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + pivot_mode = "arepas" + "#, + )]); + + assert!(nu.pipeline("config clear").execute().is_ok()); + assert!(file_contents(&file).is_empty()); + }); +} + +#[test] +fn retrieves_config_values() { + Playground::setup("config_get_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + + [arepa] + colors = ["yellow", "white"] + "#, + )]); + + assert_that!( + nu.pipeline("config get arepa.colors.0"), + says().to_stdout("yellow") + ); + }) +} + +#[test] +fn sets_a_config_value() { + Playground::setup("config_set_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + + [nu] + meal = "taco" + "#, + )]); + + assert!(nu.pipeline("config set nu.meal 'arepa'").execute().is_ok()); + + assert_that!(nu.pipeline("config get nu.meal"), says().to_stdout("arepa")); + }) +} + +#[test] +fn sets_config_values_into_one_property() { + Playground::setup("config_set_into_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + "#, + )]); + + assert!(nu + .pipeline(&input( + r#" + echo ["amarillo", "blanco"] + | config set_into arepa_colors + "#, + )) + .execute() + .is_ok()); + + assert_that!( + nu.pipeline("config get arepa_colors.1"), + says().to_stdout("blanco") + ); + }) +} + +#[test] +fn config_path() { + Playground::setup("config_path_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + "#, + )]); + + assert_that!( + nu.pipeline("config path"), + says().to_stdout(&file.display_path()) + ); + }) +} + +#[test] +fn removes_config_values() { + Playground::setup("config_remove_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + "#, + )]); + + assert!(nu + .pipeline("config remove skip_welcome_message") + .execute() + .is_ok()); + assert!(file_contents(&file).is_empty()); + }) +} diff --git a/tests/shell/environment/mod.rs b/tests/shell/environment/mod.rs index f1dce23e71..a2be916d9b 100644 --- a/tests/shell/environment/mod.rs +++ b/tests/shell/environment/mod.rs @@ -1,3 +1,4 @@ +mod configuration; mod nu_env; pub mod support {