Skip to content

Commit ab2c947

Browse files
sobolevnambv
andauthored
gh-94808: add tests covering PySequence_[InPlace_]Repeat (#99196)
Drop the basic repeat/in-place-repeat tests: test_abstract.py now covers those paths via _testlimitedcapi (including corner counts, MemoryError, TypeError and NULL cases). Keep the subtype and error tests unique to this PR. Add test_sequence_repeat_numeric_fallback for the previously untested nb_multiply fallback in PySequence_Repeat. Assert exact result types with assertIs: comparing against CustomList(...) only checks contents, and repeat of a builtin subtype returns the exact builtin type. Co-authored-by: Muse Code (Meta Muse Spark) Co-authored-by: Lukasz Langa <ambv@meta.com>
1 parent 1705e51 commit ab2c947

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

‎Lib/test/test_capi/test_misc.py‎

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,119 @@ def test_buildvalue_ints(self):
404404
def test_buildvalue_N(self):
405405
_testcapi.test_buildvalue_N()
406406

407+
def test_sequence_repeat_subtypes(self):
408+
# List subtype:
409+
class CustomList(list): ...
410+
411+
sequence = CustomList(['a', 'b'])
412+
repeated_sequence = _testcapi.sequence_repeat(sequence, 3)
413+
self.assertIs(type(repeated_sequence), list)
414+
self.assertEqual(repeated_sequence, sequence * 3)
415+
self.assertEqual(sequence, CustomList(['a', 'b']))
416+
417+
# Tuple subtype:
418+
class CustomTuple(tuple): ...
419+
420+
sequence = CustomTuple(('a', 'b'))
421+
repeated_sequence = _testcapi.sequence_repeat(sequence, 3)
422+
self.assertIs(type(repeated_sequence), tuple)
423+
self.assertEqual(repeated_sequence, sequence * 3)
424+
self.assertEqual(sequence, CustomTuple(('a', 'b')))
425+
426+
# String subtype:
427+
class CustomStr(str): ...
428+
429+
sequence = CustomStr('abc')
430+
repeated_sequence = _testcapi.sequence_repeat(sequence, 2)
431+
self.assertIs(type(repeated_sequence), str)
432+
self.assertEqual(repeated_sequence, sequence * 2)
433+
self.assertEqual(sequence, CustomStr('abc'))
434+
435+
def test_sequence_repeat_numeric_fallback(self):
436+
# PySequence_Repeat falls back to nb_multiply (__mul__) when the
437+
# object looks like a sequence but has no sq_repeat slot.
438+
class CustomSequenceWithMul:
439+
def __getitem__(self, index):
440+
return index # so that it looks like a sequence
441+
442+
def __mul__(self, count):
443+
return f'repeated {count} times'
444+
445+
sequence = CustomSequenceWithMul()
446+
self.assertEqual(_testcapi.sequence_repeat(sequence, 3),
447+
'repeated 3 times')
448+
449+
def test_sequence_repeat_errors(self):
450+
# Corner case, custom subtype returns `NotImplemented`:
451+
class CustomListWithNI(list):
452+
def __mul__(self, other):
453+
return NotImplemented
454+
455+
sequence = CustomListWithNI(['a', 'b'])
456+
with self.assertRaises(TypeError):
457+
_testcapi.sequence_repeat(sequence, 3)
458+
self.assertEqual(sequence, CustomListWithNI(['a', 'b'])) # not changed
459+
460+
# Non-sequences do not work (even with `__mul__`):
461+
class CustomTypeWithMul:
462+
def __mul__(self, other):
463+
return 5 # won't be called
464+
465+
not_sequence = CustomTypeWithMul()
466+
with self.assertRaises(TypeError):
467+
_testcapi.sequence_repeat(not_sequence, 1)
468+
469+
def test_sequence_inplace_repeat_subtypes(self):
470+
# List subtype:
471+
class CustomList(list): ...
472+
473+
sequence = CustomList(['a', 'b'])
474+
repeated_sequence = _testcapi.sequence_inplace_repeat(sequence, 3)
475+
self.assertIs(repeated_sequence, sequence)
476+
self.assertEqual(repeated_sequence, ['a', 'b', 'a', 'b', 'a', 'b'])
477+
478+
# Tuple subtype:
479+
class CustomTuple(tuple): ...
480+
481+
sequence = CustomTuple(('a', 'b'))
482+
repeated_sequence = _testcapi.sequence_inplace_repeat(sequence, 3)
483+
self.assertIs(type(repeated_sequence), tuple)
484+
self.assertEqual(repeated_sequence, sequence * 3)
485+
self.assertEqual(sequence, CustomTuple(('a', 'b')))
486+
487+
# String subtype:
488+
class CustomStr(str): ...
489+
490+
sequence = CustomStr('abc')
491+
repeated_sequence = _testcapi.sequence_inplace_repeat(sequence, 2)
492+
self.assertIs(type(repeated_sequence), str)
493+
self.assertEqual(repeated_sequence, sequence * 2)
494+
self.assertEqual(sequence, CustomStr('abc'))
495+
496+
def test_sequence_inplace_repeat_errors(self):
497+
# Corner case, custom subtype returns `NotImplemented`:
498+
class CustomListWithNI(list):
499+
def __mul__(self, other):
500+
return NotImplemented
501+
def __imul__(self, other):
502+
return NotImplemented
503+
504+
sequence = CustomListWithNI(['a', 'b'])
505+
with self.assertRaises(TypeError):
506+
_testcapi.sequence_inplace_repeat(sequence, 3)
507+
self.assertEqual(sequence, CustomListWithNI(['a', 'b'])) # not changed
508+
509+
# Non-sequences do not work (even with `__mul__` and `__imul__`):
510+
class CustomTypeWithMul:
511+
def __mul__(self, other):
512+
return 0
513+
def __imul__(self, other):
514+
return 1 # won't be called
515+
516+
not_sequence = CustomTypeWithMul()
517+
with self.assertRaises(TypeError):
518+
_testcapi.sequence_inplace_repeat(not_sequence, 1)
519+
407520
def test_trashcan_subclass(self):
408521
# bpo-35983: Check that the trashcan mechanism for "list" is NOT
409522
# activated when its tp_dealloc is being called by a subclass

‎Modules/_testcapimodule.c‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,30 @@ py_w_stopcode(PyObject *self, PyObject *args)
14911491
#endif
14921492

14931493

1494+
static PyObject *
1495+
sequence_repeat(PyObject* self, PyObject *args)
1496+
{
1497+
PyObject *sequence;
1498+
Py_ssize_t count;
1499+
if (!PyArg_ParseTuple(args, "On", &sequence, &count)) {
1500+
return NULL;
1501+
}
1502+
1503+
return PySequence_Repeat(sequence, count);
1504+
}
1505+
1506+
static PyObject *
1507+
sequence_inplace_repeat(PyObject* self, PyObject *args)
1508+
{
1509+
PyObject *sequence;
1510+
Py_ssize_t count;
1511+
if (!PyArg_ParseTuple(args, "On", &sequence, &count)) {
1512+
return NULL;
1513+
}
1514+
1515+
return PySequence_InPlaceRepeat(sequence, count);
1516+
}
1517+
14941518
static PyObject *
14951519
test_pythread_tss_key_state(PyObject *self, PyObject *args)
14961520
{
@@ -2949,6 +2973,8 @@ static PyMethodDef TestMethods[] = {
29492973
#ifdef W_STOPCODE
29502974
{"W_STOPCODE", py_w_stopcode, METH_VARARGS},
29512975
#endif
2976+
{"sequence_repeat", sequence_repeat, METH_VARARGS},
2977+
{"sequence_inplace_repeat", sequence_inplace_repeat, METH_VARARGS},
29522978
{"test_pythread_tss_key_state", test_pythread_tss_key_state, METH_VARARGS},
29532979
{"bad_get", bad_get, METH_VARARGS},
29542980
{"meth_varargs", meth_varargs, METH_VARARGS},

0 commit comments

Comments
 (0)