diff --git a/Cargo.lock b/Cargo.lock index ee7b3349..2ea5f8bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,6 +380,22 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "console" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50aab2529019abfabfa93f1e6c41ef392f91fbf179b347a7e96abb524884a08" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi", + "winapi-util", +] + [[package]] name = "console" version = "0.15.8" @@ -496,12 +512,18 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ - "console", + "console 0.15.8", "shell-words", "thiserror", "zeroize", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -1155,6 +1177,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-structural-diff" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25c7940d3c84d2079306c176c7b2b37622b6bc5e43fbd1541b1e4a4e1fd02045" +dependencies = [ + "console 0.13.0", + "difflib", + "regex", + "serde_json", +] + [[package]] name = "kqueue" version = "1.0.8" @@ -1338,7 +1372,7 @@ dependencies = [ "chrono", "clap", "colored", - "console", + "console 0.15.8", "ctrlc", "dialoguer", "dissimilar", @@ -1351,6 +1385,7 @@ dependencies = [ "hyper-staticfile", "hyper-util", "indexmap", + "json-structural-diff", "line-index", "log", "mooncake", @@ -2264,6 +2299,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "test-log" version = "0.2.16" diff --git a/crates/moonbuild/Cargo.toml b/crates/moonbuild/Cargo.toml index d35a5603..9779c847 100644 --- a/crates/moonbuild/Cargo.toml +++ b/crates/moonbuild/Cargo.toml @@ -60,6 +60,7 @@ semver.workspace = true chrono.workspace = true zip.workspace = true thiserror.workspace = true +json-structural-diff = { version = "0.1.0", features = ["colorize"] } [dev-dependencies] expect-test.workspace = true diff --git a/crates/moonbuild/src/expect.rs b/crates/moonbuild/src/expect.rs index 7e1276d8..064d3c78 100644 --- a/crates/moonbuild/src/expect.rs +++ b/crates/moonbuild/src/expect.rs @@ -48,6 +48,7 @@ pub struct BufferExpect { expect: String, actual: String, kind: TargetKind, + mode: Option, } // something like array out of bounds, moonbit panic & abort catch by js @@ -81,6 +82,7 @@ pub struct Target { kind: TargetKind, expect: String, actual: String, + mode: Option, } #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] @@ -90,6 +92,7 @@ pub struct ExpectFailedRaw { pub expect: String, pub actual: String, pub snapshot: Option, + pub mode: Option, } pub fn expect_failed_to_snapshot_result(efr: ExpectFailedRaw) -> SnapshotResult { @@ -167,6 +170,8 @@ struct Replace { pub expect: String, pub expect_loc: Option, + + pub mode: Option, } impl Replace { @@ -183,6 +188,7 @@ impl Replace { kind: TargetKind::Trivial, expect: self.expect.clone(), actual: self.actual.clone(), + mode: self.mode.clone(), }) } else { let is_pipe = self.actual_loc.ahead(&self.loc); @@ -197,6 +203,7 @@ impl Replace { kind: TargetKind::Pipe, expect: self.expect.clone(), actual: self.actual.clone(), + mode: self.mode.clone(), }) } else { // TODO: find comma @@ -211,6 +218,7 @@ impl Replace { kind: TargetKind::Call, expect: self.expect.clone(), actual: self.actual.clone(), + mode: self.mode.clone(), }) } } @@ -246,6 +254,7 @@ fn parse_expect_failed_message(msg: &str) -> anyhow::Result { expect_loc, actual: j.actual, actual_loc, + mode: j.mode, }) } @@ -431,6 +440,7 @@ fn gen_patch(targets: HashMap>) -> anyhow::Result anyhow::Result<()> { } } - if !patch.actual.contains('\n') && !patch.actual.contains('"') { - output.push_str(&format!("{:?}", &patch.actual)); - } else { - let next_char = content_chars[usize::from(end)..].first(); - let prev_char = content_chars[..usize::from(start)].last(); - push_multi_line_string( - &mut output, - spaces + 2, - &patch.actual, - prev_char, - next_char, - ); + match patch.mode.as_deref() { + None => { + if !patch.actual.contains('\n') && !patch.actual.contains('"') { + output.push_str(&format!("{:?}", &patch.actual)); + } else { + let next_char = content_chars[usize::from(end)..].first(); + let prev_char = content_chars[..usize::from(start)].last(); + push_multi_line_string( + &mut output, + spaces + 2, + &patch.actual, + prev_char, + next_char, + ); + } + } + Some("json") => { + output.push_str(&patch.actual.to_string()); + } + Some(mode) => { + anyhow::bail!("unsupported mode: {:?} in expect testing", mode); + } } if let Some(padding) = patch.right_padding { @@ -639,6 +659,19 @@ pub fn render_expect_fail(msg: &str) -> anyhow::Result<()> { let json_str = &msg[EXPECT_FAILED.len()..]; let rep = parse_expect_failed_message(json_str)?; + if let Some("json") = rep.mode.as_deref() { + let j_expect = serde_json_lenient::from_str(&rep.expect)?; + let j_actual = serde_json_lenient::from_str(&rep.actual)?; + let diffs = json_structural_diff::JsonDiff::diff(&j_expect, &j_actual, false); + if let Some(diff) = diffs.diff { + let diffs = json_structural_diff::colorize(&diff, true); + println!("inspect failed at {}", rep.loc.raw); + println!("{}", "Diff:".bold()); + println!("{}", diffs); + } + return Ok(()); + } + let d = dissimilar::diff(&rep.expect, &rep.actual); println!( r#"expect test failed at {}