This commit is contained in:
konstin 2018-11-11 12:25:53 +01:00
parent a9b05711b0
commit 9f45efebaf
11 changed files with 147 additions and 115 deletions

View file

@ -1,3 +1,4 @@
hypothesis>=3.55
pytest>=3.5.0
setuptools-rust>=0.10.2
black

View file

@ -12,7 +12,8 @@ class PyTest(TestCommand):
self.run_command("test_rust")
import subprocess
errno = subprocess.call(['pytest', 'tests'])
errno = subprocess.call(["pytest", "tests"])
raise SystemExit(errno)
@ -21,41 +22,48 @@ def get_py_version_cfgs():
version = sys.version_info[0:2]
if version[0] == 2:
return ['--cfg=Py_2']
return ["--cfg=Py_2"]
py3_min = 5
out_cfg = []
for minor in range(py3_min, version[1] + 1):
out_cfg.append('--cfg=Py_3_%d' % minor)
out_cfg.append("--cfg=Py_3_%d" % minor)
return out_cfg
install_requires = []
tests_require = install_requires + ['pytest', 'pytest-benchmark']
tests_require = install_requires + ["pytest", "pytest-benchmark"]
setup(
name='rustapi-module',
version='0.1.0',
name="rustapi-module",
version="0.1.0",
classifiers=[
'License :: OSI Approved :: MIT License',
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Programming Language :: Python',
'Programming Language :: Rust',
'Operating System :: POSIX',
'Operating System :: MacOS :: MacOS X',
"License :: OSI Approved :: MIT License",
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python",
"Programming Language :: Rust",
"Operating System :: POSIX",
"Operating System :: MacOS :: MacOS X",
],
packages=["rustapi_module"],
rust_extensions=[
RustExtension(
"rustapi_module.othermod", "Cargo.toml", rustc_flags=get_py_version_cfgs()
),
RustExtension(
"rustapi_module.datetime", "Cargo.toml", rustc_flags=get_py_version_cfgs()
),
RustExtension(
"rustapi_module.subclassing",
"Cargo.toml",
rustc_flags=get_py_version_cfgs(),
),
],
packages=['rustapi_module'],
rust_extensions=[RustExtension('rustapi_module.othermod', 'Cargo.toml',
rustc_flags=get_py_version_cfgs()),
RustExtension('rustapi_module.datetime', 'Cargo.toml',
rustc_flags=get_py_version_cfgs()),
RustExtension('rustapi_module.subclassing', 'Cargo.toml',
rustc_flags=get_py_version_cfgs())],
install_requires=install_requires,
tests_require=tests_require,
include_package_data=True,
zip_safe=False,
cmdclass=dict(test=PyTest)
cmdclass=dict(test=PyTest),
)

View file

@ -1,7 +1,7 @@
use pyo3::prelude::*;
use pyo3::types::{
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess,
PyTuple, PyTzInfo,
PyDate, PyDateAccess, PyDateTime, PyDelta, PyDeltaAccess, PyTime, PyTimeAccess, PyTuple,
PyTzInfo,
};
#[pyfunction]

View file

@ -6,6 +6,6 @@ extern crate pyo3;
use pyo3::prelude::*;
use subclassing::Subclassable;
pub mod datetime;
pub mod othermod;
pub mod subclassing;
pub mod datetime;

View file

@ -18,4 +18,3 @@ fn subclassing(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Subclassable>()?;
Ok(())
}

View file

@ -1,19 +1,20 @@
import rustapi_module.datetime as rdt
import sys
import datetime as pdt
import sys
import pytest
import pytest
import rustapi_module.datetime as rdt
from hypothesis import given
from hypothesis import strategies as st
from hypothesis.strategies import dates, datetimes
# Constants
def _get_utc():
timezone = getattr(pdt, 'timezone', None)
timezone = getattr(pdt, "timezone", None)
if timezone:
return timezone.utc
else:
class UTC(pdt.tzinfo):
def utcoffset(self, dt):
return pdt.timedelta(0)
@ -26,6 +27,7 @@ def _get_utc():
return UTC()
UTC = _get_utc()
MAX_SECONDS = int(pdt.timedelta.max.total_seconds())
@ -42,19 +44,22 @@ except Exception:
MAX_MICROSECONDS = int(pdt.timedelta.max.total_seconds() * 1e6)
MIN_MICROSECONDS = int(pdt.timedelta.min.total_seconds() * 1e6)
HAS_FOLD = getattr(pdt.datetime, 'fold', False)
HAS_FOLD = getattr(pdt.datetime, "fold", False)
# Helper functions
get_timestamp = getattr(pdt.datetime, 'timestamp', None)
get_timestamp = getattr(pdt.datetime, "timestamp", None)
if get_timestamp is None:
def get_timestamp(dt):
# Python 2 compatibility
return (dt - pdt.datetime(1970, 1, 1)).total_seconds()
xfail_date_bounds = pytest.mark.xfail(sys.version_info < (3, 6),
reason="Date bounds were not checked in the C constructor prior to version 3.6")
xfail_date_bounds = pytest.mark.xfail(
sys.version_info < (3, 6),
reason="Date bounds were not checked in the C constructor prior to version 3.6",
)
# Tests
def test_date():
@ -81,11 +86,14 @@ def test_date_from_timestamp(d):
assert rdt.date_from_timestamp(int(ts)) == pdt.date.fromtimestamp(ts)
@pytest.mark.parametrize('args, kwargs', [
((0, 0, 0, 0, None), {}),
((1, 12, 14, 124731), {}),
((1, 12, 14, 124731), {'tzinfo': UTC}),
])
@pytest.mark.parametrize(
"args, kwargs",
[
((0, 0, 0, 0, None), {}),
((1, 12, 14, 124731), {}),
((1, 12, 14, 124731), {"tzinfo": UTC}),
],
)
def test_time(args, kwargs):
act = rdt.make_time(*args, **kwargs)
exp = pdt.time(*args, **kwargs)
@ -116,55 +124,58 @@ def test_time_fold(t):
@pytest.mark.skipif(not HAS_FOLD, reason="Feature not available before 3.6")
@pytest.mark.parametrize('fold', [False, True])
@pytest.mark.parametrize("fold", [False, True])
def test_time_fold(fold):
t = rdt.time_with_fold(0, 0, 0, 0, None, fold)
assert t.fold == fold
@pytest.mark.xfail
@pytest.mark.parametrize('args', [
(-1, 0, 0, 0),
(0, -1, 0, 0),
(0, 0, -1, 0),
(0, 0, 0, -1),
])
@pytest.mark.parametrize(
"args", [(-1, 0, 0, 0), (0, -1, 0, 0), (0, 0, -1, 0), (0, 0, 0, -1)]
)
def test_invalid_time_fails_xfail(args):
with pytest.raises(ValueError):
rdt.make_time(*args)
@xfail_date_bounds
@pytest.mark.parametrize('args', [
(24, 0, 0, 0),
(25, 0, 0, 0),
(0, 60, 0, 0),
(0, 61, 0, 0),
(0, 0, 60, 0),
(0, 0, 61, 0),
(0, 0, 0, 1000000)
])
@pytest.mark.parametrize(
"args",
[
(24, 0, 0, 0),
(25, 0, 0, 0),
(0, 60, 0, 0),
(0, 61, 0, 0),
(0, 0, 60, 0),
(0, 0, 61, 0),
(0, 0, 0, 1000000),
],
)
def test_invalid_time_fails(args):
with pytest.raises(ValueError):
rdt.make_time(*args)
@pytest.mark.parametrize('args', [
('0', 0, 0, 0),
(0, '0', 0, 0),
(0, 0, '0', 0),
(0, 0, 0, '0'),
(0, 0, 0, 0, 'UTC')
])
@pytest.mark.parametrize(
"args",
[
("0", 0, 0, 0),
(0, "0", 0, 0),
(0, 0, "0", 0),
(0, 0, 0, "0"),
(0, 0, 0, 0, "UTC"),
],
)
def test_time_typeerror(args):
with pytest.raises(TypeError):
rdt.make_time(*args)
@pytest.mark.parametrize('args, kwargs', [
((2017, 9, 1, 12, 45, 30, 0), {}),
((2017, 9, 1, 12, 45, 30, 0), {'tzinfo': UTC}),
])
@pytest.mark.parametrize(
"args, kwargs",
[((2017, 9, 1, 12, 45, 30, 0), {}), ((2017, 9, 1, 12, 45, 30, 0), {"tzinfo": UTC})],
)
def test_datetime(args, kwargs):
act = rdt.make_datetime(*args, **kwargs)
exp = pdt.datetime(*args, **kwargs)
@ -176,7 +187,7 @@ def test_datetime(args, kwargs):
@given(dt=st.datetimes())
def test_datetime_tuple(dt):
act = rdt.get_datetime_tuple(dt)
exp = dt.timetuple()[0:6] + (dt.microsecond, )
exp = dt.timetuple()[0:6] + (dt.microsecond,)
assert act == exp
@ -202,7 +213,7 @@ def test_invalid_datetime_fails():
def test_datetime_typeerror():
with pytest.raises(TypeError):
rdt.make_datetime('2011', 1, 1, 0, 0, 0, 0)
rdt.make_datetime("2011", 1, 1, 0, 0, 0, 0)
@given(dt=datetimes())
@ -219,17 +230,20 @@ def test_datetime_from_timestamp_tzinfo():
assert d1.tzinfo is d2.tzinfo
@pytest.mark.parametrize('args', [
(0, 0, 0),
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(1, -1, 0),
(-1, 1, 0),
(0, 0, 123456),
(0, 0, -123456),
])
@pytest.mark.parametrize(
"args",
[
(0, 0, 0),
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(1, -1, 0),
(-1, 1, 0),
(0, 0, 123456),
(0, 0, -123456),
],
)
def test_delta(args):
act = pdt.timedelta(*args)
exp = rdt.make_delta(*args)
@ -245,24 +259,29 @@ def test_delta_accessors(td):
assert act == exp
@pytest.mark.parametrize('args,err_type', [
((MAX_DAYS + 1, 0, 0), OverflowError),
((MIN_DAYS - 1, 0, 0), OverflowError),
((0, MAX_SECONDS + 1, 0), OverflowError),
((0, MIN_SECONDS - 1, 0), OverflowError),
((0, 0, MAX_MICROSECONDS + 1), OverflowError),
((0, 0, MIN_MICROSECONDS - 1), OverflowError),
(('0', 0, 0), TypeError),
((0, '0', 0), TypeError),
((0, 0, '0'), TypeError),
])
@pytest.mark.parametrize(
"args,err_type",
[
((MAX_DAYS + 1, 0, 0), OverflowError),
((MIN_DAYS - 1, 0, 0), OverflowError),
((0, MAX_SECONDS + 1, 0), OverflowError),
((0, MIN_SECONDS - 1, 0), OverflowError),
((0, 0, MAX_MICROSECONDS + 1), OverflowError),
((0, 0, MIN_MICROSECONDS - 1), OverflowError),
(("0", 0, 0), TypeError),
((0, "0", 0), TypeError),
((0, 0, "0"), TypeError),
],
)
def test_delta_err(args, err_type):
with pytest.raises(err_type):
rdt.make_delta(*args)
def test_issue_219():
rdt.issue_219()
def test_tz_class():
tzi = rdt.TzClass()
@ -272,9 +291,9 @@ def test_tz_class():
assert dt.utcoffset() == pdt.timedelta(hours=1)
assert dt.dst() is None
def test_tz_class_introspection():
tzi = rdt.TzClass()
assert tzi.__class__ == rdt.TzClass
assert repr(tzi).startswith('<rustapi_module.datetime.TzClass object at')
assert repr(tzi).startswith("<rustapi_module.datetime.TzClass object at")

View file

@ -1,24 +1,25 @@
from rustapi_module import othermod
from hypothesis import given, assume
from hypothesis import strategies as st
from rustapi_module import othermod
INTEGER32_ST = st.integers(min_value=(-(2 ** 31)), max_value=(2 ** 31 - 1))
USIZE_ST = st.integers(min_value=othermod.USIZE_MIN, max_value=othermod.USIZE_MAX)
INTEGER32_ST = st.integers(min_value=(-(2**31)), max_value=(2**31 - 1))
USIZE_ST = st.integers(min_value=othermod.USIZE_MIN,
max_value=othermod.USIZE_MAX)
@given(x=INTEGER32_ST)
def test_double(x):
expected = x*2
assume(-(2**31) <= expected <= (2**31 - 1))
expected = x * 2
assume(-(2 ** 31) <= expected <= (2 ** 31 - 1))
assert othermod.double(x) == expected
def test_modclass():
# Test that the repr of the class itself doesn't crash anything
repr(othermod.ModClass)
assert isinstance(othermod.ModClass, type)
def test_modclass_instance():
mi = othermod.ModClass()
@ -28,9 +29,9 @@ def test_modclass_instance():
assert isinstance(mi, othermod.ModClass)
assert isinstance(mi, object)
@given(x=USIZE_ST)
def test_modclas_noop(x):
mi = othermod.ModClass()
assert mi.noop(x) == x

View file

@ -11,7 +11,7 @@ use crate::ffi;
use crate::instance;
use crate::object::PyObject;
use crate::objectprotocol::ObjectProtocol;
use crate::python::{IntoPyPointer, Python, ToPyPointer, NonNullPyObject};
use crate::python::{IntoPyPointer, NonNullPyObject, Python, ToPyPointer};
use crate::pythonrun;
use crate::typeob::PyTypeCreate;
use crate::typeob::{PyTypeInfo, PyTypeObject};
@ -122,8 +122,10 @@ impl<T> Py<T> {
#[inline]
pub unsafe fn from_owned_ptr_or_panic(ptr: *mut ffi::PyObject) -> Py<T> {
match NonNull::new(ptr) {
Some(nonnull_ptr) => { Py(nonnull_ptr, std::marker::PhantomData) },
None => { crate::err::panic_after_error(); }
Some(nonnull_ptr) => Py(nonnull_ptr, std::marker::PhantomData),
None => {
crate::err::panic_after_error();
}
}
}
@ -133,8 +135,8 @@ impl<T> Py<T> {
/// Unsafe because the pointer might be invalid.
pub unsafe fn from_owned_ptr_or_err(py: Python, ptr: *mut ffi::PyObject) -> PyResult<Py<T>> {
match NonNull::new(ptr) {
Some(nonnull_ptr) => { Ok(Py(nonnull_ptr, std::marker::PhantomData)) },
None => { Err(PyErr::fetch(py)) }
Some(nonnull_ptr) => Ok(Py(nonnull_ptr, std::marker::PhantomData)),
None => Err(PyErr::fetch(py)),
}
}

View file

@ -9,7 +9,7 @@ use crate::conversion::{
use crate::err::{PyDowncastError, PyErr, PyResult};
use crate::ffi;
use crate::instance::{AsPyRef, PyObjectWithToken};
use crate::python::{IntoPyPointer, Python, ToPyPointer, NonNullPyObject};
use crate::python::{IntoPyPointer, NonNullPyObject, Python, ToPyPointer};
use crate::pythonrun;
use crate::types::{PyDict, PyObjectRef, PyTuple};
@ -46,8 +46,10 @@ impl PyObject {
#[inline]
pub unsafe fn from_owned_ptr_or_panic(_py: Python, ptr: *mut ffi::PyObject) -> PyObject {
match NonNull::new(ptr) {
Some(nonnull_ptr) => { PyObject(nonnull_ptr) },
None => { crate::err::panic_after_error(); }
Some(nonnull_ptr) => PyObject(nonnull_ptr),
None => {
crate::err::panic_after_error();
}
}
}
@ -56,8 +58,8 @@ impl PyObject {
/// Returns `Err(PyErr)` if the pointer is `null`.
pub unsafe fn from_owned_ptr_or_err(py: Python, ptr: *mut ffi::PyObject) -> PyResult<PyObject> {
match NonNull::new(ptr) {
Some(nonnull_ptr) => { Ok(PyObject(nonnull_ptr)) },
None => { Err(PyErr::fetch(py)) }
Some(nonnull_ptr) => Ok(PyObject(nonnull_ptr)),
None => Err(PyErr::fetch(py)),
}
}
@ -66,8 +68,8 @@ impl PyObject {
/// Returns `None` if the pointer is `null`.
pub unsafe fn from_owned_ptr_or_opt(_py: Python, ptr: *mut ffi::PyObject) -> Option<PyObject> {
match NonNull::new(ptr) {
Some(nonnull_ptr) => { Some(PyObject(nonnull_ptr)) },
None => { None }
Some(nonnull_ptr) => Some(PyObject(nonnull_ptr)),
None => None,
}
}

View file

@ -11,11 +11,11 @@ use crate::pythonrun::{self, GILGuard};
use crate::typeob::PyTypeCreate;
use crate::typeob::{PyTypeInfo, PyTypeObject};
use crate::types::{PyDict, PyModule, PyObjectRef, PyType};
use std;
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr::NonNull;
use std;
pub type NonNullPyObject = NonNull<ffi::PyObject>;

View file

@ -1,6 +1,6 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::ffi;
use crate::python::{Python, NonNullPyObject};
use crate::python::{NonNullPyObject, Python};
use crate::types::PyObjectRef;
use spin;
use std::{any, marker, rc, sync};