diff options
Diffstat (limited to 'crates/test_utils')
-rw-r--r-- | crates/test_utils/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 94 |
2 files changed, 95 insertions, 0 deletions
diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index fe0998ab8..8c8fcd7ae 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml | |||
@@ -8,3 +8,4 @@ authors = ["Aleksey Kladov <[email protected]>"] | |||
8 | difference = "2.0.0" | 8 | difference = "2.0.0" |
9 | itertools = "0.7.8" | 9 | itertools = "0.7.8" |
10 | text_unit = "0.1.2" | 10 | text_unit = "0.1.2" |
11 | serde_json = "1.0.24" | ||
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index e72ec9c47..0a94adb74 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs | |||
@@ -2,6 +2,7 @@ use std::fmt; | |||
2 | 2 | ||
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | use text_unit::{TextRange, TextUnit}; | 4 | use text_unit::{TextRange, TextUnit}; |
5 | use serde_json::Value; | ||
5 | 6 | ||
6 | pub use difference::Changeset as __Changeset; | 7 | pub use difference::Changeset as __Changeset; |
7 | 8 | ||
@@ -145,3 +146,96 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { | |||
145 | flush!(); | 146 | flush!(); |
146 | res | 147 | res |
147 | } | 148 | } |
149 | |||
150 | // Comparison functionality borrowed from cargo: | ||
151 | |||
152 | /// Compare a line with an expected pattern. | ||
153 | /// - Use `[..]` as a wildcard to match 0 or more characters on the same line | ||
154 | /// (similar to `.*` in a regex). | ||
155 | pub fn lines_match(expected: &str, actual: &str) -> bool { | ||
156 | // Let's not deal with / vs \ (windows...) | ||
157 | // First replace backslash-escaped backslashes with forward slashes | ||
158 | // which can occur in, for example, JSON output | ||
159 | let expected = expected.replace("\\\\", "/").replace("\\", "/"); | ||
160 | let mut actual: &str = &actual.replace("\\\\", "/").replace("\\", "/"); | ||
161 | for (i, part) in expected.split("[..]").enumerate() { | ||
162 | match actual.find(part) { | ||
163 | Some(j) => { | ||
164 | if i == 0 && j != 0 { | ||
165 | return false; | ||
166 | } | ||
167 | actual = &actual[j + part.len()..]; | ||
168 | } | ||
169 | None => return false, | ||
170 | } | ||
171 | } | ||
172 | actual.is_empty() || expected.ends_with("[..]") | ||
173 | } | ||
174 | |||
175 | #[test] | ||
176 | fn lines_match_works() { | ||
177 | assert!(lines_match("a b", "a b")); | ||
178 | assert!(lines_match("a[..]b", "a b")); | ||
179 | assert!(lines_match("a[..]", "a b")); | ||
180 | assert!(lines_match("[..]", "a b")); | ||
181 | assert!(lines_match("[..]b", "a b")); | ||
182 | |||
183 | assert!(!lines_match("[..]b", "c")); | ||
184 | assert!(!lines_match("b", "c")); | ||
185 | assert!(!lines_match("b", "cb")); | ||
186 | } | ||
187 | |||
188 | // Compares JSON object for approximate equality. | ||
189 | // You can use `[..]` wildcard in strings (useful for OS dependent things such | ||
190 | // as paths). You can use a `"{...}"` string literal as a wildcard for | ||
191 | // arbitrary nested JSON (useful for parts of object emitted by other programs | ||
192 | // (e.g. rustc) rather than Cargo itself). Arrays are sorted before comparison. | ||
193 | pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> { | ||
194 | use serde_json::Value::*; | ||
195 | match (expected, actual) { | ||
196 | (&Number(ref l), &Number(ref r)) if l == r => None, | ||
197 | (&Bool(l), &Bool(r)) if l == r => None, | ||
198 | (&String(ref l), &String(ref r)) if lines_match(l, r) => None, | ||
199 | (&Array(ref l), &Array(ref r)) => { | ||
200 | if l.len() != r.len() { | ||
201 | return Some((expected, actual)); | ||
202 | } | ||
203 | |||
204 | let mut l = l.iter().collect::<Vec<_>>(); | ||
205 | let mut r = r.iter().collect::<Vec<_>>(); | ||
206 | |||
207 | l.retain( | ||
208 | |l| match r.iter().position(|r| find_mismatch(l, r).is_none()) { | ||
209 | Some(i) => { | ||
210 | r.remove(i); | ||
211 | false | ||
212 | } | ||
213 | None => true, | ||
214 | }, | ||
215 | ); | ||
216 | |||
217 | if !l.is_empty() { | ||
218 | assert!(!r.is_empty()); | ||
219 | Some((&l[0], &r[0])) | ||
220 | } else { | ||
221 | assert_eq!(r.len(), 0); | ||
222 | None | ||
223 | } | ||
224 | } | ||
225 | (&Object(ref l), &Object(ref r)) => { | ||
226 | let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k)); | ||
227 | if !same_keys { | ||
228 | return Some((expected, actual)); | ||
229 | } | ||
230 | |||
231 | l.values() | ||
232 | .zip(r.values()) | ||
233 | .filter_map(|(l, r)| find_mismatch(l, r)) | ||
234 | .nth(0) | ||
235 | } | ||
236 | (&Null, &Null) => None, | ||
237 | // magic string literal "{...}" acts as wildcard for any sub-JSON | ||
238 | (&String(ref l), _) if l == "{...}" => None, | ||
239 | _ => Some((expected, actual)), | ||
240 | } | ||
241 | } | ||