Skip to main content

googletest/matchers/
matches_regex_matcher.rs

1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::description::Description;
16use crate::matcher::{Matcher, MatcherBase, MatcherResult};
17use regex::Regex;
18use std::fmt::Debug;
19use std::ops::Deref;
20
21/// Matches a string the entirety of which which matches the given regular
22/// expression.
23///
24/// This is similar to [`contains_regex`][crate::matchers::contains_regex],
25/// except that the match must cover the whole string and not a substring.
26///
27/// Both the actual value and the expected regular expression may be either a
28/// `String` or a string reference.
29///
30/// ```
31/// # use googletest::prelude::*;
32/// # fn should_pass_1() -> Result<()> {
33/// verify_that!("Some value", matches_regex("S.*e"))?;  // Passes
34/// #     Ok(())
35/// # }
36/// # fn should_fail_1() -> Result<()> {
37/// verify_that!("Another value", matches_regex("Some"))?;   // Fails
38/// #     Ok(())
39/// # }
40/// # fn should_fail_2() -> Result<()> {
41/// verify_that!("Some value", matches_regex("Some"))?;   // Fails
42/// #     Ok(())
43/// # }
44/// # fn should_pass_2() -> Result<()> {
45/// verify_that!("Some value".to_string(), matches_regex(".*v.*e"))?;   // Passes
46/// verify_that!("Some value", matches_regex(".*v.*e".to_string()))?;   // Passes
47/// #     Ok(())
48/// # }
49/// # should_pass_1().unwrap();
50/// # should_fail_1().unwrap_err();
51/// # should_fail_2().unwrap_err();
52/// # should_pass_2().unwrap();
53/// ```
54///
55/// Panics if the given `pattern` is not a syntactically valid regular
56/// expression.
57// N.B. This returns the concrete type rather than an impl Matcher so that it
58// can act simultaneously as a Matcher<str> and a Matcher<String>. Otherwise the
59// compiler treats it as a Matcher<str> only and the code
60//   verify_that!("Some value".to_string(), matches_regex(".*value"))?;
61// doesn't compile.
62pub fn matches_regex<PatternT: Deref<Target = str>>(
63    pattern: PatternT,
64) -> MatchesRegexMatcher<PatternT> {
65    let adjusted_pattern = format!("^{}$", pattern.deref());
66    let regex = Regex::new(adjusted_pattern.as_str()).unwrap();
67    MatchesRegexMatcher { regex, pattern, _adjusted_pattern: adjusted_pattern }
68}
69
70/// A matcher matching a string-like type matching a given regular expression.
71///
72/// Intended only to be used from the function [`matches_regex`] only.
73/// Should not be referenced by code outside this library.
74#[derive(MatcherBase)]
75pub struct MatchesRegexMatcher<PatternT: Deref<Target = str>> {
76    regex: Regex,
77    pattern: PatternT,
78    _adjusted_pattern: String,
79}
80
81impl<PatternT, ActualT> Matcher<ActualT> for MatchesRegexMatcher<PatternT>
82where
83    PatternT: Deref<Target = str>,
84    ActualT: AsRef<str> + Debug + Copy,
85{
86    fn matches(&self, actual: ActualT) -> MatcherResult {
87        self.regex.is_match(actual.as_ref()).into()
88    }
89
90    fn describe(&self, matcher_result: MatcherResult) -> Description {
91        match matcher_result {
92            MatcherResult::Match => {
93                format!("matches the regular expression {:#?}", self.pattern.deref()).into()
94            }
95            MatcherResult::NoMatch => {
96                format!("doesn't match the regular expression {:#?}", self.pattern.deref()).into()
97            }
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use crate::matcher::MatcherResult;
105    use crate::prelude::*;
106    use crate::Result;
107
108    #[test]
109    fn matches_regex_matches_string_reference_with_pattern() -> Result<()> {
110        let matcher = matches_regex("S.*e");
111
112        let result = matcher.matches("Some value");
113
114        verify_that!(result, eq(MatcherResult::Match))
115    }
116
117    #[test]
118    fn matches_regex_does_not_match_string_without_pattern() -> Result<()> {
119        let matcher = matches_regex("Another");
120
121        let result = matcher.matches("Some value");
122
123        verify_that!(result, eq(MatcherResult::NoMatch))
124    }
125
126    #[test]
127    fn matches_regex_does_not_match_string_only_beginning_of_which_matches() -> Result<()> {
128        let matcher = matches_regex("Some");
129
130        let result = matcher.matches("Some value");
131
132        verify_that!(result, eq(MatcherResult::NoMatch))
133    }
134
135    #[test]
136    fn matches_regex_does_not_match_string_only_end_of_which_matches() -> Result<()> {
137        let matcher = matches_regex("value");
138
139        let result = matcher.matches("Some value");
140
141        verify_that!(result, eq(MatcherResult::NoMatch))
142    }
143
144    #[test]
145    fn matches_regex_matches_owned_string_with_pattern() -> Result<()> {
146        let matcher = matches_regex(".*value");
147
148        let result = matcher.matches(&"Some value".to_string());
149
150        verify_that!(result, eq(MatcherResult::Match))
151    }
152
153    #[test]
154    fn matches_regex_matches_string_when_regex_has_beginning_of_string_marker() -> Result<()> {
155        let matcher = matches_regex("^Some value");
156
157        let result = matcher.matches("Some value");
158
159        verify_that!(result, eq(MatcherResult::Match))
160    }
161
162    #[test]
163    fn matches_regex_matches_string_when_regex_has_end_of_string_marker() -> Result<()> {
164        let matcher = matches_regex("Some value$");
165
166        let result = matcher.matches("Some value");
167
168        verify_that!(result, eq(MatcherResult::Match))
169    }
170
171    #[test]
172    fn matches_regex_matches_string_when_regex_has_both_end_markers() -> Result<()> {
173        let matcher = matches_regex("^Some value$");
174
175        let result = matcher.matches("Some value");
176
177        verify_that!(result, eq(MatcherResult::Match))
178    }
179
180    #[test]
181    fn matches_regex_matches_string_reference_with_owned_string() -> Result<()> {
182        let matcher = matches_regex(".*value".to_string());
183
184        let result = matcher.matches("Some value");
185
186        verify_that!(result, eq(MatcherResult::Match))
187    }
188
189    #[test]
190    fn verify_that_works_with_owned_string() -> Result<()> {
191        verify_that!("Some value".to_string(), matches_regex(".*value"))
192    }
193
194    #[test]
195    fn matches_regex_displays_quoted_debug_of_pattern() -> Result<()> {
196        let matcher = matches_regex("\n");
197
198        verify_that!(
199            Matcher::<&str>::describe(&matcher, MatcherResult::Match),
200            displays_as(eq("matches the regular expression \"\\n\""))
201        )
202    }
203}