Skip to content

Commit

Permalink
Add various tests to verify current parsing
Browse files Browse the repository at this point in the history
Add more testing for most parsing functions.
Tests emphasize whitespace, literals, timezones, and timezone
delimiters (colons and whitespace).

Add tests for multiple-byte characters and combining characters
in and around data and parsing formats.

These tests are added to aid humans verifying the next commit that
changes parsing behavior.

Issue #660
  • Loading branch information
jtmoon79 committed Sep 10, 2022
1 parent 5305023 commit aaf4c0a
Show file tree
Hide file tree
Showing 7 changed files with 1,186 additions and 98 deletions.
5 changes: 5 additions & 0 deletions src/datetime/mod.rs
Expand Up @@ -530,13 +530,18 @@ impl DateTime<FixedOffset> {
/// RFC 2822 is the internet message standard that specifies the
/// representation of times in HTTP and email headers.
///
/// The RFC 2822 standard allows arbitrary intermixed whitespace.
/// See [RFC 2822 Appendix A.5]
///
/// ```
/// # use chrono::{DateTime, FixedOffset, TimeZone};
/// assert_eq!(
/// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(),
/// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)
/// );
/// ```
///
/// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5
pub fn parse_from_rfc2822(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)];
let mut parsed = Parsed::new();
Expand Down
561 changes: 537 additions & 24 deletions src/datetime/tests.rs

Large diffs are not rendered by default.

564 changes: 517 additions & 47 deletions src/format/parse.rs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/format/strftime.rs
Expand Up @@ -499,6 +499,7 @@ impl<'a> Iterator for StrftimeItems<'a> {
fn test_strftime_items() {
fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
// map any error into `[Item::Error]`. useful for easy testing.
eprintln!("test_strftime_items: parse_and_collect({:?})", s);
let items = StrftimeItems::new(s);
let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
Expand All @@ -518,6 +519,7 @@ fn test_strftime_items() {
parse_and_collect("%Y-%m-%d"),
[num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
);
assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]);
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
assert_eq!(parse_and_collect("%"), [Item::Error]);
Expand All @@ -543,6 +545,9 @@ fn test_strftime_items() {
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]);
assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]);
assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]);
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
}
Expand Down Expand Up @@ -643,6 +648,13 @@ fn test_strftime_docs() {
assert_eq!(dt.format("%t").to_string(), "\t");
assert_eq!(dt.format("%n").to_string(), "\n");
assert_eq!(dt.format("%%").to_string(), "%");

// complex format specifiers
assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t");
assert_eq!(
dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
" 20010807%%\t00:am:3460+09\t"
);
}

#[cfg(feature = "unstable-locales")]
Expand Down
33 changes: 26 additions & 7 deletions src/naive/date.rs
Expand Up @@ -2745,23 +2745,28 @@ mod tests {
"360-02-29",
"0360-02-29",
"2015-2 -18",
"2015-02-18",
"+70-2-18",
"+70000-2-18",
"+00007-2-18",
];
for &s in &valid {
eprintln!("test_date_from_str valid {:?}", s);
let d = match s.parse::<NaiveDate>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
};
eprintln!("d {:?} (NaiveDate)", d);
let s_ = format!("{:?}", d);
eprintln!("s_ {:?}", s_);
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveDate>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
}
};
eprintln!("d_ {:?} (NaiveDate)", d_);
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
Expand All @@ -2774,13 +2779,27 @@ mod tests {

// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
assert!("".parse::<NaiveDate>().is_err());
assert!("x".parse::<NaiveDate>().is_err());
assert!("2014".parse::<NaiveDate>().is_err());
assert!("2014-01".parse::<NaiveDate>().is_err());
assert!("2014-01-00".parse::<NaiveDate>().is_err());
assert!("2014-13-57".parse::<NaiveDate>().is_err());
assert!("9999999-9-9".parse::<NaiveDate>().is_err()); // out-of-bounds
let invalid = [
"", // empty
"x", // invalid
"Fri, 09 Aug 2013 GMT", // valid date, wrong format
"Sat Jun 30 2012", // valid date, wrong format
"1441497364.649", // valid datetime, wrong format
"+1441497364.649", // valid datetime, wrong format
"+1441497364", // valid datetime, wrong format
"2014/02/03", // valid date, wrong format
"2014", // datetime missing data
"2014-01", // datetime missing data
"2014-01-00", // invalid day
"2014-11-32", // invalid day
"2014-13-01", // invalid month
"2014-13-57", // invalid month, day
"9999999-9-9", // invalid year (out of bounds)
];
for &s in &invalid {
eprintln!("test_date_from_str invalid {:?}", s);
assert!(s.parse::<NaiveDate>().is_err());
}
}

#[test]
Expand Down
40 changes: 29 additions & 11 deletions src/naive/datetime/tests.rs
Expand Up @@ -113,11 +113,16 @@ fn test_datetime_timestamp() {
fn test_datetime_from_str() {
// valid cases
let valid = [
"2015-2-18T23:16:9.15",
"2001-02-03T04:05:06",
"2012-12-12T12:12:12",
"2015-02-18T23:16:09.153",
"2015-2-18T23:16:09.153",
"-77-02-18T23:16:09",
"+82701-05-6T15:9:60.898989898989",
" +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ",
];
for &s in &valid {
eprintln!("test_parse_naivedatetime valid {:?}", s);
let d = match s.parse::<NaiveDateTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
Expand All @@ -142,16 +147,29 @@ fn test_datetime_from_str() {

// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
assert!("".parse::<NaiveDateTime>().is_err());
assert!("x".parse::<NaiveDateTime>().is_err());
assert!("15".parse::<NaiveDateTime>().is_err());
assert!("15:8:9".parse::<NaiveDateTime>().is_err());
assert!("15-8-9".parse::<NaiveDateTime>().is_err());
assert!("2015-15-15T15:15:15".parse::<NaiveDateTime>().is_err());
assert!("2012-12-12T12:12:12x".parse::<NaiveDateTime>().is_err());
assert!("2012-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
assert!("+ 82701-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
assert!("+802701-123-12T12:12:12".parse::<NaiveDateTime>().is_err()); // out-of-bound
let invalid = [
"", // empty
"x", // invalid / missing data
"15", // missing data
"15:8:9", // looks like a time (invalid date)
"15-8-9", // looks like a date (invalid)
"Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format
"Sat Jun 30 23:59:60 2012", // valid date, wrong format
"1441497364.649", // valid date, wrong format
"+1441497364.649", // valid date, wrong format
"+1441497364", // valid date, wrong format
"2014/02/03 04:05:06", // valid date, wrong format
"2015-15-15T15:15:15", // valid date, wrong format
"2012-12-12T12:12:12x", // bad timezone / trailing literal
"2012-123-12T12:12:12", // invalid month
"2012-12-12t12:12:12", // bad divider 't'
"+ 82701-123-12T12:12:12", // strange year, invalid month
"+802701-123-12T12:12:12", // out-of-bound year, invalid month
];
for &s in &invalid {
eprintln!("test_datetime_from_str invalid {:?}", s);
assert!(s.parse::<NaiveDateTime>().is_err());
}
}

#[test]
Expand Down
69 changes: 60 additions & 9 deletions src/naive/time/tests.rs
Expand Up @@ -199,9 +199,37 @@ fn test_date_from_str() {
" 4 : 3 : 2.1 ",
" 09:08:07 ",
" 9:8:07 ",
"01:02:03",
"4:3:2.1",
"9:8:7",
"09:8:7",
"9:08:7",
"9:8:07",
"09:08:7",
"09:8:07",
"09:08:7",
"9:08:07",
"09:08:07",
"9:8:07.123",
"9:08:7.123",
"09:8:7.123",
"09:08:7.123",
"9:08:07.123",
"09:8:07.123",
"09:08:07.123",
"09:08:07.123",
"09:08:07.1234",
"09:08:07.12345",
"09:08:07.123456",
"09:08:07.1234567",
"09:08:07.12345678",
"09:08:07.123456789",
"09:08:07.1234567891",
"09:08:07.12345678912",
"23:59:60.373929310237",
];
for &s in &valid {
eprintln!("test_time_parse_from_str valid {:?}", s);
let d = match s.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
Expand All @@ -226,15 +254,29 @@ fn test_date_from_str() {

// some invalid cases
// since `ParseErrorKind` is private, all we can do is to check if there was an error
assert!("".parse::<NaiveTime>().is_err());
assert!("x".parse::<NaiveTime>().is_err());
assert!("15".parse::<NaiveTime>().is_err());
assert!("15:8".parse::<NaiveTime>().is_err());
assert!("15:8:x".parse::<NaiveTime>().is_err());
assert!("15:8:9x".parse::<NaiveTime>().is_err());
assert!("23:59:61".parse::<NaiveTime>().is_err());
assert!("12:34:56.x".parse::<NaiveTime>().is_err());
assert!("12:34:56. 0".parse::<NaiveTime>().is_err());
let invalid = [
"", // empty
"x", // invalid
"15", // missing data
"15:8", // missing data
"15:8:x", // missing data, invalid data
"15:8:9x", // missing data, invalid data
"23:59:61", // invalid second (out of bounds)
"23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime)
"1441497364.649", // valid datetime, not a NaiveTime
"+1441497364.649", // valid datetime, not a NaiveTime
"+1441497364", // valid datetime, not a NaiveTime
"001:02:03", // invalid hour
"01:002:03", // invalid minute
"01:02:003", // invalid second
"12:34:56.x", // invalid fraction
"12:34:56. 0", // invalid fraction format
"09:08:00000000007", // invalid second / invalid fraction format
];
for &s in &invalid {
eprintln!("test_time_parse_from_str invalid {:?}", s);
assert!(s.parse::<NaiveTime>().is_err());
}
}

#[test]
Expand All @@ -245,6 +287,15 @@ fn test_time_parse_from_str() {
Ok(hms(12, 34, 56))
); // ignore date and offset
assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0)));
assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0)));
assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0)));
assert_eq!(
NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"),
Ok(hms(12, 59, 0))
);
assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok());
assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok());
assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok());
assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err());
}

Expand Down

0 comments on commit aaf4c0a

Please sign in to comment.