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 6ae0dc0db..2a05adcf0 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 6550a387f..33a84f4f0 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 c34fb521d..c139bb958 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 8069da208..809bac120 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 218b445a5..3f3599a2a 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 b4e6ea937..e8f263b0c 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 9d91dfbf9..2885f90df 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 df0793915..ff432741e 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 d389ada77..000000000 --- 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 6d57aeed1..8b5c5757c 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 394942a59..6ae182acd 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 d37da4b43..da8e1d3c6 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 03b1c004a..abe09865e 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 a9561cd19..9cb687328 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 1a86aea09..6ed7bb102 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 c00817324..9a8935537 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 75d6f3a42..e8b19dffb 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 930b2ea0a..1c8e7b5ad 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 2df871d6e..17a4e83cf 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 000000000..87757b75d --- /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 000000000..025f23dda --- /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 000000000..fcbd6078d --- /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 000000000..d198ff04f --- /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 000000000..54fdd2bbe --- /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 99f1ae72c..06eb03524 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 56353ae29..000000000 --- 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 000000000..4b2f5ebcc --- /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 000000000..1c072c0bf --- /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 f1dce23e7..a2be916d9 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 {