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 hypothesis>=3.55
pytest>=3.5.0 pytest>=3.5.0
setuptools-rust>=0.10.2 setuptools-rust>=0.10.2
black

View file

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

View file

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

View file

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

View file

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

View file

@ -1,19 +1,20 @@
import rustapi_module.datetime as rdt
import sys
import datetime as pdt import datetime as pdt
import sys
import pytest import pytest
import rustapi_module.datetime as rdt
from hypothesis import given from hypothesis import given
from hypothesis import strategies as st from hypothesis import strategies as st
from hypothesis.strategies import dates, datetimes from hypothesis.strategies import dates, datetimes
# Constants # Constants
def _get_utc(): def _get_utc():
timezone = getattr(pdt, 'timezone', None) timezone = getattr(pdt, "timezone", None)
if timezone: if timezone:
return timezone.utc return timezone.utc
else: else:
class UTC(pdt.tzinfo): class UTC(pdt.tzinfo):
def utcoffset(self, dt): def utcoffset(self, dt):
return pdt.timedelta(0) return pdt.timedelta(0)
@ -26,6 +27,7 @@ def _get_utc():
return UTC() return UTC()
UTC = _get_utc() UTC = _get_utc()
MAX_SECONDS = int(pdt.timedelta.max.total_seconds()) MAX_SECONDS = int(pdt.timedelta.max.total_seconds())
@ -42,19 +44,22 @@ except Exception:
MAX_MICROSECONDS = int(pdt.timedelta.max.total_seconds() * 1e6) MAX_MICROSECONDS = int(pdt.timedelta.max.total_seconds() * 1e6)
MIN_MICROSECONDS = int(pdt.timedelta.min.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 # Helper functions
get_timestamp = getattr(pdt.datetime, 'timestamp', None) get_timestamp = getattr(pdt.datetime, "timestamp", None)
if get_timestamp is None: if get_timestamp is None:
def get_timestamp(dt): def get_timestamp(dt):
# Python 2 compatibility # Python 2 compatibility
return (dt - pdt.datetime(1970, 1, 1)).total_seconds() return (dt - pdt.datetime(1970, 1, 1)).total_seconds()
xfail_date_bounds = pytest.mark.xfail(sys.version_info < (3, 6), xfail_date_bounds = pytest.mark.xfail(
reason="Date bounds were not checked in the C constructor prior to version 3.6") sys.version_info < (3, 6),
reason="Date bounds were not checked in the C constructor prior to version 3.6",
)
# Tests # Tests
def test_date(): def test_date():
@ -81,11 +86,14 @@ def test_date_from_timestamp(d):
assert rdt.date_from_timestamp(int(ts)) == pdt.date.fromtimestamp(ts) assert rdt.date_from_timestamp(int(ts)) == pdt.date.fromtimestamp(ts)
@pytest.mark.parametrize('args, kwargs', [ @pytest.mark.parametrize(
((0, 0, 0, 0, None), {}), "args, kwargs",
((1, 12, 14, 124731), {}), [
((1, 12, 14, 124731), {'tzinfo': UTC}), ((0, 0, 0, 0, None), {}),
]) ((1, 12, 14, 124731), {}),
((1, 12, 14, 124731), {"tzinfo": UTC}),
],
)
def test_time(args, kwargs): def test_time(args, kwargs):
act = rdt.make_time(*args, **kwargs) act = rdt.make_time(*args, **kwargs)
exp = pdt.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.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): def test_time_fold(fold):
t = rdt.time_with_fold(0, 0, 0, 0, None, fold) t = rdt.time_with_fold(0, 0, 0, 0, None, fold)
assert t.fold == fold assert t.fold == fold
@pytest.mark.xfail @pytest.mark.xfail
@pytest.mark.parametrize('args', [ @pytest.mark.parametrize(
(-1, 0, 0, 0), "args", [(-1, 0, 0, 0), (0, -1, 0, 0), (0, 0, -1, 0), (0, 0, 0, -1)]
(0, -1, 0, 0), )
(0, 0, -1, 0),
(0, 0, 0, -1),
])
def test_invalid_time_fails_xfail(args): def test_invalid_time_fails_xfail(args):
with pytest.raises(ValueError): with pytest.raises(ValueError):
rdt.make_time(*args) rdt.make_time(*args)
@xfail_date_bounds @xfail_date_bounds
@pytest.mark.parametrize('args', [ @pytest.mark.parametrize(
(24, 0, 0, 0), "args",
(25, 0, 0, 0), [
(0, 60, 0, 0), (24, 0, 0, 0),
(0, 61, 0, 0), (25, 0, 0, 0),
(0, 0, 60, 0), (0, 60, 0, 0),
(0, 0, 61, 0), (0, 61, 0, 0),
(0, 0, 0, 1000000) (0, 0, 60, 0),
]) (0, 0, 61, 0),
(0, 0, 0, 1000000),
],
)
def test_invalid_time_fails(args): def test_invalid_time_fails(args):
with pytest.raises(ValueError): with pytest.raises(ValueError):
rdt.make_time(*args) rdt.make_time(*args)
@pytest.mark.parametrize('args', [ @pytest.mark.parametrize(
('0', 0, 0, 0), "args",
(0, '0', 0, 0), [
(0, 0, '0', 0), ("0", 0, 0, 0),
(0, 0, 0, '0'), (0, "0", 0, 0),
(0, 0, 0, 0, 'UTC') (0, 0, "0", 0),
]) (0, 0, 0, "0"),
(0, 0, 0, 0, "UTC"),
],
)
def test_time_typeerror(args): def test_time_typeerror(args):
with pytest.raises(TypeError): with pytest.raises(TypeError):
rdt.make_time(*args) rdt.make_time(*args)
@pytest.mark.parametrize('args, kwargs', [ @pytest.mark.parametrize(
((2017, 9, 1, 12, 45, 30, 0), {}), "args, kwargs",
((2017, 9, 1, 12, 45, 30, 0), {'tzinfo': UTC}), [((2017, 9, 1, 12, 45, 30, 0), {}), ((2017, 9, 1, 12, 45, 30, 0), {"tzinfo": UTC})],
]) )
def test_datetime(args, kwargs): def test_datetime(args, kwargs):
act = rdt.make_datetime(*args, **kwargs) act = rdt.make_datetime(*args, **kwargs)
exp = pdt.datetime(*args, **kwargs) exp = pdt.datetime(*args, **kwargs)
@ -176,7 +187,7 @@ def test_datetime(args, kwargs):
@given(dt=st.datetimes()) @given(dt=st.datetimes())
def test_datetime_tuple(dt): def test_datetime_tuple(dt):
act = rdt.get_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 assert act == exp
@ -202,7 +213,7 @@ def test_invalid_datetime_fails():
def test_datetime_typeerror(): def test_datetime_typeerror():
with pytest.raises(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()) @given(dt=datetimes())
@ -219,17 +230,20 @@ def test_datetime_from_timestamp_tzinfo():
assert d1.tzinfo is d2.tzinfo assert d1.tzinfo is d2.tzinfo
@pytest.mark.parametrize('args', [ @pytest.mark.parametrize(
(0, 0, 0), "args",
(1, 0, 0), [
(-1, 0, 0), (0, 0, 0),
(0, 1, 0), (1, 0, 0),
(0, -1, 0), (-1, 0, 0),
(1, -1, 0), (0, 1, 0),
(-1, 1, 0), (0, -1, 0),
(0, 0, 123456), (1, -1, 0),
(0, 0, -123456), (-1, 1, 0),
]) (0, 0, 123456),
(0, 0, -123456),
],
)
def test_delta(args): def test_delta(args):
act = pdt.timedelta(*args) act = pdt.timedelta(*args)
exp = rdt.make_delta(*args) exp = rdt.make_delta(*args)
@ -245,24 +259,29 @@ def test_delta_accessors(td):
assert act == exp assert act == exp
@pytest.mark.parametrize('args,err_type', [ @pytest.mark.parametrize(
((MAX_DAYS + 1, 0, 0), OverflowError), "args,err_type",
((MIN_DAYS - 1, 0, 0), OverflowError), [
((0, MAX_SECONDS + 1, 0), OverflowError), ((MAX_DAYS + 1, 0, 0), OverflowError),
((0, MIN_SECONDS - 1, 0), OverflowError), ((MIN_DAYS - 1, 0, 0), OverflowError),
((0, 0, MAX_MICROSECONDS + 1), OverflowError), ((0, MAX_SECONDS + 1, 0), OverflowError),
((0, 0, MIN_MICROSECONDS - 1), OverflowError), ((0, MIN_SECONDS - 1, 0), OverflowError),
(('0', 0, 0), TypeError), ((0, 0, MAX_MICROSECONDS + 1), OverflowError),
((0, '0', 0), TypeError), ((0, 0, MIN_MICROSECONDS - 1), OverflowError),
((0, 0, '0'), TypeError), (("0", 0, 0), TypeError),
]) ((0, "0", 0), TypeError),
((0, 0, "0"), TypeError),
],
)
def test_delta_err(args, err_type): def test_delta_err(args, err_type):
with pytest.raises(err_type): with pytest.raises(err_type):
rdt.make_delta(*args) rdt.make_delta(*args)
def test_issue_219(): def test_issue_219():
rdt.issue_219() rdt.issue_219()
def test_tz_class(): def test_tz_class():
tzi = rdt.TzClass() tzi = rdt.TzClass()
@ -272,9 +291,9 @@ def test_tz_class():
assert dt.utcoffset() == pdt.timedelta(hours=1) assert dt.utcoffset() == pdt.timedelta(hours=1)
assert dt.dst() is None assert dt.dst() is None
def test_tz_class_introspection(): def test_tz_class_introspection():
tzi = rdt.TzClass() tzi = rdt.TzClass()
assert tzi.__class__ == 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 given, assume
from hypothesis import strategies as st 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) @given(x=INTEGER32_ST)
def test_double(x): def test_double(x):
expected = x*2 expected = x * 2
assume(-(2**31) <= expected <= (2**31 - 1)) assume(-(2 ** 31) <= expected <= (2 ** 31 - 1))
assert othermod.double(x) == expected assert othermod.double(x) == expected
def test_modclass(): def test_modclass():
# Test that the repr of the class itself doesn't crash anything # Test that the repr of the class itself doesn't crash anything
repr(othermod.ModClass) repr(othermod.ModClass)
assert isinstance(othermod.ModClass, type) assert isinstance(othermod.ModClass, type)
def test_modclass_instance(): def test_modclass_instance():
mi = othermod.ModClass() mi = othermod.ModClass()
@ -28,9 +29,9 @@ def test_modclass_instance():
assert isinstance(mi, othermod.ModClass) assert isinstance(mi, othermod.ModClass)
assert isinstance(mi, object) assert isinstance(mi, object)
@given(x=USIZE_ST) @given(x=USIZE_ST)
def test_modclas_noop(x): def test_modclas_noop(x):
mi = othermod.ModClass() mi = othermod.ModClass()
assert mi.noop(x) == x assert mi.noop(x) == x

View file

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

View file

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

View file

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

View file

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