diff --git a/crates/nu-command/src/system/benchmark.rs b/crates/nu-command/src/system/benchmark.rs index a7e6e9efa..8b9645a04 100644 --- a/crates/nu-command/src/system/benchmark.rs +++ b/crates/nu-command/src/system/benchmark.rs @@ -16,13 +16,20 @@ impl Command for Benchmark { } fn usage(&self) -> &str { - "Time the running time of a block" + "Time the running time of a closure" } fn signature(&self) -> nu_protocol::Signature { Signature::build("benchmark") - .required("block", SyntaxShape::Block, "the block to run") - .input_output_types(vec![(Type::Block, Type::String)]) + .required( + "closure", + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), + "the closure to run", + ) + .input_output_types(vec![ + (Type::Any, Type::Duration), + (Type::Nothing, Type::Duration), + ]) .allow_variants_without_examples(true) .category(Category::System) } @@ -32,7 +39,7 @@ impl Command for Benchmark { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let capture_block: Closure = call.req(engine_state, stack, 0)?; let block = engine_state.get_block(capture_block.block_id); @@ -41,12 +48,27 @@ impl Command for Benchmark { let redirect_stderr = call.redirect_stderr; let mut stack = stack.captures_to_stack(&capture_block.captures); + + // In order to provide the pipeline as a positional, it must be converted into a value. + // But because pipelines do not have Clone, this one has to be cloned as a value + // and then converted back into a pipeline for eval_block(). + // So, the metadata must be saved here and restored at that point. + let input_metadata = input.metadata(); + let input_val = input.into_value(call.head); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, input_val.clone()); + } + } + + // Get the start time after all other computation has been done. let start_time = Instant::now(); eval_block( engine_state, &mut stack, block, - PipelineData::empty(), + input_val.into_pipeline_data_with_metadata(input_metadata), redirect_stdout, redirect_stderr, )? @@ -63,10 +85,47 @@ impl Command for Benchmark { } fn examples(&self) -> Vec { - vec![Example { - description: "Benchmarks a command within a block", - example: "benchmark { sleep 500ms }", - result: None, - }] + vec![ + Example { + description: "Benchmarks a command within a closure", + example: "benchmark { sleep 500ms }", + result: None, + }, + Example { + description: "Benchmark a command using an existing input", + example: "fetch https://www.nushell.sh/book/ | benchmark { split chars }", + result: None, + }, + ] } } + +#[test] +// Due to difficulty in observing side-effects from benchmark closures, +// checks that the closures have run correctly must use the filesystem. +fn test_benchmark_closure() { + use nu_test_support::{nu, nu_repl_code, playground::Playground}; + Playground::setup("test_benchmark_closure", |dirs, _| { + let inp = [ + r#"[2 3 4] | benchmark { to nuon | save foo.txt }"#, + "open foo.txt", + ]; + let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual_repl.err, ""); + assert_eq!(actual_repl.out, "[2, 3, 4]"); + }); +} + +#[test] +fn test_benchmark_closure_2() { + use nu_test_support::{nu, nu_repl_code, playground::Playground}; + Playground::setup("test_benchmark_closure", |dirs, _| { + let inp = [ + r#"[2 3 4] | benchmark {|e| {result: $e} | to nuon | save foo.txt }"#, + "open foo.txt", + ]; + let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp)); + assert_eq!(actual_repl.err, ""); + assert_eq!(actual_repl.out, "{result: [2, 3, 4]}"); + }); +}