aboutsummaryrefslogtreecommitdiff
path: root/crates/test_utils
diff options
context:
space:
mode:
Diffstat (limited to 'crates/test_utils')
-rw-r--r--crates/test_utils/Cargo.toml1
-rw-r--r--crates/test_utils/src/lib.rs94
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]>"]
8difference = "2.0.0" 8difference = "2.0.0"
9itertools = "0.7.8" 9itertools = "0.7.8"
10text_unit = "0.1.2" 10text_unit = "0.1.2"
11serde_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
3use itertools::Itertools; 3use itertools::Itertools;
4use text_unit::{TextRange, TextUnit}; 4use text_unit::{TextRange, TextUnit};
5use serde_json::Value;
5 6
6pub use difference::Changeset as __Changeset; 7pub 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).
155pub 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]
176fn 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.
193pub 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}