From e3e4ae05910582fc29cdeeb773b7cbcd285204b3 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 9 Oct 2021 14:10:10 +0100 Subject: [PATCH] example unit test --- Cargo.lock | 1 + crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/example_test.rs | 74 +++++++++++++++++++ crates/nu-command/src/filters/each.rs | 40 +++++++++- crates/nu-command/src/filters/for_.rs | 49 +++++++----- crates/nu-command/src/formats/from/json.rs | 58 ++++++++++++++- crates/nu-command/src/lib.rs | 4 +- crates/nu-command/src/strings/build_string.rs | 35 ++++++++- crates/nu-protocol/src/example.rs | 2 +- crates/nu-protocol/src/value/mod.rs | 46 ++++++++++++ 10 files changed, 285 insertions(+), 25 deletions(-) create mode 100644 crates/nu-command/src/example_test.rs diff --git a/Cargo.lock b/Cargo.lock index 5ff65ec7db..2edb362f39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,7 @@ dependencies = [ "glob", "nu-engine", "nu-json", + "nu-parser", "nu-path", "nu-protocol", "nu-table", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index d840fb7d33..6186de10d2 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -11,6 +11,7 @@ nu-json = { path = "../nu-json" } nu-path = { path = "../nu-path" } nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } +nu-parser = { path = "../nu-parser" } # Potential dependencies for extras glob = "0.3.0" diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs new file mode 100644 index 0000000000..9d546407a1 --- /dev/null +++ b/crates/nu-command/src/example_test.rs @@ -0,0 +1,74 @@ +use std::{cell::RefCell, rc::Rc}; + +use nu_engine::eval_block; +use nu_parser::parse; +use nu_protocol::{ + engine::{Command, EngineState, EvaluationContext, StateWorkingSet}, + Value, +}; + +use super::From; + +pub fn test_examples(cmd: impl Command + 'static) { + let examples = cmd.examples(); + let engine_state = Rc::new(RefCell::new(EngineState::new())); + + let delta = { + // Base functions that are needed for testing + // Try to keep this working set as small to keep tests running as fast as possible + let engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); + working_set.add_decl(Box::new(From)); + + // Adding the command that is being tested to the working set + working_set.add_decl(Box::new(cmd)); + + working_set.render() + }; + + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); + + for example in examples { + let start = std::time::Instant::now(); + + let (block, delta) = { + let engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); + let (output, err) = parse(&mut working_set, None, example.example.as_bytes(), false); + + if let Some(err) = err { + panic!("test parse error: {:?}", err) + } + + (output, working_set.render()) + }; + + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); + + let state = EvaluationContext { + engine_state: engine_state.clone(), + stack: nu_protocol::engine::Stack::new(), + }; + + match eval_block(&state, &block, Value::nothing()) { + Err(err) => panic!("test eval error: {:?}", err), + Ok(result) => { + println!("input: {}", example.example); + println!("result: {:?}", result); + println!("done: {:?}", start.elapsed()); + + // Note. Value implements PartialEq for Bool, Int, Float, String and Block + // If the command you are testing requires to compare another case, then + // you need to define its equality in the Value struct + if let Some(expected) = example.result { + if result != expected { + panic!( + "the example result is different to expected value: {:?} != {:?}", + result, expected + ) + } + } + } + } + } +} diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index e48cfb719a..ce847b9174 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -1,7 +1,7 @@ use nu_engine::eval_block; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; +use nu_protocol::{Example, IntoValueStream, Signature, Span, SyntaxShape, Value}; pub struct Each; @@ -24,6 +24,32 @@ impl Command for Each { .switch("numbered", "iterate with an index", Some('n')) } + fn examples(&self) -> Vec { + let stream_test_1 = vec![ + Value::Int { + val: 2, + span: Span::unknown(), + }, + Value::Int { + val: 4, + span: Span::unknown(), + }, + Value::Int { + val: 6, + span: Span::unknown(), + }, + ]; + + vec![Example { + example: "[1 2 3] | each { 2 * $it }", + description: "Multiplies elements in list", + result: Some(Value::Stream { + stream: stream_test_1.into_iter().into_value_stream(), + span: Span::unknown(), + }), + }] + } + fn run( &self, context: &EvaluationContext, @@ -225,3 +251,15 @@ impl Command for Each { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Each {}) + } +} diff --git a/crates/nu-command/src/filters/for_.rs b/crates/nu-command/src/filters/for_.rs index a3dfa56522..ddb69e0f96 100644 --- a/crates/nu-command/src/filters/for_.rs +++ b/crates/nu-command/src/filters/for_.rs @@ -98,34 +98,43 @@ impl Command for For { Example { description: "Echo the square of each integer", example: "for x in [1 2 3] { $x * $x }", - result: Some(vec![ - Value::Int { val: 1, span }, - Value::Int { val: 4, span }, - Value::Int { val: 9, span }, - ]), + result: Some(Value::List { + vals: vec![ + Value::Int { val: 1, span }, + Value::Int { val: 4, span }, + Value::Int { val: 9, span }, + ], + span: Span::unknown(), + }), }, Example { description: "Work with elements of a range", example: "for $x in 1..3 { $x }", - result: Some(vec![ - Value::Int { val: 1, span }, - Value::Int { val: 2, span }, - Value::Int { val: 3, span }, - ]), + result: Some(Value::List { + vals: vec![ + Value::Int { val: 1, span }, + Value::Int { val: 2, span }, + Value::Int { val: 3, span }, + ], + span: Span::unknown(), + }), }, Example { description: "Number each item and echo a message", example: "for $it in ['bob' 'fred'] --numbered { $\"($it.index) is ($it.item)\" }", - result: Some(vec![ - Value::String { - val: "0 is bob".into(), - span, - }, - Value::String { - val: "0 is fred".into(), - span, - }, - ]), + result: Some(Value::List { + vals: vec![ + Value::String { + val: "0 is bob".into(), + span, + }, + Value::String { + val: "0 is fred".into(), + span, + }, + ], + span: Span::unknown(), + }), }, ] } diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index fa206131f7..f7fe0d5f94 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, ShellError, Signature, Span, Value}; +use nu_protocol::{Example, IntoValueStream, ShellError, Signature, Span, Value}; pub struct FromJson; @@ -21,6 +21,50 @@ impl Command for FromJson { ) } + fn examples(&self) -> Vec { + vec![ + Example { + example: "'{ a:1 }' | from json", + description: "Converts json formatted string to table", + result: Some(Value::Record { + cols: vec!["a".to_string()], + vals: vec![Value::Int { + val: 1, + span: Span::unknown(), + }], + span: Span::unknown(), + }), + }, + Example { + example: "'{ a:1, b: [1, 2] }' | from json", + description: "Converts json formatted string to table", + result: Some(Value::Record { + cols: vec!["a".to_string(), "b".to_string()], + vals: vec![ + Value::Int { + val: 1, + span: Span::unknown(), + }, + Value::List { + vals: vec![ + Value::Int { + val: 1, + span: Span::unknown(), + }, + Value::Int { + val: 2, + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }), + }, + ] + } + fn run( &self, _context: &EvaluationContext, @@ -109,3 +153,15 @@ fn convert_string_to_value(string_input: String, span: Span) -> Result Vec { + vec![ + Example { + example: "build-string a b c", + description: "Builds a string from letters a b c", + result: Some(Value::String { + val: "abc".to_string(), + span: Span::unknown(), + }), + }, + Example { + example: "build-string (1 + 2) = one ' ' plus ' ' two", + description: "Builds a string from letters a b c", + result: Some(Value::String { + val: "3=one plus two".to_string(), + span: Span::unknown(), + }), + }, + ] + } + fn run( &self, context: &EvaluationContext, @@ -36,3 +57,15 @@ impl Command for BuildString { }) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(BuildString {}) + } +} diff --git a/crates/nu-protocol/src/example.rs b/crates/nu-protocol/src/example.rs index 894b4b2876..1abaca1774 100644 --- a/crates/nu-protocol/src/example.rs +++ b/crates/nu-protocol/src/example.rs @@ -3,5 +3,5 @@ use crate::Value; pub struct Example { pub example: &'static str, pub description: &'static str, - pub result: Option>, + pub result: Option, } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index afc1f90161..86ec4ae581 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -374,6 +374,52 @@ impl PartialEq for Value { (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs, (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, (Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) => b1 == b2, + (Value::List { vals: vals_lhs, .. }, Value::List { vals: vals_rhs, .. }) => { + for (lhs, rhs) in vals_lhs.iter().zip(vals_rhs) { + if lhs != rhs { + return false; + } + } + + true + } + ( + Value::Record { + cols: cols_lhs, + vals: vals_lhs, + .. + }, + Value::Record { + cols: cols_rhs, + vals: vals_rhs, + .. + }, + ) => { + if cols_lhs != cols_rhs { + return false; + } + + for (lhs, rhs) in vals_lhs.iter().zip(vals_rhs) { + if lhs != rhs { + return false; + } + } + + true + } + ( + Value::Stream { + stream: stream_lhs, .. + }, + Value::Stream { + stream: stream_rhs, .. + }, + ) => { + let vals_lhs = stream_lhs.clone().collect_string(); + let vals_rhs = stream_rhs.clone().collect_string(); + + vals_lhs == vals_rhs + } _ => false, } }