Skip to content

Commit 4036afb

Browse files
sobolevnambv
andauthored
gh-94808: add tests covering PySequence_[InPlace]Concat (#99319)
Co-authored-by: Muse Code (Meta Muse Spark) Co-authored-by: Lukasz Langa <ambv@meta.com>
1 parent aed4ad8 commit 4036afb

1 file changed

Lines changed: 215 additions & 0 deletions

File tree

‎Lib/test/test_operator.py‎

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,221 @@ def __getitem__(self, other): return 5 # so that C is a sequence
560560
self.assertEqual(operator.ixor (c, 5), "ixor")
561561
self.assertEqual(operator.iconcat (c, c), "iadd")
562562

563+
def test_concat_subclasses(self):
564+
operator = self.module
565+
566+
# Simple cases:
567+
data1 = [1, 2]
568+
data2 = ['a', 'b']
569+
self.assertEqual(operator.concat(data1, data2), [1, 2, 'a', 'b'])
570+
self.assertEqual(operator.concat(data1, data2), data1 + data2)
571+
self.assertEqual(data1, [1, 2]) # must not change
572+
self.assertEqual(data2, ['a', 'b']) # must not change
573+
574+
data1 = (1, 2)
575+
data2 = ('a', 'b')
576+
self.assertEqual(operator.concat(data1, data2), (1, 2, 'a', 'b'))
577+
self.assertEqual(operator.concat(data1, data2), data1 + data2)
578+
self.assertEqual(data1, (1, 2)) # must not change
579+
self.assertEqual(data2, ('a', 'b')) # must not change
580+
581+
data1 = '12'
582+
data2 = 'ab'
583+
self.assertEqual(operator.concat(data1, data2), '12ab')
584+
self.assertEqual(operator.concat(data1, data2), data1 + data2)
585+
self.assertEqual(data1, '12') # must not change
586+
self.assertEqual(data2, 'ab') # must not change
587+
588+
# Subclasses:
589+
class ListSubclass(list):
590+
pass
591+
592+
data1 = ListSubclass([1, 2])
593+
data2 = ListSubclass(['a', 'b'])
594+
res = operator.concat(data1, data2)
595+
self.assertIs(type(res), list)
596+
self.assertEqual(res, [1, 2, 'a', 'b'])
597+
self.assertEqual(data1, ListSubclass([1, 2])) # must not change
598+
self.assertEqual(data2, ListSubclass(['a', 'b'])) # must not change
599+
600+
data1 = ListSubclass([1, 2])
601+
data2 = ['a', 'b']
602+
res = operator.concat(data1, data2)
603+
self.assertIs(type(res), list)
604+
self.assertEqual(res, [1, 2, 'a', 'b'])
605+
self.assertEqual(data1, ListSubclass([1, 2])) # must not change
606+
self.assertEqual(data2, ['a', 'b']) # must not change
607+
608+
data1 = [1, 2]
609+
data2 = ListSubclass(['a', 'b'])
610+
res = operator.concat(data1, data2)
611+
self.assertIs(type(res), list)
612+
self.assertEqual(res, [1, 2, 'a', 'b'])
613+
self.assertEqual(data1, [1, 2]) # must not change
614+
self.assertEqual(data2, ListSubclass(['a', 'b'])) # must not change
615+
616+
# Custom type with `__add__`:
617+
class TupleSubclass(tuple):
618+
def __add__(self, other):
619+
return TupleSubclass(list(other) + list(self))
620+
621+
data1 = TupleSubclass([1, 2])
622+
data2 = TupleSubclass(['a', 'b'])
623+
res = operator.concat(data1, data2)
624+
self.assertIsInstance(res, TupleSubclass)
625+
self.assertEqual(res, TupleSubclass(['a', 'b', 1, 2]))
626+
self.assertEqual(data1, TupleSubclass([1, 2])) # must not change
627+
self.assertEqual(data2, TupleSubclass(['a', 'b'])) # must not change
628+
629+
data1 = TupleSubclass([1, 2])
630+
data2 = ('a', 'b')
631+
res = operator.concat(data1, data2)
632+
self.assertIsInstance(res, TupleSubclass)
633+
self.assertEqual(res, TupleSubclass(['a', 'b', 1, 2]))
634+
self.assertEqual(data1, TupleSubclass([1, 2])) # must not change
635+
self.assertEqual(data2, ('a', 'b')) # must not change
636+
637+
data1 = (1, 2)
638+
data2 = TupleSubclass(['a', 'b'])
639+
res = operator.concat(data1, data2)
640+
self.assertIs(type(res), tuple)
641+
self.assertEqual(res, (1, 2, 'a', 'b'))
642+
self.assertEqual(data1, (1, 2)) # must not change
643+
self.assertEqual(data2, TupleSubclass(['a', 'b'])) # must not change
644+
645+
# Corner cases:
646+
self.assertEqual(operator.concat([1, 2], []), [1, 2])
647+
self.assertEqual(operator.concat([], [1, 2]), [1, 2])
648+
self.assertEqual(operator.concat([], []), [])
649+
650+
# Type errors:
651+
self.assertRaises(TypeError, operator.concat, [], 1)
652+
self.assertRaises(TypeError, operator.concat, 1, [])
653+
self.assertRaises(TypeError, operator.concat, 1, 1)
654+
self.assertRaises(TypeError, operator.concat, (), [])
655+
self.assertRaises(TypeError, operator.concat, [], ())
656+
657+
# Returns `NotImplemented`:
658+
class BrokenSeq(tuple):
659+
def __add__(self, other):
660+
return NotImplemented
661+
662+
self.assertRaises(TypeError, operator.concat, BrokenSeq(), BrokenSeq())
663+
664+
def test_iconcat(self):
665+
operator = self.module
666+
667+
# Simple cases:
668+
data1 = [1, 2, 3]
669+
data2 = ['a', 'b']
670+
res = operator.iconcat(data1, data2)
671+
self.assertEqual(res, [1, 2, 3, 'a', 'b'])
672+
self.assertEqual(data1, [1, 2, 3, 'a', 'b']) # must change
673+
self.assertEqual(data2, ['a', 'b']) # must not change
674+
675+
data1 = (1, 2)
676+
data2 = ('a', 'b')
677+
self.assertEqual(operator.iconcat(data1, data2), (1, 2, 'a', 'b'))
678+
self.assertEqual(operator.iconcat(data1, data2), data1 + data2)
679+
self.assertEqual(data1, (1, 2)) # must not change
680+
self.assertEqual(data2, ('a', 'b')) # must not change
681+
682+
data1 = '12'
683+
data2 = 'ab'
684+
self.assertEqual(operator.iconcat(data1, data2), '12ab')
685+
self.assertEqual(operator.iconcat(data1, data2), data1 + data2)
686+
self.assertEqual(data1, '12') # must not change
687+
self.assertEqual(data2, 'ab') # must not change
688+
689+
# Subclasses:
690+
class ListSubclass(list):
691+
pass
692+
693+
data1 = ListSubclass([1, 2])
694+
data2 = ListSubclass(['a', 'b'])
695+
res = operator.iconcat(data1, data2)
696+
self.assertIsInstance(res, ListSubclass)
697+
self.assertEqual(res, ListSubclass([1, 2, 'a', 'b']))
698+
self.assertIsInstance(data1, ListSubclass)
699+
self.assertEqual(data1, ListSubclass([1, 2, 'a', 'b'])) # must change
700+
self.assertEqual(data2, ListSubclass(['a', 'b'])) # must not change
701+
702+
data1 = ListSubclass([1, 2])
703+
data2 = ['a', 'b']
704+
res = operator.iconcat(data1, data2)
705+
self.assertIsInstance(res, ListSubclass)
706+
self.assertEqual(res, ListSubclass([1, 2, 'a', 'b']))
707+
self.assertIsInstance(data1, ListSubclass)
708+
self.assertEqual(data1, ListSubclass([1, 2, 'a', 'b'])) # must change
709+
self.assertEqual(data2, ['a', 'b']) # must not change
710+
711+
data1 = [1, 2]
712+
data2 = ListSubclass(['a', 'b'])
713+
res = operator.iconcat(data1, data2)
714+
self.assertIs(type(res), list)
715+
self.assertEqual(res, [1, 2, 'a', 'b'])
716+
self.assertIs(type(data1), list)
717+
self.assertEqual(data1, [1, 2, 'a', 'b']) # must change
718+
self.assertEqual(data2, ListSubclass(['a', 'b'])) # must not change
719+
720+
# Custom type with `__add__`:
721+
class TupleSubclass(tuple):
722+
def __add__(self, other):
723+
return TupleSubclass(list(other) + list(self))
724+
725+
data1 = TupleSubclass([1, 2])
726+
data2 = TupleSubclass(['a', 'b'])
727+
res = operator.iconcat(data1, data2)
728+
self.assertIsInstance(res, TupleSubclass)
729+
self.assertEqual(res, TupleSubclass(['a', 'b', 1, 2]))
730+
self.assertEqual(data1, TupleSubclass([1, 2])) # must not change
731+
self.assertEqual(data2, TupleSubclass(['a', 'b'])) # must not change
732+
733+
data1 = TupleSubclass([1, 2])
734+
data2 = ('a', 'b')
735+
res = operator.iconcat(data1, data2)
736+
self.assertIsInstance(res, TupleSubclass)
737+
self.assertEqual(res, TupleSubclass(['a', 'b', 1, 2]))
738+
self.assertEqual(data1, TupleSubclass([1, 2])) # must not change
739+
self.assertEqual(data2, ('a', 'b')) # must not change
740+
741+
data1 = (1, 2)
742+
data2 = TupleSubclass(['a', 'b'])
743+
res = operator.iconcat(data1, data2)
744+
self.assertIs(type(res), tuple)
745+
self.assertEqual(res, TupleSubclass([1, 2, 'a', 'b']))
746+
self.assertEqual(data1, (1, 2)) # must not change
747+
self.assertEqual(data2, TupleSubclass(['a', 'b'])) # must not change
748+
749+
# Corner cases:
750+
self.assertEqual(operator.iconcat([1, 2], []), [1, 2])
751+
self.assertEqual(operator.iconcat([], [1, 2]), [1, 2])
752+
self.assertEqual(operator.iconcat([], []), [])
753+
self.assertEqual(operator.iconcat([], ()), [])
754+
755+
# Type errors:
756+
self.assertRaises(TypeError, operator.iconcat, [], 1)
757+
self.assertRaises(TypeError, operator.iconcat, 1, [])
758+
self.assertRaises(TypeError, operator.iconcat, 1, 1)
759+
self.assertRaises(TypeError, operator.iconcat, (), [])
760+
761+
# Returns `NotImplemented`:
762+
class BrokenSeq1(list):
763+
def __add__(self, other):
764+
return NotImplemented
765+
def __iadd__(self, other):
766+
return NotImplemented
767+
768+
self.assertRaises(TypeError,
769+
operator.iconcat, BrokenSeq1(), BrokenSeq1())
770+
771+
class BrokenSeq2(tuple):
772+
def __add__(self, other):
773+
return NotImplemented
774+
775+
self.assertRaises(TypeError,
776+
operator.iconcat, BrokenSeq2(), BrokenSeq2())
777+
563778
def test_iconcat_without_getitem(self):
564779
operator = self.module
565780

0 commit comments

Comments
 (0)