benchmark now pipes input into the closure (#7776)

# Description

Closes #7762. See issue for motivation.

# User-Facing Changes

Something like this is now possible without having to split this into 2
commands:
```
fetch "https://www.gutenberg.org/files/11/11-0.txt" | benchmark { str downcase | split words | uniq -c | sort-by count --reverse | first 10 }
```

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
This commit is contained in:
Leon 2023-01-23 04:18:28 +10:00 committed by GitHub
parent 4f57c5d56e
commit d8027656b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -16,13 +16,20 @@ impl Command for Benchmark {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Time the running time of a block" "Time the running time of a closure"
} }
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("benchmark") Signature::build("benchmark")
.required("block", SyntaxShape::Block, "the block to run") .required(
.input_output_types(vec![(Type::Block, Type::String)]) "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) .allow_variants_without_examples(true)
.category(Category::System) .category(Category::System)
} }
@ -32,7 +39,7 @@ impl Command for Benchmark {
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> { ) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let capture_block: Closure = call.req(engine_state, stack, 0)?; let capture_block: Closure = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id); 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 redirect_stderr = call.redirect_stderr;
let mut stack = stack.captures_to_stack(&capture_block.captures); 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(); let start_time = Instant::now();
eval_block( eval_block(
engine_state, engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::empty(), input_val.into_pipeline_data_with_metadata(input_metadata),
redirect_stdout, redirect_stdout,
redirect_stderr, redirect_stderr,
)? )?
@ -63,10 +85,47 @@ impl Command for Benchmark {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Benchmarks a command within a block", Example {
description: "Benchmarks a command within a closure",
example: "benchmark { sleep 500ms }", example: "benchmark { sleep 500ms }",
result: None, 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]}");
});
}