2021-01-21 03:07:16 +01:00
|
|
|
mod sample;
|
|
|
|
|
|
|
|
mod double_echo;
|
|
|
|
mod double_ls;
|
|
|
|
mod stub_generate;
|
|
|
|
|
|
|
|
use double_echo::Command as DoubleEcho;
|
|
|
|
use double_ls::Command as DoubleLs;
|
2021-03-31 07:52:34 +02:00
|
|
|
use stub_generate::{mock_path, Command as StubOpen};
|
|
|
|
|
2020-05-18 14:56:01 +02:00
|
|
|
use nu_errors::ShellError;
|
2020-12-18 08:53:49 +01:00
|
|
|
use nu_parser::ParserScope;
|
2021-04-21 22:54:34 +02:00
|
|
|
use nu_protocol::hir::{ClassifiedBlock, ExternalRedirection};
|
2021-01-21 03:07:16 +01:00
|
|
|
use nu_protocol::{ShellTypeName, Value};
|
|
|
|
use nu_source::AnchorLocation;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-07-25 12:01:54 +02:00
|
|
|
#[cfg(feature = "dataframe")]
|
|
|
|
use crate::commands::{
|
2021-08-24 16:10:29 +02:00
|
|
|
DataFrameDropNulls, DataFrameGroupBy, DataFrameIsNull, DataFrameShift, DataFrameToDF,
|
|
|
|
DataFrameWithColumn, StrToDatetime,
|
2021-07-25 12:01:54 +02:00
|
|
|
};
|
|
|
|
|
2020-10-03 16:06:02 +02:00
|
|
|
use crate::commands::{
|
2021-08-15 06:36:08 +02:00
|
|
|
Append, BuildString, Collect, Each, Echo, First, Get, Keep, Last, Let, Math, MathMode, Nth,
|
|
|
|
Select, StrCollect, Wrap,
|
2021-01-10 03:50:49 +01:00
|
|
|
};
|
2021-01-21 03:07:16 +01:00
|
|
|
use nu_engine::{run_block, whole_stream_command, Command, EvaluationContext, WholeStreamCommand};
|
|
|
|
use nu_stream::InputStream;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
|
|
|
|
let examples = cmd.examples();
|
|
|
|
|
2021-05-03 01:45:55 +02:00
|
|
|
let base_context = EvaluationContext::basic();
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
base_context.add_commands(vec![
|
2021-01-21 03:07:16 +01:00
|
|
|
// Command Doubles
|
|
|
|
whole_stream_command(DoubleLs {}),
|
2020-10-03 16:06:02 +02:00
|
|
|
// Minimal restricted commands to aid in testing
|
2021-02-02 18:09:19 +01:00
|
|
|
whole_stream_command(Append {}),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(Echo {}),
|
|
|
|
whole_stream_command(BuildString {}),
|
2020-10-14 11:36:11 +02:00
|
|
|
whole_stream_command(First {}),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(Get {}),
|
|
|
|
whole_stream_command(Keep {}),
|
|
|
|
whole_stream_command(Each {}),
|
2020-10-14 11:36:11 +02:00
|
|
|
whole_stream_command(Last {}),
|
|
|
|
whole_stream_command(Nth {}),
|
2021-01-05 00:30:55 +01:00
|
|
|
whole_stream_command(Let {}),
|
2021-02-02 18:09:19 +01:00
|
|
|
whole_stream_command(Select),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(StrCollect),
|
2021-08-15 06:36:08 +02:00
|
|
|
whole_stream_command(Collect),
|
2020-10-06 12:21:20 +02:00
|
|
|
whole_stream_command(Wrap),
|
2020-10-03 16:06:02 +02:00
|
|
|
cmd,
|
|
|
|
]);
|
2020-05-18 14:56:01 +02:00
|
|
|
|
2020-10-03 16:06:02 +02:00
|
|
|
for sample_pipeline in examples {
|
|
|
|
let mut ctx = base_context.clone();
|
|
|
|
|
2021-01-01 20:52:19 +01:00
|
|
|
let block = parse_line(sample_pipeline.example, &ctx)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
if let Some(expected) = &sample_pipeline.result {
|
2021-04-06 18:19:43 +02:00
|
|
|
let result = evaluate_block(block, &mut ctx)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2020-10-08 23:47:51 +02:00
|
|
|
ctx.with_errors(|reasons| reasons.iter().cloned().take(1).next())
|
|
|
|
.map_or(Ok(()), Err)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
if expected.len() != result.len() {
|
|
|
|
let rows_returned =
|
|
|
|
format!("expected: {}\nactual: {}", expected.len(), result.len());
|
|
|
|
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"example command produced unexpected number of results.\n {} {}",
|
|
|
|
failed_call, rows_returned
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (e, a) in expected.iter().zip(result.iter()) {
|
|
|
|
if !values_equal(e, a) {
|
|
|
|
let row_errored = format!("expected: {:#?}\nactual: {:#?}", e, a);
|
|
|
|
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"example command produced unexpected result.\n {} {}",
|
|
|
|
failed_call, row_errored
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
|
2020-05-18 14:56:01 +02:00
|
|
|
let examples = cmd.examples();
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-05-03 01:45:55 +02:00
|
|
|
let base_context = EvaluationContext::basic();
|
2020-07-03 10:43:55 +02:00
|
|
|
|
2020-05-18 14:56:01 +02:00
|
|
|
base_context.add_commands(vec![
|
2021-04-22 10:35:45 +02:00
|
|
|
whole_stream_command(Math),
|
|
|
|
whole_stream_command(MathMode {}),
|
2020-05-18 14:56:01 +02:00
|
|
|
whole_stream_command(Echo {}),
|
2020-07-03 10:43:55 +02:00
|
|
|
whole_stream_command(BuildString {}),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(Get {}),
|
|
|
|
whole_stream_command(Keep {}),
|
2020-08-27 07:44:18 +02:00
|
|
|
whole_stream_command(Each {}),
|
2021-01-05 00:30:55 +01:00
|
|
|
whole_stream_command(Let {}),
|
2020-05-18 14:56:01 +02:00
|
|
|
whole_stream_command(cmd),
|
2021-02-02 18:09:19 +01:00
|
|
|
whole_stream_command(Select),
|
2020-07-21 01:33:39 +02:00
|
|
|
whole_stream_command(StrCollect),
|
2021-08-15 06:36:08 +02:00
|
|
|
whole_stream_command(Collect),
|
2020-10-06 12:21:20 +02:00
|
|
|
whole_stream_command(Wrap),
|
2020-05-18 14:56:01 +02:00
|
|
|
]);
|
|
|
|
|
2020-10-03 16:06:02 +02:00
|
|
|
for sample_pipeline in examples {
|
2020-05-18 14:56:01 +02:00
|
|
|
let mut ctx = base_context.clone();
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-01-01 20:52:19 +01:00
|
|
|
let block = parse_line(sample_pipeline.example, &ctx)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
if let Some(expected) = &sample_pipeline.result {
|
2021-06-18 03:04:51 +02:00
|
|
|
let start = std::time::Instant::now();
|
2021-04-06 18:19:43 +02:00
|
|
|
let result = evaluate_block(block, &mut ctx)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-06-18 03:04:51 +02:00
|
|
|
println!("input: {}", sample_pipeline.example);
|
|
|
|
println!("result: {:?}", result);
|
|
|
|
println!("done: {:?}", start.elapsed());
|
|
|
|
|
2020-10-08 23:47:51 +02:00
|
|
|
ctx.with_errors(|reasons| reasons.iter().cloned().take(1).next())
|
|
|
|
.map_or(Ok(()), Err)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
if expected.len() != result.len() {
|
|
|
|
let rows_returned =
|
|
|
|
format!("expected: {}\nactual: {}", expected.len(), result.len());
|
|
|
|
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"example command produced unexpected number of results.\n {} {}",
|
|
|
|
failed_call, rows_returned
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (e, a) in expected.iter().zip(result.iter()) {
|
|
|
|
if !values_equal(e, a) {
|
|
|
|
let row_errored = format!("expected: {:#?}\nactual: {:#?}", e, a);
|
|
|
|
let failed_call = format!("command: {}\n", sample_pipeline.example);
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"example command produced unexpected result.\n {} {}",
|
|
|
|
failed_call, row_errored
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-05-18 14:56:01 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-07-25 12:01:54 +02:00
|
|
|
#[cfg(feature = "dataframe")]
|
|
|
|
pub fn test_dataframe(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
|
|
|
|
use nu_protocol::UntaggedValue;
|
|
|
|
|
|
|
|
let examples = cmd.examples();
|
|
|
|
|
|
|
|
let base_context = EvaluationContext::basic();
|
|
|
|
|
|
|
|
base_context.add_commands(vec![
|
|
|
|
whole_stream_command(cmd),
|
|
|
|
// Commands used with dataframe
|
|
|
|
whole_stream_command(DataFrameToDF),
|
|
|
|
whole_stream_command(DataFrameShift),
|
|
|
|
whole_stream_command(DataFrameIsNull),
|
|
|
|
whole_stream_command(DataFrameGroupBy),
|
|
|
|
whole_stream_command(DataFrameWithColumn),
|
2021-08-24 16:10:29 +02:00
|
|
|
whole_stream_command(DataFrameDropNulls),
|
2021-07-25 12:01:54 +02:00
|
|
|
// Base commands for context
|
|
|
|
whole_stream_command(Math),
|
|
|
|
whole_stream_command(MathMode {}),
|
|
|
|
whole_stream_command(Echo {}),
|
|
|
|
whole_stream_command(BuildString {}),
|
|
|
|
whole_stream_command(Get {}),
|
|
|
|
whole_stream_command(Keep {}),
|
|
|
|
whole_stream_command(Each {}),
|
|
|
|
whole_stream_command(Let {}),
|
|
|
|
whole_stream_command(Select),
|
|
|
|
whole_stream_command(StrCollect),
|
2021-08-15 06:36:08 +02:00
|
|
|
whole_stream_command(Collect),
|
2021-07-25 12:01:54 +02:00
|
|
|
whole_stream_command(Wrap),
|
2021-08-06 00:18:53 +02:00
|
|
|
whole_stream_command(StrToDatetime),
|
2021-07-25 12:01:54 +02:00
|
|
|
]);
|
|
|
|
|
|
|
|
for sample_pipeline in examples {
|
|
|
|
let mut ctx = base_context.clone();
|
|
|
|
|
|
|
|
println!("{:?}", &sample_pipeline.example);
|
|
|
|
let block = parse_line(sample_pipeline.example, &ctx)?;
|
|
|
|
|
|
|
|
if let Some(expected) = &sample_pipeline.result {
|
|
|
|
let start = std::time::Instant::now();
|
|
|
|
let result = evaluate_block(block, &mut ctx)?;
|
|
|
|
|
|
|
|
println!("input: {}", sample_pipeline.example);
|
|
|
|
println!("result: {:?}", result);
|
|
|
|
println!("done: {:?}", start.elapsed());
|
|
|
|
|
|
|
|
let value = match result.get(0) {
|
|
|
|
Some(v) => v,
|
|
|
|
None => panic!(
|
|
|
|
"Unable to extract a value after parsing example: {}",
|
|
|
|
sample_pipeline.example
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
let df = match &value.value {
|
|
|
|
UntaggedValue::DataFrame(df) => df,
|
|
|
|
_ => panic!(
|
|
|
|
"Unable to extract dataframe from parsed example: {}",
|
|
|
|
sample_pipeline.example
|
|
|
|
),
|
|
|
|
};
|
|
|
|
|
|
|
|
let expected = match expected.get(0) {
|
|
|
|
Some(v) => v,
|
|
|
|
None => panic!("Empty vector in result example"),
|
|
|
|
};
|
|
|
|
|
|
|
|
let df_expected = match &expected.value {
|
|
|
|
UntaggedValue::DataFrame(df) => df,
|
|
|
|
_ => panic!("Unable to extract dataframe from example result"),
|
|
|
|
};
|
|
|
|
|
|
|
|
println!("expected: {:?}", df_expected);
|
|
|
|
|
|
|
|
assert_eq!(df, df_expected)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-10-03 16:06:02 +02:00
|
|
|
pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
|
|
|
|
let examples = cmd.examples();
|
|
|
|
|
2021-05-03 01:45:55 +02:00
|
|
|
let base_context = EvaluationContext::basic();
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
base_context.add_commands(vec![
|
|
|
|
// Minimal restricted commands to aid in testing
|
2021-01-21 03:07:16 +01:00
|
|
|
whole_stream_command(StubOpen {}),
|
|
|
|
whole_stream_command(DoubleEcho {}),
|
|
|
|
whole_stream_command(DoubleLs {}),
|
2021-02-02 18:09:19 +01:00
|
|
|
whole_stream_command(Append {}),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(BuildString {}),
|
2020-10-14 11:36:11 +02:00
|
|
|
whole_stream_command(First {}),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(Get {}),
|
|
|
|
whole_stream_command(Keep {}),
|
|
|
|
whole_stream_command(Each {}),
|
2020-10-14 11:36:11 +02:00
|
|
|
whole_stream_command(Last {}),
|
|
|
|
whole_stream_command(Nth {}),
|
2021-01-05 00:30:55 +01:00
|
|
|
whole_stream_command(Let {}),
|
2021-02-02 18:09:19 +01:00
|
|
|
whole_stream_command(Select),
|
2020-10-03 16:06:02 +02:00
|
|
|
whole_stream_command(StrCollect),
|
2021-08-15 06:36:08 +02:00
|
|
|
whole_stream_command(Collect),
|
2020-10-06 12:21:20 +02:00
|
|
|
whole_stream_command(Wrap),
|
2020-10-03 16:06:02 +02:00
|
|
|
cmd,
|
|
|
|
]);
|
|
|
|
|
|
|
|
for sample_pipeline in examples {
|
2021-01-21 03:07:16 +01:00
|
|
|
let pipeline_with_anchor = format!("stub open --path | {}", sample_pipeline.example);
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
let mut ctx = base_context.clone();
|
|
|
|
|
2021-01-01 20:52:19 +01:00
|
|
|
let block = parse_line(&pipeline_with_anchor, &ctx)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-02-04 08:20:21 +01:00
|
|
|
if sample_pipeline.result.is_some() {
|
2021-04-06 18:19:43 +02:00
|
|
|
let result = evaluate_block(block, &mut ctx)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-02-02 18:09:19 +01:00
|
|
|
ctx.with_errors(|reasons| reasons.iter().cloned().take(1).next())
|
|
|
|
.map_or(Ok(()), Err)?;
|
2020-10-03 16:06:02 +02:00
|
|
|
|
2021-02-02 18:09:19 +01:00
|
|
|
for actual in result.iter() {
|
|
|
|
if !is_anchor_carried(actual, mock_path()) {
|
|
|
|
let failed_call = format!("command: {}\n", pipeline_with_anchor);
|
|
|
|
|
|
|
|
panic!(
|
|
|
|
"example command didn't carry anchor tag correctly.\n {} {:#?} {:#?}",
|
|
|
|
failed_call,
|
|
|
|
actual,
|
|
|
|
mock_path()
|
|
|
|
);
|
|
|
|
}
|
2020-10-03 16:06:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2020-05-18 14:56:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Parse and run a nushell pipeline
|
2020-12-19 08:47:34 +01:00
|
|
|
fn parse_line(line: &str, ctx: &EvaluationContext) -> Result<ClassifiedBlock, ShellError> {
|
2020-12-18 08:53:49 +01:00
|
|
|
//FIXME: do we still need this?
|
2020-11-22 01:37:16 +01:00
|
|
|
let line = if let Some(line) = line.strip_suffix('\n') {
|
|
|
|
line
|
2020-05-18 14:56:01 +02:00
|
|
|
} else {
|
|
|
|
line
|
|
|
|
};
|
|
|
|
|
2021-06-25 07:50:24 +02:00
|
|
|
let (lite_result, err) = nu_parser::lex(line, 0, nu_parser::NewlineMode::Normal);
|
2020-12-18 08:53:49 +01:00
|
|
|
if let Some(err) = err {
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2021-02-12 21:31:11 +01:00
|
|
|
let (lite_result, err) = nu_parser::parse_block(lite_result);
|
2020-11-09 17:27:07 +01:00
|
|
|
if let Some(err) = err {
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2020-05-18 14:56:01 +02:00
|
|
|
|
|
|
|
// TODO ensure the command whose examples we're testing is actually in the pipeline
|
2020-12-18 08:53:49 +01:00
|
|
|
let (block, err) = nu_parser::classify_block(&lite_result, &ctx.scope);
|
|
|
|
Ok(ClassifiedBlock { block, failed: err })
|
2020-05-18 14:56:01 +02:00
|
|
|
}
|
|
|
|
|
2021-04-06 18:19:43 +02:00
|
|
|
fn evaluate_block(
|
2020-05-18 14:56:01 +02:00
|
|
|
block: ClassifiedBlock,
|
2020-09-19 23:29:51 +02:00
|
|
|
ctx: &mut EvaluationContext,
|
2020-05-18 14:56:01 +02:00
|
|
|
) -> Result<Vec<Value>, ShellError> {
|
|
|
|
let input_stream = InputStream::empty();
|
|
|
|
|
2020-12-18 08:53:49 +01:00
|
|
|
ctx.scope.enter_scope();
|
|
|
|
|
2021-04-21 22:54:34 +02:00
|
|
|
let result = run_block(&block.block, ctx, input_stream, ExternalRedirection::Stdout);
|
2020-12-18 08:53:49 +01:00
|
|
|
|
|
|
|
ctx.scope.exit_scope();
|
2020-09-26 01:40:02 +02:00
|
|
|
|
2021-04-06 18:19:43 +02:00
|
|
|
let result = result?.drain_vec();
|
2020-12-18 08:53:49 +01:00
|
|
|
Ok(result)
|
2020-05-18 14:56:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO probably something already available to do this
|
|
|
|
// TODO perhaps better panic messages when things don't compare
|
|
|
|
|
|
|
|
// Deep value comparisons that ignore tags
|
|
|
|
fn values_equal(expected: &Value, actual: &Value) -> bool {
|
|
|
|
use nu_protocol::UntaggedValue::*;
|
|
|
|
|
|
|
|
match (&expected.value, &actual.value) {
|
|
|
|
(Primitive(e), Primitive(a)) => e == a,
|
|
|
|
(Row(e), Row(a)) => {
|
|
|
|
if e.entries.len() != a.entries.len() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
e.entries
|
|
|
|
.iter()
|
|
|
|
.zip(a.entries.iter())
|
|
|
|
.all(|((ek, ev), (ak, av))| ek == ak && values_equal(ev, av))
|
|
|
|
}
|
|
|
|
(Table(e), Table(a)) => e.iter().zip(a.iter()).all(|(e, a)| values_equal(e, a)),
|
|
|
|
(e, a) => unimplemented!("{} {}", e.type_name(), a.type_name()),
|
|
|
|
}
|
|
|
|
}
|
2020-10-03 16:06:02 +02:00
|
|
|
|
|
|
|
fn is_anchor_carried(actual: &Value, anchor: AnchorLocation) -> bool {
|
|
|
|
actual.tag.anchor() == Some(anchor)
|
|
|
|
}
|