Skip to content

Commit 8827144

Browse files
committed
test: Add helpers for Position and Span error checking, and use them in all unit tests that use non-trivial Spans
1 parent a11ee1f commit 8827144

File tree

3 files changed

+416
-3
lines changed

3 files changed

+416
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ serde_json = { version = "1.0.60", default-features = false, features = ["std"]
4545
option_set = { version = "0.3", default-features = false }
4646
typetag = { version = "0.2", default-features = false }
4747
bytes = { version = "1.3", default-features = false, features = ["serde"] }
48+
unicode-segmentation = "1.12.0"
4849

4950
[package.metadata.docs.rs]
5051
features = ["integer128", "indexmap"]

src/de/tests.rs

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ fn test_struct() {
8282
},
8383
}),
8484
);
85+
86+
check_error_span_exclusive::<NewType>(
87+
"NewType",
88+
Err(SpannedError {
89+
code: Error::ExpectedNamedStructLike("NewType"),
90+
span: Span {
91+
start: Position { line: 1, col: 1 },
92+
end: Position { line: 1, col: 8 },
93+
},
94+
}),
95+
"NewType",
96+
);
97+
8598
check_from_str_bytes_reader::<UnnamedNewType>(
8699
"",
87100
Err(SpannedError {
@@ -93,6 +106,7 @@ fn test_struct() {
93106
}),
94107
);
95108
check_from_str_bytes_reader("(33)", Ok(UnnamedNewType(33)));
109+
96110
check_from_str_bytes_reader::<UnnamedNewType>(
97111
"Newtype",
98112
Err(SpannedError {
@@ -103,6 +117,17 @@ fn test_struct() {
103117
},
104118
}),
105119
);
120+
check_error_span_exclusive::<UnnamedNewType>(
121+
"Newtype",
122+
Err(SpannedError {
123+
code: Error::ExpectedNamedStructLike(""),
124+
span: Span {
125+
start: Position { line: 1, col: 1 },
126+
end: Position { line: 1, col: 8 },
127+
},
128+
}),
129+
"Newtype",
130+
);
106131

107132
check_from_str_bytes_reader("TupleStruct(2,5,)", Ok(TupleStruct(2.0, 5.0)));
108133
check_from_str_bytes_reader("(3,4)", Ok(TupleStruct(3.0, 4.0)));
@@ -116,6 +141,7 @@ fn test_struct() {
116141
},
117142
}),
118143
);
144+
119145
check_from_str_bytes_reader::<UnnamedTupleStruct>(
120146
"TupleStruct(2,5,)",
121147
Err(SpannedError {
@@ -126,6 +152,19 @@ fn test_struct() {
126152
},
127153
}),
128154
);
155+
156+
check_error_span_exclusive::<UnnamedTupleStruct>(
157+
"TupleStruct(2,5,)",
158+
Err(SpannedError {
159+
code: Error::ExpectedNamedStructLike(""),
160+
span: Span {
161+
start: Position { line: 1, col: 1 },
162+
end: Position { line: 1, col: 12 },
163+
},
164+
}),
165+
"TupleStruct",
166+
);
167+
129168
check_from_str_bytes_reader("(3,4)", Ok(UnnamedTupleStruct(3.0, 4.0)));
130169
check_from_str_bytes_reader::<UnnamedTupleStruct>(
131170
"",
@@ -438,10 +477,21 @@ fn test_err_wrong_value() {
438477
"MyStruct(\n x: true)",
439478
err(Error::ExpectedFloat, (2, 7), (2, 8)),
440479
);
480+
check_error_span_inclusive::<MyStruct>(
481+
"MyStruct(\n x: true)",
482+
err(Error::ExpectedFloat, (2, 7), (2, 8)),
483+
" t",
484+
);
485+
441486
check_from_str_bytes_reader::<MyStruct>(
442487
"MyStruct(\n x: 3.5, \n y:)",
443488
err(Error::ExpectedFloat, (3, 7), (3, 7)),
444489
);
490+
check_error_span_inclusive::<MyStruct>(
491+
"MyStruct(\n x: 3.5, \n y:)",
492+
err(Error::ExpectedFloat, (3, 7), (3, 7)),
493+
")",
494+
);
445495
}
446496

447497
#[test]
@@ -480,6 +530,18 @@ fn untagged() {
480530
},
481531
}),
482532
);
533+
534+
check_error_span_exclusive::<Untagged>(
535+
"Value(()",
536+
Err(crate::error::SpannedError {
537+
code: crate::Error::Eof,
538+
span: Span {
539+
start: Position { line: 1, col: 8 },
540+
end: crate::error::Position { line: 1, col: 9 },
541+
},
542+
}),
543+
")",
544+
);
483545
}
484546

485547
#[test]
@@ -508,6 +570,18 @@ fn forgot_apostrophes() {
508570
},
509571
}),
510572
);
573+
574+
check_error_span_exclusive::<(i32, String)>(
575+
"(4, \"Hello)",
576+
Err(SpannedError {
577+
code: Error::ExpectedStringEnd,
578+
span: Span {
579+
start: Position { line: 1, col: 5 },
580+
end: Position { line: 1, col: 6 },
581+
},
582+
}),
583+
"\""
584+
);
511585
}
512586

513587
#[test]
@@ -524,6 +598,11 @@ fn expected_attribute_end() {
524598
"#![enable(unwrap_newtypes) \"Hello\"",
525599
err(Error::ExpectedAttributeEnd, (1, 27), (1, 28)),
526600
);
601+
check_error_span_inclusive::<String>(
602+
"#![enable(unwrap_newtypes) \"Hello\"",
603+
err(Error::ExpectedAttributeEnd, (1, 27), (1, 28)),
604+
" \""
605+
);
527606
}
528607

529608
#[test]
@@ -536,6 +615,16 @@ fn invalid_attribute() {
536615
(1, 18),
537616
),
538617
);
618+
619+
check_error_span_exclusive::<String>(
620+
"#![enable(invalid)] \"Hello\"",
621+
err(
622+
Error::NoSuchExtension("invalid".to_string()),
623+
(1, 11),
624+
(1, 18),
625+
),
626+
"invalid"
627+
);
539628
}
540629

541630
#[test]
@@ -699,7 +788,7 @@ fn test_leading_whitespace() {
699788
check_from_str_bytes_reader(" EmptyStruct1", Ok(EmptyStruct1));
700789
}
701790

702-
fn check_from_str_bytes_reader<T: serde::de::DeserializeOwned + PartialEq + core::fmt::Debug>(
791+
pub fn check_from_str_bytes_reader<T: serde::de::DeserializeOwned + PartialEq + core::fmt::Debug>(
703792
ron: &str,
704793
check: SpannedResult<T>,
705794
) {
@@ -716,6 +805,63 @@ fn check_from_str_bytes_reader<T: serde::de::DeserializeOwned + PartialEq + core
716805
}
717806
}
718807

808+
/// Given a string `ron`, a SpannedResult, and a substring, verify that trying to parse `ron` results in an error
809+
/// equal to the SpannedResult with a Span that exclusively (as in \[start..end\]) selects that substring.
810+
/// Note that there are two versions of this helper, inclusive and exclusive. This is because while the parser cursor
811+
/// arithmetic that computes span positions always produces exclusive spans (as in \[start..end\]),
812+
/// when doing validation against a target substring, the inclusive check including the final grapheme that triggered
813+
/// the error is often a more intuitive target to check against.
814+
/// Meanwhile, if the parser threw an EOF, for example, there is no final grapheme to check, and so
815+
/// only the exclusive check would produce a meaningful result.
816+
fn check_error_span_exclusive<T: serde::de::DeserializeOwned + PartialEq + core::fmt::Debug>(
817+
ron: &str,
818+
check: SpannedResult<T>,
819+
substr: &str,
820+
) {
821+
let res_str = super::from_str::<T>(ron);
822+
assert_eq!(res_str, check);
823+
824+
let res_bytes = super::from_bytes::<T>(ron.as_bytes());
825+
assert_eq!(res_bytes, check);
826+
827+
#[cfg(feature = "std")]
828+
{
829+
let res_reader = super::from_reader::<&[u8], T>(ron.as_bytes());
830+
assert_eq!(res_reader, check);
831+
}
832+
833+
assert_eq!(
834+
check.unwrap_err().span.substring_exclusive(ron).unwrap(),
835+
substr
836+
);
837+
}
838+
839+
/// Given a string `ron`, a SpannedResult, and a substring, verify that trying to parse `ron` results in an error
840+
/// equal to the SpannedResult with a Span that inclusively (as in \[start..=end\]) selects that substring.
841+
/// See [check_error_span_exclusive] for the rationale behind both versions of this helper.
842+
fn check_error_span_inclusive<T: serde::de::DeserializeOwned + PartialEq + core::fmt::Debug>(
843+
ron: &str,
844+
check: SpannedResult<T>,
845+
substr: &str,
846+
) {
847+
let res_str = super::from_str::<T>(ron);
848+
assert_eq!(res_str, check);
849+
850+
let res_bytes = super::from_bytes::<T>(ron.as_bytes());
851+
assert_eq!(res_bytes, check);
852+
853+
#[cfg(feature = "std")]
854+
{
855+
let res_reader = super::from_reader::<&[u8], T>(ron.as_bytes());
856+
assert_eq!(res_reader, check);
857+
}
858+
859+
assert_eq!(
860+
check.unwrap_err().span.substring_inclusive(ron).unwrap(),
861+
substr
862+
);
863+
}
864+
719865
#[test]
720866
fn test_remainder() {
721867
let mut deserializer = super::Deserializer::from_str(" 42 ").unwrap();

0 commit comments

Comments
 (0)