diff --git a/Cargo.lock b/Cargo.lock index bd5245b8..397fbc6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,12 +97,33 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "bstr" version = "1.9.1" @@ -253,6 +274,29 @@ dependencies = [ "rustversion", ] +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "globset" version = "0.4.14" @@ -414,6 +458,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libtest-mimic" version = "0.7.3" @@ -432,6 +482,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.21" @@ -457,6 +513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -475,6 +532,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.82" @@ -484,6 +547,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.36" @@ -493,6 +582,45 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + [[package]] name = "regex" version = "1.10.4" @@ -534,20 +662,45 @@ version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.8", "windows-sys 0.48.0", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.4.14", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.18" @@ -670,6 +823,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix 0.38.34", + "windows-sys 0.52.0", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -685,7 +850,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ - "rustix", + "rustix 0.37.27", "windows-sys 0.48.0", ] @@ -792,6 +957,7 @@ dependencies = [ "indexmap", "kstring", "libtest-mimic", + "proptest", "serde", "serde_json", "serde_spanned", @@ -816,6 +982,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -828,6 +1000,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -838,6 +1019,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi-util" version = "0.1.8" diff --git a/crates/toml/tests/testsuite/display.rs b/crates/toml/tests/testsuite/display.rs index dddb4e04..a0e0cff2 100644 --- a/crates/toml/tests/testsuite/display.rs +++ b/crates/toml/tests/testsuite/display.rs @@ -1,9 +1,4 @@ -use serde::Deserialize; -use snapbox::assert_data_eq; -use snapbox::prelude::*; -use snapbox::str; use toml::map::Map; -use toml::Value; use toml::Value::{Array, Boolean, Float, Integer, String, Table}; macro_rules! map( ($($k:expr => $v:expr),*) => ({ @@ -119,64 +114,3 @@ fn table() { test2 = 2\n" ); } - -#[test] -fn string_roundtrip() { - assert_string_round_trip(r#""""#, str![[r#""""#]]); - assert_string_round_trip(r#""a""#, str![[r#""a""#]]); - - assert_string_round_trip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]); - assert_string_round_trip( - r#""lf \n lf""#, - str![[r#" -""" -lf - lf""" -"#]], - ); - assert_string_round_trip( - r#""crlf \r\n crlf""#, - str![[r#" -""" -crlf /r - crlf""" -"#]], - ); - assert_string_round_trip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]); - assert_string_round_trip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]); - assert_string_round_trip( - r#""backslash \\ backslash""#, - str!["'backslash / backslash'"], - ); - - assert_string_round_trip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]); - assert_string_round_trip( - r#""triple squote ''' triple squote""#, - str![[r#""triple squote ''' triple squote""#]], - ); - assert_string_round_trip(r#""end squote '""#, str![[r#""end squote '""#]]); - - assert_string_round_trip(r#""quote \" quote""#, str![[r#"'quote " quote'"#]]); - assert_string_round_trip( - r#""triple quote \"\"\" triple quote""#, - str![[r#"'triple quote """ triple quote'"#]], - ); - assert_string_round_trip(r#""end quote \"""#, str![[r#"'end quote "'"#]]); -} - -#[track_caller] -fn assert_string_round_trip(input: &str, expected: impl IntoData) { - let value = Value::deserialize(toml::de::ValueDeserializer::new(input)).unwrap(); - let actual = value.to_string(); - let _ = Value::deserialize(toml::de::ValueDeserializer::new(input)).unwrap_or_else(|_err| { - panic!( - "invalid value: -``` -{actual} -``` -" - ) - }); - let expected = expected.into_data(); - assert_data_eq!(actual, expected); -} diff --git a/crates/toml_edit/Cargo.toml b/crates/toml_edit/Cargo.toml index 59352b6a..1049ef83 100644 --- a/crates/toml_edit/Cargo.toml +++ b/crates/toml_edit/Cargo.toml @@ -53,6 +53,7 @@ toml-test-harness = "0.4.8" toml-test-data = "1.11.0" libtest-mimic = "0.7.2" snapbox = "0.6.0" +proptest = "1.5.0" [[test]] name = "testsuite" diff --git a/crates/toml_edit/src/encode.rs b/crates/toml_edit/src/encode.rs index b6142a6c..b45f0e34 100644 --- a/crates/toml_edit/src/encode.rs +++ b/crates/toml_edit/src/encode.rs @@ -332,12 +332,7 @@ pub(crate) fn to_string_repr( style: Option, literal: Option, ) -> Repr { - let (style, literal) = match (style, literal) { - (Some(style), Some(literal)) => (style, literal), - (_, Some(literal)) => (infer_style(value).0, literal), - (Some(style), _) => (style, infer_style(value).1), - (_, _) => infer_style(value), - }; + let (style, literal) = infer_style(value, style, literal); let mut output = String::with_capacity(value.len() * 2); if literal { @@ -415,7 +410,38 @@ impl StringStyle { } } -fn infer_style(value: &str) -> (StringStyle, bool) { +fn infer_style( + value: &str, + style: Option, + literal: Option, +) -> (StringStyle, bool) { + match (style, literal) { + (Some(style), Some(literal)) => (style, literal), + (None, Some(literal)) => (infer_all_style(value).0, literal), + (Some(style), None) => { + let literal = infer_literal(value); + (style, literal) + } + (None, None) => infer_all_style(value), + } +} + +fn infer_literal(value: &str) -> bool { + #[cfg(feature = "parse")] + { + use winnow::stream::ContainsToken as _; + (value.contains('"') | value.contains('\\')) + && value + .chars() + .all(|c| crate::parser::strings::LITERAL_CHAR.contains_token(c)) + } + #[cfg(not(feature = "parse"))] + { + false + } +} + +fn infer_all_style(value: &str) -> (StringStyle, bool) { // We need to determine: // - if we are a "multi-line" pretty (if there are \n) // - if ['''] appears if multi or ['] if single @@ -527,3 +553,45 @@ impl ValueRepr for Datetime { Repr::new_unchecked(self.to_string()) } } + +#[cfg(test)] +mod test { + use super::*; + use proptest::prelude::*; + + proptest! { + #[test] + #[cfg(feature = "parse")] + fn parseable_string(string in "\\PC*") { + let string = Value::from(string); + let encoded = string.to_string(); + let _: Value = encoded.parse().unwrap_or_else(|err| { + panic!("error: {err} + +string: +``` +{string} +``` +") + }); + } + } + + proptest! { + #[test] + #[cfg(feature = "parse")] + fn parseable_key(string in "\\PC*") { + let string = Key::new(string); + let encoded = string.to_string(); + let _: Key = encoded.parse().unwrap_or_else(|err| { + panic!("error: {err} + +string: +``` +{string} +``` +") + }); + } + } +} diff --git a/crates/toml_edit/tests/testsuite/parse.rs b/crates/toml_edit/tests/testsuite/parse.rs index 7f40e7ed..62b33bdd 100644 --- a/crates/toml_edit/tests/testsuite/parse.rs +++ b/crates/toml_edit/tests/testsuite/parse.rs @@ -1607,42 +1607,247 @@ clippy.exhaustive_enums = "warn" } #[test] -fn string_roundtrip() { - assert_string_round_trip(r#""""#, str![[r#""""#]]); - assert_string_round_trip(r#""a""#, str![[r#""a""#]]); - - assert_string_round_trip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]); - assert_string_round_trip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]); - assert_string_round_trip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]); - assert_string_round_trip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]); - assert_string_round_trip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]); - assert_string_round_trip( +fn string_repr_roundtrip() { + assert_string_repr_roundtrip(r#""""#, str![[r#""""#]]); + assert_string_repr_roundtrip(r#""a""#, str![[r#""a""#]]); + + assert_string_repr_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]); + assert_string_repr_roundtrip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]); + assert_string_repr_roundtrip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]); + assert_string_repr_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]); + assert_string_repr_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]); + assert_string_repr_roundtrip( r#""backslash \\ backslash""#, str![[r#""backslash // backslash""#]], ); - assert_string_round_trip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]); - assert_string_round_trip( + assert_string_repr_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]); + assert_string_repr_roundtrip( r#""triple squote ''' triple squote""#, str![[r#""triple squote ''' triple squote""#]], ); - assert_string_round_trip(r#""end squote '""#, str![[r#""end squote '""#]]); + assert_string_repr_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]); - assert_string_round_trip(r#""quote \" quote""#, str![[r#""quote /" quote""#]]); - assert_string_round_trip( + assert_string_repr_roundtrip(r#""quote \" quote""#, str![[r#""quote /" quote""#]]); + assert_string_repr_roundtrip( r#""triple quote \"\"\" triple quote""#, str![[r#""triple quote /"/"/" triple quote""#]], ); - assert_string_round_trip(r#""end quote \"""#, str![[r#""end quote /"""#]]); + assert_string_repr_roundtrip(r#""end quote \"""#, str![[r#""end quote /"""#]]); + assert_string_repr_roundtrip( + r#""quoted \"content\" quoted""#, + str![[r#""quoted /"content/" quoted""#]], + ); + assert_string_repr_roundtrip( + r#""squoted 'content' squoted""#, + str![[r#""squoted 'content' squoted""#]], + ); + assert_string_repr_roundtrip( + r#""mixed quoted \"start\" 'end'' mixed quote""#, + str![[r#""mixed quoted /"start/" 'end'' mixed quote""#]], + ); } #[track_caller] -fn assert_string_round_trip(input: &str, expected: impl IntoData) { +fn assert_string_repr_roundtrip(input: &str, expected: impl IntoData) { let value: Value = input.parse().unwrap(); let actual = value.to_string(); let _: Value = actual.parse().unwrap_or_else(|_err| { panic!( - "invalid document: + "invalid `Value`: +``` +{actual} +``` +" + ) + }); + let expected = expected.into_data(); + assert_data_eq!(actual, expected); +} + +#[test] +fn string_value_roundtrip() { + assert_string_value_roundtrip(r#""""#, str![[r#""""#]]); + assert_string_value_roundtrip(r#""a""#, str![[r#""a""#]]); + + assert_string_value_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]); + assert_string_value_roundtrip( + r#""lf \n lf""#, + str![[r#" +""" +lf + lf""" +"#]], + ); + assert_string_value_roundtrip( + r#""crlf \r\n crlf""#, + str![[r#" +""" +crlf /r + crlf""" +"#]], + ); + assert_string_value_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]); + assert_string_value_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]); + assert_string_value_roundtrip( + r#""backslash \\ backslash""#, + str!["'backslash / backslash'"], + ); + + assert_string_value_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]); + assert_string_value_roundtrip( + r#""triple squote ''' triple squote""#, + str![[r#""triple squote ''' triple squote""#]], + ); + assert_string_value_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]); + + assert_string_value_roundtrip(r#""quote \" quote""#, str![[r#"'quote " quote'"#]]); + assert_string_value_roundtrip( + r#""triple quote \"\"\" triple quote""#, + str![[r#"'triple quote """ triple quote'"#]], + ); + assert_string_value_roundtrip(r#""end quote \"""#, str![[r#"'end quote "'"#]]); + assert_string_value_roundtrip( + r#""quoted \"content\" quoted""#, + str![[r#"'quoted "content" quoted'"#]], + ); + assert_string_value_roundtrip( + r#""squoted 'content' squoted""#, + str![[r#""squoted 'content' squoted""#]], + ); + assert_string_value_roundtrip( + r#""mixed quoted \"start\" 'end'' mixed quote""#, + str![[r#"'''mixed quoted "start" 'end'' mixed quote'''"#]], + ); +} + +#[track_caller] +fn assert_string_value_roundtrip(input: &str, expected: impl IntoData) { + let value: Value = input.parse().unwrap(); + let value = Value::from(value.as_str().unwrap()); // Remove repr + let actual = value.to_string(); + let _: Value = actual.parse().unwrap_or_else(|_err| { + panic!( + "invalid `Value`: +``` +{actual} +``` +" + ) + }); + let expected = expected.into_data(); + assert_data_eq!(actual, expected); +} + +#[test] +fn key_repr_roundtrip() { + assert_key_repr_roundtrip(r#""""#, str![[r#""""#]]); + assert_key_repr_roundtrip(r#""a""#, str![[r#""a""#]]); + + assert_key_repr_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]); + assert_key_repr_roundtrip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]); + assert_key_repr_roundtrip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]); + assert_key_repr_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]); + assert_key_repr_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]); + assert_key_repr_roundtrip( + r#""backslash \\ backslash""#, + str![[r#""backslash // backslash""#]], + ); + + assert_key_repr_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]); + assert_key_repr_roundtrip( + r#""triple squote ''' triple squote""#, + str![[r#""triple squote ''' triple squote""#]], + ); + assert_key_repr_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]); + + assert_key_repr_roundtrip(r#""quote \" quote""#, str![[r#""quote /" quote""#]]); + assert_key_repr_roundtrip( + r#""triple quote \"\"\" triple quote""#, + str![[r#""triple quote /"/"/" triple quote""#]], + ); + assert_key_repr_roundtrip(r#""end quote \"""#, str![[r#""end quote /"""#]]); + assert_key_repr_roundtrip( + r#""quoted \"content\" quoted""#, + str![[r#""quoted /"content/" quoted""#]], + ); + assert_key_repr_roundtrip( + r#""squoted 'content' squoted""#, + str![[r#""squoted 'content' squoted""#]], + ); + assert_key_repr_roundtrip( + r#""mixed quoted \"start\" 'end'' mixed quote""#, + str![[r#""mixed quoted /"start/" 'end'' mixed quote""#]], + ); +} + +#[track_caller] +fn assert_key_repr_roundtrip(input: &str, expected: impl IntoData) { + let value: Key = input.parse().unwrap(); + let actual = value.to_string(); + let _: Key = actual.parse().unwrap_or_else(|_err| { + panic!( + "invalid `Key`: +``` +{actual} +``` +" + ) + }); + let expected = expected.into_data(); + assert_data_eq!(actual, expected); +} + +#[test] +fn key_value_roundtrip() { + assert_key_value_roundtrip(r#""""#, str![[r#""""#]]); + assert_key_value_roundtrip(r#""a""#, str!["a"]); + + assert_key_value_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]); + assert_key_value_roundtrip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]); + assert_key_value_roundtrip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]); + assert_key_value_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]); + assert_key_value_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]); + assert_key_value_roundtrip( + r#""backslash \\ backslash""#, + str!["'backslash / backslash'"], + ); + + assert_key_value_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]); + assert_key_value_roundtrip( + r#""triple squote ''' triple squote""#, + str![[r#""triple squote ''' triple squote""#]], + ); + assert_key_value_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]); + + assert_key_value_roundtrip(r#""quote \" quote""#, str![[r#"'quote " quote'"#]]); + assert_key_value_roundtrip( + r#""triple quote \"\"\" triple quote""#, + str![[r#"'triple quote """ triple quote'"#]], + ); + assert_key_value_roundtrip(r#""end quote \"""#, str![[r#"'end quote "'"#]]); + assert_key_value_roundtrip( + r#""quoted \"content\" quoted""#, + str![[r#"'quoted "content" quoted'"#]], + ); + assert_key_value_roundtrip( + r#""squoted 'content' squoted""#, + str![[r#""squoted 'content' squoted""#]], + ); + assert_key_value_roundtrip( + r#""mixed quoted \"start\" 'end'' mixed quote""#, + str![[r#""mixed quoted /"start/" 'end'' mixed quote""#]], + ); +} + +#[track_caller] +fn assert_key_value_roundtrip(input: &str, expected: impl IntoData) { + let value: Key = input.parse().unwrap(); + let value = Key::new(value.get()); // Remove repr + let actual = value.to_string(); + let _: Key = actual.parse().unwrap_or_else(|_err| { + panic!( + "invalid `Key`: ``` {actual} ``` diff --git a/crates/toml_edit_fuzz/parse_document.rs b/crates/toml_edit_fuzz/parse_document.rs index a503df41..f8d16495 100644 --- a/crates/toml_edit_fuzz/parse_document.rs +++ b/crates/toml_edit_fuzz/parse_document.rs @@ -7,24 +7,48 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> libfuzzer_sys::Corpus { return libfuzzer_sys::Corpus::Reject; }; - println!("parsing: {data:?}"); let doc = match data.parse::() { Ok(doc) => doc, Err(err) => { - println!("{err}"); + println!( + "parse error: {err} + +data: +```toml +{data} +``` +" + ); return libfuzzer_sys::Corpus::Keep; } }; let toml = doc.to_string(); - println!("parsing: {toml:?}"); let doc = toml.parse::(); assert!( doc.is_ok(), - "Failed to parse `doc.to_string()`: {}\n```\n{}\n```", + "parse error: {} + +data: +```toml +{data} +``` + +doc.to_string(): +```toml +{} +```", doc.unwrap_err(), toml ); let doc = doc.unwrap(); - assert_eq!(doc.to_string(), toml); + assert_eq!( + doc.to_string(), + toml, + "data: +```toml +{data} +``` +" + ); libfuzzer_sys::Corpus::Keep });