Skip to content

Commit

Permalink
Test helpers to match against regular expressions. (#813)
Browse files Browse the repository at this point in the history
* Test helpers to match against regular expressions.
* Swap `regex-lite` for `regex`
  • Loading branch information
colincasey authored May 21, 2024
1 parent a4e48fd commit 097bef0
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `libcnb`:
- Made `Target` (the type of `DetectContext::target` and `BuildContext::target`) public. ([#815](https://github.com/heroku/libcnb.rs/pull/815))
- `libcnb-test`
- Added the macro `assert_contains_match!` for testing if a value contains a regular expression match.
- Added the macro `assert_not_contains_match!` for testing if a value does not contain a regular expression match.

### Changed

Expand Down
1 change: 1 addition & 0 deletions libcnb-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fs_extra = "1.3.0"
libcnb-common.workspace = true
libcnb-data.workspace = true
libcnb-package.workspace = true
regex = "1.10.4"
tempfile = "3.10.1"
thiserror = "1.0.59"

Expand Down
2 changes: 2 additions & 0 deletions libcnb-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ use indoc as _;
use libcnb as _;
#[cfg(test)]
use ureq as _;
// This dependency is used by the `assert_not_contains` and `assert_not_contains_match` macros
use regex as _;
304 changes: 304 additions & 0 deletions libcnb-test/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,116 @@ value (escaped): `{:?}`: {}"#,
}};
}

/// Asserts that `left` contains the `right` pattern (regular expression).
///
/// Commonly used when asserting `pack` output in integration tests. Expands to a regular
/// expression match test and logs `left` (in unescaped and escaped form) as well as `right`
/// on failure.
///
/// Multi-line mode is automatically enabled on regular expressions. If this is not what you
/// want it can be disabled by adding `(?-m)` to the start of your pattern.
///
/// # Example
///
/// ```
/// use libcnb_test::assert_contains_match;
///
/// let output = "Hello World!\nHello Integration Test!";
/// assert_contains_match!(output, "Test!$");
/// ```
#[macro_export]
macro_rules! assert_contains_match {
($left:expr, $right:expr $(,)?) => {{
let regex = regex::Regex::new(&format!("(?m){}", $right)).expect("should be a valid regex");
if !regex.is_match(&$left) {
::std::panic!(
r#"assertion failed: `(left matches right pattern)`
left (unescaped):
{}
left (escaped): `{:?}`
right: `{:?}`"#,
$left,
$left,
regex
)
}
}};

($left:expr, $right:expr, $($arg:tt)+) => {{
let regex = regex::Regex::new(&format!("(?m){}", $right)).expect("should be a valid regex");
if !regex.is_match(&$left) {
::std::panic!(
r#"assertion failed: `(left matches right pattern)`
left (unescaped):
{}
left (escaped): `{:?}`
right: `{:?}`: {}"#,
$left,
$left,
regex,
::core::format_args!($($arg)+)
)
}
}};
}

/// Asserts that `left` does not contain the `right` pattern (regular expression).
///
/// Commonly used when asserting `pack` output in integration tests. Expands to a regular
/// expression match test and logs `left` (in unescaped and escaped form) as well as `right`
/// on failure.
///
/// Multi-line mode is automatically enabled on regular expressions. If this is not what you
/// want it can be disabled by adding `(?-m)` to the start of your pattern.
///
/// # Example
///
/// ```
/// use libcnb_test::assert_not_contains_match;
///
/// let output = "Hello World!\nHello Integration Test!";
/// assert_not_contains_match!(output, "^Test!");
/// ```
#[macro_export]
macro_rules! assert_not_contains_match {
($left:expr, $right:expr $(,)?) => {{
let regex = regex::Regex::new(&format!("(?m){}", $right)).expect("should be a valid regex");
if regex.is_match(&$left) {
::std::panic!(
r#"assertion failed: `(left does not match right pattern)`
left (unescaped):
{}
left (escaped): `{:?}`
right: `{:?}`"#,
$left,
$left,
regex
)
}
}};

($left:expr, $right:expr, $($arg:tt)+) => {{
let regex = regex::Regex::new(&format!("(?m){}", $right)).expect("should be a valid regex");
if regex.is_match(&$left) {
::std::panic!(
r#"assertion failed: `(left does not match right pattern)`
left (unescaped):
{}
left (escaped): `{:?}`
right: `{:?}`: {}"#,
$left,
$left,
regex,
::core::format_args!($($arg)+)
)
}
}};
}

#[cfg(test)]
mod tests {
#[test]
Expand Down Expand Up @@ -332,4 +442,198 @@ value (escaped): `\"Hello World!\\nFoo\\nBar\\nBaz\"`: Greeting must be empty!")
fn empty_multiline_failure_with_args() {
assert_empty!("Hello World!\nFoo\nBar\nBaz", "Greeting must be empty!");
}

#[test]
fn contains_match_simple() {
assert_contains_match!("Hello World!", "(?i)hello world!");
}

#[test]
fn contains_match_simple_with_args() {
assert_contains_match!("Hello World!", "(?i)hello world!", "World must be greeted");
}

#[test]
#[should_panic(expected = "assertion failed: `(left matches right pattern)`
left (unescaped):
foo
left (escaped): `\"foo\"`
right: `Regex(\"(?m)bar\")`")]
fn contains_match_simple_failure() {
assert_contains_match!("foo", "bar");
}

#[test]
#[should_panic(expected = "assertion failed: `(left matches right pattern)`
left (unescaped):
Hello World!
left (escaped): `\"Hello World!\"`
right: `Regex(\"(?m)(?-i)world\")`: World must be case-sensitively greeted!")]
fn contains_match_simple_failure_with_args() {
assert_contains_match!(
"Hello World!",
"(?-i)world",
"World must be case-sensitively greeted!"
);
}

#[test]
fn contains_match_multiline() {
assert_contains_match!("Hello World!\nFoo\nBar\nBaz", "^Bar$");
}

#[test]
#[should_panic(expected = "assertion failed: `(left matches right pattern)`
left (unescaped):
Hello World!
Foo
Bar
Baz
left (escaped): `\"Hello World!\\nFoo\\nBar\\nBaz\"`
right: `Regex(\"(?m)Eggs\")`")]
fn contains_match_multiline_failure() {
assert_contains_match!("Hello World!\nFoo\nBar\nBaz", "Eggs");
}

#[test]
#[should_panic(expected = "assertion failed: `(left matches right pattern)`
left (unescaped):
Hello World!
Foo
Bar
Baz
left (escaped): `\"Hello World!\\nFoo\\nBar\\nBaz\"`
right: `Regex(\"(?m)Eggs\")`: We need eggs!")]
fn contains_match_multiline_failure_with_args() {
assert_contains_match!("Hello World!\nFoo\nBar\nBaz", "Eggs", "We need eggs!");
}

#[test]
#[should_panic(expected = "should be a valid regex: Syntax(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
regex parse error:
(?m)(unclosed group
^
error: unclosed group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)")]
fn contains_match_with_invalid_regex() {
assert_contains_match!("Hello World!", "(unclosed group");
}

#[test]
#[should_panic(expected = "should be a valid regex: Syntax(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
regex parse error:
(?m)(unclosed group
^
error: unclosed group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)")]
fn contains_match_with_invalid_regex_and_args() {
assert_contains_match!("Hello World!", "(unclosed group", "This should fail.");
}

#[test]
fn not_contains_match_simple() {
assert_not_contains_match!("Hello World!", "^World");
}

#[test]
fn not_contains_match_simple_with_args() {
assert_not_contains_match!("Hello World!", "^World", "World must not be at the start!");
}

#[test]
#[should_panic(expected = "assertion failed: `(left does not match right pattern)`
left (unescaped):
foobar
left (escaped): `\"foobar\"`
right: `Regex(\"(?m)bar\")`")]
fn not_contains_match_simple_failure() {
assert_not_contains_match!("foobar", "bar");
}

#[test]
#[should_panic(expected = "assertion failed: `(left does not match right pattern)`
left (unescaped):
Hello Germany!
left (escaped): `\"Hello Germany!\"`
right: `Regex(\"(?m)Germany!$\")`: Germany must not be greeted!")]
fn not_contains_match_simple_failure_with_args() {
assert_not_contains_match!(
"Hello Germany!",
"Germany!$",
"Germany must not be greeted!"
);
}

#[test]
fn not_contains_match_multiline() {
assert_not_contains_match!("Hello World!\nFoo\nBar\nBaz", "^Germany$");
}

#[test]
#[should_panic(expected = "assertion failed: `(left does not match right pattern)`
left (unescaped):
Hello World!
Foo
Bar
Baz
left (escaped): `\"Hello World!\\nFoo\\nBar\\nBaz\"`
right: `Regex(\"(?m)^Bar$\")`")]
fn not_contains_match_multiline_failure() {
assert_not_contains_match!("Hello World!\nFoo\nBar\nBaz", "^Bar$");
}

#[test]
#[should_panic(expected = "assertion failed: `(left does not match right pattern)`
left (unescaped):
Hello Eggs!
Foo
Bar
Baz
left (escaped): `\"Hello Eggs!\\nFoo\\nBar\\nBaz\"`
right: `Regex(\"(?m)Eggs!$\")`: We must not have eggs!")]
fn not_contains_match_multiline_failure_with_args() {
assert_not_contains_match!(
"Hello Eggs!\nFoo\nBar\nBaz",
"Eggs!$",
"We must not have eggs!"
);
}

#[test]
#[should_panic(expected = "should be a valid regex: Syntax(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
regex parse error:
(?m)(unclosed group
^
error: unclosed group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)")]
fn not_contains_match_with_invalid_regex() {
assert_not_contains_match!("Hello World!", "(unclosed group");
}

#[test]
#[should_panic(expected = "should be a valid regex: Syntax(
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
regex parse error:
(?m)(unclosed group
^
error: unclosed group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)")]
fn not_contains_match_with_invalid_regex_and_args() {
assert_not_contains_match!("Hello World!", "(unclosed group", "This will fail");
}
}

0 comments on commit 097bef0

Please sign in to comment.