gh-158102: Add tests for the slice C API - #158120
Conversation
Test PySlice_Check(), PySlice_New(), PySlice_GetIndices(), PySlice_Unpack(), PySlice_AdjustIndices() and PySlice_GetIndicesEx() -- both the macro and the deprecated function. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
vstinner
left a comment
There was a problem hiding this comment.
Thanks for writing an exhaustive test suite for the PySlice C API. It's more complicated than what I expected! Here is a first review.
| SIZE_MAX = sys.maxsize | ||
| SIZE_MIN = -sys.maxsize - 1 |
There was a problem hiding this comment.
In C, SIZE_MAX is a different constant: size_t maximum (_testcapi.SIZE_MAX). Since this file tests C functions, it's confusing.
Can you rename the constants to SSIZE_MAX and SSIZE_MIN?
The _testcapi uses different names: PY_SSIZE_T_MAX and PY_SSIZE_T_MIN.
| VALUES = [None, 0, 1, 2, 3, 5, 7, -1, -2, -3, -5, -7] | ||
| STEPS = [None, 1, 2, 3, 5, -1, -2, -3, -5] | ||
| LENGTHS = [0, 1, 2, 5, 10] |
There was a problem hiding this comment.
./python -m test test_capi.test_slice -v -m test_adjustindices takes 700 ms. I'm not sure that it's worth it to test so many combinations:
| VALUES = [None, 0, 1, 2, 3, 5, 7, -1, -2, -3, -5, -7] | |
| STEPS = [None, 1, 2, 3, 5, -1, -2, -3, -5] | |
| LENGTHS = [0, 1, 2, 5, 10] | |
| VALUES = [None, 0, 1, 3, 7, -1, -3, -7] | |
| STEPS = [None, 1, 3, 5, -1, -3, -5] | |
| LENGTHS = [0, 1, 3, 10] |
| # The step is asserted to be neither zero nor less than | ||
| # -PY_SSIZE_T_MAX. | ||
| # CRASHES adjust(10, 0, 10, 0) | ||
| # CRASHES adjust(10, 0, 10, SIZE_MIN) |
There was a problem hiding this comment.
PySlice_Unpack() replaces step=PY_SSIZE_T_MIN with step=PY_SSIZE_T_MAX. Would it be acceptable do the same here? Well, if we change the behavior, I would prefer to do it in a separated PR. It's better to test the current behavior without changing code first, as you do :-)
There was a problem hiding this comment.
Normally, PySlice_AdjustIndices() is always called after PySlice_Unpack(), which guarantees that step is never 0 or less than -SSIZE_MAX. An additional check here would be redundant.
|
|
||
| # The step is asserted to be neither zero nor less than | ||
| # -PY_SSIZE_T_MAX. | ||
| # CRASHES adjust(10, 0, 10, 0) |
There was a problem hiding this comment.
It's unfortunate that the function cannot fail. Can you update PySlice_AdjustIndices() document to mention that step must not be equal to zero and must not be equal to PY_SSIZE_T_MIN?
There was a problem hiding this comment.
I'll update the docs if they already do not mentioned this.
| self.assertEqual(adjust(10, SIZE_MIN, SIZE_MAX, 1), (10, 0, 10)) | ||
| self.assertEqual(adjust(10, SIZE_MAX, SIZE_MIN, -1), (10, 9, -1)) | ||
| self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0)) | ||
| self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1)) |
There was a problem hiding this comment.
Can you add tests on negative length?
|
|
||
| // Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices() | ||
| // and for PySlice_GetIndicesEx() implemented as a macro. | ||
| #if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API) |
There was a problem hiding this comment.
The limited C API can now be tested on Free Threading. You can also define Py_LIMITED_API if Py_GIL_DISABLED is defined, Py_TARGET_ABI3T is set to Py_LIMITED_API in this case by Include/pyabi.h.
| /* Test PySlice_GetIndicesEx() implemented as a macro using PySlice_Unpack() | ||
| * and PySlice_AdjustIndices(). */ | ||
| static PyObject * | ||
| slice_getindicesex(PyObject *Py_UNUSED(module), PyObject *args) |
There was a problem hiding this comment.
I suggest to rename this function to "slice_getindicesex_macro()", or rename "slice_getindicesex_deprecated()" to "slice_getindicesex_func()". Same remark for the test case class name.
| self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10), | ||
| (7, 1, -SIZE_MAX, 1)) | ||
| self.assertEqual(getindicesex(slice(7, 1, SIZE_MIN), 10), | ||
| (7, 1, -SIZE_MAX, 1)) |
There was a problem hiding this comment.
Sadly, PySlice_GetIndicesEx() also accepts negative length. You may also add tests on negative length.
Co-authored-by: Victor Stinner <vstinner@python.org>
Also remove trailing whitespace added by a suggestion.
Rename SIZE_MAX and SIZE_MIN to SSIZE_MAX and SSIZE_MIN, and the PySlice_GetIndicesEx() wrappers to slice_getindicesex_macro() and slice_getindicesex_func(). Test the limited C API also on free threading. Reduce the number of tested combinations. Add tests for zero and negative length, and document that the length must not be negative.
vstinner
left a comment
There was a problem hiding this comment.
LGTM. Thanks for the update. Tests should now cover all code paths.
|
Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15. |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
|
Sorry, @serhiy-storchaka, I could not cleanly backport this to |
Test
PySlice_Check(),PySlice_New(),PySlice_GetIndices(),PySlice_Unpack(),PySlice_AdjustIndices()andPySlice_GetIndicesEx().PySlice_GetIndicesEx()is tested both as a macro and as a deprecated function, including the case when__index__()resizes the sequence whose size is passed as the length: the macro evaluates it afterPySlice_Unpack(), the function before the call.