From 0a34989eae9b952d0d3bc45b5a0b4e18d4436bac Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Wed, 16 Jun 2021 10:55:36 -0700 Subject: [PATCH] enhancement(remap): Add assert_eq! function (#7866) * enhancement(remap): Add assert_eq! function This PR adds an `assert_eq!` function with the goal of making writing unit tests easier. The output may change with https://github.com/timberio/vector/issues/6261 to make it easier to see the condition that failed. Closes #7833 Signed-off-by: Jesse Szwedko --- lib/vrl/stdlib/Cargo.toml | 2 + lib/vrl/stdlib/benches/benches.rs | 10 +++ lib/vrl/stdlib/src/assert_eq.rs | 124 ++++++++++++++++++++++++++++++ lib/vrl/stdlib/src/lib.rs | 6 ++ 4 files changed, 142 insertions(+) create mode 100644 lib/vrl/stdlib/src/assert_eq.rs diff --git a/lib/vrl/stdlib/Cargo.toml b/lib/vrl/stdlib/Cargo.toml index e75b74a52fd3a..1d597fb65b9db 100644 --- a/lib/vrl/stdlib/Cargo.toml +++ b/lib/vrl/stdlib/Cargo.toml @@ -43,6 +43,7 @@ default = [ "append", "array", "assert", + "assert_eq", "boolean", "ceil", "compact", @@ -144,6 +145,7 @@ default = [ append = [] array = [] assert = [] +assert_eq = [] boolean = [] ceil = [] compact = [] diff --git a/lib/vrl/stdlib/benches/benches.rs b/lib/vrl/stdlib/benches/benches.rs index 6ec0bcc03b423..700db5f24f3ee 100644 --- a/lib/vrl/stdlib/benches/benches.rs +++ b/lib/vrl/stdlib/benches/benches.rs @@ -10,6 +10,7 @@ criterion_group!( // https://github.com/timberio/vector/pull/6408 config = Criterion::default().noise_threshold(0.05); targets = assert, + assert_eq, ceil, compact, contains, @@ -122,6 +123,15 @@ bench_function! { } } +bench_function! { + assert_eq=> vrl_stdlib::AssertEq; + + literal { + args: func_args![left: value!(true), right: value!(true), message: "must be true"], + want: Ok(value!(true)), + } +} + bench_function! { ceil => vrl_stdlib::Ceil; diff --git a/lib/vrl/stdlib/src/assert_eq.rs b/lib/vrl/stdlib/src/assert_eq.rs new file mode 100644 index 0000000000000..bb4c7b02731f6 --- /dev/null +++ b/lib/vrl/stdlib/src/assert_eq.rs @@ -0,0 +1,124 @@ +use vrl::prelude::*; + +#[derive(Clone, Copy, Debug)] +pub struct AssertEq; + +impl Function for AssertEq { + fn identifier(&self) -> &'static str { + "assert_eq" + } + + fn parameters(&self) -> &'static [Parameter] { + &[ + Parameter { + keyword: "left", + kind: kind::ANY, + required: true, + }, + Parameter { + keyword: "right", + kind: kind::ANY, + required: true, + }, + Parameter { + keyword: "message", + kind: kind::BYTES, + required: false, + }, + ] + } + + fn examples(&self) -> &'static [Example] { + &[ + Example { + title: "success", + source: "assert_eq!(true, true)", + result: Ok("true"), + }, + Example { + title: "failure", + source: "assert_eq!(true, false)", + result: Err( + r#"function call error for "assert_eq" at (0:23): assertion failed: true == false"#, + ), + }, + Example { + title: "custom message", + source: "assert_eq!(true, false, s'custom error')", + result: Err(r#"function call error for "assert_eq" at (0:40): custom error"#), + }, + ] + } + + fn compile(&self, mut arguments: ArgumentList) -> Compiled { + let left = arguments.required("left"); + let right = arguments.required("right"); + let message = arguments.optional("message"); + + Ok(Box::new(AssertEqFn { + left, + right, + message, + })) + } +} + +#[derive(Debug, Clone)] +struct AssertEqFn { + left: Box, + right: Box, + message: Option>, +} + +impl Expression for AssertEqFn { + fn resolve(&self, ctx: &mut Context) -> Resolved { + let left = self.left.resolve(ctx)?; + let right = self.right.resolve(ctx)?; + + if left == right { + Ok(true.into()) + } else { + Err(self + .message + .as_ref() + .map(|m| { + m.resolve(ctx) + .and_then(|v| Ok(v.try_bytes_utf8_lossy()?.into_owned())) + }) + .transpose()? + .unwrap_or_else(|| format!("assertion failed: {} == {}", left, right)) + .into()) + } + } + + fn type_def(&self, _state: &state::Compiler) -> TypeDef { + TypeDef::new().fallible().boolean() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + test_function![ + assert_eq => AssertEq; + + pass { + args: func_args![left: "foo", right: "foo"], + want: Ok(true), + tdef: TypeDef::new().fallible().boolean(), + } + + fail { + args: func_args![left: "foo", right: "bar"], + want: Err(r#"assertion failed: "foo" == "bar""#), + tdef: TypeDef::new().fallible().boolean(), + } + + message { + args: func_args![left: "foo", right: "bar", message: "failure!"], + want: Err("failure!"), + tdef: TypeDef::new().fallible().boolean(), + } + ]; +} diff --git a/lib/vrl/stdlib/src/lib.rs b/lib/vrl/stdlib/src/lib.rs index 7ed158bebd19c..9a070dc6259c4 100644 --- a/lib/vrl/stdlib/src/lib.rs +++ b/lib/vrl/stdlib/src/lib.rs @@ -6,6 +6,8 @@ mod append; mod array; #[cfg(feature = "assert")] mod assert; +#[cfg(feature = "assert_eq")] +mod assert_eq; #[cfg(feature = "boolean")] mod boolean; #[cfg(feature = "ceil")] @@ -219,6 +221,8 @@ pub use crate::sha1::Sha1; pub use append::Append; #[cfg(feature = "assert")] pub use assert::Assert; +#[cfg(feature = "assert_eq")] +pub use assert_eq::AssertEq; #[cfg(feature = "boolean")] pub use boolean::Boolean; #[cfg(feature = "ceil")] @@ -418,6 +422,8 @@ pub fn all() -> Vec> { Box::new(Append), #[cfg(feature = "assert")] Box::new(Assert), + #[cfg(feature = "assert_eq")] + Box::new(AssertEq), #[cfg(feature = "boolean")] Box::new(Boolean), #[cfg(feature = "ceil")]