summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Berg <sebastian@sipsolutions.net>2020-09-17 11:47:31 -0500
committerSebastian Berg <sebastian@sipsolutions.net>2020-09-17 13:09:02 -0500
commite5f2ce3c6465222f3dfdc186c930b3e049a4d597 (patch)
treecd029ba40969eee3e8bbf39a30761899d3600d83
parentfc0297e3311de5f47a46767f6d6c5181c9a7b07d (diff)
downloadnumpy-e5f2ce3c6465222f3dfdc186c930b3e049a4d597.tar.gz
TST: Add test for creating an xpress like user-dtype
This includes a few error paths. Note that the test creates a new dtype ever time it is run, and dtypes in NumPy are persistent so that this leaks references.
-rw-r--r--numpy/core/src/multiarray/_multiarray_tests.c.src68
-rw-r--r--numpy/core/tests/test_dtype.py37
2 files changed, 105 insertions, 0 deletions
diff --git a/numpy/core/src/multiarray/_multiarray_tests.c.src b/numpy/core/src/multiarray/_multiarray_tests.c.src
index ea04c82bd..cbfb1e179 100644
--- a/numpy/core/src/multiarray/_multiarray_tests.c.src
+++ b/numpy/core/src/multiarray/_multiarray_tests.c.src
@@ -610,6 +610,71 @@ fromstring_null_term_c_api(PyObject *dummy, PyObject *byte_obj)
}
+/*
+ * Create a custom field dtype from an existing void one (and test some errors).
+ * The dtypes created by this function may be not be usable (or even crash
+ * while using).
+ */
+static PyObject *
+create_custom_field_dtype(PyObject *NPY_UNUSED(mod), PyObject *args)
+{
+ PyArray_Descr *dtype;
+ PyTypeObject *scalar_type;
+ PyTypeObject *original_type = NULL;
+ int error_path;
+
+ if (!PyArg_ParseTuple(args, "O!O!i",
+ &PyArrayDescr_Type, &dtype,
+ &PyType_Type, &scalar_type,
+ &error_path)) {
+ return NULL;
+ }
+ /* check that the result should be more or less valid */
+ if (dtype->type_num != NPY_VOID || dtype->fields == NULL ||
+ !PyDict_CheckExact(dtype->fields) ||
+ PyTuple_Size(dtype->names) != 1 ||
+ !PyDataType_REFCHK(dtype) ||
+ dtype->elsize != sizeof(PyObject *)) {
+ PyErr_SetString(PyExc_ValueError,
+ "Bad dtype passed to test function, must be an object "
+ "containing void with a single field.");
+ return NULL;
+ }
+
+ /* Copy and then appropriate this dtype */
+ original_type = Py_TYPE(dtype);
+ dtype = PyArray_DescrNew(dtype);
+ if (dtype == NULL) {
+ return NULL;
+ }
+
+ Py_INCREF(scalar_type);
+ Py_SETREF(dtype->typeobj, scalar_type);
+ if (error_path == 1) {
+ /* Test that we reject this, if fields was not already set */
+ Py_SETREF(dtype->fields, NULL);
+ }
+ else if (error_path == 2) {
+ /*
+ * Test that we reject this if the type is not set to something that
+ * we are pretty sure can be safely replaced.
+ */
+ Py_SET_TYPE(dtype, scalar_type);
+ }
+ else if (error_path != 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "invalid error argument to test function.");
+ }
+ if (PyArray_RegisterDataType(dtype) < 0) {
+ /* Fix original type in the error_path == 2 case. */
+ Py_SET_TYPE(dtype, original_type);
+ return NULL;
+ }
+ Py_INCREF(dtype);
+ return (PyObject *)dtype;
+}
+
+
/* check no elison for avoided increfs */
static PyObject *
incref_elide(PyObject *dummy, PyObject *args)
@@ -2081,6 +2146,9 @@ static PyMethodDef Multiarray_TestsMethods[] = {
{"fromstring_null_term_c_api",
fromstring_null_term_c_api,
METH_O, NULL},
+ {"create_custom_field_dtype",
+ create_custom_field_dtype,
+ METH_VARARGS, NULL},
{"incref_elide",
incref_elide,
METH_VARARGS, NULL},
diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py
index 2e2b0dbe2..898ceebcd 100644
--- a/numpy/core/tests/test_dtype.py
+++ b/numpy/core/tests/test_dtype.py
@@ -6,6 +6,7 @@ import gc
import numpy as np
from numpy.core._rational_tests import rational
+from numpy.core._multiarray_tests import create_custom_field_dtype
from numpy.testing import (
assert_, assert_equal, assert_array_equal, assert_raises, HAS_REFCOUNT)
from numpy.compat import pickle
@@ -1338,3 +1339,39 @@ class TestFromCTypes:
pair_type = np.dtype('{},{}'.format(*pair))
expected = np.dtype([('f0', pair[0]), ('f1', pair[1])])
assert_equal(pair_type, expected)
+
+
+class TestUserDType:
+ @pytest.mark.leaks_references(reason="dynamically creates custom dtype.")
+ def test_custom_structured_dtype(self):
+ class mytype:
+ pass
+
+ blueprint = np.dtype([("field", object)])
+ dt = create_custom_field_dtype(blueprint, mytype, 0)
+ assert dt.type == mytype
+ # We cannot (currently) *create* this dtype with `np.dtype` because
+ # mytype does not inherit from `np.generic`. This seems like an
+ # unnecessary restriction, but one that has been around forever:
+ assert np.dtype(mytype) == np.dtype("O")
+
+ with pytest.raises(RuntimeError):
+ # Registering a second time should fail
+ create_custom_field_dtype(blueprint, mytype, 0)
+
+ def test_custom_structured_dtype_errors(self):
+ class mytype:
+ pass
+
+ blueprint = np.dtype([("field", object)])
+
+ with pytest.raises(ValueError):
+ # Tests what happens if fields are unset during creation
+ # which is currently rejected due to the containing object
+ # (see PyArray_RegisterDataType).
+ create_custom_field_dtype(blueprint, mytype, 1)
+
+ with pytest.raises(RuntimeError):
+ # Tests that a dtype must have its type field set up to np.dtype
+ # or in this case a builtin instance.
+ create_custom_field_dtype(blueprint, mytype, 2)