use fundu::{Duration, ParseError, TimeUnit};
use fundu_systemd::{
parse, parse_nanos, TimeSpanParser, SYSTEMD_MAX_MICRO_DURATION, SYSTEMD_MAX_NANOS_DURATION,
};
use rstest::rstest;
const YEAR: u64 = 60 * 60 * 24 * 365 + 60 * 60 * 24 / 4; const MONTH: u64 = YEAR / 12;
const MAX_USEC_DURATION_SECS: u64 = u64::MAX / 1_000_000;
const MAX_USEC_DURATION_NANO: u32 = (u64::MAX % 1_000_000) as u32 * 1000;
#[rstest]
#[case::empty("", ParseError::Empty)]
#[case::negative("-1", ParseError::NegativeNumber)]
#[case::only_point(".", ParseError::Syntax(0, "Either the whole number part or the fraction must be present".to_string()))]
#[case::two_points_leading("..1", ParseError::Syntax(0, "Either the whole number part or the fraction must be present".to_string()))]
#[case::two_points_trailing("1..", ParseError::Syntax(2, "Either the whole number part or the fraction must be present".to_string()))]
#[case::exponent("234e10", ParseError::Syntax(3, "No exponent allowed".to_string()))]
#[case::invalid_time_unit("3.invalid", ParseError::InvalidInput("invalid".to_string()))]
fn test_parse_parse_when_invalid(#[case] input: &str, #[case] expected: ParseError) {
assert_eq!(TimeSpanParser::new().parse(input), Err(expected.clone()));
assert_eq!(parse(input, None, None), Err(expected.clone()));
assert_eq!(
TimeSpanParser::new().parse_nanos(input),
Err(expected.clone())
);
assert_eq!(parse_nanos(input, None, None), Err(expected));
}
#[rstest]
#[case::short_inf("inf", ParseError::InvalidInput("inf".to_string()))]
#[case::capitalized("Infinity", ParseError::InvalidInput("Infinity".to_string()))]
#[case::one_letter_less("infinit", ParseError::InvalidInput("infinit".to_string()))]
#[case::one_letter_too_much("infinitys", ParseError::InvalidInput("infinitys".to_string()))]
fn test_parser_parse_infinity_when_invalid_then_error(
#[case] input: &str,
#[case] expected: ParseError,
) {
assert_eq!(TimeSpanParser::new().parse(input), Err(expected.clone()));
assert_eq!(parse(input, None, None), Err(expected.clone()));
assert_eq!(
TimeSpanParser::new().parse_nanos(input),
Err(expected.clone())
);
assert_eq!(parse_nanos(input, None, None), Err(expected));
}
#[rstest]
#[case::simple("infinity")]
#[case::leading_spaces(" infinity")]
#[case::leading_all_posix_whitespace("\x09\x0A\x0B\x0C\x0D infinity")]
#[case::trailing_whitespace("infinity ")]
#[case::trailing_all_posix_whitespace("infinity \x09\x0A\x0B\x0C\x0D")]
fn test_parser_parse_infinity(#[case] input: &str) {
assert_eq!(
TimeSpanParser::new().parse(input),
Ok(SYSTEMD_MAX_MICRO_DURATION)
);
assert_eq!(parse(input, None, None), Ok(SYSTEMD_MAX_MICRO_DURATION));
assert_eq!(
TimeSpanParser::new().parse_nanos(input),
Ok(SYSTEMD_MAX_NANOS_DURATION)
);
assert_eq!(
parse_nanos(input, None, None),
Ok(SYSTEMD_MAX_NANOS_DURATION)
);
}
#[rstest]
#[case::micro(&["us", "\u{03BC}s", "\u{00B5}s", "usec"], Duration::positive(0, 1_000))]
#[case::micro_sign(&["µs",], Duration::positive(0, 1_000))]
#[case::greek_mu(&["μs",], Duration::positive(0, 1_000))]
#[case::milli(&["ms", "msec"], Duration::positive(0, 1_000_000))]
#[case::second(&["s", "sec", "second", "seconds"], Duration::positive(1, 0))]
#[case::minute(&["m", "min", "minute", "minutes"], Duration::positive(60, 0))]
#[case::hour(&["h", "hr", "hour", "hours"], Duration::positive(60 * 60, 0))]
#[case::day(&["d", "day", "days"], Duration::positive(60 * 60 * 24, 0))]
#[case::week(&["w", "week", "weeks"], Duration::positive(60 * 60 * 24 * 7, 0))]
#[case::month(&["M", "month", "months"], Duration::positive(MONTH, 0))]
#[case::year(&["y", "year", "years"], Duration::positive(YEAR, 0))]
fn test_parser_parse_with_valid_time_units(
#[case] time_units: &[&str],
#[case] expected: Duration,
) {
for unit in time_units {
assert_eq!(TimeSpanParser::new().parse(unit), Ok(expected));
}
}
#[rstest]
#[case::nano_nsec("nsec", ParseError::InvalidInput("nsec".to_string()))]
#[case::nano_ns("ns", ParseError::InvalidInput("ns".to_string()))]
fn test_parser_parse_when_invalid_time_units(#[case] input: &str, #[case] expected: ParseError) {
assert_eq!(TimeSpanParser::new().parse(input), Err(expected.clone()));
assert_eq!(parse(input, None, None), Err(expected));
}
#[rstest]
#[case::max_duration_plus_one_sec(&format!("{}.{}", MAX_USEC_DURATION_SECS + 1, MAX_USEC_DURATION_NANO))]
#[case::max_duration_plus_one_nano(&format!("{}.{}", MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO + 1))]
#[case::exact_max_duration(&format!("{}.{}", MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO))]
#[case::large_just_number(&"1".repeat(1022))]
#[case::barely_with_multiple_durations("1year 10000years 100000years 14975376516109.55s 1616usec")]
fn test_parser_parse_when_saturating(#[case] input: &str) {
assert_eq!(
TimeSpanParser::new().parse(input),
Ok(SYSTEMD_MAX_MICRO_DURATION)
);
assert_eq!(parse(input, None, None), Ok(SYSTEMD_MAX_MICRO_DURATION));
}
#[rstest]
#[case::max_duration_with_usec_time_unit(&format!("{}us", u64::MAX), Duration::positive(MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO))]
#[case::max_duration_with_usec_time_unit_minus_one(&format!("{}us", u64::MAX - 1), Duration::positive(MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO - 1_000))]
#[case::max_duration_minus_one_sec(&format!("{}.{}", MAX_USEC_DURATION_SECS - 1, MAX_USEC_DURATION_NANO), Duration::positive(MAX_USEC_DURATION_SECS - 1, MAX_USEC_DURATION_NANO))]
#[case::max_duration_minus_one_nano(&format!("{}.{}", MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO -1), Duration::positive(MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO - 1))]
#[case::max_duration_minus_one_micro(&format!("{}.{}", MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO -1000), Duration::positive(MAX_USEC_DURATION_SECS, MAX_USEC_DURATION_NANO - 1000))]
fn test_parser_parse_when_barely_not_saturating(#[case] input: &str, #[case] expected: Duration) {
assert_eq!(TimeSpanParser::new().parse(input), Ok(expected));
assert_eq!(parse(input, None, None), Ok(expected));
}
#[rstest]
#[case::nanos(&["ns", "nsec"], Duration::positive(0, 1))]
#[case::micro(&["us", "\u{03BC}s", "μs", "\u{00B5}s", "µs", "usec"], Duration::positive(0, 1_000))]
#[case::milli(&["ms", "msec"], Duration::positive(0, 1_000_000))]
#[case::second(&["s", "sec", "second", "seconds"], Duration::positive(1, 0))]
#[case::minute(&["m", "min", "minute", "minutes"], Duration::positive(60, 0))]
#[case::hour(&["h", "hr", "hour", "hours"], Duration::positive(60 * 60, 0))]
#[case::day(&["d", "day", "days"], Duration::positive(60 * 60 * 24, 0))]
#[case::week(&["w", "week", "weeks"], Duration::positive(60 * 60 * 24 * 7, 0))]
#[case::month(&["M", "month", "months"], Duration::positive(MONTH, 0))]
#[case::year(&["y", "year", "years"], Duration::positive(YEAR, 0))]
fn test_parser_parse_with_nanos_with_valid_time_units(
#[case] time_units: &[&str],
#[case] expected: Duration,
) {
for unit in time_units {
assert_eq!(TimeSpanParser::new().parse_nanos(unit), Ok(expected));
assert_eq!(parse_nanos(unit, None, None), Ok(expected));
}
}
#[rstest]
#[case::none(
"123.123",
Some(TimeUnit::Second),
Duration::positive(123, 123_000_000)
)]
#[case::second(
"123.123",
Some(TimeUnit::Second),
Duration::positive(123, 123_000_000)
)]
#[case::smaller_than_second("123.123", Some(TimeUnit::MicroSecond), Duration::positive(0, 123_123))]
#[case::larger_than_second(
"123.123",
Some(TimeUnit::Year),
Duration::positive(3885466384, 800_000_000)
)]
#[case::nano_second("123.123", Some(TimeUnit::NanoSecond), Duration::positive(0, 123))]
fn test_parse_and_parse_nanos_with_time_units(
#[case] input: &str,
#[case] time_unit: Option<TimeUnit>,
#[case] expected: Duration,
) {
assert_eq!(parse(input, time_unit, None), Ok(expected));
assert_eq!(parse_nanos(input, time_unit, None), Ok(expected));
}
#[rstest]
#[case::none("123.123", None, Duration::positive(123, 123_000_000))]
#[case::none_when_saturating("123456789y", None, SYSTEMD_MAX_MICRO_DURATION)]
#[case::duration_zero("123.123", Some(Duration::ZERO), Duration::ZERO)]
#[case::duration_max("123.123", Some(Duration::MAX), Duration::positive(123, 123_000_000))]
#[case::duration_max_when_saturating(&format!("{}", u128::MAX), Some(Duration::MAX), Duration::MAX)]
#[case::duration_middle(
"123456",
Some(Duration::positive(123456, 789)),
Duration::positive(123456, 0)
)]
#[case::duration_middle_when_saturating(
"123457",
Some(Duration::positive(123456, 789)),
Duration::positive(123456, 789)
)]
fn test_parse_with_max(
#[case] input: &str,
#[case] max: Option<Duration>,
#[case] expected: Duration,
) {
assert_eq!(parse(input, None, max), Ok(expected));
}
#[rstest]
#[case::negative_zero(Duration::negative(0, 0))]
#[case::negative(Duration::negative(1, 0))]
#[should_panic]
fn test_parse_with_invalid_max_then_panic(#[case] max: Duration) {
_ = parse("123", None, Some(max));
}
#[rstest]
#[case::negative_zero(Duration::negative(0, 0))]
#[case::negative(Duration::negative(1, 0))]
#[should_panic]
fn test_parse_nanos_with_invalid_max_then_panic(#[case] max: Duration) {
_ = parse_nanos("123", None, Some(max));
}