]> Cypherpunks.ru repositories - pyderasn.git/blobdiff - tests/test_pyderasn.py
copy/pickle friendly Obj
[pyderasn.git] / tests / test_pyderasn.py
index dec37f3f3c65823612ff5a68bf23492a954ea0bb..a39d125ee036d8c12259732254a11543aec88c15 100644 (file)
 # License along with this program.  If not, see
 # <http://www.gnu.org/licenses/>.
 
+from copy import copy
 from copy import deepcopy
 from datetime import datetime
+from importlib import import_module
 from string import ascii_letters
 from string import digits
 from string import printable
 from string import whitespace
+from time import time
 from unittest import TestCase
 
 from hypothesis import assume
@@ -51,6 +54,9 @@ from six import iterbytes
 from six import PY2
 from six import text_type
 from six import unichr as six_unichr
+from six.moves.cPickle import dumps as pickle_dumps
+from six.moves.cPickle import HIGHEST_PROTOCOL as pickle_proto
+from six.moves.cPickle import loads as pickle_loads
 
 from pyderasn import _pp
 from pyderasn import abs_decode_path
@@ -65,6 +71,7 @@ from pyderasn import DecodePathDefBy
 from pyderasn import Enumerated
 from pyderasn import EOC
 from pyderasn import EOC_LEN
+from pyderasn import ExceedingData
 from pyderasn import GeneralizedTime
 from pyderasn import GeneralString
 from pyderasn import GraphicString
@@ -133,6 +140,24 @@ decode_path_strat = lists(integers(), max_size=3).map(
     lambda decode_path: tuple(str(dp) for dp in decode_path)
 )
 ctx_dummy = dictionaries(integers(), integers(), min_size=2, max_size=4).example()
+copy_funcs = (
+    copy,
+    lambda obj: pickle_loads(pickle_dumps(obj, pickle_proto)),
+)
+self_module = import_module(__name__)
+
+
+def register_class(klass):
+    klassname = klass.__name__ + str(time()).replace(".", "")
+    klass.__name__ = klassname
+    klass.__qualname__ = klassname
+    setattr(self_module, klassname, klass)
+
+
+def assert_exceeding_data(self, call, junk):
+    if len(junk) > 0:
+        with assertRaisesRegex(self, ExceedingData, "%d trailing bytes" % len(junk)):
+            call()
 
 
 class TestHex(TestCase):
@@ -449,8 +474,9 @@ class TestBoolean(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Boolean, BooleanInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
 
     @given(
         booleans(),
@@ -591,6 +617,11 @@ class TestBoolean(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=2))
     def test_invalid_len(self, l):
@@ -621,7 +652,7 @@ class TestBoolean(CommonMixin, TestCase):
         self.assertTrue(obj.ber_encoded)
         self.assertFalse(obj.lenindef)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.ber_encoded)
         self.assertFalse(obj.lenindef)
         self.assertTrue(obj.bered)
@@ -644,7 +675,7 @@ class TestBoolean(CommonMixin, TestCase):
         self.assertFalse(obj.lenindef)
         self.assertFalse(obj.ber_encoded)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.expl_lenindef)
         self.assertFalse(obj.lenindef)
         self.assertFalse(obj.ber_encoded)
@@ -942,12 +973,13 @@ class TestInteger(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Integer, IntegerInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj.specs, obj_copied.specs)
-            self.assertEqual(obj._bound_min, obj_copied._bound_min)
-            self.assertEqual(obj._bound_max, obj_copied._bound_max)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj.specs, obj_copied.specs)
+                self.assertEqual(obj._bound_min, obj_copied._bound_min)
+                self.assertEqual(obj._bound_max, obj_copied._bound_max)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         integers(),
@@ -1083,6 +1115,11 @@ class TestInteger(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     def test_go_vectors_valid(self):
         for data, expect in ((
@@ -1342,6 +1379,7 @@ class TestBitString(CommonMixin, TestCase):
 
             class BS(klass):
                 schema = _schema
+            register_class(BS)
             obj = BS(
                 value=value,
                 impl=impl,
@@ -1350,10 +1388,11 @@ class TestBitString(CommonMixin, TestCase):
                 optional=optional or False,
                 _decoded=_decoded,
             )
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj.specs, obj_copied.specs)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj.specs, obj_copied.specs)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         binary(),
@@ -1473,6 +1512,11 @@ class TestBitString(CommonMixin, TestCase):
                 self.assertSetEqual(set(value), set(obj_decoded.named))
                 for name in value:
                     obj_decoded[name]
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=1, max_value=255))
     def test_bad_zero_value(self, pad_size):
@@ -1592,7 +1636,7 @@ class TestBitString(CommonMixin, TestCase):
             self.assertTrue(obj.ber_encoded)
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
-            obj = obj.copy()
+            obj = copy(obj)
             self.assertTrue(obj.ber_encoded)
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
@@ -1728,7 +1772,7 @@ class TestBitString(CommonMixin, TestCase):
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.lenindef)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.lenindef)
         self.assertTrue(obj.bered)
@@ -1925,11 +1969,12 @@ class TestOctetString(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (OctetString, OctetStringInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj._bound_min, obj_copied._bound_min)
-            self.assertEqual(obj._bound_max, obj_copied._bound_max)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj._bound_min, obj_copied._bound_min)
+                self.assertEqual(obj._bound_max, obj_copied._bound_max)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         binary(),
@@ -2058,6 +2103,11 @@ class TestOctetString(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         integers(min_value=1, max_value=30),
@@ -2119,7 +2169,7 @@ class TestOctetString(CommonMixin, TestCase):
             self.assertTrue(obj.ber_encoded)
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
-            obj = obj.copy()
+            obj = copy(obj)
             self.assertTrue(obj.ber_encoded)
             self.assertEqual(obj.lenindef, lenindef_expected)
             self.assertTrue(obj.bered)
@@ -2259,8 +2309,9 @@ class TestNull(CommonMixin, TestCase):
                 optional=optional or False,
                 _decoded=_decoded,
             )
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
 
     @given(integers(min_value=1).map(tag_encode))
     def test_stripped(self, tag_impl):
@@ -2360,6 +2411,11 @@ class TestNull(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(integers(min_value=1))
     def test_invalid_len(self, l):
@@ -2530,9 +2586,10 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 optional=optional,
                 _decoded=_decoded,
             )
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(
@@ -2698,6 +2755,11 @@ class TestObjectIdentifier(CommonMixin, TestCase):
                 offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
             )
             self.assertEqual(obj_decoded.expl_offset, offset)
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         oid_strategy().map(ObjectIdentifier),
@@ -2751,7 +2813,7 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True})
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.bered)
         with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"):
@@ -2784,7 +2846,7 @@ class TestObjectIdentifier(CommonMixin, TestCase):
         obj, _ = ObjectIdentifier().decode(tampered, ctx={"bered": True})
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.bered)
         with assertRaisesRegex(self, DecodeError, "non normalized arc encoding"):
@@ -2970,6 +3032,7 @@ class TestEnumerated(CommonMixin, TestCase):
 
         class E(Enumerated):
             schema = schema_input
+        register_class(E)
         obj = E(
             value=value,
             impl=impl,
@@ -2978,9 +3041,10 @@ class TestEnumerated(CommonMixin, TestCase):
             optional=optional,
             _decoded=_decoded,
         )
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj.specs, obj_copied.specs)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj.specs, obj_copied.specs)
 
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
@@ -3041,6 +3105,11 @@ class TestEnumerated(CommonMixin, TestCase):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 @composite
@@ -3239,11 +3308,12 @@ class StringMixin(object):
     def test_copy(self, d):
         values = d.draw(string_values_strategy(self.text_alphabet()))
         obj = self.base_klass(*values)
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._bound_min, obj_copied._bound_min)
-        self.assertEqual(obj._bound_max, obj_copied._bound_max)
-        self.assertEqual(obj._value, obj_copied._value)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj._bound_min, obj_copied._bound_min)
+            self.assertEqual(obj._bound_max, obj_copied._bound_max)
+            self.assertEqual(obj._value, obj_copied._value)
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -3371,6 +3441,11 @@ class StringMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 class TestUTF8String(StringMixin, CommonMixin, TestCase):
@@ -3459,6 +3534,21 @@ class TestPrintableString(
         self.assertEqual(err.exception.offset, offset)
         self.assertEqual(err.exception.decode_path, decode_path)
 
+    def test_allowable_invalid_chars(self):
+        for c, kwargs in (
+                ("*", {"allow_asterisk": True}),
+                ("&", {"allow_ampersand": True}),
+                ("&*", {"allow_asterisk": True, "allow_ampersand": True}),
+        ):
+            s = "hello invalid " + c
+            with assertRaisesRegex(self, DecodeError, "non-printable"):
+                self.base_klass(s)
+            self.base_klass(s, **kwargs)
+            klass = self.base_klass(**kwargs)
+            obj = klass(s)
+            obj = copy(obj)
+            obj(s)
+
 
 class TestTeletexString(
         UnicodeDecodeErrorMixin,
@@ -3521,7 +3611,7 @@ class TestVisibleString(
         self.assertTrue(obj.ber_encoded)
         self.assertFalse(obj.lenindef)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.ber_encoded)
         self.assertFalse(obj.lenindef)
         self.assertTrue(obj.bered)
@@ -3535,7 +3625,7 @@ class TestVisibleString(
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.lenindef)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.ber_encoded)
         self.assertTrue(obj.lenindef)
         self.assertTrue(obj.bered)
@@ -3719,9 +3809,10 @@ class TimeMixin(object):
             max_datetime=self.max_datetime,
         ))
         obj = self.base_klass(*values)
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._value, obj_copied._value)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj._value, obj_copied._value)
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -3806,6 +3897,11 @@ class TimeMixin(object):
             offset + obj_decoded.expl_tlen + obj_decoded.expl_llen,
         )
         self.assertEqual(obj_decoded.expl_offset, offset)
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
 
 class TestGeneralizedTime(TimeMixin, CommonMixin, TestCase):
@@ -4141,9 +4237,10 @@ class TestAny(CommonMixin, TestCase):
     def test_copy(self, values):
         for klass in (Any, AnyInherited):
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(binary().map(OctetString))
     def test_stripped(self, value):
@@ -4246,6 +4343,11 @@ class TestAny(CommonMixin, TestCase):
             self.assertEqual(obj_decoded.tlen, 0)
             self.assertEqual(obj_decoded.llen, 0)
             self.assertEqual(obj_decoded.vlen, len(value))
+            assert_exceeding_data(
+                self,
+                lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+                tail_junk,
+            )
 
     @given(
         integers(min_value=1).map(tag_ctxc),
@@ -4280,7 +4382,7 @@ class TestAny(CommonMixin, TestCase):
         self.assertTrue(obj.lenindef)
         self.assertFalse(obj.ber_encoded)
         self.assertTrue(obj.bered)
-        obj = obj.copy()
+        obj = copy(obj)
         self.assertTrue(obj.lenindef)
         self.assertFalse(obj.ber_encoded)
         self.assertTrue(obj.bered)
@@ -4512,6 +4614,7 @@ class TestChoice(CommonMixin, TestCase):
 
         class Wahl(self.base_klass):
             schema = _schema
+        register_class(Wahl)
         obj = Wahl(
             value=value,
             expl=expl,
@@ -4519,15 +4622,17 @@ class TestChoice(CommonMixin, TestCase):
             optional=optional or False,
             _decoded=_decoded,
         )
-        obj_copied = obj.copy()
-        self.assertIsNone(obj.tag)
-        self.assertIsNone(obj_copied.tag)
-        # hack for assert_copied_basic_fields
-        obj.tag = "whatever"
-        obj_copied.tag = "whatever"
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._value, obj_copied._value)
-        self.assertEqual(obj.specs, obj_copied.specs)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assertIsNone(obj.tag)
+            self.assertIsNone(obj_copied.tag)
+            # hack for assert_copied_basic_fields
+            obj.tag = "whatever"
+            obj_copied.tag = "whatever"
+            self.assert_copied_basic_fields(obj, obj_copied)
+            obj.tag = None
+            self.assertEqual(obj._value, obj_copied._value)
+            self.assertEqual(obj.specs, obj_copied.specs)
 
     @given(booleans())
     def test_stripped(self, value):
@@ -4610,6 +4715,11 @@ class TestChoice(CommonMixin, TestCase):
             ],
             obj_encoded,
         )
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
 
     @given(integers())
     def test_set_get(self, value):
@@ -4958,13 +5068,15 @@ class SeqMixing(object):
     def test_copy(self, d):
         class SeqInherited(self.base_klass):
             pass
+        register_class(SeqInherited)
         for klass in (self.base_klass, SeqInherited):
             values = d.draw(seq_values_strategy(seq_klass=klass))
             obj = klass(*values)
-            obj_copied = obj.copy()
-            self.assert_copied_basic_fields(obj, obj_copied)
-            self.assertEqual(obj.specs, obj_copied.specs)
-            self.assertEqual(obj._value, obj_copied._value)
+            for copy_func in copy_funcs:
+                obj_copied = copy_func(obj)
+                self.assert_copied_basic_fields(obj, obj_copied)
+                self.assertEqual(obj.specs, obj_copied.specs)
+                self.assertEqual(obj._value, obj_copied._value)
 
     @given(data_strategy())
     def test_stripped(self, d):
@@ -5093,7 +5205,7 @@ class SeqMixing(object):
         self.assertDictEqual(ctx_copied, ctx_dummy)
         self.assertTrue(seq_decoded_lenindef.lenindef)
         self.assertTrue(seq_decoded_lenindef.bered)
-        seq_decoded_lenindef = seq_decoded_lenindef.copy()
+        seq_decoded_lenindef = copy(seq_decoded_lenindef)
         self.assertTrue(seq_decoded_lenindef.lenindef)
         self.assertTrue(seq_decoded_lenindef.bered)
         with self.assertRaises(DecodeError):
@@ -5128,6 +5240,12 @@ class SeqMixing(object):
                     obj.encode(),
                 )
 
+        assert_exceeding_data(
+            self,
+            lambda: seq.decod(seq_encoded_lenindef + tail_junk, ctx={"bered": True}),
+            tail_junk,
+        )
+
     @settings(max_examples=LONG_TEST_MAX_EXAMPLES)
     @given(data_strategy())
     def test_symmetric_with_seq(self, d):
@@ -5213,7 +5331,7 @@ class SeqMixing(object):
             seq_decoded, _ = seq_with_default.decode(seq_encoded, ctx=ctx)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
-            seq_decoded = seq_decoded.copy()
+            seq_decoded = copy(seq_decoded)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
             for name, value in _schema:
@@ -5253,7 +5371,7 @@ class SeqMixing(object):
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
-        decoded = decoded.copy()
+        decoded = copy(decoded)
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
@@ -5273,7 +5391,7 @@ class SeqMixing(object):
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
-        decoded = decoded.copy()
+        decoded = copy(decoded)
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
@@ -5373,7 +5491,7 @@ class TestSet(SeqMixing, CommonMixin, TestCase):
             seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
-            seq_decoded = seq_decoded.copy()
+            seq_decoded = copy(seq_decoded)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
             self.assertSequenceEqual(
@@ -5651,6 +5769,7 @@ class SeqOfMixing(object):
 
         class SeqOf(self.base_klass):
             schema = _schema
+        register_class(SeqOf)
         obj = SeqOf(
             value=value,
             bounds=bounds,
@@ -5660,11 +5779,12 @@ class SeqOfMixing(object):
             optional=optional or False,
             _decoded=_decoded,
         )
-        obj_copied = obj.copy()
-        self.assert_copied_basic_fields(obj, obj_copied)
-        self.assertEqual(obj._bound_min, obj_copied._bound_min)
-        self.assertEqual(obj._bound_max, obj_copied._bound_max)
-        self.assertEqual(obj._value, obj_copied._value)
+        for copy_func in copy_funcs:
+            obj_copied = copy_func(obj)
+            self.assert_copied_basic_fields(obj, obj_copied)
+            self.assertEqual(obj._bound_min, obj_copied._bound_min)
+            self.assertEqual(obj._bound_max, obj_copied._bound_max)
+            self.assertEqual(obj._value, obj_copied._value)
 
     @given(
         lists(binary()),
@@ -5801,18 +5921,25 @@ class SeqOfMixing(object):
         )
         self.assertTrue(obj_decoded_lenindef.lenindef)
         self.assertTrue(obj_decoded_lenindef.bered)
-        obj_decoded_lenindef = obj_decoded_lenindef.copy()
+        obj_decoded_lenindef = copy(obj_decoded_lenindef)
         self.assertTrue(obj_decoded_lenindef.lenindef)
         self.assertTrue(obj_decoded_lenindef.bered)
         repr(obj_decoded_lenindef)
         list(obj_decoded_lenindef.pps())
         pprint(obj_decoded_lenindef, big_blobs=True, with_decode_path=True)
+        self.assertEqual(tail_lenindef, tail_junk)
         self.assertEqual(obj_decoded_lenindef.tlvlen, len(obj_encoded_lenindef))
         with self.assertRaises(DecodeError):
             obj.decode(obj_encoded_lenindef[:-1], ctx={"bered": True})
         with self.assertRaises(DecodeError):
             obj.decode(obj_encoded_lenindef[:-2], ctx={"bered": True})
 
+        assert_exceeding_data(
+            self,
+            lambda: obj_expled.decod(obj_expled_encoded + tail_junk),
+            tail_junk,
+        )
+
     def test_bered(self):
         class SeqOf(self.base_klass):
             schema = Boolean()
@@ -5825,7 +5952,7 @@ class SeqOfMixing(object):
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
-        decoded = decoded.copy()
+        decoded = copy(decoded)
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
@@ -5846,7 +5973,7 @@ class SeqOfMixing(object):
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
-        decoded = decoded.copy()
+        decoded = copy(decoded)
         self.assertFalse(decoded.ber_encoded)
         self.assertFalse(decoded.lenindef)
         self.assertTrue(decoded.bered)
@@ -5915,7 +6042,7 @@ class TestSetOf(SeqOfMixing, CommonMixin, TestCase):
             seq_decoded, _ = Seq().decode(seq_encoded, ctx=ctx)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
-            seq_decoded = seq_decoded.copy()
+            seq_decoded = copy(seq_decoded)
             self.assertTrue(seq_decoded.ber_encoded)
             self.assertTrue(seq_decoded.bered)
             self.assertSequenceEqual(
@@ -6426,13 +6553,13 @@ class TestStrictDefaultExistence(TestCase):
             decoded, _ = seq.decode(raw, ctx={"allow_default_values": True})
             self.assertTrue(decoded.ber_encoded)
             self.assertTrue(decoded.bered)
-            decoded = decoded.copy()
+            decoded = copy(decoded)
             self.assertTrue(decoded.ber_encoded)
             self.assertTrue(decoded.bered)
             decoded, _ = seq.decode(raw, ctx={"bered": True})
             self.assertTrue(decoded.ber_encoded)
             self.assertTrue(decoded.bered)
-            decoded = decoded.copy()
+            decoded = copy(decoded)
             self.assertTrue(decoded.ber_encoded)
             self.assertTrue(decoded.bered)
 
@@ -6484,3 +6611,15 @@ class TestExplOOB(TestCase):
         with assertRaisesRegex(self, DecodeError, "explicit tag out-of-bound"):
             Integer(expl=expl).decode(raw)
         Integer(expl=expl).decode(raw, ctx={"allow_expl_oob": True})
+
+
+class TestPickleDifferentVersion(TestCase):
+    def runTest(self):
+        pickled = pickle_dumps(Integer(123), pickle_proto)
+        import pyderasn
+        version_orig = pyderasn.__version__
+        pyderasn.__version__ += "different"
+        with assertRaisesRegex(self, ValueError, "different PyDERASN version"):
+            pickle_loads(pickled)
+        pyderasn.__version__ = version_orig
+        pickle_loads(pickled)