Chrono: makes test independent of datetime C-API

This commit is contained in:
Tpt 2023-12-18 15:30:35 +01:00
parent ff50285d1f
commit 7705181049
1 changed files with 100 additions and 111 deletions

View File

@ -398,11 +398,9 @@ fn py_time_to_naive_time(py_time: &impl PyTimeAccess) -> PyResult<NaiveTime> {
#[cfg(test)]
mod tests {
use std::{cmp::Ordering, panic};
use crate::{tests::common::CatchWarnings, PyTypeInfo};
use super::*;
use crate::{tests::common::CatchWarnings, types::PyTuple, Py, PyTypeInfo};
use std::{cmp::Ordering, panic};
#[test]
// Only Python>=3.9 has the zoneinfo package
@ -432,15 +430,14 @@ mod tests {
// Test that if a user tries to convert a python's timezone aware datetime into a naive
// one, the conversion fails.
Python::with_gil(|py| {
let utc = timezone_utc(py);
let py_datetime = PyDateTime::new(py, 2022, 1, 1, 1, 0, 0, 0, Some(utc)).unwrap();
let py_datetime =
new_py_datetime_ob(py, "datetime", (2022, 1, 1, 1, 0, 0, 0, python_utc(py)));
// Now test that converting a PyDateTime with tzinfo to a NaiveDateTime fails
let res: PyResult<NaiveDateTime> = py_datetime.extract();
assert!(res.is_err());
let res = res.err().unwrap();
// Also check the error message is what we expect
let msg = res.value(py).repr().unwrap().to_string();
assert_eq!(msg, "TypeError('expected a datetime without tzinfo')");
assert_eq!(
res.unwrap_err().value(py).repr().unwrap().to_string(),
"TypeError('expected a datetime without tzinfo')"
);
});
}
@ -449,22 +446,20 @@ mod tests {
// Test that if a user tries to convert a python's timezone aware datetime into a naive
// one, the conversion fails.
Python::with_gil(|py| {
let py_datetime = PyDateTime::new(py, 2022, 1, 1, 1, 0, 0, 0, None).unwrap();
let py_datetime = new_py_datetime_ob(py, "datetime", (2022, 1, 1, 1, 0, 0, 0));
// Now test that converting a PyDateTime with tzinfo to a NaiveDateTime fails
let res: PyResult<DateTime<Utc>> = py_datetime.extract();
assert!(res.is_err());
let res = res.err().unwrap();
// Also check the error message is what we expect
let msg = res.value(py).repr().unwrap().to_string();
assert_eq!(msg, "TypeError('expected a datetime with non-None tzinfo')");
assert_eq!(
res.unwrap_err().value(py).repr().unwrap().to_string(),
"TypeError('expected a datetime with non-None tzinfo')"
);
// Now test that converting a PyDateTime with tzinfo to a NaiveDateTime fails
let res: PyResult<DateTime<FixedOffset>> = py_datetime.extract();
assert!(res.is_err());
let res = res.err().unwrap();
// Also check the error message is what we expect
let msg = res.value(py).repr().unwrap().to_string();
assert_eq!(msg, "TypeError('expected a datetime with non-None tzinfo')");
assert_eq!(
res.unwrap_err().value(py).repr().unwrap().to_string(),
"TypeError('expected a datetime with non-None tzinfo')"
);
});
}
@ -518,10 +513,9 @@ mod tests {
let check = |name: &'static str, delta: Duration, py_days, py_seconds, py_ms| {
Python::with_gil(|py| {
let delta = delta.to_object(py);
let delta: &PyDelta = delta.extract(py).unwrap();
let py_delta = PyDelta::new(py, py_days, py_seconds, py_ms, true).unwrap();
let py_delta = new_py_datetime_ob(py, "timedelta", (py_days, py_seconds, py_ms));
assert!(
delta.eq(py_delta).unwrap(),
delta.as_ref(py).eq(py_delta).unwrap(),
"{}: {} != {}",
name,
delta,
@ -555,7 +549,7 @@ mod tests {
// The `name` parameter is used to identify the check in case of a failure.
let check = |name: &'static str, delta: Duration, py_days, py_seconds, py_ms| {
Python::with_gil(|py| {
let py_delta = PyDelta::new(py, py_days, py_seconds, py_ms, true).unwrap();
let py_delta = new_py_datetime_ob(py, "timedelta", (py_days, py_seconds, py_ms));
let py_delta: Duration = py_delta.extract().unwrap();
assert_eq!(py_delta, delta, "{}: {} != {}", name, py_delta, delta);
})
@ -587,7 +581,7 @@ mod tests {
assert!(panic::catch_unwind(|| Duration::days(low_days as i64)).is_ok());
// This panics on PyDelta::new
assert!(panic::catch_unwind(|| {
let py_delta = PyDelta::new(py, low_days, 0, 0, true).unwrap();
let py_delta = new_py_datetime_ob(py, "timedelta", (low_days, 0, 0));
if let Ok(_duration) = py_delta.extract::<Duration>() {
// So we should never get here
}
@ -599,7 +593,7 @@ mod tests {
assert!(panic::catch_unwind(|| Duration::days(high_days as i64)).is_ok());
// This panics on PyDelta::new
assert!(panic::catch_unwind(|| {
let py_delta = PyDelta::new(py, high_days, 0, 0, true).unwrap();
let py_delta = new_py_datetime_ob(py, "timedelta", (high_days, 0, 0));
if let Ok(_duration) = py_delta.extract::<Duration>() {
// So we should never get here
}
@ -615,10 +609,9 @@ mod tests {
let date = NaiveDate::from_ymd_opt(year, month, day)
.unwrap()
.to_object(py);
let date: &PyDate = date.extract(py).unwrap();
let py_date = PyDate::new(py, year, month as u8, day as u8).unwrap();
let py_date = new_py_datetime_ob(py, "date", (year, month, day));
assert_eq!(
date.compare(py_date).unwrap(),
date.as_ref(py).compare(py_date).unwrap(),
Ordering::Equal,
"{}: {} != {}",
name,
@ -638,7 +631,7 @@ mod tests {
fn test_pyo3_date_frompyobject() {
let eq_ymd = |name: &'static str, year, month, day| {
Python::with_gil(|py| {
let py_date = PyDate::new(py, year, month as u8, day as u8).unwrap();
let py_date = new_py_datetime_ob(py, "date", (year, month, day));
let py_date: NaiveDate = py_date.extract().unwrap();
let date = NaiveDate::from_ymd_opt(year, month, day).unwrap();
assert_eq!(py_date, date, "{}: {} != {}", name, date, py_date);
@ -662,22 +655,22 @@ mod tests {
.unwrap()
.and_utc();
let datetime = datetime.to_object(py);
let datetime: &PyDateTime = datetime.extract(py).unwrap();
let py_tz = timezone_utc(py);
let py_datetime = PyDateTime::new(
let py_datetime = new_py_datetime_ob(
py,
year,
month as u8,
day as u8,
hour as u8,
minute as u8,
second as u8,
py_ms,
Some(py_tz),
)
.unwrap();
"datetime",
(
year,
month,
day,
hour,
minute,
second,
py_ms,
python_utc(py),
),
);
assert_eq!(
datetime.compare(py_datetime).unwrap(),
datetime.as_ref(py).compare(py_datetime).unwrap(),
Ordering::Equal,
"{}: {} != {}",
name,
@ -712,23 +705,14 @@ mod tests {
.and_local_timezone(offset)
.unwrap();
let datetime = datetime.to_object(py);
let datetime: &PyDateTime = datetime.extract(py).unwrap();
let py_tz = offset.to_object(py);
let py_tz = py_tz.downcast(py).unwrap();
let py_datetime = PyDateTime::new(
let py_datetime = new_py_datetime_ob(
py,
year,
month as u8,
day as u8,
hour as u8,
minute as u8,
second as u8,
py_ms,
Some(py_tz),
)
.unwrap();
"datetime",
(year, month, day, hour, minute, second, py_ms, py_tz),
);
assert_eq!(
datetime.compare(py_datetime).unwrap(),
datetime.as_ref(py).compare(py_datetime).unwrap(),
Ordering::Equal,
"{}: {} != {}",
name,
@ -760,23 +744,16 @@ mod tests {
let minute = 8;
let second = 9;
let micro = 999_999;
let py_tz = timezone_utc(py);
let py_datetime = PyDateTime::new(
let tz_utc = timezone_utc(py);
let py_datetime = new_py_datetime_ob(
py,
year,
month,
day,
hour,
minute,
second,
micro,
Some(py_tz),
)
.unwrap();
"datetime",
(year, month, day, hour, minute, second, micro, tz_utc),
);
let py_datetime: DateTime<Utc> = py_datetime.extract().unwrap();
let datetime = NaiveDate::from_ymd_opt(year, month.into(), day.into())
let datetime = NaiveDate::from_ymd_opt(year, month, day)
.unwrap()
.and_hms_micro_opt(hour.into(), minute.into(), second.into(), micro)
.and_hms_micro_opt(hour, minute, second, micro)
.unwrap()
.and_utc();
assert_eq!(py_datetime, datetime,);
@ -795,23 +772,15 @@ mod tests {
let micro = 999_999;
let offset = FixedOffset::east_opt(3600).unwrap();
let py_tz = offset.to_object(py);
let py_tz = py_tz.downcast(py).unwrap();
let py_datetime = PyDateTime::new(
let py_datetime = new_py_datetime_ob(
py,
year,
month,
day,
hour,
minute,
second,
micro,
Some(py_tz),
)
.unwrap();
"datetime",
(year, month, day, hour, minute, second, micro, py_tz),
);
let datetime_from_py: DateTime<FixedOffset> = py_datetime.extract().unwrap();
let datetime = NaiveDate::from_ymd_opt(year, month.into(), day.into())
let datetime = NaiveDate::from_ymd_opt(year, month, day)
.unwrap()
.and_hms_micro_opt(hour.into(), minute.into(), second.into(), micro)
.and_hms_micro_opt(hour, minute, second, micro)
.unwrap();
let datetime = datetime.and_local_timezone(offset).unwrap();
@ -821,10 +790,12 @@ mod tests {
"Extracting Utc from nonzero FixedOffset timezone will fail"
);
let utc = timezone_utc(py);
let py_datetime_utc =
PyDateTime::new(py, year, month, day, hour, minute, second, micro, Some(utc))
.unwrap();
let utc = python_utc(py);
let py_datetime_utc = new_py_datetime_ob(
py,
"datetime",
(year, month, day, hour, minute, second, micro, utc),
);
assert!(
py_datetime_utc.extract::<DateTime<FixedOffset>>().is_ok(),
"Extracting FixedOffset from Utc timezone will succeed"
@ -838,15 +809,15 @@ mod tests {
// Chrono offset
let offset = FixedOffset::east_opt(3600).unwrap().to_object(py);
// Python timezone from timedelta
let td = PyDelta::new(py, 0, 3600, 0, true).unwrap();
let py_timedelta = timezone_from_offset(py, td).unwrap();
let td = new_py_datetime_ob(py, "timedelta", (0, 3600, 0));
let py_timedelta = new_py_datetime_ob(py, "timezone", (td,));
// Should be equal
assert!(offset.as_ref(py).eq(py_timedelta).unwrap());
// Same but with negative values
let offset = FixedOffset::east_opt(-3600).unwrap().to_object(py);
let td = PyDelta::new(py, 0, -3600, 0, true).unwrap();
let py_timedelta = timezone_from_offset(py, td).unwrap();
let td = new_py_datetime_ob(py, "timedelta", (0, -3600, 0));
let py_timedelta = new_py_datetime_ob(py, "timezone", (td,));
assert!(offset.as_ref(py).eq(py_timedelta).unwrap());
})
}
@ -854,8 +825,8 @@ mod tests {
#[test]
fn test_pyo3_offset_fixed_frompyobject() {
Python::with_gil(|py| {
let py_timedelta = PyDelta::new(py, 0, 3600, 0, true).unwrap();
let py_tzinfo = timezone_from_offset(py, py_timedelta).unwrap();
let py_timedelta = new_py_datetime_ob(py, "timedelta", (0, 3600, 0));
let py_tzinfo = new_py_datetime_ob(py, "timezone", (py_timedelta,));
let offset: FixedOffset = py_tzinfo.extract().unwrap();
assert_eq!(FixedOffset::east_opt(3600).unwrap(), offset);
})
@ -865,7 +836,7 @@ mod tests {
fn test_pyo3_offset_utc_topyobject() {
Python::with_gil(|py| {
let utc = Utc.to_object(py);
let py_utc = timezone_utc(py);
let py_utc = python_utc(py);
assert!(utc.as_ref(py).is(py_utc));
})
}
@ -873,17 +844,17 @@ mod tests {
#[test]
fn test_pyo3_offset_utc_frompyobject() {
Python::with_gil(|py| {
let py_utc = timezone_utc(py);
let py_utc = python_utc(py);
let py_utc: Utc = py_utc.extract().unwrap();
assert_eq!(Utc, py_utc);
let py_timedelta = PyDelta::new(py, 0, 0, 0, true).unwrap();
let py_timezone_utc = timezone_from_offset(py, py_timedelta).unwrap();
let py_timedelta = new_py_datetime_ob(py, "timedelta", (0, 0, 0));
let py_timezone_utc = new_py_datetime_ob(py, "timezone", (py_timedelta,));
let py_timezone_utc: Utc = py_timezone_utc.extract().unwrap();
assert_eq!(Utc, py_timezone_utc);
let py_timedelta = PyDelta::new(py, 0, 3600, 0, true).unwrap();
let py_timezone = timezone_from_offset(py, py_timedelta).unwrap();
let py_timedelta = new_py_datetime_ob(py, "timedelta", (0, 3600, 0));
let py_timezone = new_py_datetime_ob(py, "timezone", (py_timedelta,));
assert!(py_timezone.extract::<Utc>().is_err());
})
}
@ -895,11 +866,9 @@ mod tests {
let time = NaiveTime::from_hms_micro_opt(hour, minute, second, ms)
.unwrap()
.to_object(py);
let time: &PyTime = time.extract(py).unwrap();
let py_time =
PyTime::new(py, hour as u8, minute as u8, second as u8, py_ms, None).unwrap();
let py_time = new_py_datetime_ob(py, "time", (hour, minute, second, py_ms));
assert!(
time.eq(py_time).unwrap(),
time.as_ref(py).eq(py_time).unwrap(),
"{}: {} != {}",
name,
time,
@ -927,19 +896,39 @@ mod tests {
let second = 7;
let micro = 999_999;
Python::with_gil(|py| {
let py_time =
PyTime::new(py, hour as u8, minute as u8, second as u8, micro, None).unwrap();
let py_time = new_py_datetime_ob(py, "time", (hour, minute, second, micro));
let py_time: NaiveTime = py_time.extract().unwrap();
let time = NaiveTime::from_hms_micro_opt(hour, minute, second, micro).unwrap();
assert_eq!(py_time, time);
})
}
fn new_py_datetime_ob<'a>(
py: Python<'a>,
name: &str,
args: impl IntoPy<Py<PyTuple>>,
) -> &'a PyAny {
py.import("datetime")
.unwrap()
.getattr(name)
.unwrap()
.call1(args)
.unwrap()
}
fn python_utc(py: Python<'_>) -> &PyAny {
py.import("datetime")
.unwrap()
.getattr("timezone")
.unwrap()
.getattr("utc")
.unwrap()
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod proptests {
use super::*;
use crate::types::IntoPyDict;
use proptest::prelude::*;
proptest! {