From cc1b784e3db1c550eedb011ac47496b9e1b32d41 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 2 Feb 2022 15:59:01 -0500 Subject: [PATCH] Add initial nu-test-support port (#913) * Add initial nu-test-support port * finish changing binary name * Oops, these aren't Windows-safe tests --- Cargo.lock | 222 ++- Cargo.toml | 14 +- crates/nu-command/Cargo.toml | 7 + crates/nu-engine/src/eval.rs | 24 +- crates/nu-parser/src/type_check.rs | 32 +- crates/nu-protocol/src/engine/command.rs | 4 +- crates/nu-protocol/src/value/mod.rs | 44 + crates/nu-test-support/Cargo.toml | 23 + crates/nu-test-support/src/commands.rs | 53 + crates/nu-test-support/src/fs.rs | 277 ++++ crates/nu-test-support/src/lib.rs | 70 + crates/nu-test-support/src/macros.rs | 170 +++ crates/nu-test-support/src/playground.rs | 12 + .../src/playground/director.rs | 163 ++ .../src/playground/matchers.rs | 105 ++ .../src/playground/nu_process.rs | 104 ++ crates/nu-test-support/src/playground/play.rs | 248 +++ .../nu-test-support/src/playground/tests.rs | 41 + src/config_files.rs | 2 +- src/main.rs | 48 +- src/test_bins.rs | 123 ++ src/tests.rs | 6 +- tests/assets/nu_json/charset_result.hjson | 5 + tests/assets/nu_json/charset_result.json | 5 + tests/assets/nu_json/charset_test.hjson | 6 + tests/assets/nu_json/comments_result.hjson | 26 + tests/assets/nu_json/comments_result.json | 26 + tests/assets/nu_json/comments_test.hjson | 48 + tests/assets/nu_json/empty_result.hjson | 3 + tests/assets/nu_json/empty_result.json | 3 + tests/assets/nu_json/empty_test.hjson | 3 + tests/assets/nu_json/failCharset1_test.hjson | 4 + tests/assets/nu_json/failJSON02_test.json | 1 + tests/assets/nu_json/failJSON05_test.json | 1 + tests/assets/nu_json/failJSON06_test.json | 1 + tests/assets/nu_json/failJSON07_test.json | 1 + tests/assets/nu_json/failJSON08_test.json | 1 + tests/assets/nu_json/failJSON10_test.json | 1 + tests/assets/nu_json/failJSON11_test.json | 1 + tests/assets/nu_json/failJSON12_test.json | 1 + tests/assets/nu_json/failJSON13_test.json | 1 + tests/assets/nu_json/failJSON14_test.json | 1 + tests/assets/nu_json/failJSON15_test.json | 1 + tests/assets/nu_json/failJSON16_test.json | 1 + tests/assets/nu_json/failJSON17_test.json | 1 + tests/assets/nu_json/failJSON19_test.json | 1 + tests/assets/nu_json/failJSON20_test.json | 1 + tests/assets/nu_json/failJSON21_test.json | 1 + tests/assets/nu_json/failJSON22_test.json | 1 + tests/assets/nu_json/failJSON23_test.json | 1 + tests/assets/nu_json/failJSON24_test.json | 1 + tests/assets/nu_json/failJSON26_test.json | 1 + tests/assets/nu_json/failJSON28_test.json | 2 + tests/assets/nu_json/failJSON29_test.json | 1 + tests/assets/nu_json/failJSON30_test.json | 1 + tests/assets/nu_json/failJSON31_test.json | 1 + tests/assets/nu_json/failJSON32_test.json | 1 + tests/assets/nu_json/failJSON33_test.json | 1 + tests/assets/nu_json/failJSON34_test.json | 2 + tests/assets/nu_json/failKey1_test.hjson | 4 + tests/assets/nu_json/failKey2_test.hjson | 4 + tests/assets/nu_json/failKey3_test.hjson | 4 + tests/assets/nu_json/failKey4_test.hjson | 4 + tests/assets/nu_json/failMLStr1_test.hjson | 3 + tests/assets/nu_json/failObj1_test.hjson | 6 + tests/assets/nu_json/failObj2_test.hjson | 6 + tests/assets/nu_json/failObj3_test.hjson | 7 + tests/assets/nu_json/failStr1a_test.hjson | 4 + tests/assets/nu_json/failStr1b_test.hjson | 4 + tests/assets/nu_json/failStr1c_test.hjson | 5 + tests/assets/nu_json/failStr1d_test.hjson | 5 + tests/assets/nu_json/failStr2a_test.hjson | 4 + tests/assets/nu_json/failStr2b_test.hjson | 4 + tests/assets/nu_json/failStr2c_test.hjson | 5 + tests/assets/nu_json/failStr2d_test.hjson | 5 + tests/assets/nu_json/failStr3a_test.hjson | 4 + tests/assets/nu_json/failStr3b_test.hjson | 4 + tests/assets/nu_json/failStr3c_test.hjson | 5 + tests/assets/nu_json/failStr3d_test.hjson | 5 + tests/assets/nu_json/failStr4a_test.hjson | 4 + tests/assets/nu_json/failStr4b_test.hjson | 4 + tests/assets/nu_json/failStr4c_test.hjson | 5 + tests/assets/nu_json/failStr4d_test.hjson | 5 + tests/assets/nu_json/failStr5a_test.hjson | 4 + tests/assets/nu_json/failStr5b_test.hjson | 4 + tests/assets/nu_json/failStr5c_test.hjson | 5 + tests/assets/nu_json/failStr5d_test.hjson | 5 + tests/assets/nu_json/failStr6a_test.hjson | 4 + tests/assets/nu_json/failStr6b_test.hjson | 4 + tests/assets/nu_json/failStr6c_test.hjson | 6 + tests/assets/nu_json/failStr6d_test.hjson | 6 + tests/assets/nu_json/kan_result.hjson | 48 + tests/assets/nu_json/kan_result.json | 45 + tests/assets/nu_json/kan_test.hjson | 49 + tests/assets/nu_json/keys_result.hjson | 34 + tests/assets/nu_json/keys_result.json | 34 + tests/assets/nu_json/keys_test.hjson | 48 + tests/assets/nu_json/oa_result.hjson | 13 + tests/assets/nu_json/oa_result.json | 13 + tests/assets/nu_json/oa_test.hjson | 13 + tests/assets/nu_json/pass1_result.hjson | 78 + tests/assets/nu_json/pass1_result.json | 75 + tests/assets/nu_json/pass1_test.json | 58 + tests/assets/nu_json/pass2_result.hjson | 39 + tests/assets/nu_json/pass2_result.json | 39 + tests/assets/nu_json/pass2_test.json | 1 + tests/assets/nu_json/pass3_result.hjson | 7 + tests/assets/nu_json/pass3_result.json | 6 + tests/assets/nu_json/pass3_test.json | 6 + tests/assets/nu_json/pass4_result.hjson | 1 + tests/assets/nu_json/pass4_result.json | 1 + tests/assets/nu_json/pass4_test.json | 2 + tests/assets/nu_json/passSingle_result.hjson | 1 + tests/assets/nu_json/passSingle_result.json | 1 + tests/assets/nu_json/passSingle_test.hjson | 1 + tests/assets/nu_json/root_result.hjson | 7 + tests/assets/nu_json/root_result.json | 6 + tests/assets/nu_json/root_test.hjson | 6 + tests/assets/nu_json/stringify1_result.hjson | 49 + tests/assets/nu_json/stringify1_result.json | 47 + tests/assets/nu_json/stringify1_test.hjson | 50 + tests/assets/nu_json/strings_result.hjson | 75 + tests/assets/nu_json/strings_result.json | 55 + tests/assets/nu_json/strings_test.hjson | 80 + tests/assets/nu_json/testlist.txt | 75 + tests/assets/nu_json/trail_result.hjson | 3 + tests/assets/nu_json/trail_result.json | 3 + tests/assets/nu_json/trail_test.hjson | 2 + tests/fixtures/formats/appveyor.yml | 31 + tests/fixtures/formats/caco3_plastics.csv | 10 + tests/fixtures/formats/caco3_plastics.tsv | 10 + tests/fixtures/formats/cargo_sample.toml | 55 + tests/fixtures/formats/jonathan.xml | 22 + tests/fixtures/formats/lines_test.txt | 2 + tests/fixtures/formats/random_numbers.csv | 51 + tests/fixtures/formats/sample-ls-output.json | 1 + tests/fixtures/formats/sample-ps-output.json | 1 + tests/fixtures/formats/sample-simple.json | 4 + tests/fixtures/formats/sample-sys-output.json | 125 ++ tests/fixtures/formats/sample.bson | Bin 0 -> 561 bytes tests/fixtures/formats/sample.db | Bin 0 -> 16384 bytes tests/fixtures/formats/sample.eml | 20 + tests/fixtures/formats/sample.ini | 19 + tests/fixtures/formats/sample.url | 1 + tests/fixtures/formats/sample_data.ods | Bin 0 -> 49626 bytes tests/fixtures/formats/sample_data.xlsx | Bin 0 -> 65801 bytes tests/fixtures/formats/sample_headers.xlsx | Bin 0 -> 4807 bytes tests/fixtures/formats/script.nu | 2 + tests/fixtures/formats/script_multiline.nu | 2 + tests/fixtures/formats/sgml_description.json | 30 + tests/fixtures/formats/utf16.ini | Bin 0 -> 504 bytes tests/fixtures/playground/config/default.toml | 3 + tests/fixtures/playground/config/startup.toml | 3 + tests/main.rs | 5 + tests/path/canonicalize.rs | 423 +++++ tests/path/expand_path.rs | 246 +++ tests/path/mod.rs | 2 + tests/plugins/core_inc.rs | 135 ++ tests/plugins/mod.rs | 2 + tests/shell/environment/configuration.rs | 142 ++ tests/shell/environment/env.rs | 107 ++ tests/shell/environment/in_sync.rs | 116 ++ tests/shell/environment/mod.rs | 22 + tests/shell/environment/nu_env.rs | 672 ++++++++ tests/shell/mod.rs | 76 + tests/shell/pipeline/commands/external.rs | 457 ++++++ tests/shell/pipeline/commands/internal.rs | 1354 +++++++++++++++++ tests/shell/pipeline/commands/mod.rs | 2 + tests/shell/pipeline/mod.rs | 10 + 169 files changed, 7276 insertions(+), 56 deletions(-) create mode 100644 crates/nu-test-support/Cargo.toml create mode 100644 crates/nu-test-support/src/commands.rs create mode 100644 crates/nu-test-support/src/fs.rs create mode 100644 crates/nu-test-support/src/lib.rs create mode 100644 crates/nu-test-support/src/macros.rs create mode 100644 crates/nu-test-support/src/playground.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 create mode 100644 src/test_bins.rs create mode 100644 tests/assets/nu_json/charset_result.hjson create mode 100644 tests/assets/nu_json/charset_result.json create mode 100644 tests/assets/nu_json/charset_test.hjson create mode 100644 tests/assets/nu_json/comments_result.hjson create mode 100644 tests/assets/nu_json/comments_result.json create mode 100644 tests/assets/nu_json/comments_test.hjson create mode 100644 tests/assets/nu_json/empty_result.hjson create mode 100644 tests/assets/nu_json/empty_result.json create mode 100644 tests/assets/nu_json/empty_test.hjson create mode 100644 tests/assets/nu_json/failCharset1_test.hjson create mode 100644 tests/assets/nu_json/failJSON02_test.json create mode 100644 tests/assets/nu_json/failJSON05_test.json create mode 100644 tests/assets/nu_json/failJSON06_test.json create mode 100644 tests/assets/nu_json/failJSON07_test.json create mode 100644 tests/assets/nu_json/failJSON08_test.json create mode 100644 tests/assets/nu_json/failJSON10_test.json create mode 100644 tests/assets/nu_json/failJSON11_test.json create mode 100644 tests/assets/nu_json/failJSON12_test.json create mode 100644 tests/assets/nu_json/failJSON13_test.json create mode 100644 tests/assets/nu_json/failJSON14_test.json create mode 100644 tests/assets/nu_json/failJSON15_test.json create mode 100644 tests/assets/nu_json/failJSON16_test.json create mode 100644 tests/assets/nu_json/failJSON17_test.json create mode 100644 tests/assets/nu_json/failJSON19_test.json create mode 100644 tests/assets/nu_json/failJSON20_test.json create mode 100644 tests/assets/nu_json/failJSON21_test.json create mode 100644 tests/assets/nu_json/failJSON22_test.json create mode 100644 tests/assets/nu_json/failJSON23_test.json create mode 100644 tests/assets/nu_json/failJSON24_test.json create mode 100644 tests/assets/nu_json/failJSON26_test.json create mode 100644 tests/assets/nu_json/failJSON28_test.json create mode 100644 tests/assets/nu_json/failJSON29_test.json create mode 100644 tests/assets/nu_json/failJSON30_test.json create mode 100644 tests/assets/nu_json/failJSON31_test.json create mode 100644 tests/assets/nu_json/failJSON32_test.json create mode 100644 tests/assets/nu_json/failJSON33_test.json create mode 100644 tests/assets/nu_json/failJSON34_test.json create mode 100644 tests/assets/nu_json/failKey1_test.hjson create mode 100644 tests/assets/nu_json/failKey2_test.hjson create mode 100644 tests/assets/nu_json/failKey3_test.hjson create mode 100644 tests/assets/nu_json/failKey4_test.hjson create mode 100644 tests/assets/nu_json/failMLStr1_test.hjson create mode 100644 tests/assets/nu_json/failObj1_test.hjson create mode 100644 tests/assets/nu_json/failObj2_test.hjson create mode 100644 tests/assets/nu_json/failObj3_test.hjson create mode 100644 tests/assets/nu_json/failStr1a_test.hjson create mode 100644 tests/assets/nu_json/failStr1b_test.hjson create mode 100644 tests/assets/nu_json/failStr1c_test.hjson create mode 100644 tests/assets/nu_json/failStr1d_test.hjson create mode 100644 tests/assets/nu_json/failStr2a_test.hjson create mode 100644 tests/assets/nu_json/failStr2b_test.hjson create mode 100644 tests/assets/nu_json/failStr2c_test.hjson create mode 100644 tests/assets/nu_json/failStr2d_test.hjson create mode 100644 tests/assets/nu_json/failStr3a_test.hjson create mode 100644 tests/assets/nu_json/failStr3b_test.hjson create mode 100644 tests/assets/nu_json/failStr3c_test.hjson create mode 100644 tests/assets/nu_json/failStr3d_test.hjson create mode 100644 tests/assets/nu_json/failStr4a_test.hjson create mode 100644 tests/assets/nu_json/failStr4b_test.hjson create mode 100644 tests/assets/nu_json/failStr4c_test.hjson create mode 100644 tests/assets/nu_json/failStr4d_test.hjson create mode 100644 tests/assets/nu_json/failStr5a_test.hjson create mode 100644 tests/assets/nu_json/failStr5b_test.hjson create mode 100644 tests/assets/nu_json/failStr5c_test.hjson create mode 100644 tests/assets/nu_json/failStr5d_test.hjson create mode 100644 tests/assets/nu_json/failStr6a_test.hjson create mode 100644 tests/assets/nu_json/failStr6b_test.hjson create mode 100644 tests/assets/nu_json/failStr6c_test.hjson create mode 100644 tests/assets/nu_json/failStr6d_test.hjson create mode 100644 tests/assets/nu_json/kan_result.hjson create mode 100644 tests/assets/nu_json/kan_result.json create mode 100644 tests/assets/nu_json/kan_test.hjson create mode 100644 tests/assets/nu_json/keys_result.hjson create mode 100644 tests/assets/nu_json/keys_result.json create mode 100644 tests/assets/nu_json/keys_test.hjson create mode 100644 tests/assets/nu_json/oa_result.hjson create mode 100644 tests/assets/nu_json/oa_result.json create mode 100644 tests/assets/nu_json/oa_test.hjson create mode 100644 tests/assets/nu_json/pass1_result.hjson create mode 100644 tests/assets/nu_json/pass1_result.json create mode 100644 tests/assets/nu_json/pass1_test.json create mode 100644 tests/assets/nu_json/pass2_result.hjson create mode 100644 tests/assets/nu_json/pass2_result.json create mode 100644 tests/assets/nu_json/pass2_test.json create mode 100644 tests/assets/nu_json/pass3_result.hjson create mode 100644 tests/assets/nu_json/pass3_result.json create mode 100644 tests/assets/nu_json/pass3_test.json create mode 100644 tests/assets/nu_json/pass4_result.hjson create mode 100644 tests/assets/nu_json/pass4_result.json create mode 100644 tests/assets/nu_json/pass4_test.json create mode 100644 tests/assets/nu_json/passSingle_result.hjson create mode 100644 tests/assets/nu_json/passSingle_result.json create mode 100644 tests/assets/nu_json/passSingle_test.hjson create mode 100644 tests/assets/nu_json/root_result.hjson create mode 100644 tests/assets/nu_json/root_result.json create mode 100644 tests/assets/nu_json/root_test.hjson create mode 100644 tests/assets/nu_json/stringify1_result.hjson create mode 100644 tests/assets/nu_json/stringify1_result.json create mode 100644 tests/assets/nu_json/stringify1_test.hjson create mode 100644 tests/assets/nu_json/strings_result.hjson create mode 100644 tests/assets/nu_json/strings_result.json create mode 100644 tests/assets/nu_json/strings_test.hjson create mode 100644 tests/assets/nu_json/testlist.txt create mode 100644 tests/assets/nu_json/trail_result.hjson create mode 100644 tests/assets/nu_json/trail_result.json create mode 100644 tests/assets/nu_json/trail_test.hjson create mode 100644 tests/fixtures/formats/appveyor.yml create mode 100644 tests/fixtures/formats/caco3_plastics.csv create mode 100644 tests/fixtures/formats/caco3_plastics.tsv create mode 100644 tests/fixtures/formats/cargo_sample.toml create mode 100644 tests/fixtures/formats/jonathan.xml create mode 100644 tests/fixtures/formats/lines_test.txt create mode 100644 tests/fixtures/formats/random_numbers.csv create mode 100644 tests/fixtures/formats/sample-ls-output.json create mode 100644 tests/fixtures/formats/sample-ps-output.json create mode 100644 tests/fixtures/formats/sample-simple.json create mode 100644 tests/fixtures/formats/sample-sys-output.json create mode 100644 tests/fixtures/formats/sample.bson create mode 100644 tests/fixtures/formats/sample.db create mode 100644 tests/fixtures/formats/sample.eml create mode 100644 tests/fixtures/formats/sample.ini create mode 100644 tests/fixtures/formats/sample.url create mode 100644 tests/fixtures/formats/sample_data.ods create mode 100644 tests/fixtures/formats/sample_data.xlsx create mode 100644 tests/fixtures/formats/sample_headers.xlsx create mode 100755 tests/fixtures/formats/script.nu create mode 100644 tests/fixtures/formats/script_multiline.nu create mode 100644 tests/fixtures/formats/sgml_description.json create mode 100644 tests/fixtures/formats/utf16.ini create mode 100644 tests/fixtures/playground/config/default.toml create mode 100644 tests/fixtures/playground/config/startup.toml create mode 100644 tests/main.rs create mode 100644 tests/path/canonicalize.rs create mode 100644 tests/path/expand_path.rs create mode 100644 tests/path/mod.rs create mode 100644 tests/plugins/core_inc.rs create mode 100644 tests/plugins/mod.rs create mode 100644 tests/shell/environment/configuration.rs create mode 100644 tests/shell/environment/env.rs create mode 100644 tests/shell/environment/in_sync.rs create mode 100644 tests/shell/environment/mod.rs create mode 100644 tests/shell/environment/nu_env.rs create mode 100644 tests/shell/mod.rs create mode 100644 tests/shell/pipeline/commands/external.rs create mode 100644 tests/shell/pipeline/commands/internal.rs create mode 100644 tests/shell/pipeline/commands/mod.rs create mode 100644 tests/shell/pipeline/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6b6ef5ee0e..ff23500396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,18 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint 0.4.3", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -908,40 +920,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "engine-q" -version = "0.1.0" -dependencies = [ - "assert_cmd", - "crossterm", - "crossterm_winapi", - "ctrlc", - "log", - "miette", - "nu-ansi-term", - "nu-cli", - "nu-color-config", - "nu-command", - "nu-engine", - "nu-json", - "nu-parser", - "nu-path", - "nu-plugin", - "nu-pretty-hex", - "nu-protocol", - "nu-system", - "nu-table", - "nu-term-grid", - "nu_plugin_example", - "nu_plugin_gstat", - "nu_plugin_inc", - "nu_plugin_query", - "pretty_assertions", - "pretty_env_logger", - "reedline", - "tempfile", -] - [[package]] name = "env_logger" version = "0.7.1" @@ -955,6 +933,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + [[package]] name = "erased-serde" version = "0.3.16" @@ -1212,6 +1200,18 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ghost" version = "0.1.2" @@ -1275,6 +1275,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "hamcrest2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f837c62de05dc9cc71ff6486cd85de8856a330395ae338a04bfcefe5e91075" +dependencies = [ + "num 0.2.1", + "regex", +] + [[package]] name = "hash32" version = "0.1.1" @@ -2020,6 +2030,45 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "crossterm", + "crossterm_winapi", + "ctrlc", + "hamcrest2", + "itertools", + "log", + "miette", + "nu-ansi-term", + "nu-cli", + "nu-color-config", + "nu-command", + "nu-engine", + "nu-json", + "nu-parser", + "nu-path", + "nu-plugin", + "nu-pretty-hex", + "nu-protocol", + "nu-system", + "nu-table", + "nu-term-grid", + "nu-test-support", + "nu_plugin_example", + "nu_plugin_gstat", + "nu_plugin_inc", + "nu_plugin_query", + "pretty_assertions", + "pretty_env_logger", + "reedline", + "rstest", + "serial_test", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.42.0" @@ -2073,10 +2122,12 @@ dependencies = [ "csv", "dialoguer", "digest 0.10.0", + "dirs-next", "dtparse", "eml-parser", "encoding_rs", "glob", + "hamcrest2", "htmlescape", "ical", "indexmap", @@ -2098,10 +2149,13 @@ dependencies = [ "nu-system", "nu-table", "nu-term-grid", + "nu-test-support", "num 0.4.0", "pathdiff", "polars", "quick-xml 0.22.0", + "quickcheck", + "quickcheck_macros", "rand 0.8.4", "rayon", "regex", @@ -2251,6 +2305,22 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "nu-test-support" +version = "0.43.0" +dependencies = [ + "bigdecimal", + "chrono", + "getset", + "glob", + "hamcrest2", + "indexmap", + "nu-path", + "nu-protocol", + "num-bigint 0.4.3", + "tempfile", +] + [[package]] name = "nu_plugin_example" version = "0.1.0" @@ -2339,6 +2409,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "serde", ] [[package]] @@ -2875,7 +2946,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "env_logger", + "env_logger 0.7.1", "log", ] @@ -2893,6 +2964,30 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.3", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.3", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2948,6 +3043,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger 0.8.4", + "log", + "rand 0.8.4", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.10" @@ -3224,6 +3341,19 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rstest" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d912f35156a3f99a66ee3e11ac2e0b3f34ac85a07e05263d05a7e2c8810d616f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -3487,6 +3617,28 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "serial_test" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" +dependencies = [ + "lazy_static", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "servo_arc" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 1029f669a9..2c907d1e08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "engine-q" +name = "nu" version = "0.1.0" edition = "2021" -default-run = "engine-q" +default-run = "nu" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -36,6 +36,7 @@ nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-system = { path = "./crates/nu-system"} nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } + nu-ansi-term = "0.42.0" nu-color-config = { path = "./crates/nu-color-config" } miette = "3.0.0" @@ -52,11 +53,14 @@ nu_plugin_gstat = { version = "0.1.0", path = "./crates/nu_plugin_gstat", option nu_plugin_query = { version = "0.1.0", path = "./crates/nu_plugin_query", optional = true } [dev-dependencies] +nu-test-support = { path="./crates/nu-test-support" } tempfile = "3.2.0" assert_cmd = "2.0.2" pretty_assertions = "1.0.0" - -[build-dependencies] +serial_test = "0.5.1" +hamcrest2 = "0.3.0" +rstest = "0.12.0" +itertools = "0.10.3" [features] plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] @@ -119,5 +123,5 @@ required-features = ["query"] # Main nu binary [[bin]] -name = "engine-q" +name = "nu" path = "src/main.rs" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c81b5f9556..3bd5313aab 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -15,6 +15,7 @@ nu-pretty-hex = { path = "../nu-pretty-hex" } nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } +nu-test-support = { path = "../nu-test-support" } nu-parser = { path = "../nu-parser" } nu-system = { path = "../nu-system" } # nu-ansi-term = { path = "../nu-ansi-term" } @@ -95,3 +96,9 @@ dataframe = ["polars", "num"] [build-dependencies] shadow-rs = "0.8.1" + +[dev-dependencies] +hamcrest2 = "0.3.0" +dirs-next = "2.0.0" +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" \ No newline at end of file diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 35a912bb0b..d94f1b9ed8 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1028,13 +1028,25 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value { val: size * 1000 * 1000 * 1000 * 60 * 60, span, }, - Unit::Day => Value::Duration { - val: size * 1000 * 1000 * 1000 * 60 * 60 * 24, - span, + Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) { + Some(val) => Value::Duration { val, span }, + None => Value::Error { + error: ShellError::SpannedLabeledError( + "duration too large".into(), + "duration too large".into(), + span, + ), + }, }, - Unit::Week => Value::Duration { - val: size * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7, - span, + Unit::Week => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 7) { + Some(val) => Value::Duration { val, span }, + None => Value::Error { + error: ShellError::SpannedLabeledError( + "duration too large".into(), + "duration too large".into(), + span, + ), + }, }, } } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 2d6bff4bcc..217b2643ec 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -88,7 +88,34 @@ pub fn math_result_type( ) } }, - Operator::Multiply | Operator::Pow => match (&lhs.ty, &rhs.ty) { + Operator::Multiply => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Int, Type::Filesize) => (Type::Filesize, None), + (Type::Duration, Type::Int) => (Type::Filesize, None), + (Type::Int, Type::Duration) => (Type::Filesize, None), + + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Pow => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), @@ -118,6 +145,9 @@ pub fn math_result_type( (Type::Filesize, Type::Filesize) => (Type::Float, None), (Type::Duration, Type::Duration) => (Type::Float, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 9615e6b5b4..3727723dec 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -7,9 +7,7 @@ use super::{EngineState, Stack}; pub trait Command: Send + Sync + CommandClone { fn name(&self) -> &str; - fn signature(&self) -> Signature { - Signature::new(self.name()).desc(self.usage()).filter() - } + fn signature(&self) -> Signature; fn usage(&self) -> &str; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 99d67650a5..29e6a4653e 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1122,6 +1122,30 @@ impl Value { val: lhs * rhs, span, }), + (Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs * *rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs * *rhs, + span, + }) + } + (Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs * *rhs, + span, + }) + } + (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs * *rhs, + span, + }) + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::Multiply, op, rhs) } @@ -1220,6 +1244,26 @@ impl Value { Err(ShellError::DivisionByZero(op)) } } + (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Filesize { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Duration { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::Divide, op, rhs) } diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml new file mode 100644 index 0000000000..8329e24639 --- /dev/null +++ b/crates/nu-test-support/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors = ["The Nu Project Contributors"] +description = "Support for writing Nushell tests" +edition = "2018" +license = "MIT" +name = "nu-test-support" +version = "0.43.0" + +[lib] +doctest = false + +[dependencies] +nu-path = { path="../nu-path" } +nu-protocol = { path="../nu-protocol" } + +bigdecimal = { package = "bigdecimal", version = "0.3.0", features = ["serde"] } +chrono = "0.4.19" +getset = "0.1.1" +glob = "0.3.0" +indexmap = { version="1.6.1", features=["serde-1"] } +num-bigint = { version="0.4.3", features=["serde"] } +tempfile = "3.2.0" +hamcrest2 = "0.3.0" diff --git a/crates/nu-test-support/src/commands.rs b/crates/nu-test-support/src/commands.rs new file mode 100644 index 0000000000..d59c2f3dad --- /dev/null +++ b/crates/nu-test-support/src/commands.rs @@ -0,0 +1,53 @@ +// use nu_protocol::{ +// ast::{Expr, Expression}, +// Span, Spanned, Type, +// }; + +// pub struct ExternalBuilder { +// name: String, +// args: Vec, +// } + +// impl ExternalBuilder { +// pub fn for_name(name: &str) -> ExternalBuilder { +// ExternalBuilder { +// name: name.to_string(), +// args: vec![], +// } +// } + +// pub fn arg(&mut self, value: &str) -> &mut Self { +// self.args.push(value.to_string()); +// self +// } + +// pub fn build(&mut self) -> ExternalCommand { +// let mut path = crate::fs::binaries(); +// path.push(&self.name); + +// let name = Spanned { +// item: path.to_string_lossy().to_string(), +// span: Span::new(0, 0), +// }; + +// let args = self +// .args +// .iter() +// .map(|arg| Expression { +// expr: Expr::String(arg.to_string()), +// span: Span::new(0, 0), +// ty: Type::Unknown, +// custom_completion: None, +// }) +// .collect::>(); + +// ExternalCommand { +// name: name.to_string(), +// name_tag: Tag::unknown(), +// args: ExternalArgs { +// list: args, +// span: name.span, +// }, +// } +// } +// } diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs new file mode 100644 index 0000000000..bf7d87df77 --- /dev/null +++ b/crates/nu-test-support/src/fs.rs @@ -0,0 +1,277 @@ +use std::io::Read; +use std::ops::Div; +use std::path::{Path, PathBuf}; + +pub struct AbsoluteFile { + inner: PathBuf, +} + +impl AbsoluteFile { + pub fn new(path: impl AsRef) -> AbsoluteFile { + let path = path.as_ref(); + + if !path.is_absolute() { + panic!( + "AbsoluteFile::new must take an absolute path :: {}", + path.display() + ) + } else if path.is_dir() { + // At the moment, this is not an invariant, but rather a way to catch bugs + // in tests. + panic!( + "AbsoluteFile::new must not take a directory :: {}", + path.display() + ) + } else { + AbsoluteFile { + inner: path.to_path_buf(), + } + } + } + + pub fn dir(&self) -> AbsolutePath { + AbsolutePath::new(if let Some(parent) = self.inner.parent() { + parent + } else { + unreachable!("Internal error: could not get parent in dir") + }) + } +} + +impl From for PathBuf { + fn from(file: AbsoluteFile) -> Self { + file.inner + } +} + +pub struct AbsolutePath { + pub inner: PathBuf, +} + +impl AbsolutePath { + pub fn new(path: impl AsRef) -> AbsolutePath { + let path = path.as_ref(); + + if path.is_absolute() { + AbsolutePath { + inner: path.to_path_buf(), + } + } else { + panic!("AbsolutePath::new must take an absolute path") + } + } +} + +impl Div<&str> for &AbsolutePath { + type Output = AbsolutePath; + + fn div(self, rhs: &str) -> Self::Output { + let parts = rhs.split('/'); + let mut result = self.inner.clone(); + + for part in parts { + result = result.join(part); + } + + AbsolutePath::new(result) + } +} + +impl AsRef for AbsolutePath { + fn as_ref(&self) -> &Path { + self.inner.as_path() + } +} + +pub struct RelativePath { + inner: PathBuf, +} + +impl RelativePath { + pub fn new(path: impl Into) -> RelativePath { + let path = path.into(); + + if path.is_relative() { + RelativePath { inner: path } + } else { + panic!("RelativePath::new must take a relative path") + } + } +} + +impl> Div for &RelativePath { + type Output = RelativePath; + + fn div(self, rhs: T) -> Self::Output { + let parts = rhs.as_ref().split('/'); + let mut result = self.inner.clone(); + + for part in parts { + result = result.join(part); + } + + RelativePath::new(result) + } +} +pub trait DisplayPath { + fn display_path(&self) -> String; +} + +impl DisplayPath for AbsolutePath { + fn display_path(&self) -> String { + self.inner.display().to_string() + } +} + +impl DisplayPath for PathBuf { + fn display_path(&self) -> String { + self.display().to_string() + } +} + +impl DisplayPath for str { + fn display_path(&self) -> String { + self.to_string() + } +} + +impl DisplayPath for &str { + fn display_path(&self) -> String { + (*self).to_string() + } +} + +impl DisplayPath for String { + fn display_path(&self) -> String { + self.clone() + } +} + +impl DisplayPath for &String { + fn display_path(&self) -> String { + (*self).to_string() + } +} +pub enum Stub<'a> { + FileWithContent(&'a str, &'a str), + FileWithContentToBeTrimmed(&'a str, &'a str), + EmptyFile(&'a str), +} + +pub fn file_contents(full_path: impl AsRef) -> String { + let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("can not read file"); + contents +} + +pub fn file_contents_binary(full_path: impl AsRef) -> Vec { + let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).expect("can not read file"); + contents +} + +pub fn line_ending() -> String { + #[cfg(windows)] + { + String::from("\r\n") + } + + #[cfg(not(windows))] + { + String::from("\n") + } +} + +pub fn delete_file_at(full_path: impl AsRef) { + let full_path = full_path.as_ref(); + + if full_path.exists() { + std::fs::remove_file(full_path).expect("can not delete file"); + } +} + +pub fn create_file_at(full_path: impl AsRef) -> Result<(), std::io::Error> { + let full_path = full_path.as_ref(); + + if full_path.parent().is_some() { + panic!("path exists"); + } + + std::fs::write(full_path, b"fake data") +} + +pub fn copy_file_to(source: &str, destination: &str) { + std::fs::copy(source, destination).expect("can not copy file"); +} + +pub fn files_exist_at(files: Vec>, path: impl AsRef) -> bool { + files.iter().all(|f| { + let mut loc = PathBuf::from(path.as_ref()); + loc.push(f); + loc.exists() + }) +} + +pub fn delete_directory_at(full_path: &str) { + std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory"); +} + +pub fn executable_path() -> PathBuf { + let mut path = binaries(); + path.push("nu"); + path +} + +pub fn root() -> PathBuf { + let manifest_dir = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { + PathBuf::from(manifest_dir) + } else { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + }; + + let test_path = manifest_dir.join("Cargo.lock"); + if test_path.exists() { + manifest_dir + } else { + manifest_dir + .parent() + .expect("Couldn't find the debug binaries directory") + .parent() + .expect("Couldn't find the debug binaries directory") + .to_path_buf() + } +} + +pub fn binaries() -> PathBuf { + let mut build_type = "debug"; + if !cfg!(debug_assertions) { + build_type = "release" + } + + std::env::var("CARGO_TARGET_DIR") + .ok() + .map(|target_dir| PathBuf::from(target_dir).join(&build_type)) + .unwrap_or_else(|| root().join(format!("target/{}", &build_type))) +} + +pub fn fixtures() -> PathBuf { + root().join("tests/fixtures") +} + +pub fn assets() -> PathBuf { + root().join("tests/assets") +} + +pub fn in_directory(str: impl AsRef) -> String { + let path = str.as_ref(); + let path = if path.is_relative() { + root().join(path) + } else { + path.to_path_buf() + }; + + path.display().to_string() +} diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs new file mode 100644 index 0000000000..b0f0685736 --- /dev/null +++ b/crates/nu-test-support/src/lib.rs @@ -0,0 +1,70 @@ +pub mod commands; +pub mod fs; +pub mod macros; +pub mod playground; + +pub struct Outcome { + pub out: String, + pub err: String, +} + +#[cfg(windows)] +pub const NATIVE_PATH_ENV_VAR: &str = "Path"; +#[cfg(not(windows))] +pub const NATIVE_PATH_ENV_VAR: &str = "PATH"; + +#[cfg(windows)] +pub const NATIVE_PATH_ENV_SEPARATOR: char = ';'; +#[cfg(not(windows))] +pub const NATIVE_PATH_ENV_SEPARATOR: char = ':'; + +impl Outcome { + pub fn new(out: String, err: String) -> Outcome { + Outcome { out, err } + } +} + +pub fn pipeline(commands: &str) -> String { + commands + .lines() + .skip(1) + .map(|line| line.trim()) + .collect::>() + .join(" ") + .trim_end() + .to_string() +} + +pub fn shell_os_paths() -> Vec { + let mut original_paths = vec![]; + + if let Some(paths) = std::env::var_os(NATIVE_PATH_ENV_VAR) { + original_paths = std::env::split_paths(&paths).collect::>(); + } + + original_paths +} + +#[cfg(test)] +mod tests { + use super::pipeline; + + #[test] + fn constructs_a_pipeline() { + let actual = pipeline( + r#" + open los_tres_amigos.txt + | from-csv + | get rusty_luck + | str to-int + | math sum + | echo "$it" + "#, + ); + + assert_eq!( + actual, + r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str to-int | math sum | echo "$it""# + ); + } +} diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs new file mode 100644 index 0000000000..f692754411 --- /dev/null +++ b/crates/nu-test-support/src/macros.rs @@ -0,0 +1,170 @@ +#[macro_export] +macro_rules! nu { + (cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{ + use $crate::fs::DisplayPath; + + let path = format!($path, $( + $part.display_path() + ),*); + + nu!($cwd, &path) + }}; + + (cwd: $cwd:expr, $path:expr) => {{ + nu!($cwd, $path) + }}; + + ($cwd:expr, $path:expr) => {{ + pub use itertools::Itertools; + pub use std::error::Error; + pub use std::io::prelude::*; + pub use std::process::{Command, Stdio}; + pub use $crate::NATIVE_PATH_ENV_VAR; + + // let commands = &*format!( + // " + // cd \"{}\" + // {} + // exit", + // $crate::fs::in_directory($cwd), + // $crate::fs::DisplayPath::display_path(&$path) + // ); + + let test_bins = $crate::fs::binaries(); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let test_bins = nu_path::canonicalize_with(&test_bins, cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize dummy binaries path {}: {:?}", + test_bins.display(), + e + ) + }); + + let mut paths = $crate::shell_os_paths(); + paths.insert(0, test_bins); + + let path = $path.lines().collect::>().join("; "); + + let paths_joined = match std::env::join_paths(paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + let mut process = match Command::new($crate::fs::executable_path()) + .env(NATIVE_PATH_ENV_VAR, paths_joined) + // .arg("--skip-plugins") + // .arg("--no-history") + // .arg("--config-file") + // .arg($crate::fs::DisplayPath::display_path(&$crate::fs::fixtures().join("playground/config/default.toml"))) + .arg(format!("-c 'cd {}; {}'", $crate::fs::in_directory($cwd), $crate::fs::DisplayPath::display_path(&path))) + .stdout(Stdio::piped()) + // .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {:?} {}", $crate::fs::executable_path(), why.to_string()), + }; + + // let stdin = process.stdin.as_mut().expect("couldn't open stdin"); + // stdin + // .write_all(b"exit\n") + // .expect("couldn't write to stdin"); + + let output = process + .wait_with_output() + .expect("couldn't read from stdout/stderr"); + + let out = $crate::macros::read_std(&output.stdout); + let err = String::from_utf8_lossy(&output.stderr); + + println!("=== stderr\n{}", err); + + $crate::Outcome::new(out,err.into_owned()) + }}; +} + +#[macro_export] +macro_rules! nu_with_plugins { + (cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{ + use $crate::fs::DisplayPath; + + let path = format!($path, $( + $part.display_path() + ),*); + + nu_with_plugins!($cwd, &path) + }}; + + (cwd: $cwd:expr, $path:expr) => {{ + nu_with_plugins!($cwd, $path) + }}; + + ($cwd:expr, $path:expr) => {{ + pub use std::error::Error; + pub use std::io::prelude::*; + pub use std::process::{Command, Stdio}; + pub use crate::NATIVE_PATH_ENV_VAR; + + let commands = &*format!( + " + cd \"{}\" + {} + exit", + $crate::fs::in_directory($cwd), + $crate::fs::DisplayPath::display_path(&$path) + ); + + let test_bins = $crate::fs::binaries(); + let test_bins = nu_path::canonicalize(&test_bins).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize dummy binaries path {}: {:?}", + test_bins.display(), + e + ) + }); + + let mut paths = $crate::shell_os_paths(); + paths.insert(0, test_bins); + + let paths_joined = match std::env::join_paths(paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + let mut process = match Command::new($crate::fs::executable_path()) + .env(NATIVE_PATH_ENV_VAR, paths_joined) + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {}", why.to_string()), + }; + + let stdin = process.stdin.as_mut().expect("couldn't open stdin"); + stdin + .write_all(commands.as_bytes()) + .expect("couldn't write to stdin"); + + let output = process + .wait_with_output() + .expect("couldn't read from stdout/stderr"); + + let out = $crate::macros::read_std(&output.stdout); + let err = String::from_utf8_lossy(&output.stderr); + + println!("=== stderr\n{}", err); + + $crate::Outcome::new(out,err.into_owned()) + }}; +} + +pub fn read_std(std: &[u8]) -> String { + let out = String::from_utf8_lossy(std); + let out = out.lines().collect::>().join("\n"); + let out = out.replace("\r\n", ""); + out.replace("\n", "") +} diff --git a/crates/nu-test-support/src/playground.rs b/crates/nu-test-support/src/playground.rs new file mode 100644 index 0000000000..caeb4f26cf --- /dev/null +++ b/crates/nu-test-support/src/playground.rs @@ -0,0 +1,12 @@ +mod director; +pub mod matchers; +pub mod nu_process; +mod play; + +#[cfg(test)] +mod tests; + +pub use director::Director; +pub use matchers::says; +pub use nu_process::{Executable, NuProcess, NuResult, Outcome}; +pub use play::{Dirs, EnvironmentVariable, 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..ba8b9922f4 --- /dev/null +++ b/crates/nu-test-support/src/playground/director.rs @@ -0,0 +1,163 @@ +use super::nu_process::*; +use super::EnvironmentVariable; +use std::ffi::OsString; +use std::fmt; + +#[derive(Default, Debug)] +pub struct Director { + pub cwd: Option, + pub environment_vars: Vec, + pub config: Option, + pub pipeline: Option>, + pub executable: Option, +} + +impl Director { + pub fn cococo(&self, arg: &str) -> Self { + let mut process = NuProcess { + environment_vars: self.environment_vars.clone(), + ..Default::default() + }; + + process.args(&["--testbin", "cococo", arg]); + Director { + config: self.config.clone(), + executable: Some(process), + environment_vars: self.environment_vars.clone(), + ..Default::default() + } + } + + pub fn and_then(&mut self, commands: &str) -> &mut Self { + let commands = commands.to_string(); + + if let Some(ref mut pipeline) = self.pipeline { + pipeline.push(commands); + } else { + self.pipeline = Some(vec![commands]); + } + + self + } + + pub fn pipeline(&self, commands: &str) -> Self { + let mut director = Director { + pipeline: if commands.is_empty() { + None + } else { + Some(vec![commands.to_string()]) + }, + ..Default::default() + }; + + let mut process = NuProcess { + environment_vars: self.environment_vars.clone(), + ..Default::default() + }; + + if let Some(working_directory) = &self.cwd { + process.cwd(working_directory); + } + + process.arg("--skip-plugins"); + process.arg("--no-history"); + if let Some(config_file) = self.config.as_ref() { + process.args(&[ + "--config-file", + config_file.to_str().expect("failed to convert."), + ]); + } + process.arg("--perf"); + + 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(&mut 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), + }; + + if let Some(pipelines) = &self.pipeline { + let child = process.stdin.as_mut().expect("Failed to open stdin"); + + for pipeline in pipelines { + child + .write_all(format!("{}\n", pipeline).as_bytes()) + .expect("Could not write to"); + } + + child.write_all(b"exit\n").expect("Could not write to"); + } + + 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..7c36489e2d --- /dev/null +++ b/crates/nu-test-support/src/playground/matchers.rs @@ -0,0 +1,105 @@ +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 { + pub fn 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..ffb3271ac4 --- /dev/null +++ b/crates/nu-test-support/src/playground/nu_process.rs @@ -0,0 +1,104 @@ +use super::EnvironmentVariable; +use crate::fs::{binaries as test_bins_path, executable_path}; +use std::ffi::{OsStr, OsString}; +use std::fmt; +use std::path::Path; +use std::process::{Command, ExitStatus}; + +pub trait Executable { + fn execute(&mut 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, Default)] +pub struct NuProcess { + pub arguments: Vec, + pub environment_vars: Vec, + 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 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); + } + + command.env_clear(); + + let paths = vec![test_bins_path()]; + + let paths_joined = match std::env::join_paths(&paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + command.env(crate::NATIVE_PATH_ENV_VAR, paths_joined); + + for env_var in &self.environment_vars { + command.env(&env_var.name, &env_var.value); + } + + 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..2129352913 --- /dev/null +++ b/crates/nu-test-support/src/playground/play.rs @@ -0,0 +1,248 @@ +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}; + +#[derive(Default, Clone, Debug)] +pub struct EnvironmentVariable { + pub name: String, + pub value: String, +} + +impl EnvironmentVariable { + fn new(name: &str, value: &str) -> Self { + Self { + name: name.to_string(), + value: value.to_string(), + } + } +} + +pub struct Playground<'a> { + root: TempDir, + tests: String, + cwd: PathBuf, + config: PathBuf, + environment_vars: Vec, + 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") + } + + pub fn config_fixtures(&self) -> PathBuf { + self.fixtures.join("playground/config") + } +} + +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 cwd = std::env::current_dir().expect("Could not get current working directory."); + let fixtures = nu_path::canonicalize_with(fixtures.clone(), cwd).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"), + environment_vars: Vec::default(), + dirs: &Dirs::default(), + }; + + let playground_root = playground.root.path(); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let test = + nu_path::canonicalize_with(playground_root.join(topic), cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize test path {}: {:?}", + playground_root.join(topic).display(), + e + ) + }); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let root = nu_path::canonicalize_with(playground_root, cwd).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 with_env(&mut self, name: &str, value: &str) -> &mut Self { + self.environment_vars + .push(EnvironmentVariable::new(name, value)); + 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()), + environment_vars: self.environment_vars.clone(), + ..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..014a86910e --- /dev/null +++ b/crates/nu-test-support/src/playground/tests.rs @@ -0,0 +1,41 @@ +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 { + let cwd = std::env::current_dir().expect("Could not get current working directory."); + nu_path::canonicalize_with(p, cwd) + .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().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/config_files.rs b/src/config_files.rs index 727cbed376..ea053051fa 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -39,7 +39,7 @@ pub(crate) fn read_config_file(engine_state: &mut EngineState, stack: &mut Stack if config_path.exists() { // FIXME: remove this message when we're ready - println!("Loading config from: {:?}", config_path); + //println!("Loading config from: {:?}", config_path); let config_filename = config_path.to_string_lossy().to_owned(); if let Ok(contents) = std::fs::read_to_string(&config_path) { diff --git a/src/main.rs b/src/main.rs index 8363b8e379..0a3cd51872 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ mod utils; #[cfg(test)] mod tests; +mod test_bins; + use miette::Result; use nu_command::{create_default_context, BufferedReader}; use nu_engine::get_full_help; @@ -21,7 +23,7 @@ use nu_protocol::{ Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID, }; use std::{ - io::BufReader, + io::{BufReader, Write}, path::Path, sync::{ atomic::{AtomicBool, Ordering}, @@ -92,6 +94,7 @@ fn main() -> Result<()> { // Cool, it's a flag if arg == "-c" || arg == "--commands" + || arg == "--testbin" || arg == "--develop" || arg == "--debug" || arg == "--loglevel" @@ -115,6 +118,21 @@ fn main() -> Result<()> { match nushell_config { Ok(nushell_config) => { + if let Some(testbin) = &nushell_config.testbin { + // Call out to the correct testbin + match testbin.item.as_str() { + "echo_env" => test_bins::echo_env(), + "cococo" => test_bins::cococo(), + "meow" => test_bins::meow(), + "iecho" => test_bins::iecho(), + "fail" => test_bins::fail(), + "nonu" => test_bins::nonu(), + "chop" => test_bins::chop(), + "repeater" => test_bins::repeater(), + _ => std::process::exit(1), + } + std::process::exit(0) + } let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { let stdin = std::io::stdin(); let buf_reader = BufReader::new(stdin); @@ -193,6 +211,7 @@ fn parse_commandline_args( let login_shell = call.get_named_arg("login"); let interactive_shell = call.get_named_arg("interactive"); let commands: Option = call.get_flag_expr("commands"); + let testbin: Option = call.get_flag_expr("testbin"); let commands = if let Some(expression) = commands { let contents = engine_state.get_span_contents(&expression.span); @@ -205,12 +224,29 @@ fn parse_commandline_args( None }; + let testbin = if let Some(expression) = testbin { + let contents = engine_state.get_span_contents(&expression.span); + + Some(Spanned { + item: String::from_utf8_lossy(contents).to_string(), + span: expression.span, + }) + } else { + None + }; + let help = call.has_flag("help"); if help { let full_help = get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack); - print!("{}", full_help); + + let _ = std::panic::catch_unwind(move || { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let _ = stdout.write_all(full_help.as_bytes()); + }); + std::process::exit(1); } @@ -219,6 +255,7 @@ fn parse_commandline_args( login_shell, interactive_shell, commands, + testbin, }); } } @@ -235,6 +272,7 @@ struct NushellConfig { login_shell: Option>, interactive_shell: Option>, commands: Option>, + testbin: Option>, } #[derive(Clone)] @@ -251,6 +289,12 @@ impl Command for Nu { .switch("stdin", "redirect the stdin", None) .switch("login", "start as a login shell", Some('l')) .switch("interactive", "start as an interactive shell", Some('i')) + .named( + "testbin", + SyntaxShape::String, + "run internal test binary", + None, + ) .named( "commands", SyntaxShape::String, diff --git a/src/test_bins.rs b/src/test_bins.rs new file mode 100644 index 0000000000..b02f00e09f --- /dev/null +++ b/src/test_bins.rs @@ -0,0 +1,123 @@ +use std::io::{self, BufRead, Write}; + +/// Echo's value of env keys from args +/// Example: nu --testbin env_echo FOO BAR +/// If it it's not present echo's nothing +pub fn echo_env() { + let args = args(); + for arg in args { + if let Ok(v) = std::env::var(arg) { + println!("{}", v); + } + } +} + +pub fn cococo() { + let args: Vec = args(); + + if args.len() > 1 { + // Write back out all the arguments passed + // if given at least 1 instead of chickens + // speaking co co co. + println!("{}", &args[1..].join(" ")); + } else { + println!("cococo"); + } +} + +pub fn meow() { + let args: Vec = args(); + + for arg in args.iter().skip(1) { + let contents = std::fs::read_to_string(arg).expect("Expected a filepath"); + println!("{}", contents); + } +} + +pub fn nonu() { + args().iter().skip(1).for_each(|arg| print!("{}", arg)); +} + +pub fn repeater() { + let mut stdout = io::stdout(); + let args = args(); + let mut args = args.iter().skip(1); + let letter = args.next().expect("needs a character to iterate"); + let count = args.next().expect("need the number of times to iterate"); + + let count: u64 = count.parse().expect("can't convert count to number"); + + for _ in 0..count { + let _ = write!(stdout, "{}", letter); + } + let _ = stdout.flush(); +} + +pub fn iecho() { + // println! panics if stdout gets closed, whereas writeln gives us an error + let mut stdout = io::stdout(); + let _ = args() + .iter() + .skip(1) + .cycle() + .try_for_each(|v| writeln!(stdout, "{}", v)); +} + +pub fn fail() { + std::process::exit(1); +} + +pub fn chop() { + if did_chop_arguments() { + // we are done and don't care about standard input. + std::process::exit(0); + } + + // if no arguments given, chop from standard input and exit. + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + for given in stdin.lock().lines().flatten() { + let chopped = if given.is_empty() { + &given + } else { + let to = given.len() - 1; + &given[..to] + }; + + if let Err(_e) = writeln!(stdout, "{}", chopped) { + break; + } + } + + std::process::exit(0); +} + +fn did_chop_arguments() -> bool { + let args: Vec = args(); + + if args.len() > 1 { + let mut arguments = args.iter(); + arguments.next(); + + for arg in arguments { + let chopped = if arg.is_empty() { + &arg + } else { + let to = arg.len() - 1; + &arg[..to] + }; + + println!("{}", chopped); + } + + return true; + } + + false +} + +fn args() -> Vec { + // skip (--testbin bin_name args) + std::env::args().skip(2).collect() +} diff --git a/src/tests.rs b/src/tests.rs index 3a422fab48..a41fed6ea9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -26,7 +26,7 @@ pub fn run_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); - let mut cmd = Command::cargo_bin("engine-q")?; + let mut cmd = Command::cargo_bin("nu")?; cmd.arg(name); writeln!(file, "{}", input)?; @@ -51,7 +51,7 @@ pub fn run_test_contains(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); - let mut cmd = Command::cargo_bin("engine-q")?; + let mut cmd = Command::cargo_bin("nu")?; cmd.arg(name); writeln!(file, "{}", input)?; @@ -76,7 +76,7 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); - let mut cmd = Command::cargo_bin("engine-q")?; + let mut cmd = Command::cargo_bin("nu")?; cmd.arg(name); cmd.env( "PWD", diff --git a/tests/assets/nu_json/charset_result.hjson b/tests/assets/nu_json/charset_result.hjson new file mode 100644 index 0000000000..d1573b6162 --- /dev/null +++ b/tests/assets/nu_json/charset_result.hjson @@ -0,0 +1,5 @@ +{ + ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ +} \ No newline at end of file diff --git a/tests/assets/nu_json/charset_result.json b/tests/assets/nu_json/charset_result.json new file mode 100644 index 0000000000..6357d96db6 --- /dev/null +++ b/tests/assets/nu_json/charset_result.json @@ -0,0 +1,5 @@ +{ + "ql-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + "js-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + "ml-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" +} \ No newline at end of file diff --git a/tests/assets/nu_json/charset_test.hjson b/tests/assets/nu_json/charset_test.hjson new file mode 100644 index 0000000000..7527b1e45b --- /dev/null +++ b/tests/assets/nu_json/charset_test.hjson @@ -0,0 +1,6 @@ +ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ +js-ascii: "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" +ml-ascii: + ''' + ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + ''' diff --git a/tests/assets/nu_json/comments_result.hjson b/tests/assets/nu_json/comments_result.hjson new file mode 100644 index 0000000000..a99ce23dba --- /dev/null +++ b/tests/assets/nu_json/comments_result.hjson @@ -0,0 +1,26 @@ +{ + foo1: This is a string value. # part of the string + foo2: This is a string value. + bar1: This is a string value. // part of the string + bar2: This is a string value. + foobar1: This is a string value./* part of the string */ + foobar2: This is a string value. + rem1: "# test" + rem2: "// test" + rem3: "/* test */" + num1: 0 + num2: 0 + num3: 2 + true1: true + true2: true + true3: true + false1: false + false2: false + false3: false + null1: null + null2: null + null3: null + str1: 00 # part of the string + str2: 00.0 // part of the string + str3: 02 /* part of the string */ +} \ No newline at end of file diff --git a/tests/assets/nu_json/comments_result.json b/tests/assets/nu_json/comments_result.json new file mode 100644 index 0000000000..e247803e65 --- /dev/null +++ b/tests/assets/nu_json/comments_result.json @@ -0,0 +1,26 @@ +{ + "foo1": "This is a string value. # part of the string", + "foo2": "This is a string value.", + "bar1": "This is a string value. // part of the string", + "bar2": "This is a string value.", + "foobar1": "This is a string value./* part of the string */", + "foobar2": "This is a string value.", + "rem1": "# test", + "rem2": "// test", + "rem3": "/* test */", + "num1": 0, + "num2": 0, + "num3": 2, + "true1": true, + "true2": true, + "true3": true, + "false1": false, + "false2": false, + "false3": false, + "null1": null, + "null2": null, + "null3": null, + "str1": "00 # part of the string", + "str2": "00.0 // part of the string", + "str3": "02 /* part of the string */" +} \ No newline at end of file diff --git a/tests/assets/nu_json/comments_test.hjson b/tests/assets/nu_json/comments_test.hjson new file mode 100644 index 0000000000..7f1dfdcab6 --- /dev/null +++ b/tests/assets/nu_json/comments_test.hjson @@ -0,0 +1,48 @@ +// test +# all +// comment +/* +styles +*/ +# with lf + + + +# ! + +{ + # hjson style comment + foo1: This is a string value. # part of the string + foo2: "This is a string value." # a comment + + // js style comment + bar1: This is a string value. // part of the string + bar2: "This is a string value." // a comment + + /* js block style comments */foobar1:/* more */This is a string value./* part of the string */ + /* js block style comments */foobar2:/* more */"This is a string value."/* a comment */ + + rem1: "# test" + rem2: "// test" + rem3: "/* test */" + + num1: 0 # comment + num2: 0.0 // comment + num3: 2 /* comment */ + + true1: true # comment + true2: true // comment + true3: true /* comment */ + + false1: false # comment + false2: false // comment + false3: false /* comment */ + + null1: null # comment + null2: null // comment + null3: null /* comment */ + + str1: 00 # part of the string + str2: 00.0 // part of the string + str3: 02 /* part of the string */ +} diff --git a/tests/assets/nu_json/empty_result.hjson b/tests/assets/nu_json/empty_result.hjson new file mode 100644 index 0000000000..a75b45b28b --- /dev/null +++ b/tests/assets/nu_json/empty_result.hjson @@ -0,0 +1,3 @@ +{ + "": empty +} \ No newline at end of file diff --git a/tests/assets/nu_json/empty_result.json b/tests/assets/nu_json/empty_result.json new file mode 100644 index 0000000000..47f710fe23 --- /dev/null +++ b/tests/assets/nu_json/empty_result.json @@ -0,0 +1,3 @@ +{ + "": "empty" +} \ No newline at end of file diff --git a/tests/assets/nu_json/empty_test.hjson b/tests/assets/nu_json/empty_test.hjson new file mode 100644 index 0000000000..ac97a9d7ce --- /dev/null +++ b/tests/assets/nu_json/empty_test.hjson @@ -0,0 +1,3 @@ +{ + "": empty +} diff --git a/tests/assets/nu_json/failCharset1_test.hjson b/tests/assets/nu_json/failCharset1_test.hjson new file mode 100644 index 0000000000..da280393b2 --- /dev/null +++ b/tests/assets/nu_json/failCharset1_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid \u char + char: "\uxxxx" +} diff --git a/tests/assets/nu_json/failJSON02_test.json b/tests/assets/nu_json/failJSON02_test.json new file mode 100644 index 0000000000..6b7c11e5a5 --- /dev/null +++ b/tests/assets/nu_json/failJSON02_test.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON05_test.json b/tests/assets/nu_json/failJSON05_test.json new file mode 100644 index 0000000000..ddf3ce3d24 --- /dev/null +++ b/tests/assets/nu_json/failJSON05_test.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON06_test.json b/tests/assets/nu_json/failJSON06_test.json new file mode 100644 index 0000000000..ed91580e1b --- /dev/null +++ b/tests/assets/nu_json/failJSON06_test.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON07_test.json b/tests/assets/nu_json/failJSON07_test.json new file mode 100644 index 0000000000..8a96af3e4e --- /dev/null +++ b/tests/assets/nu_json/failJSON07_test.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON08_test.json b/tests/assets/nu_json/failJSON08_test.json new file mode 100644 index 0000000000..b28479c6ec --- /dev/null +++ b/tests/assets/nu_json/failJSON08_test.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON10_test.json b/tests/assets/nu_json/failJSON10_test.json new file mode 100644 index 0000000000..5d8c0047bd --- /dev/null +++ b/tests/assets/nu_json/failJSON10_test.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON11_test.json b/tests/assets/nu_json/failJSON11_test.json new file mode 100644 index 0000000000..76eb95b458 --- /dev/null +++ b/tests/assets/nu_json/failJSON11_test.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON12_test.json b/tests/assets/nu_json/failJSON12_test.json new file mode 100644 index 0000000000..77580a4522 --- /dev/null +++ b/tests/assets/nu_json/failJSON12_test.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON13_test.json b/tests/assets/nu_json/failJSON13_test.json new file mode 100644 index 0000000000..379406b59b --- /dev/null +++ b/tests/assets/nu_json/failJSON13_test.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON14_test.json b/tests/assets/nu_json/failJSON14_test.json new file mode 100644 index 0000000000..0ed366b38a --- /dev/null +++ b/tests/assets/nu_json/failJSON14_test.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON15_test.json b/tests/assets/nu_json/failJSON15_test.json new file mode 100644 index 0000000000..fc8376b605 --- /dev/null +++ b/tests/assets/nu_json/failJSON15_test.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON16_test.json b/tests/assets/nu_json/failJSON16_test.json new file mode 100644 index 0000000000..3fe21d4b53 --- /dev/null +++ b/tests/assets/nu_json/failJSON16_test.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON17_test.json b/tests/assets/nu_json/failJSON17_test.json new file mode 100644 index 0000000000..62b9214aed --- /dev/null +++ b/tests/assets/nu_json/failJSON17_test.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON19_test.json b/tests/assets/nu_json/failJSON19_test.json new file mode 100644 index 0000000000..3b9c46fa9a --- /dev/null +++ b/tests/assets/nu_json/failJSON19_test.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON20_test.json b/tests/assets/nu_json/failJSON20_test.json new file mode 100644 index 0000000000..27c1af3e72 --- /dev/null +++ b/tests/assets/nu_json/failJSON20_test.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON21_test.json b/tests/assets/nu_json/failJSON21_test.json new file mode 100644 index 0000000000..62474573b2 --- /dev/null +++ b/tests/assets/nu_json/failJSON21_test.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON22_test.json b/tests/assets/nu_json/failJSON22_test.json new file mode 100644 index 0000000000..a7752581bc --- /dev/null +++ b/tests/assets/nu_json/failJSON22_test.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON23_test.json b/tests/assets/nu_json/failJSON23_test.json new file mode 100644 index 0000000000..494add1ca1 --- /dev/null +++ b/tests/assets/nu_json/failJSON23_test.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON24_test.json b/tests/assets/nu_json/failJSON24_test.json new file mode 100644 index 0000000000..caff239bfc --- /dev/null +++ b/tests/assets/nu_json/failJSON24_test.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON26_test.json b/tests/assets/nu_json/failJSON26_test.json new file mode 100644 index 0000000000..845d26a6a5 --- /dev/null +++ b/tests/assets/nu_json/failJSON26_test.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON28_test.json b/tests/assets/nu_json/failJSON28_test.json new file mode 100644 index 0000000000..621a0101c6 --- /dev/null +++ b/tests/assets/nu_json/failJSON28_test.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON29_test.json b/tests/assets/nu_json/failJSON29_test.json new file mode 100644 index 0000000000..47ec421bb6 --- /dev/null +++ b/tests/assets/nu_json/failJSON29_test.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON30_test.json b/tests/assets/nu_json/failJSON30_test.json new file mode 100644 index 0000000000..8ab0bc4b8b --- /dev/null +++ b/tests/assets/nu_json/failJSON30_test.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON31_test.json b/tests/assets/nu_json/failJSON31_test.json new file mode 100644 index 0000000000..1cce602b51 --- /dev/null +++ b/tests/assets/nu_json/failJSON31_test.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON32_test.json b/tests/assets/nu_json/failJSON32_test.json new file mode 100644 index 0000000000..45cba7396f --- /dev/null +++ b/tests/assets/nu_json/failJSON32_test.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON33_test.json b/tests/assets/nu_json/failJSON33_test.json new file mode 100644 index 0000000000..ca5eb19dc9 --- /dev/null +++ b/tests/assets/nu_json/failJSON33_test.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON34_test.json b/tests/assets/nu_json/failJSON34_test.json new file mode 100644 index 0000000000..921436427e --- /dev/null +++ b/tests/assets/nu_json/failJSON34_test.json @@ -0,0 +1,2 @@ +A quoteless string is OK, +but two must be contained in an array. diff --git a/tests/assets/nu_json/failKey1_test.hjson b/tests/assets/nu_json/failKey1_test.hjson new file mode 100644 index 0000000000..0026d2a1ae --- /dev/null +++ b/tests/assets/nu_json/failKey1_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + wrong name: 0 +} diff --git a/tests/assets/nu_json/failKey2_test.hjson b/tests/assets/nu_json/failKey2_test.hjson new file mode 100644 index 0000000000..4b5771a7d2 --- /dev/null +++ b/tests/assets/nu_json/failKey2_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + {name: 0 +} diff --git a/tests/assets/nu_json/failKey3_test.hjson b/tests/assets/nu_json/failKey3_test.hjson new file mode 100644 index 0000000000..3443a87f4b --- /dev/null +++ b/tests/assets/nu_json/failKey3_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + key,name: 0 +} diff --git a/tests/assets/nu_json/failKey4_test.hjson b/tests/assets/nu_json/failKey4_test.hjson new file mode 100644 index 0000000000..a7e9ce2aea --- /dev/null +++ b/tests/assets/nu_json/failKey4_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + : 0 +} diff --git a/tests/assets/nu_json/failMLStr1_test.hjson b/tests/assets/nu_json/failMLStr1_test.hjson new file mode 100644 index 0000000000..ca6410720b --- /dev/null +++ b/tests/assets/nu_json/failMLStr1_test.hjson @@ -0,0 +1,3 @@ +{ + # invalid multiline string + ml: ''' diff --git a/tests/assets/nu_json/failObj1_test.hjson b/tests/assets/nu_json/failObj1_test.hjson new file mode 100644 index 0000000000..ac7b761fbf --- /dev/null +++ b/tests/assets/nu_json/failObj1_test.hjson @@ -0,0 +1,6 @@ +{ + # invalid obj + noDelimiter + { + } +} diff --git a/tests/assets/nu_json/failObj2_test.hjson b/tests/assets/nu_json/failObj2_test.hjson new file mode 100644 index 0000000000..cdad47eefe --- /dev/null +++ b/tests/assets/nu_json/failObj2_test.hjson @@ -0,0 +1,6 @@ +{ + # invalid obj + noEnd + { + +} diff --git a/tests/assets/nu_json/failObj3_test.hjson b/tests/assets/nu_json/failObj3_test.hjson new file mode 100644 index 0000000000..f1176842de --- /dev/null +++ b/tests/assets/nu_json/failObj3_test.hjson @@ -0,0 +1,7 @@ +{ + # missing key + + [ + test + ] +} diff --git a/tests/assets/nu_json/failStr1a_test.hjson b/tests/assets/nu_json/failStr1a_test.hjson new file mode 100644 index 0000000000..91b930ca44 --- /dev/null +++ b/tests/assets/nu_json/failStr1a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: ] +} diff --git a/tests/assets/nu_json/failStr1b_test.hjson b/tests/assets/nu_json/failStr1b_test.hjson new file mode 100644 index 0000000000..91da1a8dee --- /dev/null +++ b/tests/assets/nu_json/failStr1b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: ]x +} diff --git a/tests/assets/nu_json/failStr1c_test.hjson b/tests/assets/nu_json/failStr1c_test.hjson new file mode 100644 index 0000000000..20a73079f2 --- /dev/null +++ b/tests/assets/nu_json/failStr1c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + ] +] diff --git a/tests/assets/nu_json/failStr1d_test.hjson b/tests/assets/nu_json/failStr1d_test.hjson new file mode 100644 index 0000000000..555f88a915 --- /dev/null +++ b/tests/assets/nu_json/failStr1d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + ]x +] diff --git a/tests/assets/nu_json/failStr2a_test.hjson b/tests/assets/nu_json/failStr2a_test.hjson new file mode 100644 index 0000000000..5a8b28feb7 --- /dev/null +++ b/tests/assets/nu_json/failStr2a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: } +} diff --git a/tests/assets/nu_json/failStr2b_test.hjson b/tests/assets/nu_json/failStr2b_test.hjson new file mode 100644 index 0000000000..a7fc00de73 --- /dev/null +++ b/tests/assets/nu_json/failStr2b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: }x +} diff --git a/tests/assets/nu_json/failStr2c_test.hjson b/tests/assets/nu_json/failStr2c_test.hjson new file mode 100644 index 0000000000..1fe46067b6 --- /dev/null +++ b/tests/assets/nu_json/failStr2c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + } +] diff --git a/tests/assets/nu_json/failStr2d_test.hjson b/tests/assets/nu_json/failStr2d_test.hjson new file mode 100644 index 0000000000..ea8c99adcc --- /dev/null +++ b/tests/assets/nu_json/failStr2d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + }x +] diff --git a/tests/assets/nu_json/failStr3a_test.hjson b/tests/assets/nu_json/failStr3a_test.hjson new file mode 100644 index 0000000000..ec5d0ad209 --- /dev/null +++ b/tests/assets/nu_json/failStr3a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: { +} diff --git a/tests/assets/nu_json/failStr3b_test.hjson b/tests/assets/nu_json/failStr3b_test.hjson new file mode 100644 index 0000000000..2d0fff1fb9 --- /dev/null +++ b/tests/assets/nu_json/failStr3b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: {x +} diff --git a/tests/assets/nu_json/failStr3c_test.hjson b/tests/assets/nu_json/failStr3c_test.hjson new file mode 100644 index 0000000000..2872a4d95f --- /dev/null +++ b/tests/assets/nu_json/failStr3c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + { +] diff --git a/tests/assets/nu_json/failStr3d_test.hjson b/tests/assets/nu_json/failStr3d_test.hjson new file mode 100644 index 0000000000..949502ffc8 --- /dev/null +++ b/tests/assets/nu_json/failStr3d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + {x +] diff --git a/tests/assets/nu_json/failStr4a_test.hjson b/tests/assets/nu_json/failStr4a_test.hjson new file mode 100644 index 0000000000..941f35bcd7 --- /dev/null +++ b/tests/assets/nu_json/failStr4a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: [ +} diff --git a/tests/assets/nu_json/failStr4b_test.hjson b/tests/assets/nu_json/failStr4b_test.hjson new file mode 100644 index 0000000000..b7bb236281 --- /dev/null +++ b/tests/assets/nu_json/failStr4b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: [x +} diff --git a/tests/assets/nu_json/failStr4c_test.hjson b/tests/assets/nu_json/failStr4c_test.hjson new file mode 100644 index 0000000000..ee927a4757 --- /dev/null +++ b/tests/assets/nu_json/failStr4c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + [ +] diff --git a/tests/assets/nu_json/failStr4d_test.hjson b/tests/assets/nu_json/failStr4d_test.hjson new file mode 100644 index 0000000000..db50a529d8 --- /dev/null +++ b/tests/assets/nu_json/failStr4d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + [x +] diff --git a/tests/assets/nu_json/failStr5a_test.hjson b/tests/assets/nu_json/failStr5a_test.hjson new file mode 100644 index 0000000000..4093f7a58d --- /dev/null +++ b/tests/assets/nu_json/failStr5a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: : +} diff --git a/tests/assets/nu_json/failStr5b_test.hjson b/tests/assets/nu_json/failStr5b_test.hjson new file mode 100644 index 0000000000..eda96192ba --- /dev/null +++ b/tests/assets/nu_json/failStr5b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: :x +} diff --git a/tests/assets/nu_json/failStr5c_test.hjson b/tests/assets/nu_json/failStr5c_test.hjson new file mode 100644 index 0000000000..63280735b4 --- /dev/null +++ b/tests/assets/nu_json/failStr5c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + : +] diff --git a/tests/assets/nu_json/failStr5d_test.hjson b/tests/assets/nu_json/failStr5d_test.hjson new file mode 100644 index 0000000000..bfaef8e7da --- /dev/null +++ b/tests/assets/nu_json/failStr5d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + :x +] diff --git a/tests/assets/nu_json/failStr6a_test.hjson b/tests/assets/nu_json/failStr6a_test.hjson new file mode 100644 index 0000000000..522333773d --- /dev/null +++ b/tests/assets/nu_json/failStr6a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: , +} diff --git a/tests/assets/nu_json/failStr6b_test.hjson b/tests/assets/nu_json/failStr6b_test.hjson new file mode 100644 index 0000000000..90ad67bf5a --- /dev/null +++ b/tests/assets/nu_json/failStr6b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: ,x +} diff --git a/tests/assets/nu_json/failStr6c_test.hjson b/tests/assets/nu_json/failStr6c_test.hjson new file mode 100644 index 0000000000..81d2af4c9c --- /dev/null +++ b/tests/assets/nu_json/failStr6c_test.hjson @@ -0,0 +1,6 @@ +[ + # invalid quoteless string + # note that if there were a preceding value the comma would + # be allowed/ignored as a separator/trailing comma + , +] diff --git a/tests/assets/nu_json/failStr6d_test.hjson b/tests/assets/nu_json/failStr6d_test.hjson new file mode 100644 index 0000000000..c1477293b9 --- /dev/null +++ b/tests/assets/nu_json/failStr6d_test.hjson @@ -0,0 +1,6 @@ +[ + # invalid quoteless string + # note that if there were a preceding value the comma would + # be allowed/ignored as a separator/trailing comma + ,x +] diff --git a/tests/assets/nu_json/kan_result.hjson b/tests/assets/nu_json/kan_result.hjson new file mode 100644 index 0000000000..d2b839add0 --- /dev/null +++ b/tests/assets/nu_json/kan_result.hjson @@ -0,0 +1,48 @@ +{ + numbers: + [ + 0 + 0 + 0 + 42 + 42.1 + -5 + -5.1 + 1701 + -1701 + 12.345 + -12.345 + ] + native: + [ + true + true + false + false + null + null + ] + strings: + [ + x 0 + .0 + 00 + 01 + 0 0 0 + 42 x + 42.1 asdf + 1.2.3 + -5 0 - + -5.1 -- + 17.01e2 + + -17.01e2 : + 12345e-3 @ + -12345e-3 $ + true true + x true + false false + x false + null null + x null + ] +} \ No newline at end of file diff --git a/tests/assets/nu_json/kan_result.json b/tests/assets/nu_json/kan_result.json new file mode 100644 index 0000000000..babb9d4e06 --- /dev/null +++ b/tests/assets/nu_json/kan_result.json @@ -0,0 +1,45 @@ +{ + "numbers": [ + 0, + 0, + 0, + 42, + 42.1, + -5, + -5.1, + 1701, + -1701, + 12.345, + -12.345 + ], + "native": [ + true, + true, + false, + false, + null, + null + ], + "strings": [ + "x 0", + ".0", + "00", + "01", + "0 0 0", + "42 x", + "42.1 asdf", + "1.2.3", + "-5 0 -", + "-5.1 --", + "17.01e2 +", + "-17.01e2 :", + "12345e-3 @", + "-12345e-3 $", + "true true", + "x true", + "false false", + "x false", + "null null", + "x null" + ] +} \ No newline at end of file diff --git a/tests/assets/nu_json/kan_test.hjson b/tests/assets/nu_json/kan_test.hjson new file mode 100644 index 0000000000..1e6906a96a --- /dev/null +++ b/tests/assets/nu_json/kan_test.hjson @@ -0,0 +1,49 @@ +{ + # the comma forces a whitespace check + numbers: + [ + 0 + 0 , + -0 + 42 , + 42.1 , + -5 + -5.1 + 17.01e2 + -17.01e2 + 12345e-3 , + -12345e-3 , + ] + native: + [ + true , + true + false , + false + null , + null + ] + strings: + [ + x 0 + .0 + 00 + 01 + 0 0 0 + 42 x + 42.1 asdf + 1.2.3 + -5 0 - + -5.1 -- + 17.01e2 + + -17.01e2 : + 12345e-3 @ + -12345e-3 $ + true true + x true + false false + x false + null null + x null + ] +} diff --git a/tests/assets/nu_json/keys_result.hjson b/tests/assets/nu_json/keys_result.hjson new file mode 100644 index 0000000000..876e6c3462 --- /dev/null +++ b/tests/assets/nu_json/keys_result.hjson @@ -0,0 +1,34 @@ +{ + unquoted_key: test + _unquoted: test + test-key: test + -test: test + .key: test + trailing: test + trailing2: test + "#c1": test + "foo#bar": test + "//bar": test + "foo//bar": test + "/*foo*/": test + "foo/*foo*/bar": test + "/*": test + "foo/*bar": test + "\"": test + "foo\"bar": test + "'''": test + "foo'''bar": test + ":": test + "foo:bar": test + "{": test + "foo{bar": test + "}": test + "foo}bar": test + "[": test + "foo[bar": test + "]": test + "foo]bar": test + nl1: test + nl2: test + nl3: test +} \ No newline at end of file diff --git a/tests/assets/nu_json/keys_result.json b/tests/assets/nu_json/keys_result.json new file mode 100644 index 0000000000..81fa480b21 --- /dev/null +++ b/tests/assets/nu_json/keys_result.json @@ -0,0 +1,34 @@ +{ + "unquoted_key": "test", + "_unquoted": "test", + "test-key": "test", + "-test": "test", + ".key": "test", + "trailing": "test", + "trailing2": "test", + "#c1": "test", + "foo#bar": "test", + "//bar": "test", + "foo//bar": "test", + "/*foo*/": "test", + "foo/*foo*/bar": "test", + "/*": "test", + "foo/*bar": "test", + "\"": "test", + "foo\"bar": "test", + "'''": "test", + "foo'''bar": "test", + ":": "test", + "foo:bar": "test", + "{": "test", + "foo{bar": "test", + "}": "test", + "foo}bar": "test", + "[": "test", + "foo[bar": "test", + "]": "test", + "foo]bar": "test", + "nl1": "test", + "nl2": "test", + "nl3": "test" +} \ No newline at end of file diff --git a/tests/assets/nu_json/keys_test.hjson b/tests/assets/nu_json/keys_test.hjson new file mode 100644 index 0000000000..38f5603814 --- /dev/null +++ b/tests/assets/nu_json/keys_test.hjson @@ -0,0 +1,48 @@ +{ + # unquoted keys + unquoted_key: test + _unquoted: test + test-key: test + -test: test + .key: test + # trailing spaces in key names are ignored + trailing : test + trailing2 : test + # comment char in key name + "#c1": test + "foo#bar": test + "//bar": test + "foo//bar": test + "/*foo*/": test + "foo/*foo*/bar": test + "/*": test + "foo/*bar": test + # quotes in key name + "\"": test + "foo\"bar": test + "'''": test + "foo'''bar": test + # control char in key name + ":": test + "foo:bar": test + "{": test + "foo{bar": test + "}": test + "foo}bar": test + "[": test + "foo[bar": test + "]": test + "foo]bar": test + # newline + nl1: + test + nl2 + : + test + + nl3 + + : + + test +} diff --git a/tests/assets/nu_json/oa_result.hjson b/tests/assets/nu_json/oa_result.hjson new file mode 100644 index 0000000000..db42ac9012 --- /dev/null +++ b/tests/assets/nu_json/oa_result.hjson @@ -0,0 +1,13 @@ +[ + a + {} + {} + [] + [] + { + b: 1 + c: [] + d: {} + } + [] +] \ No newline at end of file diff --git a/tests/assets/nu_json/oa_result.json b/tests/assets/nu_json/oa_result.json new file mode 100644 index 0000000000..f0955abeb5 --- /dev/null +++ b/tests/assets/nu_json/oa_result.json @@ -0,0 +1,13 @@ +[ + "a", + {}, + {}, + [], + [], + { + "b": 1, + "c": [], + "d": {} + }, + [] +] \ No newline at end of file diff --git a/tests/assets/nu_json/oa_test.hjson b/tests/assets/nu_json/oa_test.hjson new file mode 100644 index 0000000000..35bcdb4522 --- /dev/null +++ b/tests/assets/nu_json/oa_test.hjson @@ -0,0 +1,13 @@ +[ + a + {} + {} + [] + [] + { + b: 1 + c: [] + d: {} + } + [] +] diff --git a/tests/assets/nu_json/pass1_result.hjson b/tests/assets/nu_json/pass1_result.hjson new file mode 100644 index 0000000000..2a3f4c97f1 --- /dev/null +++ b/tests/assets/nu_json/pass1_result.hjson @@ -0,0 +1,78 @@ +[ + JSON Test Pattern pass1 + { + "object with 1 member": + [ + array with 1 element + ] + } + {} + [] + -42 + true + false + null + { + integer: 1234567890 + real: -9876.54321 + e: 1.23456789e-13 + E: 1.23456789e+34 + -: 2.3456789012e+76 + zero: 0 + one: 1 + space: " " + quote: '''"''' + backslash: \ + controls: "\b\f\n\r\t" + slash: / & / + alpha: abcdefghijklmnopqrstuvwyz + ALPHA: ABCDEFGHIJKLMNOPQRSTUVWYZ + digit: 0123456789 + 0123456789: digit + special: `1~!@#$%^&*()_+-={':[,]}|;.? + hex: ģ䕧覫췯ꯍ + true: true + false: false + null: null + array: [] + object: {} + address: 50 St. James Street + url: http://www.JSON.org/ + comment: "// /* */": " " + " s p a c e d ": + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + ] + compact: + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + ] + jsontext: '''{"object with 1 member":["array with 1 element"]}''' + quotes: " " %22 0x22 034 " + "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": A key can be any string + } + 0.5 + 98.6 + 99.44 + 1066 + 10 + 1 + 0.1 + 1 + 2 + 2 + rosebud +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass1_result.json b/tests/assets/nu_json/pass1_result.json new file mode 100644 index 0000000000..69b354d05e --- /dev/null +++ b/tests/assets/nu_json/pass1_result.json @@ -0,0 +1,75 @@ +[ + "JSON Test Pattern pass1", + { + "object with 1 member": [ + "array with 1 element" + ] + }, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.54321, + "e": 1.23456789e-13, + "E": 1.23456789e+34, + "-": 2.3456789012e+76, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & /", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "ģ䕧覫췯ꯍ", + "true": true, + "false": false, + "null": null, + "array": [], + "object": {}, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d ": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "compact": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \" %22 0x22 034 "", + "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" + }, + 0.5, + 98.6, + 99.44, + 1066, + 10, + 1, + 0.1, + 1, + 2, + 2, + "rosebud" +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass1_test.json b/tests/assets/nu_json/pass1_test.json new file mode 100644 index 0000000000..61cfd90c94 --- /dev/null +++ b/tests/assets/nu_json/pass1_test.json @@ -0,0 +1,58 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "-": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] \ No newline at end of file diff --git a/tests/assets/nu_json/pass2_result.hjson b/tests/assets/nu_json/pass2_result.hjson new file mode 100644 index 0000000000..5a9fd5e82b --- /dev/null +++ b/tests/assets/nu_json/pass2_result.hjson @@ -0,0 +1,39 @@ +[ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + Not too deep + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass2_result.json b/tests/assets/nu_json/pass2_result.json new file mode 100644 index 0000000000..2a71f5850e --- /dev/null +++ b/tests/assets/nu_json/pass2_result.json @@ -0,0 +1,39 @@ +[ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + "Not too deep" + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass2_test.json b/tests/assets/nu_json/pass2_test.json new file mode 100644 index 0000000000..d3c63c7ad8 --- /dev/null +++ b/tests/assets/nu_json/pass2_test.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/tests/assets/nu_json/pass3_result.hjson b/tests/assets/nu_json/pass3_result.hjson new file mode 100644 index 0000000000..6db3fb61d7 --- /dev/null +++ b/tests/assets/nu_json/pass3_result.hjson @@ -0,0 +1,7 @@ +{ + "JSON Test Pattern pass3": + { + "The outermost value": must be an object or array. + "In this test": It is an object. + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/pass3_result.json b/tests/assets/nu_json/pass3_result.json new file mode 100644 index 0000000000..d98cd2f881 --- /dev/null +++ b/tests/assets/nu_json/pass3_result.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/pass3_test.json b/tests/assets/nu_json/pass3_test.json new file mode 100644 index 0000000000..4528d51f1a --- /dev/null +++ b/tests/assets/nu_json/pass3_test.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/tests/assets/nu_json/pass4_result.hjson b/tests/assets/nu_json/pass4_result.hjson new file mode 100644 index 0000000000..9a037142aa --- /dev/null +++ b/tests/assets/nu_json/pass4_result.hjson @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/tests/assets/nu_json/pass4_result.json b/tests/assets/nu_json/pass4_result.json new file mode 100644 index 0000000000..9a037142aa --- /dev/null +++ b/tests/assets/nu_json/pass4_result.json @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/tests/assets/nu_json/pass4_test.json b/tests/assets/nu_json/pass4_test.json new file mode 100644 index 0000000000..069c2ae6b8 --- /dev/null +++ b/tests/assets/nu_json/pass4_test.json @@ -0,0 +1,2 @@ + +10 diff --git a/tests/assets/nu_json/passSingle_result.hjson b/tests/assets/nu_json/passSingle_result.hjson new file mode 100644 index 0000000000..e580fce159 --- /dev/null +++ b/tests/assets/nu_json/passSingle_result.hjson @@ -0,0 +1 @@ +allow quoteless strings \ No newline at end of file diff --git a/tests/assets/nu_json/passSingle_result.json b/tests/assets/nu_json/passSingle_result.json new file mode 100644 index 0000000000..1829d36f86 --- /dev/null +++ b/tests/assets/nu_json/passSingle_result.json @@ -0,0 +1 @@ +"allow quoteless strings" \ No newline at end of file diff --git a/tests/assets/nu_json/passSingle_test.hjson b/tests/assets/nu_json/passSingle_test.hjson new file mode 100644 index 0000000000..e580fce159 --- /dev/null +++ b/tests/assets/nu_json/passSingle_test.hjson @@ -0,0 +1 @@ +allow quoteless strings \ No newline at end of file diff --git a/tests/assets/nu_json/root_result.hjson b/tests/assets/nu_json/root_result.hjson new file mode 100644 index 0000000000..736372f62a --- /dev/null +++ b/tests/assets/nu_json/root_result.hjson @@ -0,0 +1,7 @@ +{ + database: + { + host: 127.0.0.1 + port: 555 + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/root_result.json b/tests/assets/nu_json/root_result.json new file mode 100644 index 0000000000..21b01cd003 --- /dev/null +++ b/tests/assets/nu_json/root_result.json @@ -0,0 +1,6 @@ +{ + "database": { + "host": "127.0.0.1", + "port": 555 + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/root_test.hjson b/tests/assets/nu_json/root_test.hjson new file mode 100644 index 0000000000..c0acd16eeb --- /dev/null +++ b/tests/assets/nu_json/root_test.hjson @@ -0,0 +1,6 @@ +// a object with the root braces omitted +database: +{ + host: 127.0.0.1 + port: 555 +} diff --git a/tests/assets/nu_json/stringify1_result.hjson b/tests/assets/nu_json/stringify1_result.hjson new file mode 100644 index 0000000000..77b2eddc13 --- /dev/null +++ b/tests/assets/nu_json/stringify1_result.hjson @@ -0,0 +1,49 @@ +{ + quotes: + { + num1: "1,2" + num2: "-1.1 ," + num3: "1e10 ,2" + num4: "-1e-10," + kw1: "true," + kw2: "false ," + kw3: "null,123" + close1: "1}" + close1b: "1 }" + close2: "1]" + close2b: "1 ]" + close3: "1," + close3b: "1 ," + comment1: "1#str" + comment2: "1//str" + comment3: "1/*str*/" + punc1: "{" + punc1b: "{foo" + punc2: "}" + punc2b: "}foo" + punc3: "[" + punc3b: "[foo" + punc4: "]" + punc4b: "]foo" + punc5: "," + punc5b: ",foo" + punc6: ":" + punc6b: ":foo" + } + noquotes: + { + num0: .1,2 + num1: 1.1.1,2 + num2: -.1, + num3: 1e10e,2 + num4: -1e--10, + kw1: true1, + kw2: false0, + kw3: null0, + close1: a} + close2: a] + comment1: a#str + comment2: a//str + comment3: a/*str*/ + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/stringify1_result.json b/tests/assets/nu_json/stringify1_result.json new file mode 100644 index 0000000000..12514f8786 --- /dev/null +++ b/tests/assets/nu_json/stringify1_result.json @@ -0,0 +1,47 @@ +{ + "quotes": { + "num1": "1,2", + "num2": "-1.1 ,", + "num3": "1e10 ,2", + "num4": "-1e-10,", + "kw1": "true,", + "kw2": "false ,", + "kw3": "null,123", + "close1": "1}", + "close1b": "1 }", + "close2": "1]", + "close2b": "1 ]", + "close3": "1,", + "close3b": "1 ,", + "comment1": "1#str", + "comment2": "1//str", + "comment3": "1/*str*/", + "punc1": "{", + "punc1b": "{foo", + "punc2": "}", + "punc2b": "}foo", + "punc3": "[", + "punc3b": "[foo", + "punc4": "]", + "punc4b": "]foo", + "punc5": ",", + "punc5b": ",foo", + "punc6": ":", + "punc6b": ":foo" + }, + "noquotes": { + "num0": ".1,2", + "num1": "1.1.1,2", + "num2": "-.1,", + "num3": "1e10e,2", + "num4": "-1e--10,", + "kw1": "true1,", + "kw2": "false0,", + "kw3": "null0,", + "close1": "a}", + "close2": "a]", + "comment1": "a#str", + "comment2": "a//str", + "comment3": "a/*str*/" + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/stringify1_test.hjson b/tests/assets/nu_json/stringify1_test.hjson new file mode 100644 index 0000000000..b353bf19cf --- /dev/null +++ b/tests/assets/nu_json/stringify1_test.hjson @@ -0,0 +1,50 @@ +// test if stringify produces correct output +{ + quotes: + { + num1: "1,2" + num2: "-1.1 ," + num3: "1e10 ,2" + num4: "-1e-10," + kw1: "true," + kw2: "false ," + kw3: "null,123" + close1: "1}" + close1b: "1 }" + close2: "1]" + close2b: "1 ]" + close3: "1," + close3b: "1 ," + comment1: "1#str" + comment2: "1//str" + comment3: "1/*str*/" + punc1: "{" + punc1b: "{foo" + punc2: "}" + punc2b: "}foo" + punc3: "[" + punc3b: "[foo" + punc4: "]" + punc4b: "]foo" + punc5: "," + punc5b: ",foo" + punc6: ":" + punc6b: ":foo" + } + noquotes: + { + num0: ".1,2" + num1: "1.1.1,2" + num2: "-.1," + num3: "1e10e,2" + num4: "-1e--10," + kw1: "true1," + kw2: "false0," + kw3: "null0," + close1: "a}" + close2: "a]" + comment1: "a#str" + comment2: "a//str" + comment3: "a/*str*/" + } +} diff --git a/tests/assets/nu_json/strings_result.hjson b/tests/assets/nu_json/strings_result.hjson new file mode 100644 index 0000000000..6ef06f8741 --- /dev/null +++ b/tests/assets/nu_json/strings_result.hjson @@ -0,0 +1,75 @@ +{ + text1: This is a valid string value. + text2: a \ is just a \ + text3: "You need quotes\tfor escapes" + text4a: " untrimmed " + text4b: " untrimmed" + text4c: "untrimmed " + multiline1: + ''' + first line + indented line + last line + ''' + multiline2: + ''' + first line + indented line + last line + ''' + multiline3: + ''' + first line + indented line + last line + + ''' + foo1a: asdf\"'a\s\w + foo1b: asdf\"'a\s\w + foo1c: asdf\"'a\s\w + foo2a: '''"asdf"''' + foo2b: '''"asdf"''' + foo3a: asdf''' + foo3b: "'''asdf" + foo4a: "asdf'''\nasdf" + foo4b: "asdf\n'''asdf" + arr: + [ + one + two + three + four + ] + not: + { + number: 5 + negative: -4.2 + yes: true + no: false + null: null + array: + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + -1 + 0.5 + ] + } + special: + { + true: "true" + false: "false" + null: "null" + one: "1" + two: "2" + minus: "-3" + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/strings_result.json b/tests/assets/nu_json/strings_result.json new file mode 100644 index 0000000000..16321ba7a9 --- /dev/null +++ b/tests/assets/nu_json/strings_result.json @@ -0,0 +1,55 @@ +{ + "text1": "This is a valid string value.", + "text2": "a \\ is just a \\", + "text3": "You need quotes\tfor escapes", + "text4a": " untrimmed ", + "text4b": " untrimmed", + "text4c": "untrimmed ", + "multiline1": "first line\n indented line\nlast line", + "multiline2": "first line\n indented line\nlast line", + "multiline3": "first line\n indented line\nlast line\n", + "foo1a": "asdf\\\"'a\\s\\w", + "foo1b": "asdf\\\"'a\\s\\w", + "foo1c": "asdf\\\"'a\\s\\w", + "foo2a": "\"asdf\"", + "foo2b": "\"asdf\"", + "foo3a": "asdf'''", + "foo3b": "'''asdf", + "foo4a": "asdf'''\nasdf", + "foo4b": "asdf\n'''asdf", + "arr": [ + "one", + "two", + "three", + "four" + ], + "not": { + "number": 5, + "negative": -4.2, + "yes": true, + "no": false, + "null": null, + "array": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + -1, + 0.5 + ] + }, + "special": { + "true": "true", + "false": "false", + "null": "null", + "one": "1", + "two": "2", + "minus": "-3" + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/strings_test.hjson b/tests/assets/nu_json/strings_test.hjson new file mode 100644 index 0000000000..616895209f --- /dev/null +++ b/tests/assets/nu_json/strings_test.hjson @@ -0,0 +1,80 @@ +{ + # simple + + text1: This is a valid string value. + text2:a \ is just a \ + + text3: "You need quotes\tfor escapes" + + text4a: " untrimmed " + text4b: " untrimmed" + text4c: "untrimmed " + + # multiline string + + multiline1: + ''' + first line + indented line + last line + ''' + + multiline2: + '''first line + indented line + last line''' + + multiline3: + ''' + first line + indented line + last line + + ''' # trailing lf + + # escapes/no escape + + foo1a: asdf\"'a\s\w + foo1b: '''asdf\"'a\s\w''' + foo1c: "asdf\\\"'a\\s\\w" + + foo2a: "\"asdf\"" + foo2b: '''"asdf"''' + + foo3a: "asdf'''" + foo3b: "'''asdf" + + foo4a: "asdf'''\nasdf" + foo4b: "asdf\n'''asdf" + + # in arrays + arr: + [ + one + two + "three" + '''four''' + ] + + # not strings + not: + { + number: 5 + negative: -4.2 + yes: true + no: false + null: null + array: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -1, 0.5 ] + } + + # special quoted + special: + { + true: "true" + false: "false" + null: "null" + one: "1" + two: "2" + minus: "-3" + } +} diff --git a/tests/assets/nu_json/testlist.txt b/tests/assets/nu_json/testlist.txt new file mode 100644 index 0000000000..debb52c83e --- /dev/null +++ b/tests/assets/nu_json/testlist.txt @@ -0,0 +1,75 @@ +charset_test.hjson +comments_test.hjson +empty_test.hjson +failCharset1_test.hjson +failJSON02_test.json +failJSON05_test.json +failJSON06_test.json +failJSON07_test.json +failJSON08_test.json +failJSON10_test.json +failJSON11_test.json +failJSON12_test.json +failJSON13_test.json +failJSON14_test.json +failJSON15_test.json +failJSON16_test.json +failJSON17_test.json +failJSON19_test.json +failJSON20_test.json +failJSON21_test.json +failJSON22_test.json +failJSON23_test.json +failJSON24_test.json +failJSON26_test.json +failJSON28_test.json +failJSON29_test.json +failJSON30_test.json +failJSON31_test.json +failJSON32_test.json +failJSON33_test.json +failJSON34_test.json +failKey1_test.hjson +failKey2_test.hjson +failKey3_test.hjson +failKey4_test.hjson +failMLStr1_test.hjson +failObj1_test.hjson +failObj2_test.hjson +failObj3_test.hjson +failStr1a_test.hjson +failStr1b_test.hjson +failStr1c_test.hjson +failStr1d_test.hjson +failStr2a_test.hjson +failStr2b_test.hjson +failStr2c_test.hjson +failStr2d_test.hjson +failStr3a_test.hjson +failStr3b_test.hjson +failStr3c_test.hjson +failStr3d_test.hjson +failStr4a_test.hjson +failStr4b_test.hjson +failStr4c_test.hjson +failStr4d_test.hjson +failStr5a_test.hjson +failStr5b_test.hjson +failStr5c_test.hjson +failStr5d_test.hjson +failStr6a_test.hjson +failStr6b_test.hjson +failStr6c_test.hjson +failStr6d_test.hjson +kan_test.hjson +keys_test.hjson +oa_test.hjson +pass1_test.json +pass2_test.json +pass3_test.json +pass4_test.json +passSingle_test.hjson +root_test.hjson +stringify1_test.hjson +strings_test.hjson +trail_test.hjson \ No newline at end of file diff --git a/tests/assets/nu_json/trail_result.hjson b/tests/assets/nu_json/trail_result.hjson new file mode 100644 index 0000000000..57ffc716bd --- /dev/null +++ b/tests/assets/nu_json/trail_result.hjson @@ -0,0 +1,3 @@ +{ + foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 +} \ No newline at end of file diff --git a/tests/assets/nu_json/trail_result.json b/tests/assets/nu_json/trail_result.json new file mode 100644 index 0000000000..451c8ceb94 --- /dev/null +++ b/tests/assets/nu_json/trail_result.json @@ -0,0 +1,3 @@ +{ + "foo": "0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1" +} \ No newline at end of file diff --git a/tests/assets/nu_json/trail_test.hjson b/tests/assets/nu_json/trail_test.hjson new file mode 100644 index 0000000000..62d98e98a3 --- /dev/null +++ b/tests/assets/nu_json/trail_test.hjson @@ -0,0 +1,2 @@ +// the following line contains trailing whitespace: +foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 diff --git a/tests/fixtures/formats/appveyor.yml b/tests/fixtures/formats/appveyor.yml new file mode 100644 index 0000000000..770f32a2a9 --- /dev/null +++ b/tests/fixtures/formats/appveyor.yml @@ -0,0 +1,31 @@ +image: Visual Studio 2017 + +environment: + global: + PROJECT_NAME: nushell + RUST_BACKTRACE: 1 + matrix: + - TARGET: x86_64-pc-windows-msvc + CHANNEL: nightly + BITS: 64 + +install: + - set PATH=C:\msys64\mingw%BITS%\bin;C:\msys64\usr\bin;%PATH% + - curl -sSf -o rustup-init.exe https://win.rustup.rs + # Install rust + - rustup-init.exe -y --default-host %TARGET% --default-toolchain %CHANNEL%-%TARGET% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + # Required for Racer autoconfiguration + - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" + + +build: false + +test_script: + # compile #[cfg(not(test))] code + - cargo build --verbose + - cargo test --all --verbose + +cache: + - target -> Cargo.lock + - C:\Users\appveyor\.cargo\registry -> Cargo.lock diff --git a/tests/fixtures/formats/caco3_plastics.csv b/tests/fixtures/formats/caco3_plastics.csv new file mode 100644 index 0000000000..8b04b00c0e --- /dev/null +++ b/tests/fixtures/formats/caco3_plastics.csv @@ -0,0 +1,10 @@ +importer,shipper,tariff_item,name,origin,shipped_at,arrived_at,net_weight,fob_price,cif_price,cif_per_net_weight +PLASTICOS RIVAL CIA LTDA,S A REVERTE,2509000000,CARBONATO DE CALCIO TIPO CALCIPORE 160 T AL,SPAIN,18/03/2016,17/04/2016,"81,000.00","14,417.58","18,252.34",0.23 +MEXICHEM ECUADOR S.A.,OMYA ANDINA S A,2836500000,CARBONATO,COLOMBIA,07/07/2016,10/07/2016,"26,000.00","7,072.00","8,127.18",0.31 +PLASTIAZUAY SA,SA REVERTE,2836500000,CARBONATO DE CALCIO,SPAIN,27/07/2016,09/08/2016,"81,000.00","8,100.00","11,474.55",0.14 +PLASTICOS RIVAL CIA LTDA,AND ENDUSTRIYEL HAMMADDELER DIS TCARET LTD.STI.,2836500000,CALCIUM CARBONATE ANADOLU ANDCARB CT-1,TURKEY,04/10/2016,11/11/2016,"100,000.00","17,500.00","22,533.75",0.23 +QUIMICA COMERCIAL QUIMICIAL CIA. LTDA.,SA REVERTE,2836500000,CARBONATO DE CALCIO,SPAIN,24/06/2016,12/07/2016,"27,000.00","3,258.90","5,585.00",0.21 +PICA PLASTICOS INDUSTRIALES C.A.,OMYA ANDINA S.A,3824909999,CARBONATO DE CALCIO,COLOMBIA,01/01/1900,18/01/2016,"66,500.00","12,635.00","18,670.52",0.28 +PLASTIQUIM S.A.,OMYA ANDINA S.A NIT 830.027.386-6,3824909999,CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYA CARB 1T CG BBS 1000,COLOMBIA,01/01/1900,25/10/2016,"33,000.00","6,270.00","9,999.00",0.30 +QUIMICOS ANDINOS QUIMANDI S.A.,SIBELCO COLOMBIA SAS,3824909999,CARBONATO DE CALCIO RECUBIERTO,COLOMBIA,01/11/2016,03/11/2016,"52,000.00","8,944.00","13,039.05",0.25 +TIGRE ECUADOR S.A. ECUATIGRE,OMYA ANDINA S.A NIT 830.027.386-6,3824909999,CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYACARB 1T CG BPA 25 NO,COLOMBIA,01/01/1900,28/10/2016,"66,000.00","11,748.00","18,216.00",0.28 diff --git a/tests/fixtures/formats/caco3_plastics.tsv b/tests/fixtures/formats/caco3_plastics.tsv new file mode 100644 index 0000000000..071baaae30 --- /dev/null +++ b/tests/fixtures/formats/caco3_plastics.tsv @@ -0,0 +1,10 @@ +importer shipper tariff_item name origin shipped_at arrived_at net_weight fob_price cif_price cif_per_net_weight +PLASTICOS RIVAL CIA LTDA S A REVERTE 2509000000 CARBONATO DE CALCIO TIPO CALCIPORE 160 T AL SPAIN 18/03/2016 17/04/2016 81,000.00 14,417.58 18,252.34 0.23 +MEXICHEM ECUADOR S.A. OMYA ANDINA S A 2836500000 CARBONATO COLOMBIA 07/07/2016 10/07/2016 26,000.00 7,072.00 8,127.18 0.31 +PLASTIAZUAY SA SA REVERTE 2836500000 CARBONATO DE CALCIO SPAIN 27/07/2016 09/08/2016 81,000.00 8,100.00 11,474.55 0.14 +PLASTICOS RIVAL CIA LTDA AND ENDUSTRIYEL HAMMADDELER DIS TCARET LTD.STI. 2836500000 CALCIUM CARBONATE ANADOLU ANDCARB CT-1 TURKEY 04/10/2016 11/11/2016 100,000.00 17,500.00 22,533.75 0.23 +QUIMICA COMERCIAL QUIMICIAL CIA. LTDA. SA REVERTE 2836500000 CARBONATO DE CALCIO SPAIN 24/06/2016 12/07/2016 27,000.00 3,258.90 5,585.00 0.21 +PICA PLASTICOS INDUSTRIALES C.A. OMYA ANDINA S.A 3824909999 CARBONATO DE CALCIO COLOMBIA 01/01/1900 18/01/2016 66,500.00 12,635.00 18,670.52 0.28 +PLASTIQUIM S.A. OMYA ANDINA S.A NIT 830.027.386-6 3824909999 CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYA CARB 1T CG BBS 1000 COLOMBIA 01/01/1900 25/10/2016 33,000.00 6,270.00 9,999.00 0.30 +QUIMICOS ANDINOS QUIMANDI S.A. SIBELCO COLOMBIA SAS 3824909999 CARBONATO DE CALCIO RECUBIERTO COLOMBIA 01/11/2016 03/11/2016 52,000.00 8,944.00 13,039.05 0.25 +TIGRE ECUADOR S.A. ECUATIGRE OMYA ANDINA S.A NIT 830.027.386-6 3824909999 CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYACARB 1T CG BPA 25 NO COLOMBIA 01/01/1900 28/10/2016 66,000.00 11,748.00 18,216.00 0.28 diff --git a/tests/fixtures/formats/cargo_sample.toml b/tests/fixtures/formats/cargo_sample.toml new file mode 100644 index 0000000000..c36a40e0ad --- /dev/null +++ b/tests/fixtures/formats/cargo_sample.toml @@ -0,0 +1,55 @@ +[package] +name = "nu" +version = "0.1.1" +authors = ["The Nu Project Contributors"] +description = "a new type of shell" +license = "ISC" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rustyline = "4.1.0" +sysinfo = "0.8.4" +chrono = { version = "0.4.6", features = ["serde"] } +chrono-tz = "0.5.1" +derive-new = "0.5.6" +prettytable-rs = "0.8.0" +itertools = "0.8.0" +ansi_term = "0.11.0" +conch-parser = "0.1.1" +nom = "5.0.0-beta1" +dunce = "1.0.0" +indexmap = { version = "1.0.2", features = ["serde-1"] } +chrono-humanize = "0.0.11" +byte-unit = "2.1.0" +ordered-float = "1.0.2" +prettyprint = "0.6.0" +cursive = { version = "0.12.0", features = ["pancurses-backend"], default-features = false } +futures-preview = { version = "0.3.0-alpha.16", features = ["compat", "io-compat"] } +futures-sink-preview = "0.3.0-alpha.16" +tokio-fs = "0.1.6" +futures_codec = "0.2.2" +term = "0.5.2" +bytes = "0.4.12" +log = "0.4.6" +pretty_env_logger = "0.3.0" +lalrpop-util = "0.17.0" +regex = "1.1.6" +serde = "1.0.91" +serde_json = "1.0.39" +serde_derive = "1.0.91" +getset = "0.0.7" +logos = "0.10.0-rc2" +logos-derive = "0.10.0-rc2" +language-reporting = "0.3.0" +directories = "2.0.2" +toml = "0.5.1" +toml-query = "0.9.0" + +[dependencies.pancurses] +version = "0.16" +features = ["win32a"] + +[dev-dependencies] +pretty_assertions = "0.6.1" diff --git a/tests/fixtures/formats/jonathan.xml b/tests/fixtures/formats/jonathan.xml new file mode 100644 index 0000000000..0ce0016c19 --- /dev/null +++ b/tests/fixtures/formats/jonathan.xml @@ -0,0 +1,22 @@ + + + + Jonathan Turner + http://www.jonathanturner.org + + + + Creating crossplatform Rust terminal apps + <p><img src="/images/pikachu.jpg" alt="Pikachu animation in Windows" /></p> + +<p><em>Look Mom, Pikachu running in Windows CMD!</em></p> + +<p>Part of the adventure is not seeing the way ahead and going anyway.</p> + + Mon, 05 Oct 2015 00:00:00 +0000 + http://www.jonathanturner.org/2015/10/off-to-new-adventures.html + http://www.jonathanturner.org/2015/10/off-to-new-adventures.html + + + + diff --git a/tests/fixtures/formats/lines_test.txt b/tests/fixtures/formats/lines_test.txt new file mode 100644 index 0000000000..034e56b61e --- /dev/null +++ b/tests/fixtures/formats/lines_test.txt @@ -0,0 +1,2 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +yyy diff --git a/tests/fixtures/formats/random_numbers.csv b/tests/fixtures/formats/random_numbers.csv new file mode 100644 index 0000000000..5e5c694657 --- /dev/null +++ b/tests/fixtures/formats/random_numbers.csv @@ -0,0 +1,51 @@ +random numbers +5 +2 +0 +5 +1 +3 +5 +2 +1 +1 +0 +5 +1 +0 +0 +4 +0 +4 +5 +2 +2 +4 +3 +2 +5 +3 +1 +0 +5 +1 +2 +2 +5 +0 +1 +1 +5 +1 +1 +3 +3 +1 +5 +0 +2 +1 +3 +1 +1 +2 diff --git a/tests/fixtures/formats/sample-ls-output.json b/tests/fixtures/formats/sample-ls-output.json new file mode 100644 index 0000000000..4636f0353d --- /dev/null +++ b/tests/fixtures/formats/sample-ls-output.json @@ -0,0 +1 @@ +[{"name":"a.txt","type":"File","size":3444,"modified":"2020-07-1918:26:30.560716967UTC"},{"name":"B.txt","type":"File","size":1341,"modified":"2020-07-1918:26:30.561021953UTC"},{"name":"C","type":"Dir","size":118253,"modified":"2020-07-1918:26:30.562092480UTC"}] diff --git a/tests/fixtures/formats/sample-ps-output.json b/tests/fixtures/formats/sample-ps-output.json new file mode 100644 index 0000000000..7ae34d66e9 --- /dev/null +++ b/tests/fixtures/formats/sample-ps-output.json @@ -0,0 +1 @@ +[{"pid":10390,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":132112384,"virtual":4989624320},{"pid":10461,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":126992384,"virtual":4995346432},{"pid":10530,"name":"kworker/6:1-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":10593,"name":"kworker/1:1-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":10650,"name":"chrome","status":"Sleeping","cpu":8.026974,"mem":262434816,"virtual":5217419264},{"pid":10803,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":48173056,"virtual":542531584},{"pid":11191,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":124092416,"virtual":4975763456},{"pid":11210,"name":"kworker/7:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":11254,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":113070080,"virtual":4971659264},{"pid":11279,"name":"kworker/u16:0-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":11476,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":88993792,"virtual":4937097216},{"pid":12755,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":163397632,"virtual":5034328064},{"pid":12772,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":113561600,"virtual":4985073664},{"pid":14351,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":111861760,"virtual":4962754560},{"pid":17818,"name":"udisksd","status":"Sleeping","cpu":0.0,"mem":14409728,"virtual":402935808},{"pid":17815,"name":".gvfs-udisks2-v","status":"Sleeping","cpu":0.0,"mem":16199680,"virtual":585306112},{"pid":17831,"name":".gvfs-mtp-volum","status":"Sleeping","cpu":0.0,"mem":6393856,"virtual":454680576},{"pid":17836,"name":".gvfs-gphoto2-v","status":"Sleeping","cpu":0.0,"mem":7110656,"virtual":456966144},{"pid":17841,"name":".gvfs-afc-volum","status":"Sleeping","cpu":0.0,"mem":8585216,"virtual":537448448},{"pid":17846,"name":".gvfsd-trash-wr","status":"Sleeping","cpu":0.0,"mem":12767232,"virtual":577998848},{"pid":17856,"name":".gvfsd-network-","status":"Sleeping","cpu":0.0,"mem":13295616,"virtual":654110720},{"pid":17862,"name":".gvfsd-dnssd-wr","status":"Sleeping","cpu":0.0,"mem":7639040,"virtual":533233664},{"pid":17869,"name":"dconf-service","status":"Sleeping","cpu":0.0,"mem":5365760,"virtual":158957568},{"pid":18153,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":183738368,"virtual":5128962048},{"pid":23033,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":166035456,"virtual":5074878464},{"pid":24101,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":101224448,"virtual":4956262400},{"pid":24832,"name":"kworker/7:2-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":24912,"name":"kworker/5:2-events_power_efficient","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":25228,"name":"kworker/4:3-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":25678,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":117522432,"virtual":4970983424},{"pid":25706,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":30760960,"virtual":528375808},{"pid":26080,"name":"kworker/1:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26818,"name":"kworker/2:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26827,"name":"kworker/6:2-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26832,"name":"kworker/0:2-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26843,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":116621312,"virtual":4982403072},{"pid":27163,"name":"kworker/3:2-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":27800,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":128200704,"virtual":4965363712},{"pid":27820,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":54960128,"virtual":4895596544},{"pid":27898,"name":"kworker/3:0-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":27977,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":141930496,"virtual":4982546432},{"pid":28035,"name":"kworker/u16:1-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":28104,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":126853120,"virtual":5003902976},{"pid":28158,"name":"nu","status":"Sleeping","cpu":0.0,"mem":27344896,"virtual":870764544},{"pid":28236,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":450560000,"virtual":5389582336},{"pid":29186,"name":"kworker/5:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":30140,"name":"kworker/u16:2-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":30142,"name":"nu_plugin_core_","status":"Zombie","cpu":0.0,"mem":0,"virtual":0},{"pid":30356,"name":"sh","status":"Sleeping","cpu":0.0,"mem":3743744,"virtual":224092160},{"pid":30360,"name":"nu_plugin_core_ps","status":"Sleeping","cpu":80.23046000000001,"mem":6422528,"virtual":633016320}] \ No newline at end of file diff --git a/tests/fixtures/formats/sample-simple.json b/tests/fixtures/formats/sample-simple.json new file mode 100644 index 0000000000..7986b6e546 --- /dev/null +++ b/tests/fixtures/formats/sample-simple.json @@ -0,0 +1,4 @@ +{ + "first": "first", + "second": "this\nshould\nbe\nseparate\nlines" +} \ No newline at end of file diff --git a/tests/fixtures/formats/sample-sys-output.json b/tests/fixtures/formats/sample-sys-output.json new file mode 100644 index 0000000000..122fd85941 --- /dev/null +++ b/tests/fixtures/formats/sample-sys-output.json @@ -0,0 +1,125 @@ +{ + "host": { + "name": "Linux", + "release": "5.4.33", + "version": "#1-NixOS SMP Fri Apr 17 08:50:26 UTC 2020", + "hostname": "nixos", + "arch": "x86_64", + "uptime": 105126, + "sessions": [ + "alexj" + ] + }, + "cpu": { + "cores": 8, + "current ghz": 2.4200000000000004, + "min ghz": 0.39999999999999997, + "max ghz": 3.4000000000000004 + }, + "disks": [ + { + "device": "/dev/disk/by-uuid/e9adff48-c37b-4631-b98b-eaec9b410ba3", + "type": "ext4", + "mount": "/", + "total": 483445473280, + "used": 121866776576, + "free": 336949624832 + }, + { + "device": "/dev/disk/by-uuid/e9adff48-c37b-4631-b98b-eaec9b410ba3", + "type": "ext4", + "mount": "/nix/store", + "total": 483445473280, + "used": 121866776576, + "free": 336949624832 + }, + { + "device": "/dev/sda3", + "type": "vfat", + "mount": "/boot", + "total": 534757376, + "used": 72650752, + "free": 462106624 + } + ], + "mem": { + "total": 16256524000, + "free": 3082268000, + "swap total": 18874344000, + "swap free": 18874344000 + }, + "temp": [ + { + "unit": "iwlwifi_1", + "temp": 42.0 + }, + { + "unit": "acpitz", + "temp": 53.00000000000001, + "critical": 103.0 + }, + { + "unit": "coretemp", + "label": "Core 1", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 2", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Package id 0", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 3", + "temp": 51.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 0", + "temp": 51.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "pch_skylake", + "temp": 48.00000000000001 + } + ], + "net": [ + { + "name": "wlp2s0", + "sent": 387642399, + "recv": 15324719784 + }, + { + "name": "lo", + "sent": 2667, + "recv": 2667 + }, + { + "name": "vboxnet0", + "sent": 0, + "recv": 0 + } + ], + "battery": [ + { + "vendor": "ASUSTeK", + "model": "ASUS Battery", + "cycles": 445 + } + ] +} diff --git a/tests/fixtures/formats/sample.bson b/tests/fixtures/formats/sample.bson new file mode 100644 index 0000000000000000000000000000000000000000..ff51ae2072e1415afe592cfac9fc464acc920a20 GIT binary patch literal 561 zcmXqHVqjp8&rD&6&05L7&dlbK;e_8NEJgYGB@CiKDP{wPOa_>W#8k-%V$}@pM#9?| z6M>>2@WGxbiGdBs$Vkn}$!B0mX0QNqfZ9ZW6jOF;C0Me!q%~;mixsG(9B3H>Q!IluJS*N{> zF*tdvUX42u;xdKu{Gyx`kY|{U8N3iG&)qLJtf&=u!)VK##9#;%XG+dbNo4@JE+@4_ zp+dn{q0*Xxxi~q$AeBJ?D9Ts~BtSsG0Z1^LFem~ksGBb^<-~5UQ9bX&A(+g-2oz_! zkQmFrY>J}B5~_w1s>Twm#*D!YZcdV!_sqD`q2- z&_g}QY|dbUWKb5=AR*2~h7~W1j^qGc1=S{iqRoOq6{w97loBA|gS~Jf!@>VT&lHa1Z!Q|-k)cVTi%b|*2h^Ap$$ zegHd58*BXl;ui>_kV*(xB%m{UnrLIG82%4CFVE~V&957FXX*VyW)d~Xi%+pp9oAr+ zvl*orV`Q6W+igfYxtsIb7Y~fY-oD%mY=NW|MhCQI2LuEl009U<00Izz00bZa0SG|g zKN9FjKboB6b0+TVWT5ldl%w98TkLh0duq8myUWi+v#7kV`euUHt={-w;Oo=ww4 z>pXN3$h#>I#NU*MA+K{8HL7C9-TA*pXN>;PC0)=@J4HYM0uX=z1Rwwb2tWV=5P$## zAn-f|L`^Wy6QZ%>Ng?>I>)r@1#@+efqiy&7|D1l<2?7ETfB*y_009U<00Izz00bZa zfkzaW_vB{Nq* +Subject: Test Message +From: "from@example.com" +Reply-To: "replyto@example.com" +To: to@example.com +Content-Type: multipart/alternative; boundary="0000000000009d71fb05adf6528a" + +--0000000000009d71fb05adf6528a +Content-Type: text/plain; charset="UTF-8" + +Test Message + +--0000000000009d71fb05adf6528a +Content-Type: text/html; charset="UTF-8" + +
Test Message
+ +--0000000000009d71fb05adf6528a-- diff --git a/tests/fixtures/formats/sample.ini b/tests/fixtures/formats/sample.ini new file mode 100644 index 0000000000..c8f2485287 --- /dev/null +++ b/tests/fixtures/formats/sample.ini @@ -0,0 +1,19 @@ +[SectionOne] + +key = value +integer = 1234 +real = 3.14 +string1 = 'Case 1' +string2 = "Case 2" + +[SectionTwo] + +; comment line +key = new value +integer = 5678 +real = 3.14 +string1 = 'Case 1' +string2 = "Case 2" +string3 = 'Case 3' + + diff --git a/tests/fixtures/formats/sample.url b/tests/fixtures/formats/sample.url new file mode 100644 index 0000000000..361d70dbb6 --- /dev/null +++ b/tests/fixtures/formats/sample.url @@ -0,0 +1 @@ +bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter \ No newline at end of file diff --git a/tests/fixtures/formats/sample_data.ods b/tests/fixtures/formats/sample_data.ods new file mode 100644 index 0000000000000000000000000000000000000000..5bcd2bda44433949cc386b3be0bfc7db036108f3 GIT binary patch literal 49626 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%0A3#0NBD8Dr8%^7fPX(Yjg66_s^#sK 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 + .join("-"); + + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + + path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"] + "#, + )]) + .with_config(&file) + .with_env( + nu_test_support::NATIVE_PATH_ENV_VAR, + &PathBuf::from("/path/to/be/added").display_path(), + ); + + assert_that!( + nu.pipeline("echo $nu.path | str collect '-'"), + says().stdout(&expected_paths) + ); + }); +} diff --git a/tests/shell/environment/mod.rs b/tests/shell/environment/mod.rs new file mode 100644 index 0000000000..f505dcbf35 --- /dev/null +++ b/tests/shell/environment/mod.rs @@ -0,0 +1,22 @@ +mod configuration; +mod env; +mod in_sync; +mod nu_env; + +pub mod support { + use nu_test_support::{nu, playground::*, Outcome}; + + pub struct Trusted; + + impl Trusted { + pub fn in_path(dirs: &Dirs, block: impl FnOnce() -> Outcome) -> Outcome { + let for_env_manifest = dirs.test().to_string_lossy(); + + nu!(cwd: dirs.root(), format!("autoenv trust \"{}\"", for_env_manifest)); + let out = block(); + nu!(cwd: dirs.root(), format!("autoenv untrust \"{}\"", for_env_manifest)); + + out + } + } +} diff --git a/tests/shell/environment/nu_env.rs b/tests/shell/environment/nu_env.rs new file mode 100644 index 0000000000..14cc3b1f29 --- /dev/null +++ b/tests/shell/environment/nu_env.rs @@ -0,0 +1,672 @@ +use super::support::Trusted; + +use nu_test_support::fs::Stub::FileWithContent; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +use serial_test::serial; + +const SCRIPTS: &str = r#"startup = ["touch hello.txt"] + on_exit = ["touch bye.txt"]"#; + +#[test] +#[serial] +fn picks_up_env_keys_when_entering_trusted_directory() { + Playground::setup("autoenv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let expected = "testvalue"; + + let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $env.testkey")); + + assert_eq!(actual.out, expected); + }) +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn picks_up_and_lets_go_env_keys_when_entering_trusted_directory_with_implied_cd() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("foo/bar"); + sandbox.with_files(vec![ + FileWithContent( + "foo/.nu-env", + r#"[env] + testkey = "testvalue" + "#, + ), + FileWithContent( + "foo/bar/.nu-env", + r#" + [env] + bar = "true" + "#, + ), + ]); + let actual = nu!( + cwd: dirs.test(), + r#" + do {autoenv trust -q foo ; = $nothing } + foo + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey is gone when leaving foo + let actual = nu!( + cwd: dirs.test(), + r#" + do {autoenv trust -q foo; = $nothing } ; + foo + .. + echo $env.testkey + "# + ); + assert!(actual.err.contains("Unknown")); + //Assert testkey is present also when jumping over foo + let actual = nu!( + cwd: dirs.test(), + r#" + do {autoenv trust -q foo; = $nothing } ; + do {autoenv trust -q foo/bar; = $nothing } ; + foo/bar + echo $env.testkey + echo $env.bar + "# + ); + assert_eq!(actual.out, "testvaluetrue"); + //Assert bar removed after leaving bar + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; + foo/bar + ../.. + echo $env.bar"# + ); + assert!(actual.err.contains("Unknown")); + }); +} + +#[test] +#[serial] +#[ignore] +fn picks_up_script_vars_when_entering_trusted_directory() { + Playground::setup("autoenv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let expected = "myval"; + + let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $env.myscript")); + + // scriptvars are not supported + // and why is myval expected when myscript is "echo myval" + assert_eq!(actual.out, expected); + }) +} + +#[test] +#[serial] +fn picks_up_env_keys_when_entering_trusted_directory_indirectly() { + Playground::setup("autoenv_test_3", |dirs, sandbox| { + sandbox.mkdir("crates"); + sandbox.with_files(vec![FileWithContent( + ".nu-env", + r#"[env] + nu-ver = "0.30.0" "#, + )]); + + let expected = "0.30.0"; + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().join("crates"), r#" + cd ../../autoenv_test_3 + echo $env.nu-ver + "#) + }); + + assert_eq!(actual.out, expected); + }) +} + +#[test] +#[serial] +fn entering_a_trusted_directory_runs_entry_scripts() { + Playground::setup("autoenv_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), pipeline(r#" + ls + | where name == "hello.txt" + | get name + "#)) + }); + + assert_eq!(actual.out, "hello.txt"); + }) +} + +#[test] +#[serial] +fn leaving_a_trusted_directory_runs_exit_scripts() { + Playground::setup("autoenv_test_5", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + cd .. + ls autoenv_test_5 | get name | path basename | where $it == "bye.txt" + "#) + }); + + assert_eq!(actual.out, "bye.txt"); + }) +} + +#[test] +#[serial] +fn entry_scripts_are_called_when_revisiting_a_trusted_directory() { + Playground::setup("autoenv_test_6", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + do { rm hello.txt | ignore } ; # Silence file deletion message from output + cd .. + cd autoenv_test_6 + ls | where name == "hello.txt" | get name + "#) + }); + + assert_eq!(actual.out, "hello.txt"); + }) +} + +#[test] +#[serial] +fn given_a_trusted_directory_with_entry_scripts_when_entering_a_subdirectory_entry_scripts_are_not_called( +) { + Playground::setup("autoenv_test_7", |dirs, sandbox| { + sandbox.mkdir("time_to_cook_arepas"); + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + cd time_to_cook_arepas + ls | where name == "hello.txt" | length + "#) + }); + + assert_eq!(actual.out, "0"); + }) +} + +#[test] +#[serial] +fn given_a_trusted_directory_with_exit_scripts_when_entering_a_subdirectory_exit_scripts_are_not_called( +) { + Playground::setup("autoenv_test_8", |dirs, sandbox| { + sandbox.mkdir("time_to_cook_arepas"); + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + cd time_to_cook_arepas + ls | where name == "bye.txt" | length + "#) + }); + + assert_eq!(actual.out, "0"); + }) +} + +#[test] +#[serial] +fn given_a_hierachy_of_trusted_directories_when_entering_in_any_nested_ones_should_carry_over_variables_set_from_the_root( +) { + Playground::setup("autoenv_test_9", |dirs, sandbox| { + sandbox.mkdir("nu_plugin_rb"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nushell""#, + ), + FileWithContent( + "nu_plugin_rb/.nu-env", + r#"[env] + language = "Ruby""#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().parent().unwrap(), r#" + do { autoenv trust -q autoenv_test_9/nu_plugin_rb ; = $nothing } # Silence autoenv trust -q message from output + cd autoenv_test_9/nu_plugin_rb + echo $env.organization + "#) + }); + + assert_eq!(actual.out, "nushell"); + }) +} + +#[test] +#[serial] +fn given_a_hierachy_of_trusted_directories_nested_ones_should_overwrite_variables_from_parent_directories( +) { + Playground::setup("autoenv_test_10", |dirs, sandbox| { + sandbox.mkdir("nu_plugin_rb"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nushell""#, + ), + FileWithContent( + "nu_plugin_rb/.nu-env", + r#"[env] + organization = "Andrab""#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().parent().unwrap(), r#" + do { autoenv trust -q autoenv_test_10/nu_plugin_rb ; = $nothing } # Silence autoenv trust -q message from output + cd autoenv_test_10/nu_plugin_rb + echo $env.organization + "#) + }); + + assert_eq!(actual.out, "Andrab"); + }) +} + +#[test] +#[serial] +#[cfg(not(windows))] //TODO figure out why this test doesn't work on windows +fn local_config_should_not_be_added_when_running_scripts() { + Playground::setup("autoenv_test_10", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nu""#, + ), + FileWithContent( + "foo/.nu-env", + r#"[env] + organization = "foo""#, + ), + FileWithContent( + "script.nu", + r#"cd foo + echo $env.organization"#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + do { autoenv trust -q foo } # Silence autoenv trust message from output + nu script.nu + "#) + }); + + assert_eq!(actual.out, "nu"); + }) +} +#[test] +#[serial] +fn given_a_hierachy_of_trusted_directories_going_back_restores_overwritten_variables() { + Playground::setup("autoenv_test_11", |dirs, sandbox| { + sandbox.mkdir("nu_plugin_rb"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nushell""#, + ), + FileWithContent( + "nu_plugin_rb/.nu-env", + r#"[env] + organization = "Andrab""#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().parent().unwrap(), r#" + do { autoenv trust -q autoenv_test_11/nu_plugin_rb } # Silence autoenv trust message from output + cd autoenv_test_11 + cd nu_plugin_rb + do { rm ../.nu-env | ignore } # By deleting the root nu-env we have guarantees that the variable gets restored (not by autoenv when re-entering) + cd .. + echo $env.organization + "#) + }); + + assert_eq!(actual.out, "nushell"); + }) +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn local_config_env_var_present_and_removed_correctly() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("foo/bar"); + sandbox.with_files(vec![FileWithContent( + "foo/.nu-env", + r#"[env] + testkey = "testvalue" + "#, + )]); + //Assert testkey is not present before entering directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; + echo $env.testkey"# + ); + assert!(actual.err.contains("Unknown")); + //Assert testkey is present in foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey is present also in subdirectories + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + cd bar + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey is present also when jumping over foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo/bar + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey removed after leaving foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + cd .. + echo $env.testkey"# + ); + assert!(actual.err.contains("Unknown")); + }); +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn local_config_env_var_gets_overwritten() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("foo/bar"); + sandbox.with_files(vec![ + FileWithContent( + "foo/.nu-env", + r#"[env] + overwrite_me = "foo" + "#, + ), + FileWithContent( + "foo/bar/.nu-env", + r#"[env] + overwrite_me = "bar" + "#, + ), + ]); + //Assert overwrite_me is not present before entering directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; + echo $env.overwrite_me"# + ); + assert!(actual.err.contains("Unknown")); + //Assert overwrite_me is foo in foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + echo $env.overwrite_me"# + ); + assert_eq!(actual.out, "foo"); + //Assert overwrite_me is bar in bar + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + autoenv trust -q foo/bar + cd foo + cd bar + echo $env.overwrite_me"# + ); + assert_eq!(actual.out, "bar"); + //Assert overwrite_me is present also when jumping over foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; autoenv trust -q foo/bar; cd foo/bar + echo $env.overwrite_me + "# + ); + assert_eq!(actual.out, "bar"); + //Assert overwrite_me removed after leaving bar + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; autoenv trust -q foo/bar; cd foo + cd bar + cd .. + echo $env.overwrite_me"# + ); + assert_eq!(actual.out, "foo"); + }); +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn autoenv_test_entry_scripts() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo/bar"); + + // Windows uses a different command to create an empty file so we need to have different content on windows. + let nu_env = if cfg!(target_os = "windows") { + r#"startup = ["echo nul > hello.txt"]"# + } else { + r#"startup = ["touch hello.txt"]"# + }; + + sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]); + + // Make sure entryscript is run when entering directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + ls | where name == "hello.txt" | get name"# + ); + assert!(actual.out.contains("hello.txt")); + + // Make sure entry scripts are also run when jumping over directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo/bar + ls .. | where name == "../hello.txt" | get name"# + ); + assert!(actual.out.contains("hello.txt")); + + // Entryscripts should not run after changing to a subdirectory. + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + rm hello.txt + cd bar + ls .. | where name == "../hello.txt" | length"# + ); + assert!(actual.out.contains('0')); + }); +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn autoenv_test_exit_scripts() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo/bar"); + + // Windows uses a different command to create an empty file so we need to have different content on windows. + let nu_env = r#"on_exit = ["touch bye.txt"]"#; + + sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]); + + // Make sure exitscript is run + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + cd .. + ls foo | where name =~ "bye.txt" | length + rm foo/bye.txt | ignore; cd . + "# + ); + assert_eq!(actual.out, "1"); + + // Entering a subdir should not trigger exitscripts + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + cd bar + ls .. | where name =~ "bye.txt" | length"# + ); + assert_eq!(actual.out, "0"); + + // Also run exitscripts when jumping over directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo/bar + cd ../.. + ls foo | where name =~ "bye.txt" | length + rm foo/bye.txt | ignore; cd ."# + ); + assert_eq!(actual.out, "1"); + }); +} + +#[test] +#[serial] +#[cfg(unix)] +fn prepends_path_from_local_config() { + //If this test fails for you, make sure that your environment from which you start nu + //contains some env vars + Playground::setup("autoenv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + r#" + path = ["/hi", "/nushell"] + "#, + )]); + + let expected = "[\"/hi\",\"/nushell\","; + + let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $nu.path | to json")); + // assert_eq!("", actual.out); + assert!(actual.out.starts_with(expected)); + assert!(actual.out.len() > expected.len()); + }) +} diff --git a/tests/shell/mod.rs b/tests/shell/mod.rs new file mode 100644 index 0000000000..24b62ccd09 --- /dev/null +++ b/tests/shell/mod.rs @@ -0,0 +1,76 @@ +use nu_test_support::fs::AbsolutePath; +use nu_test_support::playground::{says, Playground}; +use nu_test_support::{nu, pipeline}; + +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +#[cfg(feature = "which-support")] +mod environment; + +mod pipeline; + +//FIXME: jt: this approach may no longer be right for running from startup scripts, needs investigation +#[ignore] +#[test] +fn runs_configuration_startup_commands() { + Playground::setup("init_config_startup_commands_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.config_fixtures().join("startup.toml")); + + nu.with_config(&file); + + assert_that!(nu.pipeline("hello-world"), says().stdout("Nu World")); + }); +} + +//FIXME: jt: we need to focus some fixes on wix as the plugins will differ +#[ignore] +#[test] +fn plugins_are_declared_with_wix() { + let actual = nu!( + cwd: ".", pipeline( + r#" + open Cargo.toml + | get bin.name + | str find-replace "nu_plugin_(extra|core)_(.*)" "nu_plugin_$2" + | drop + | sort-by + | wrap cargo | merge { + open wix/main.wxs --raw | from xml + | get Wix.children.Product.children.0.Directory.children.0 + | where Directory.attributes.Id == "$(var.PlatformProgramFilesFolder)" + | get Directory.children.Directory.children.0 | last + | get Directory.children.Component.children + | each { echo $it | first } + | skip + | where File.attributes.Name =~ "nu_plugin" + | str substring [_, -4] File.attributes.Name + | get File.attributes.Name + | sort-by + | wrap wix + } + | default wix _ + | each { if $it.wix != $it.cargo { 1 } { 0 } } + | math sum + "# + )); + + assert_eq!(actual.out, "0"); +} + +#[test] +#[cfg(not(windows))] +fn do_not_panic_if_broken_pipe() { + // `nu -h | false` + // used to panic with a BrokenPipe error + let child_output = std::process::Command::new("sh") + .arg("-c") + .arg(format!( + "{:?} -h | false", + nu_test_support::fs::executable_path() + )) + .output() + .expect("failed to execute process"); + + assert!(child_output.stderr.is_empty()); +} diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs new file mode 100644 index 0000000000..bd6efaded2 --- /dev/null +++ b/tests/shell/pipeline/commands/external.rs @@ -0,0 +1,457 @@ +use nu_test_support::nu; + +#[cfg(feature = "which")] +#[test] +fn shows_error_for_command_not_found() { + let actual = nu!( + cwd: ".", + "ferris_is_not_here.exe" + ); + + assert!(!actual.err.is_empty()); +} + +#[cfg(feature = "which")] +#[test] +fn shows_error_for_command_not_found_in_pipeline() { + let actual = nu!( + cwd: ".", + "ferris_is_not_here.exe | echo done" + ); + + assert!(!actual.err.is_empty()); +} + +#[ignore] // jt: we can't test this using the -c workaround currently +#[cfg(feature = "which")] +#[test] +fn automatically_change_directory() { + use nu_test_support::playground::Playground; + + Playground::setup("cd_test_5_1", |dirs, sandbox| { + sandbox.mkdir("autodir"); + + let actual = nu!( + cwd: dirs.test(), + r#" + autodir + echo (pwd) + "# + ); + + assert!(actual.out.ends_with("autodir")); + }) +} + +// FIXME: jt: we don't currently support autocd in testing +#[ignore] +#[test] +fn automatically_change_directory_with_trailing_slash_and_same_name_as_command() { + use nu_test_support::playground::Playground; + + Playground::setup("cd_test_5_1", |dirs, sandbox| { + sandbox.mkdir("cd"); + + let actual = nu!( + cwd: dirs.test(), + r#" + cd/ + pwd + "# + ); + + assert!(actual.out.ends_with("cd")); + }) +} + +#[test] +fn correctly_escape_external_arguments() { + let actual = nu!(cwd: ".", r#"^echo '$0'"#); + + assert_eq!(actual.out, "$0"); +} + +#[test] +fn execute_binary_in_string() { + let actual = nu!( + cwd: ".", + r#" + let cmd = "echo" + ^$"($cmd)" "$0" + "#); + + assert_eq!(actual.out, "$0"); +} + +//FIXME: jt - this is blocked on https://github.com/nushell/engine-q/issues/875 +#[ignore] +#[test] +fn redirects_custom_command_external() { + let actual = nu!(cwd: ".", r#"def foo [] { nu --testbin cococo foo bar }; foo | str length"#); + + assert_eq!(actual.out, "8"); +} + +mod it_evaluation { + use super::nu; + use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed}; + use nu_test_support::{pipeline, playground::Playground}; + + #[test] + fn takes_rows_of_nu_value_strings() { + Playground::setup("it_argument_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("jonathan_likes_cake.txt"), + EmptyFile("andres_likes_arepas.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | sort-by name + | get name + | each { nu --testbin cococo $it | lines } + | get 1 + "# + )); + + assert_eq!(actual.out, "jonathan_likes_cake.txt"); + }) + } + + #[test] + fn takes_rows_of_nu_value_lines() { + Playground::setup("it_argument_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_candies.txt", + r#" + AndrásWithKitKatzz + AndrásWithKitKatz + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nu_candies.txt + | lines + | each { nu --testbin chop $it | lines} + | get 1 + "# + )); + + assert_eq!(actual.out, "AndrásWithKitKat"); + }) + } + + #[test] + fn can_properly_buffer_lines_externally() { + let actual = nu!( + cwd: ".", + r#" + nu --testbin repeater c 8197 | lines | length + "# + ); + + assert_eq!(actual.out, "1"); + } + #[test] + fn supports_fetching_given_a_column_path_to_it() { + Playground::setup("it_argument_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + nu_party_venue = "zion" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | nu --testbin cococo $in.nu_party_venue + "# + )); + + assert_eq!(actual.out, "zion"); + }) + } +} + +mod stdin_evaluation { + use super::nu; + use nu_test_support::pipeline; + + #[test] + fn does_not_panic_with_no_newline_in_stream() { + let actual = nu!( + cwd: ".", + pipeline(r#" + nu --testbin nonu "wheres the nuline?" | length + "# + )); + + assert_eq!(actual.err, ""); + } + + // FIXME: JT: `lines` doesn't currently support this kind of streaming + #[ignore] + #[test] + fn does_not_block_indefinitely() { + let stdout = nu!( + cwd: ".", + pipeline(r#" + ( nu --testbin iecho yes + | nu --testbin chop + | nu --testbin chop + | lines + | first 1 ) + "# + )) + .out; + + assert_eq!(stdout, "y"); + } +} + +mod external_words { + use super::nu; + use nu_test_support::fs::Stub::FileWithContent; + use nu_test_support::{pipeline, playground::Playground}; + #[test] + fn relaxed_external_words() { + let actual = nu!(cwd: ".", r#" + nu --testbin cococo joturner@foo.bar.baz + "#); + + assert_eq!(actual.out, "joturner@foo.bar.baz"); + } + + //FIXME: jt: limitation in testing - can't use single ticks currently + #[ignore] + #[test] + fn no_escaping_for_single_quoted_strings() { + let actual = nu!(cwd: ".", r#" + nu --testbin cococo 'test "things"' + "#); + + assert_eq!(actual.out, "test \"things\""); + } + + #[rstest::rstest] + #[case("sample.toml", r#""sample.toml""#)] + #[case("a sample file.toml", r#""a sample file.toml""#)] + //FIXME: jt: we don't currently support single ticks in tests + //#[case("quote'mark.toml", r#""quote'mark.toml""#)] + #[cfg_attr( + not(target_os = "windows"), + case(r#"quote"mark.toml"#, r#"$"quote(char double_quote)mark.toml""#) + )] + #[cfg_attr(not(target_os = "windows"), case("?mark.toml", r#""?mark.toml""#))] + #[cfg_attr(not(target_os = "windows"), case("*.toml", r#""*.toml""#))] + #[cfg_attr(not(target_os = "windows"), case("*.toml", "*.toml"))] + #[case("$ sign.toml", r#""$ sign.toml""#)] + fn external_arg_with_special_characters(#[case] path: &str, #[case] nu_path_argument: &str) { + Playground::setup("external_arg_with_quotes", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + path, + r#" + nu_party_venue = "zion" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + &format!(r#" + nu --testbin meow {} | from toml | get nu_party_venue + "#, nu_path_argument) + )); + + assert_eq!(actual.out, "zion"); + }) + } +} + +mod nu_commands { + use super::nu; + + #[test] + fn echo_internally_externally() { + let actual = nu!(cwd: ".", r#" + nu -c "echo 'foo'" + "#); + + assert_eq!(actual.out, "foo"); + } +} + +mod nu_script { + use super::nu; + + #[test] + fn run_nu_script() { + let actual = nu!(cwd: "tests/fixtures/formats", r#" + nu script.nu + "#); + + assert_eq!(actual.out, "done"); + } + + #[test] + fn run_nu_script_multiline() { + let actual = nu!(cwd: "tests/fixtures/formats", r#" + nu script_multiline.nu + "#); + + assert_eq!(actual.out, "23"); + } +} + +mod tilde_expansion { + use super::nu; + + #[test] + fn as_home_directory_when_passed_as_argument_and_begins_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + nu --testbin cococo ~ + "# + ); + + assert!(!actual.out.contains('~')); + } + + #[test] + fn does_not_expand_when_passed_as_argument_and_does_not_start_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + nu --testbin cococo "1~1" + "# + ); + + assert_eq!(actual.out, "1~1"); + } +} + +mod external_command_arguments { + use super::nu; + use nu_test_support::fs::Stub::EmptyFile; + use nu_test_support::{pipeline, playground::Playground}; + #[test] + fn expands_table_of_primitives_to_positional_arguments() { + Playground::setup( + "expands_table_of_primitives_to_positional_arguments", + |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("jonathan_likes_cake.txt"), + EmptyFile("andres_likes_arepas.txt"), + EmptyFile("ferris_not_here.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + nu --testbin cococo (ls | get name) + "# + )); + + assert_eq!( + actual.out, + "andres_likes_arepas.txt ferris_not_here.txt jonathan_likes_cake.txt" + ); + }, + ) + } + + #[test] + fn proper_subexpression_paths_in_external_args() { + Playground::setup( + "expands_table_of_primitives_to_positional_arguments", + |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("jonathan_likes_cake.txt"), + EmptyFile("andres_likes_arepas.txt"), + EmptyFile("ferris_not_here.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + nu --testbin cococo (ls | sort-by name | get name).1 + "# + )); + + assert_eq!(actual.out, "ferris_not_here.txt"); + }, + ) + } + + #[cfg(not(windows))] + #[test] + fn string_interpolation_with_an_external_command() { + Playground::setup( + "string_interpolation_with_an_external_command", + |dirs, sandbox| { + sandbox.mkdir("cd"); + + sandbox.with_files(vec![EmptyFile("cd/jt_likes_cake.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + nu --testbin cococo $"(pwd)/cd" + "# + )); + + assert!(actual.out.contains("cd")); + }, + ) + } + + #[cfg(not(windows))] + #[test] + fn semicolons_are_sanitized_before_passing_to_subshell() { + let actual = nu!( + cwd: ".", + "^echo \"a;b\"" + ); + + assert_eq!(actual.out, "a;b"); + } + + #[cfg(not(windows))] + #[test] + fn ampersands_are_sanitized_before_passing_to_subshell() { + let actual = nu!( + cwd: ".", + "^echo \"a&b\"" + ); + + assert_eq!(actual.out, "a&b"); + } + + #[cfg(not(windows))] + #[test] + fn subcommands_are_sanitized_before_passing_to_subshell() { + let actual = nu!( + cwd: ".", + "nu --testbin cococo \"$(ls)\"" + ); + + assert_eq!(actual.out, "$(ls)"); + } + + #[cfg(not(windows))] + #[test] + fn shell_arguments_are_sanitized_even_if_coming_from_other_commands() { + let actual = nu!( + cwd: ".", + "nu --testbin cococo (echo \"a;&$(hello)\")" + ); + + assert_eq!(actual.out, "a;&$(hello)"); + } +} diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs new file mode 100644 index 0000000000..0766f8ca26 --- /dev/null +++ b/tests/shell/pipeline/commands/internal.rs @@ -0,0 +1,1354 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::nu; +use nu_test_support::pipeline; +use nu_test_support::playground::Playground; + +#[test] +fn takes_rows_of_nu_value_strings_and_pipes_it_to_stdin_of_external() { + Playground::setup("internal_to_external_pipe_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_times.csv", + r#" + name,rusty_luck,origin + Jason,1,Canada + Jonathan,1,New Zealand + Andrés,1,Ecuador + AndKitKatz,1,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nu_times.csv + | get origin + | each { ^echo $it | nu --testbin chop | lines } + | get 2 + "# + )); + + // chop will remove the last escaped double quote from \"Estados Unidos\" + assert_eq!(actual.out, "Ecuado"); + }) +} + +#[test] +fn treats_dot_dot_as_path_not_range() { + Playground::setup("dot_dot_dir", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_times.csv", + r#" + name,rusty_luck,origin + Jason,1,Canada + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + mkdir temp; + cd temp; + echo (open ../nu_times.csv).name.0 | table; + cd ..; + rmdir temp + "# + )); + + // chop will remove the last escaped double quote from \"Estados Unidos\" + assert_eq!(actual.out, "Jason"); + }) +} + +#[test] +fn subexpression_properly_redirects() { + let actual = nu!( + cwd: ".", + r#" + echo (nu --testbin cococo "hello") | str collect + "# + ); + + assert_eq!(actual.out, "hello"); +} + +#[test] +fn argument_subexpression() { + let actual = nu!( + cwd: ".", + r#" + echo "foo" | each { echo (echo $it) } + "# + ); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn subexpression_handles_dot() { + Playground::setup("subexpression_handles_dot", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_times.csv", + r#" + name,rusty_luck,origin + Jason,1,Canada + Jonathan,1,New Zealand + Andrés,1,Ecuador + AndKitKatz,1,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo (open nu_times.csv) + | get name + | each { nu --testbin chop $it | lines } + | get 3 + "# + )); + + assert_eq!(actual.out, "AndKitKat"); + }) +} + +#[test] +fn string_interpolation_with_it() { + let actual = nu!( + cwd: ".", + r#" + echo "foo" | each { echo $"($it)" } + "# + ); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn string_interpolation_with_it_column_path() { + let actual = nu!( + cwd: ".", + r#" + echo [[name]; [sammie]] | each { echo $"($it.name)" } + "# + ); + + assert_eq!(actual.out, "sammie"); +} + +#[test] +fn string_interpolation_shorthand_overlap() { + let actual = nu!( + cwd: ".", + r#" + $"3 + 4 = (3 + 4)" + "# + ); + + assert_eq!(actual.out, "3 + 4 = 7"); +} + +// FIXME: jt - we don't currently have a way to escape the single ticks easily +#[ignore] +#[test] +fn string_interpolation_and_paren() { + let actual = nu!( + cwd: ".", + r#" + $"a paren is ('(')" + "# + ); + + assert_eq!(actual.out, "a paren is ("); +} + +#[test] +fn string_interpolation_with_unicode() { + //カ = U+30AB : KATAKANA LETTER KA + let actual = nu!( + cwd: ".", + r#" + $"カ" + "# + ); + + assert_eq!(actual.out, "カ"); +} + +#[test] +fn run_custom_command() { + let actual = nu!( + cwd: ".", + r#" + def add-me [x y] { $x + $y}; add-me 10 5 + "# + ); + + assert_eq!(actual.out, "15"); +} + +#[test] +fn run_custom_command_with_flag() { + let actual = nu!( + cwd: ".", + r#" + def foo [--bar:number] { if ($bar | empty?) { echo "empty" } else { echo $bar } }; foo --bar 10 + "# + ); + + assert_eq!(actual.out, "10"); +} + +#[test] +fn run_custom_command_with_flag_missing() { + let actual = nu!( + cwd: ".", + r#" + def foo [--bar:number] { if ($bar | empty?) { echo "empty" } else { echo $bar } }; foo + "# + ); + + assert_eq!(actual.out, "empty"); +} + +#[test] +fn run_custom_subcommand() { + let actual = nu!( + cwd: ".", + r#" + def "str double" [x] { echo $x $x | str collect }; str double bob + "# + ); + + assert_eq!(actual.out, "bobbob"); +} + +#[test] +fn run_inner_custom_command() { + let actual = nu!( + cwd: ".", + r#" + def outer [x] { def inner [y] { echo $y }; inner $x }; outer 10 + "# + ); + + assert_eq!(actual.out, "10"); +} + +#[test] +fn run_broken_inner_custom_command() { + let actual = nu!( + cwd: ".", + r#" + def outer [x] { def inner [y] { echo $y }; inner $x }; inner 10 + "# + ); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn run_custom_command_with_rest() { + let actual = nu!( + cwd: ".", + r#" + def rest-me [...rest: string] { echo $rest.1 $rest.0}; rest-me "hello" "world" | to json --raw + "# + ); + + assert_eq!(actual.out, r#"["world","hello"]"#); +} + +#[test] +fn run_custom_command_with_rest_and_arg() { + let actual = nu!( + cwd: ".", + r#" + def rest-me-with-arg [name: string, ...rest: string] { echo $rest.1 $rest.0 $name}; rest-me-with-arg "hello" "world" "yay" | to json --raw + "# + ); + + assert_eq!(actual.out, r#"["yay","world","hello"]"#); +} + +#[test] +fn run_custom_command_with_rest_and_flag() { + let actual = nu!( + cwd: ".", + r#" + def rest-me-with-flag [--name: string, ...rest: string] { echo $rest.1 $rest.0 $name}; rest-me-with-flag "hello" "world" --name "yay" | to json --raw + "# + ); + + assert_eq!(actual.out, r#"["world","hello","yay"]"#); +} + +#[test] +fn run_custom_command_with_empty_rest() { + let actual = nu!( + cwd: ".", + r#" + def rest-me-with-empty-rest [...rest: string] { echo $rest }; rest-me-with-empty-rest + "# + ); + + assert_eq!(actual.out, r#""#); + assert_eq!(actual.err, r#""#); +} + +//FIXME: jt: blocked on https://github.com/nushell/engine-q/issues/912 +#[ignore] +#[test] +fn run_custom_command_with_rest_other_name() { + let actual = nu!( + cwd: ".", + r#" + def say-hello [ + greeting:string, + ...names:string # All of the names + ] { + echo $"($greeting), ($names | sort-by | str collect)" + } + say-hello Salutations E D C A B + "# + ); + + assert_eq!(actual.out, r#"Salutations, ABCDE"#); + assert_eq!(actual.err, r#""#); +} + +#[test] +fn alias_a_load_env() { + let actual = nu!( + cwd: ".", + r#" + def activate-helper [] { {BOB: SAM} }; alias activate = load-env (activate-helper); activate; $env.BOB + "# + ); + + assert_eq!(actual.out, r#"SAM"#); +} + +#[test] +fn let_variable() { + let actual = nu!( + cwd: ".", + r#" + let x = 5 + let y = 12 + $x + $y + "# + ); + + assert_eq!(actual.out, "17"); +} + +#[test] +fn let_doesnt_leak() { + let actual = nu!( + cwd: ".", + r#" + do { let x = 5 }; echo $x + "# + ); + + assert!(actual.err.contains("variable not found")); +} + +#[test] +fn let_env_variable() { + let actual = nu!( + cwd: ".", + r#" + let-env TESTENVVAR = "hello world" + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); +} + +#[test] +fn let_env_hides_variable() { + let actual = nu!( + cwd: ".", + r#" + let-env TESTENVVAR = "hello world" + echo $env.TESTENVVAR + hide TESTENVVAR + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn let_env_hides_variable_in_parent_scope() { + let actual = nu!( + cwd: ".", + r#" + let-env TESTENVVAR = "hello world" + echo $env.TESTENVVAR + do { + hide TESTENVVAR + echo $env.TESTENVVAR + } + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn unlet_env_variable() { + let actual = nu!( + cwd: ".", + r#" + let-env TEST_VAR = "hello world" + hide TEST_VAR + echo $env.TEST_VAR + "# + ); + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn unlet_nonexistent_variable() { + let actual = nu!( + cwd: ".", + r#" + hide NONEXISTENT_VARIABLE + "# + ); + + assert!(actual.err.contains("did not find")); +} + +#[test] +fn unlet_variable_in_parent_scope() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = "1" + echo $env.DEBUG + do { + let-env DEBUG = "2" + echo $env.DEBUG + hide DEBUG + echo $env.DEBUG + } + echo $env.DEBUG + "# + ); + + assert_eq!(actual.out, "1211"); +} + +#[test] +fn let_env_doesnt_leak() { + let actual = nu!( + cwd: ".", + r#" + do { let-env xyz = "my message" }; echo $env.xyz + "# + ); + + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn proper_shadow_let_env_aliases() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = true; echo $env.DEBUG | table; do { let-env DEBUG = false; echo $env.DEBUG } | table; echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "truefalsetrue"); +} + +#[test] +fn load_env_variable() { + let actual = nu!( + cwd: ".", + r#" + echo {TESTENVVAR: "hello world"} | load-env + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); +} + +#[test] +fn load_env_variable_arg() { + let actual = nu!( + cwd: ".", + r#" + load-env {TESTENVVAR: "hello world"} + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); +} + +#[test] +fn load_env_doesnt_leak() { + let actual = nu!( + cwd: ".", + r#" + do { echo { name: xyz, value: "my message" } | load-env }; echo $env.xyz + "# + ); + + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn proper_shadow_load_env_aliases() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = true; echo $env.DEBUG | table; do { echo {DEBUG: "false"} | load-env; echo $env.DEBUG } | table; echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "truefalsetrue"); +} + +//FIXME: jt: load-env can not currently hide variables because $nothing no longer hides +#[ignore] +#[test] +fn load_env_can_hide_var_envs() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = "1" + echo $env.DEBUG + load-env [[name, value]; [DEBUG $nothing]] + echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "1"); + assert!(actual.err.contains("error")); + assert!(actual.err.contains("Unknown column")); +} + +//FIXME: jt: load-env can not currently hide variables because $nothing no longer hides +#[ignore] +#[test] +fn load_env_can_hide_var_envs_in_parent_scope() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = "1" + echo $env.DEBUG + do { + load-env [[name, value]; [DEBUG $nothing]] + echo $env.DEBUG + } + echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "11"); + assert!(actual.err.contains("error")); + assert!(actual.err.contains("Unknown column")); +} + +#[test] +fn proper_shadow_let_aliases() { + let actual = nu!( + cwd: ".", + r#" + let DEBUG = $false; echo $DEBUG | table; do { let DEBUG = $true; echo $DEBUG } | table; echo $DEBUG + "# + ); + assert_eq!(actual.out, "falsetruefalse"); +} + +#[test] +fn block_params_override() { + let actual = nu!( + cwd: ".", + r#" + [1, 2, 3] | each { |a| echo $it } + "# + ); + assert!(actual.err.contains("variable not found")); +} + +#[test] +fn block_params_override_correct() { + let actual = nu!( + cwd: ".", + r#" + [1, 2, 3] | each { |a| echo $a } | to json --raw + "# + ); + assert_eq!(actual.out, "[1,2,3]"); +} + +#[test] +fn hex_number() { + let actual = nu!( + cwd: ".", + r#" + 0x10 + "# + ); + assert_eq!(actual.out, "16"); +} + +#[test] +fn binary_number() { + let actual = nu!( + cwd: ".", + r#" + 0b10 + "# + ); + assert_eq!(actual.out, "2"); +} + +#[test] +fn octal_number() { + let actual = nu!( + cwd: ".", + r#" + 0o10 + "# + ); + assert_eq!(actual.out, "8"); +} + +#[test] +fn run_dynamic_blocks() { + let actual = nu!( + cwd: ".", + r#" + let block = { echo "holaaaa" }; do $block + "# + ); + assert_eq!(actual.out, "holaaaa"); +} + +#[cfg(feature = "which")] +#[test] +fn argument_subexpression_reports_errors() { + let actual = nu!( + cwd: ".", + "echo (ferris_is_not_here.exe)" + ); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn can_process_one_row_from_internal_and_pipes_it_to_stdin_of_external() { + let actual = nu!( + cwd: ".", + r#"echo "nushelll" | nu --testbin chop"# + ); + + assert_eq!(actual.out, "nushell"); +} + +#[test] +fn bad_operator() { + let actual = nu!( + cwd: ".", + r#" + 2 $ 2 + "# + ); + + assert!(actual.err.contains("operator")); +} + +#[test] +fn index_out_of_bounds() { + let actual = nu!( + cwd: ".", + r#" + let foo = [1, 2, 3]; echo $foo.5 + "# + ); + + assert!(actual.err.contains("too large")); +} + +//FIXME: jt - umm, do we actually want to support this? +#[ignore] +#[test] +fn dash_def() { + let actual = nu!( + cwd: ".", + r#" + def - [x, y] { $x - $y }; - 4 1 + "# + ); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn negative_decimal_start() { + let actual = nu!( + cwd: ".", + r#" + -1.3 + 4 + "# + ); + + assert_eq!(actual.out, "2.7"); +} + +#[test] +fn string_inside_of() { + let actual = nu!( + cwd: ".", + r#" + "bob" in "bobby" + "# + ); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn string_not_inside_of() { + let actual = nu!( + cwd: ".", + r#" + "bob" not-in "bobby" + "# + ); + + assert_eq!(actual.out, "false"); +} + +#[test] +fn index_row() { + let actual = nu!( + cwd: ".", + r#" + let foo = [[name]; [joe] [bob]]; echo $foo.1 | to json --raw + "# + ); + + assert_eq!(actual.out, r#"{"name": "bob"}"#); +} + +#[test] +fn index_cell() { + let actual = nu!( + cwd: ".", + r#" + let foo = [[name]; [joe] [bob]]; echo $foo.name.1 + "# + ); + + assert_eq!(actual.out, "bob"); +} + +#[test] +fn index_cell_alt() { + let actual = nu!( + cwd: ".", + r#" + let foo = [[name]; [joe] [bob]]; echo $foo.1.name + "# + ); + + assert_eq!(actual.out, "bob"); +} + +#[test] +fn not_echoing_ranges_without_numbers() { + let actual = nu!( + cwd: ".", + r#" + echo .. + "# + ); + + assert_eq!(actual.out, ".."); +} + +#[test] +fn not_echoing_exclusive_ranges_without_numbers() { + let actual = nu!( + cwd: ".", + r#" + echo ..< + "# + ); + + assert_eq!(actual.out, "..<"); +} + +#[test] +fn echoing_ranges() { + let actual = nu!( + cwd: ".", + r#" + echo 1..3 | math sum + "# + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn echoing_exclusive_ranges() { + let actual = nu!( + cwd: ".", + r#" + echo 1..<4 | math sum + "# + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn table_literals1() { + let actual = nu!( + cwd: ".", + r#" + echo [[name age]; [foo 13]] | get age + "# + ); + + assert_eq!(actual.out, "13"); +} + +#[test] +fn table_literals2() { + let actual = nu!( + cwd: ".", + r#" + echo [[name age] ; [bob 13] [sally 20]] | get age | math sum + "# + ); + + assert_eq!(actual.out, "33"); +} + +#[test] +fn list_with_commas() { + let actual = nu!( + cwd: ".", + r#" + echo [1, 2, 3] | math sum + "# + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn range_with_left_var() { + let actual = nu!( + cwd: ".", + r#" + ({ size: 3}.size)..10 | math sum + "# + ); + + assert_eq!(actual.out, "52"); +} + +#[test] +fn range_with_right_var() { + let actual = nu!( + cwd: ".", + r#" + 4..({ size: 30}.size) | math sum + "# + ); + + assert_eq!(actual.out, "459"); +} + +#[test] +fn range_with_open_left() { + let actual = nu!( + cwd: ".", + r#" + echo ..30 | math sum + "# + ); + + assert_eq!(actual.out, "465"); +} + +#[test] +fn exclusive_range_with_open_left() { + let actual = nu!( + cwd: ".", + r#" + echo ..<31 | math sum + "# + ); + + assert_eq!(actual.out, "465"); +} + +#[test] +fn range_with_open_right() { + let actual = nu!( + cwd: ".", + r#" + echo 5.. | first 10 | math sum + "# + ); + + assert_eq!(actual.out, "95"); +} + +#[test] +fn exclusive_range_with_open_right() { + let actual = nu!( + cwd: ".", + r#" + echo 5..< | first 10 | math sum + "# + ); + + assert_eq!(actual.out, "95"); +} + +#[test] +fn range_with_mixed_types() { + let actual = nu!( + cwd: ".", + r#" + echo 1..10.5 | math sum + "# + ); + + assert_eq!(actual.out, "55"); +} + +#[test] +fn filesize_math() { + let actual = nu!( + cwd: ".", + r#" + 100 * 10kib + "# + ); + + assert_eq!(actual.out, "1000.0 KiB"); + // why 1000.0 KB instead of 1.0 MB? + // looks like `byte.get_appropriate_unit(false)` behaves this way +} + +#[test] +fn filesize_math2() { + let actual = nu!( + cwd: ".", + r#" + 100 / 10kb + "# + ); + + assert!(actual.err.contains("doesn't support")); +} + +#[test] +fn filesize_math3() { + let actual = nu!( + cwd: ".", + r#" + 100kib / 10 + "# + ); + + assert_eq!(actual.out, "10.0 KiB"); +} +#[test] +fn filesize_math4() { + let actual = nu!( + cwd: ".", + r#" + 100kib * 5 + "# + ); + + assert_eq!(actual.out, "500.0 KiB"); +} + +#[test] +fn filesize_math5() { + let actual = nu!( + cwd: ".", + r#" + 1000 * 1kib + "# + ); + + assert_eq!(actual.out, "1000.0 KiB"); +} + +#[test] +fn filesize_math6() { + let actual = nu!( + cwd: ".", + r#" + 1000 * 1mib + "# + ); + + assert_eq!(actual.out, "1000.0 MiB"); +} + +#[test] +fn filesize_math7() { + let actual = nu!( + cwd: ".", + r#" + 1000 * 1gib + "# + ); + + assert_eq!(actual.out, "1000.0 GiB"); +} + +#[test] +fn exclusive_range_with_mixed_types() { + let actual = nu!( + cwd: ".", + r#" + echo 1..<10.5 | math sum + "# + ); + + assert_eq!(actual.out, "55"); +} + +#[test] +fn table_with_commas() { + let actual = nu!( + cwd: ".", + r#" + echo [[name, age, height]; [JT, 42, 185] [Unknown, 99, 99]] | get age | math sum + "# + ); + + assert_eq!(actual.out, "141"); +} + +#[test] +fn duration_overflow() { + let actual = nu!( + cwd: ".", pipeline( + r#" + ls | get modified | each { $it + 10000000000000000day } + "#) + ); + + assert!(actual.err.contains("duration too large")); +} + +#[test] +fn date_and_duration_overflow() { + let actual = nu!( + cwd: ".", pipeline( + r#" + ls | get modified | each { $it + 1000000000day } + "#) + ); + + // assert_eq!(actual.err, "overflow"); + assert!(actual.err.contains("duration too large")); +} + +#[test] +fn pipeline_params_simple() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1 2 3 | $in.1 * $in.2 + "#) + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn pipeline_params_inner() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1 2 3 | (echo $in.2 6 7 | $in.0 * $in.1 * $in.2) + "#) + ); + + assert_eq!(actual.out, "126"); +} + +#[test] +fn better_table_lex() { + let actual = nu!( + cwd: ".", pipeline( + r#" + let table = [ + [name, size]; + [small, 7] + [medium, 10] + [large, 12] + ]; + $table.1.size + "#) + ); + + assert_eq!(actual.out, "10"); +} + +#[test] +fn better_subexpr_lex() { + let actual = nu!( + cwd: ".", pipeline( + r#" + (echo boo + sam | str length | math sum) + "#) + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn subsubcommand() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def "aws s3 rb" [url] { $url + " loaded" }; aws s3 rb localhost + "#) + ); + + assert_eq!(actual.out, "localhost loaded"); +} + +#[test] +fn manysubcommand() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def "aws s3 rb ax vf qqqq rrrr" [url] { $url + " loaded" }; aws s3 rb ax vf qqqq rrrr localhost + "#) + ); + + assert_eq!(actual.out, "localhost loaded"); +} + +#[test] +fn nothing_string_1() { + let actual = nu!( + cwd: ".", pipeline( + r#" + $nothing == "foo" + "#) + ); + + assert_eq!(actual.out, "false"); +} + +// FIXME: no current way to hide aliases +#[ignore] +#[test] +fn unalias_shadowing() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def test-shadowing [] { + alias greet = echo hello; + let xyz = { greet }; + unalias greet; + do $xyz + }; + test-shadowing + "#) + ); + assert_eq!(actual.out, "hello"); +} + +// FIXME: no current way to hide aliases +#[ignore] +#[test] +fn unalias_does_not_escape_scope() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def test-alias [] { + alias greet = echo hello; + (unalias greet); + greet + }; + test-alias + "#) + ); + assert_eq!(actual.out, "hello"); +} + +// FIXME: no current way to hide aliases +#[ignore] +#[test] +fn unalias_hides_alias() { + let actual = nu!(cwd: ".", pipeline( + r#" + def test-alias [] { + alias ll = ls -l; + unalias ll; + ll + }; + test-alias + "#) + ); + + assert!(actual.err.contains("not found")); +} + +mod parse { + use nu_test_support::nu; + + /* + The debug command's signature is: + + Usage: + > debug {flags} + + flags: + -h, --help: Display this help message + -r, --raw: Prints the raw value representation. + */ + + #[test] + fn errors_if_flag_passed_is_not_exact() { + let actual = nu!(cwd: ".", "debug -ra"); + + assert!(actual.err.contains("unknown flag"),); + + let actual = nu!(cwd: ".", "debug --rawx"); + + assert!(actual.err.contains("unknown flag"),); + } + + #[test] + fn errors_if_flag_is_not_supported() { + let actual = nu!(cwd: ".", "debug --ferris"); + + assert!(actual.err.contains("unknown flag"),); + } + + #[test] + fn errors_if_passed_an_unexpected_argument() { + let actual = nu!(cwd: ".", "debug ferris"); + + assert!(actual.err.contains("extra positional argument"),); + } +} + +mod tilde_expansion { + use nu_test_support::nu; + + #[test] + #[should_panic] + fn as_home_directory_when_passed_as_argument_and_begins_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + echo ~ + "# + ); + + assert!(!actual.out.contains('~'),); + } + + #[test] + fn does_not_expand_when_passed_as_argument_and_does_not_start_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + echo "1~1" + "# + ); + + assert_eq!(actual.out, "1~1"); + } +} + +mod variable_scoping { + use nu_test_support::nu; + + macro_rules! test_variable_scope { + ($func:literal == $res:literal $(,)*) => { + let actual = nu!( + cwd: ".", + $func + ); + + assert_eq!(actual.out, $res); + }; + } + macro_rules! test_variable_scope_list { + ($func:literal == $res:expr $(,)*) => { + let actual = nu!( + cwd: ".", + $func + ); + + let result: Vec<&str> = actual.out.matches("ZZZ").collect(); + assert_eq!(result, $res); + }; + } + + #[test] + fn access_variables_in_scopes() { + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { do { echo $input } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { do { if $input == "ZZZ" { echo $input } else { echo $input } } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { do { if $input == "ZZZ" { echo $input } else { echo $input } } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { echo $input } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { if $input == $input { echo $input } else { echo $input } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope_list!( + r#" def test [input] { echo [0 1 2] | each { echo $input } } + test ZZZ "# + == ["ZZZ", "ZZZ", "ZZZ"] + ); + test_variable_scope_list!( + r#" def test [input] { echo [0 1 2] | each { if $it > 0 {echo $input} else {echo $input}} } + test ZZZ "# + == ["ZZZ", "ZZZ", "ZZZ"] + ); + test_variable_scope_list!( + r#" def test [input] { echo [0 1 2] | each { if $input == $input {echo $input} else {echo $input}} } + test ZZZ "# + == ["ZZZ", "ZZZ", "ZZZ"] + ); + } +} diff --git a/tests/shell/pipeline/commands/mod.rs b/tests/shell/pipeline/commands/mod.rs new file mode 100644 index 0000000000..5b7aa7e195 --- /dev/null +++ b/tests/shell/pipeline/commands/mod.rs @@ -0,0 +1,2 @@ +mod external; +mod internal; diff --git a/tests/shell/pipeline/mod.rs b/tests/shell/pipeline/mod.rs new file mode 100644 index 0000000000..e8fb3af058 --- /dev/null +++ b/tests/shell/pipeline/mod.rs @@ -0,0 +1,10 @@ +mod commands; + +use nu_test_support::nu; + +#[test] +fn doesnt_break_on_utf8() { + let actual = nu!(cwd: ".", "echo ö"); + + assert_eq!(actual.out, "ö", "'{}' should contain ö", actual.out); +}