From 1dfd06fc8aadd5d621112716fe0e59aed5dfc767 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Thu, 6 Dec 2018 21:07:31 +0100 Subject: Use json comparison code from cargo for heavy tests --- crates/ra_lsp_server/tests/heavy_tests/main.rs | 10 ++- crates/ra_lsp_server/tests/heavy_tests/support.rs | 27 +++---- crates/ra_syntax/src/grammar/items/mod.rs | 2 +- crates/test_utils/Cargo.toml | 1 + crates/test_utils/src/lib.rs | 94 +++++++++++++++++++++++ 5 files changed, 116 insertions(+), 18 deletions(-) (limited to 'crates') diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs index cbc0c8844..63d53fb01 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/main.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs @@ -1,5 +1,7 @@ mod support; +use serde_json::json; + use ra_lsp_server::req::{Runnables, RunnablesParams}; use crate::support::project; @@ -21,7 +23,7 @@ fn foo() { text_document: server.doc_id("lib.rs"), position: None, }, - r#"[ + json!([ { "args": [ "test", "--", "foo", "--nocapture" ], "bin": "cargo", @@ -51,7 +53,7 @@ fn foo() { } } } - ]"#, + ]), ); } @@ -78,7 +80,7 @@ fn test_eggs() {} text_document: server.doc_id("tests/spam.rs"), position: None, }, - r#"[ + json!([ { "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ], "bin": "cargo", @@ -111,6 +113,6 @@ fn test_eggs() {} } } } - ]"# + ]) ); } diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs index 019048a3a..4b75be3ee 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/support.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs @@ -15,9 +15,9 @@ use languageserver_types::{ DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, }; use serde::Serialize; -use serde_json::{from_str, to_string_pretty, Value}; +use serde_json::{to_string_pretty, Value}; use tempdir::TempDir; -use test_utils::parse_fixture; +use test_utils::{parse_fixture, find_mismatch}; use ra_lsp_server::{ main_loop, req, @@ -88,23 +88,24 @@ impl Server { } } - pub fn request(&self, params: R::Params, expected_resp: &str) + pub fn request(&self, params: R::Params, expected_resp: Value) where R: Request, R::Params: Serialize, { let id = self.req_id.get(); self.req_id.set(id + 1); - let expected_resp: Value = from_str(expected_resp).unwrap(); let actual = self.send_request::(id, params); - assert_eq!( - expected_resp, - actual, - "Expected:\n{}\n\ - Actual:\n{}\n", - to_string_pretty(&expected_resp).unwrap(), - to_string_pretty(&actual).unwrap(), - ); + match find_mismatch(&expected_resp, &actual) { + Some((expected_part, actual_part)) => panic!( + "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n", + to_string_pretty(&expected_resp).unwrap(), + to_string_pretty(&actual).unwrap(), + to_string_pretty(expected_part).unwrap(), + to_string_pretty(actual_part).unwrap(), + ), + None => {} + } } fn send_request(&self, id: u64, params: R::Params) -> Value @@ -139,7 +140,7 @@ impl Server { pub fn wait_for_feedback_n(&self, feedback: &str, n: usize) { let f = |msg: &RawMessage| match msg { RawMessage::Notification(n) if n.method == "internalFeedback" => { - return n.clone().cast::().unwrap() == feedback + return n.clone().cast::().unwrap() == feedback; } _ => false, }; diff --git a/crates/ra_syntax/src/grammar/items/mod.rs b/crates/ra_syntax/src/grammar/items/mod.rs index 682266908..4473c2fab 100644 --- a/crates/ra_syntax/src/grammar/items/mod.rs +++ b/crates/ra_syntax/src/grammar/items/mod.rs @@ -159,7 +159,7 @@ pub(super) fn maybe_item(p: &mut Parser, flavor: ItemFlavor) -> MaybeItem { MaybeItem::Modifiers } else { MaybeItem::None - } + }; } }; 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 "] difference = "2.0.0" itertools = "0.7.8" text_unit = "0.1.2" +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; use itertools::Itertools; use text_unit::{TextRange, TextUnit}; +use serde_json::Value; pub use difference::Changeset as __Changeset; @@ -145,3 +146,96 @@ pub fn parse_fixture(fixture: &str) -> Vec { flush!(); res } + +// Comparison functionality borrowed from cargo: + +/// Compare a line with an expected pattern. +/// - Use `[..]` as a wildcard to match 0 or more characters on the same line +/// (similar to `.*` in a regex). +pub fn lines_match(expected: &str, actual: &str) -> bool { + // Let's not deal with / vs \ (windows...) + // First replace backslash-escaped backslashes with forward slashes + // which can occur in, for example, JSON output + let expected = expected.replace("\\\\", "/").replace("\\", "/"); + let mut actual: &str = &actual.replace("\\\\", "/").replace("\\", "/"); + for (i, part) in expected.split("[..]").enumerate() { + match actual.find(part) { + Some(j) => { + if i == 0 && j != 0 { + return false; + } + actual = &actual[j + part.len()..]; + } + None => return false, + } + } + actual.is_empty() || expected.ends_with("[..]") +} + +#[test] +fn lines_match_works() { + assert!(lines_match("a b", "a b")); + assert!(lines_match("a[..]b", "a b")); + assert!(lines_match("a[..]", "a b")); + assert!(lines_match("[..]", "a b")); + assert!(lines_match("[..]b", "a b")); + + assert!(!lines_match("[..]b", "c")); + assert!(!lines_match("b", "c")); + assert!(!lines_match("b", "cb")); +} + +// Compares JSON object for approximate equality. +// You can use `[..]` wildcard in strings (useful for OS dependent things such +// as paths). You can use a `"{...}"` string literal as a wildcard for +// arbitrary nested JSON (useful for parts of object emitted by other programs +// (e.g. rustc) rather than Cargo itself). Arrays are sorted before comparison. +pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> { + use serde_json::Value::*; + match (expected, actual) { + (&Number(ref l), &Number(ref r)) if l == r => None, + (&Bool(l), &Bool(r)) if l == r => None, + (&String(ref l), &String(ref r)) if lines_match(l, r) => None, + (&Array(ref l), &Array(ref r)) => { + if l.len() != r.len() { + return Some((expected, actual)); + } + + let mut l = l.iter().collect::>(); + let mut r = r.iter().collect::>(); + + l.retain( + |l| match r.iter().position(|r| find_mismatch(l, r).is_none()) { + Some(i) => { + r.remove(i); + false + } + None => true, + }, + ); + + if !l.is_empty() { + assert!(!r.is_empty()); + Some((&l[0], &r[0])) + } else { + assert_eq!(r.len(), 0); + None + } + } + (&Object(ref l), &Object(ref r)) => { + let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k)); + if !same_keys { + return Some((expected, actual)); + } + + l.values() + .zip(r.values()) + .filter_map(|(l, r)| find_mismatch(l, r)) + .nth(0) + } + (&Null, &Null) => None, + // magic string literal "{...}" acts as wildcard for any sub-JSON + (&String(ref l), _) if l == "{...}" => None, + _ => Some((expected, actual)), + } +} -- cgit v1.2.3