// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::description::Description; use crate::matcher::{Matcher, MatcherResult}; use regex::Regex; use std::fmt::Debug; use std::marker::PhantomData; use std::ops::Deref; /// Matches a string the entirety of which which matches the given regular /// expression. /// /// This is similar to [`contains_regex`][crate::matchers::contains_regex], /// except that the match must cover the whole string and not a substring. /// /// Both the actual value and the expected regular expression may be either a /// `String` or a string reference. /// /// ``` /// # use googletest::prelude::*; /// # fn should_pass_1() -> Result<()> { /// verify_that!("Some value", matches_regex("S.*e"))?; // Passes /// # Ok(()) /// # } /// # fn should_fail_1() -> Result<()> { /// verify_that!("Another value", matches_regex("Some"))?; // Fails /// # Ok(()) /// # } /// # fn should_fail_2() -> Result<()> { /// verify_that!("Some value", matches_regex("Some"))?; // Fails /// # Ok(()) /// # } /// # fn should_pass_2() -> Result<()> { /// verify_that!("Some value".to_string(), matches_regex(".*v.*e"))?; // Passes /// verify_that!("Some value", matches_regex(".*v.*e".to_string()))?; // Passes /// # Ok(()) /// # } /// # should_pass_1().unwrap(); /// # should_fail_1().unwrap_err(); /// # should_fail_2().unwrap_err(); /// # should_pass_2().unwrap(); /// ``` /// /// Panics if the given `pattern` is not a syntactically valid regular /// expression. // N.B. This returns the concrete type rather than an impl Matcher so that it // can act simultaneously as a Matcher and a Matcher. Otherwise the // compiler treats it as a Matcher only and the code // verify_that!("Some value".to_string(), matches_regex(".*value"))?; // doesn't compile. pub fn matches_regex>( pattern: PatternT, ) -> MatchesRegexMatcher { let adjusted_pattern = format!("^{}$", pattern.deref()); let regex = Regex::new(adjusted_pattern.as_str()).unwrap(); MatchesRegexMatcher { regex, pattern, _adjusted_pattern: adjusted_pattern, phantom: Default::default(), } } /// A matcher matching a string-like type matching a given regular expression. /// /// Intended only to be used from the function [`matches_regex`] only. /// Should not be referenced by code outside this library. pub struct MatchesRegexMatcher> { regex: Regex, pattern: PatternT, _adjusted_pattern: String, phantom: PhantomData, } impl Matcher for MatchesRegexMatcher where PatternT: Deref, ActualT: AsRef + Debug + ?Sized, { type ActualT = ActualT; fn matches(&self, actual: &Self::ActualT) -> MatcherResult { self.regex.is_match(actual.as_ref()).into() } fn describe(&self, matcher_result: MatcherResult) -> Description { match matcher_result { MatcherResult::Match => { format!("matches the regular expression {:#?}", self.pattern.deref()).into() } MatcherResult::NoMatch => { format!("doesn't match the regular expression {:#?}", self.pattern.deref()).into() } } } } #[cfg(test)] mod tests { use super::{matches_regex, MatchesRegexMatcher}; use crate::matcher::{Matcher, MatcherResult}; use crate::prelude::*; #[test] fn matches_regex_matches_string_reference_with_pattern() -> Result<()> { let matcher = matches_regex("S.*e"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::Match)) } #[test] fn matches_regex_does_not_match_string_without_pattern() -> Result<()> { let matcher = matches_regex("Another"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::NoMatch)) } #[test] fn matches_regex_does_not_match_string_only_beginning_of_which_matches() -> Result<()> { let matcher = matches_regex("Some"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::NoMatch)) } #[test] fn matches_regex_does_not_match_string_only_end_of_which_matches() -> Result<()> { let matcher = matches_regex("value"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::NoMatch)) } #[test] fn matches_regex_matches_owned_string_with_pattern() -> Result<()> { let matcher = matches_regex(".*value"); let result = matcher.matches(&"Some value".to_string()); verify_that!(result, eq(MatcherResult::Match)) } #[test] fn matches_regex_matches_string_when_regex_has_beginning_of_string_marker() -> Result<()> { let matcher = matches_regex("^Some value"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::Match)) } #[test] fn matches_regex_matches_string_when_regex_has_end_of_string_marker() -> Result<()> { let matcher = matches_regex("Some value$"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::Match)) } #[test] fn matches_regex_matches_string_when_regex_has_both_end_markers() -> Result<()> { let matcher = matches_regex("^Some value$"); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::Match)) } #[test] fn matches_regex_matches_string_reference_with_owned_string() -> Result<()> { let matcher = matches_regex(".*value".to_string()); let result = matcher.matches("Some value"); verify_that!(result, eq(MatcherResult::Match)) } #[test] fn verify_that_works_with_owned_string() -> Result<()> { verify_that!("Some value".to_string(), matches_regex(".*value")) } #[test] fn matches_regex_displays_quoted_debug_of_pattern() -> Result<()> { let matcher: MatchesRegexMatcher<&str, _> = matches_regex("\n"); verify_that!( Matcher::describe(&matcher, MatcherResult::Match), displays_as(eq("matches the regular expression \"\\n\"")) ) } }