[pypy-commit] pypy py3.7-time-minor-bpos: merge py3.7 into py3.7-time-minor-bpos

Yannick_Jadoul pypy.commits at gmail.com
Sun Dec 29 11:58:52 EST 2019


Author: Yannick Jadoul <yannick.jadoul at belgacom.net>
Branch: py3.7-time-minor-bpos
Changeset: r98403:29f5a19c932a
Date: 2019-12-29 15:44 +0100
http://bitbucket.org/pypy/pypy/changeset/29f5a19c932a/

Log:	merge py3.7 into py3.7-time-minor-bpos

diff too long, truncating to 2000 out of 25236 lines

diff --git a/.hgtags b/.hgtags
--- a/.hgtags
+++ b/.hgtags
@@ -57,3 +57,7 @@
 4a68d8d3d2fc1faec2e83bcb4d28559099092574 release-pypy2.7-v7.2.0rc2
 4a68d8d3d2fc1faec2e83bcb4d28559099092574 release-pypy2.7-v7.2.0
 5da45ced70e515f94686be0df47c59abd1348ebc release-pypy3.6-v7.2.0
+e6471221abc16f4584a07fbfeece7ebcaeb7fc38 release-pypy2.7-v7.3.0rc1
+533398cfd64e5146a07c4824e90a1b629c8b6523 release-pypy3.6-v7.3.0rc1
+285307a0f5a77ffa46781b5c54c52eb1c385081d release-pypy2.7-v7.3.0rc2
+008914050baeedb6d3ca30fe26ef43b78bb63841 release-pypy3.6-v7.3.0rc2
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -99,16 +99,16 @@
   Spenser Bauman
   Michal Bendowski
   Jan de Mooij
+  Stefano Rivera
   Tyler Wade
+  Stefan Beyer
   Vincent Legoll
   Michael Foord
   Stephan Diehl
-  Stefano Rivera
   Jean-Paul Calderone
   Stefan Schwarzer
   Tomek Meka
   Valentino Volonghi
-  Stefan Beyer
   Patrick Maupin
   Devin Jeanpierre
   Bob Ippolito
@@ -137,9 +137,10 @@
   Jean-Philippe St. Pierre
   Guido van Rossum
   Pavel Vinogradov
+  Stian Andreassen
+  Julian Berman
   William Leslie
   Paweł Piotr Przeradowski
-  Stian Andreassen
   marky1991
   Ilya Osadchiy
   Tobias Oberstein
@@ -150,7 +151,7 @@
   tav
   Georg Brandl
   Joannah Nanjekye
-  Julian Berman
+  Yannick Jadoul
   Bert Freudenberg
   Wanja Saatkamp
   Mike Blume
@@ -275,6 +276,7 @@
   Lutz Paelike
   Ian Foote
   Philipp Rustemeuer
+  Bernd Schoeller
   Logan Chien
   Catalin Gabriel Manciu
   Jacob Oscarson
@@ -302,7 +304,6 @@
   Laurens Van Houtven
   Bobby Impollonia
   Roberto De Ioris
-  Yannick Jadoul
   Jeong YunWon
   Christopher Armstrong
   Aaron Tubbs
@@ -357,6 +358,7 @@
   Daniil Yarancev
   Min RK
   OlivierBlanvillain
+  bernd.schoeller at inf.ethz.ch
   dakarpov at gmail.com
   Jonas Pfannschmidt
   Zearin
@@ -398,6 +400,7 @@
   Jesdi
   Konrad Delong
   Dinu Gherman
+  Sam Edwards
   pizi
   Tomáš Pružina
   James Robert
diff --git a/extra_tests/cffi_tests/cffi0/test_verify.py b/extra_tests/cffi_tests/cffi0/test_verify.py
--- a/extra_tests/cffi_tests/cffi0/test_verify.py
+++ b/extra_tests/cffi_tests/cffi0/test_verify.py
@@ -4,6 +4,7 @@
 import sys, os, math, weakref
 from cffi import FFI, VerificationError, VerificationMissing, model, FFIError
 from extra_tests.cffi_tests.support import *
+from extra_tests.cffi_tests.support import extra_compile_args
 
 
 lib_m = ['m']
@@ -14,17 +15,6 @@
         lib_m = ['msvcrt']
     pass      # no obvious -Werror equivalent on MSVC
 else:
-    if (sys.platform == 'darwin' and
-          [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]):
-        # assume a standard clang or gcc
-        extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion']
-        # special things for clang
-        extra_compile_args.append('-Qunused-arguments')
-    else:
-        # assume a standard gcc
-        extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion',
-                              '-Wno-unused-parameter']
-
     class FFI(FFI):
         def verify(self, *args, **kwds):
             return super(FFI, self).verify(
diff --git a/extra_tests/cffi_tests/cffi1/test_recompiler.py b/extra_tests/cffi_tests/cffi1/test_recompiler.py
--- a/extra_tests/cffi_tests/cffi1/test_recompiler.py
+++ b/extra_tests/cffi_tests/cffi1/test_recompiler.py
@@ -35,8 +35,9 @@
         source = 'extern "C" {\n%s\n}' % (source,)
     elif sys.platform != 'win32':
         # add '-Werror' to the existing 'extra_compile_args' flags
+        from extra_tests.cffi_tests.support import extra_compile_args
         kwds['extra_compile_args'] = (kwds.get('extra_compile_args', []) +
-                                      ['-Werror'])
+                                      extra_compile_args)
     return _verify(ffi, module_name, source, *args, **kwds)
 
 def test_set_source_no_slashes():
@@ -2039,7 +2040,7 @@
     ffi.cdef("float _Complex f1(float a, float b);");
     lib = verify(ffi, "test_function_returns_float_complex", """
         #include <complex.h>
-        static float _Complex f1(float a, float b) { return a + I*2.0*b; }
+        static float _Complex f1(float a, float b) { return a + I*2.0f*b; }
     """, no_cpp=True)    # <complex.h> fails on some systems with C++
     result = lib.f1(1.25, 5.1)
     assert type(result) == complex
diff --git a/extra_tests/cffi_tests/cffi1/test_verify1.py b/extra_tests/cffi_tests/cffi1/test_verify1.py
--- a/extra_tests/cffi_tests/cffi1/test_verify1.py
+++ b/extra_tests/cffi_tests/cffi1/test_verify1.py
@@ -5,7 +5,7 @@
 from cffi import CDefError
 from cffi import recompiler
 from extra_tests.cffi_tests.support import *
-from extra_tests.cffi_tests.support import _verify
+from extra_tests.cffi_tests.support import _verify, extra_compile_args
 import _cffi_backend
 
 lib_m = ['m']
@@ -14,18 +14,6 @@
     import distutils.ccompiler
     if distutils.ccompiler.get_default_compiler() == 'msvc':
         lib_m = ['msvcrt']
-    extra_compile_args = []      # no obvious -Werror equivalent on MSVC
-else:
-    if (sys.platform == 'darwin' and
-          [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]):
-        # assume a standard clang or gcc
-        extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion']
-        # special things for clang
-        extra_compile_args.append('-Qunused-arguments')
-    else:
-        # assume a standard gcc
-        extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion',
-                              '-Wno-unused-parameter']
 
 class FFI(FFI):
     error = _cffi_backend.FFI.error
diff --git a/extra_tests/cffi_tests/support.py b/extra_tests/cffi_tests/support.py
--- a/extra_tests/cffi_tests/support.py
+++ b/extra_tests/cffi_tests/support.py
@@ -1,5 +1,5 @@
 # Generated by pypy/tool/import_cffi.py
-import sys
+import sys, os
 
 if sys.version_info < (3,):
     __all__ = ['u']
@@ -87,3 +87,24 @@
         if not name.startswith('_') and not hasattr(module.ffi, name):
             setattr(ffi, name, NotImplemented)
     return module.lib
+
+
+# For testing, we call gcc with "-Werror".  This is fragile because newer
+# versions of gcc are always better at producing warnings, particularly for
+# auto-generated code.  We need here to adapt and silence them as needed.
+
+if sys.platform == 'win32':
+    extra_compile_args = []      # no obvious -Werror equivalent on MSVC
+else:
+    if (sys.platform == 'darwin' and
+          [int(x) for x in os.uname()[2].split('.')] >= [11, 0, 0]):
+        # assume a standard clang or gcc
+        extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion',
+                              '-Wno-unreachable-code']
+        # special things for clang
+        extra_compile_args.append('-Qunused-arguments')
+    else:
+        # assume a standard gcc
+        extra_compile_args = ['-Werror', '-Wall', '-Wextra', '-Wconversion',
+                              '-Wno-unused-parameter',
+                              '-Wno-unreachable-code']
diff --git a/extra_tests/test_datetime.py b/extra_tests/test_datetime.py
--- a/extra_tests/test_datetime.py
+++ b/extra_tests/test_datetime.py
@@ -350,3 +350,31 @@
     d2 = d.replace(hour=7)
     assert type(d2) is MyDatetime
     assert d2 == datetime.datetime(2016, 4, 5, 7, 2, 3)
+
+def test_normalize_pair():
+    normalize = datetime._normalize_pair
+
+    assert normalize(1, 59, 60) == (1, 59)
+    assert normalize(1, 60, 60) == (2, 0)
+    assert normalize(1, 95, 60) == (2, 35)
+
+def test_normalize_date():
+    normalize = datetime._normalize_date
+
+    # Huge year is caught correctly
+    with pytest.raises(OverflowError):
+        normalize(1000 * 1000, 1, 1)
+    # Normal dates should be unchanged
+    assert normalize(3000, 1, 1) == (3000, 1, 1)
+    # Month overflows year boundary
+    assert normalize(2001, 24, 1) == (2002, 12, 1)
+    # Day overflows month boundary
+    assert normalize(2001, 14, 31) == (2002, 3, 3)
+    # Leap years? :S
+    assert normalize(2001, 1, 61) == (2001, 3, 2)
+    assert normalize(2000, 1, 61) == (2000, 3, 1)
+
+def test_normalize_datetime():
+    normalize = datetime._normalize_datetime
+    abnormal = (2002, 13, 35, 30, 95, 75, 1000001)
+    assert normalize(*abnormal) == (2003, 2, 5, 7, 36, 16, 1)
diff --git a/extra_tests/test_immutables_map.py b/extra_tests/test_immutables_map.py
new file mode 100644
--- /dev/null
+++ b/extra_tests/test_immutables_map.py
@@ -0,0 +1,1298 @@
+import collections.abc
+import gc
+import pickle
+import random
+import sys
+import weakref
+import pytest
+
+from _immutables_map import Map
+
+
+class HashKey:
+    _crasher = None
+
+    def __init__(self, hash, name, *, error_on_eq_to=None):
+        assert hash != -1
+        self.name = name
+        self.hash = hash
+        self.error_on_eq_to = error_on_eq_to
+
+    def __repr__(self):
+        if self._crasher is not None and self._crasher.error_on_repr:
+            raise ReprError
+        return '<Key name:{} hash:{}>'.format(self.name, self.hash)
+
+    def __hash__(self):
+        if self._crasher is not None and self._crasher.error_on_hash:
+            raise HashingError
+
+        return self.hash
+
+    def __eq__(self, other):
+        if not isinstance(other, HashKey):
+            return NotImplemented
+
+        if self._crasher is not None and self._crasher.error_on_eq:
+            raise EqError
+
+        if self.error_on_eq_to is not None and self.error_on_eq_to is other:
+            raise ValueError('cannot compare {!r} to {!r}'.format(self, other))
+        if other.error_on_eq_to is not None and other.error_on_eq_to is self:
+            raise ValueError('cannot compare {!r} to {!r}'.format(other, self))
+
+        return (self.name, self.hash) == (other.name, other.hash)
+
+
+class KeyStr(str):
+
+    def __hash__(self):
+        if HashKey._crasher is not None and HashKey._crasher.error_on_hash:
+            raise HashingError
+        return super().__hash__()
+
+    def __eq__(self, other):
+        if HashKey._crasher is not None and HashKey._crasher.error_on_eq:
+            raise EqError
+        return super().__eq__(other)
+
+    def __repr__(self, other):
+        if HashKey._crasher is not None and HashKey._crasher.error_on_repr:
+            raise ReprError
+        return super().__eq__(other)
+
+
+class HashKeyCrasher:
+
+    def __init__(self, *, error_on_hash=False, error_on_eq=False,
+                 error_on_repr=False):
+        self.error_on_hash = error_on_hash
+        self.error_on_eq = error_on_eq
+        self.error_on_repr = error_on_repr
+
+    def __enter__(self):
+        if HashKey._crasher is not None:
+            raise RuntimeError('cannot nest crashers')
+        HashKey._crasher = self
+
+    def __exit__(self, *exc):
+        HashKey._crasher = None
+
+
+class HashingError(Exception):
+    pass
+
+
+class EqError(Exception):
+    pass
+
+
+class ReprError(Exception):
+    pass
+
+
+class BaseMapTest:
+
+    def test_hashkey_helper_1(self):
+        k1 = HashKey(10, 'aaa')
+        k2 = HashKey(10, 'bbb')
+
+        assert k1 != k2
+        assert hash(k1) == hash(k2)
+
+        d = dict()
+        d[k1] = 'a'
+        d[k2] = 'b'
+
+        assert d[k1] == 'a'
+        assert d[k2] == 'b'
+
+    def test_map_basics_1(self):
+        h = self.Map()
+        h = None  # NoQA
+
+    def test_map_basics_2(self):
+        h = self.Map()
+        assert len(h) == 0
+
+        h2 = h.set('a', 'b')
+        assert h is not h2
+        assert len(h) == 0
+        assert len(h2) == 1
+
+        assert h.get('a') is None
+        assert h.get('a', 42) == 42
+
+        assert h2.get('a') == 'b'
+
+        h3 = h2.set('b', 10)
+        assert h2 is not h3
+        assert len(h) == 0
+        assert len(h2) == 1
+        assert len(h3) == 2
+        assert h3.get('a') == 'b'
+        assert h3.get('b') == 10
+
+        assert h.get('b') is None
+        assert h2.get('b') is None
+
+        assert h.get('a') is None
+        assert h2.get('a') == 'b'
+
+        h = h2 = h3 = None
+
+    def test_map_basics_3(self):
+        h = self.Map()
+        o = object()
+        h1 = h.set('1', o)
+        h2 = h1.set('1', o)
+        assert h1 is h2
+
+    def test_map_basics_4(self):
+        h = self.Map()
+        h1 = h.set('key', [])
+        h2 = h1.set('key', [])
+        assert h1 is not h2
+        assert len(h1) == 1
+        assert len(h2) == 1
+        assert h1.get('key') is not h2.get('key')
+
+    def test_map_collision_1(self):
+        k1 = HashKey(10, 'aaa')
+        k2 = HashKey(10, 'bbb')
+        k3 = HashKey(10, 'ccc')
+
+        h = self.Map()
+        h2 = h.set(k1, 'a')
+        h3 = h2.set(k2, 'b')
+
+        assert h.get(k1) == None
+        assert h.get(k2) == None
+
+        assert h2.get(k1) == 'a'
+        assert h2.get(k2) == None
+
+        assert h3.get(k1) == 'a'
+        assert h3.get(k2) == 'b'
+
+        h4 = h3.set(k2, 'cc')
+        h5 = h4.set(k3, 'aa')
+
+        assert h3.get(k1) == 'a'
+        assert h3.get(k2) == 'b'
+        assert h4.get(k1) == 'a'
+        assert h4.get(k2) == 'cc'
+        assert h4.get(k3) == None
+        assert h5.get(k1) == 'a'
+        assert h5.get(k2) == 'cc'
+        assert h5.get(k2) == 'cc'
+        assert h5.get(k3) == 'aa'
+
+        assert len(h) == 0
+        assert len(h2) == 1
+        assert len(h3) == 2
+        assert len(h4) == 2
+        assert len(h5) == 3
+
+    def test_map_collision_2(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(0b011000011100000100, 'C')
+        D = HashKey(0b011000011100000100, 'D')
+        E = HashKey(0b1011000011100000100, 'E')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+
+        # BitmapNode(size=6 bitmap=0b100110000):
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b1000000000000000000001000):
+        #             <Key name:A hash:100>: 'a'
+        #             NULL:
+        #                 CollisionNode(size=4 id=0x108572410):
+        #                     <Key name:C hash:100100>: 'c'
+        #                     <Key name:D hash:100100>: 'd'
+        #     <Key name:B hash:101>: 'b'
+
+        h = h.set(E, 'e')
+
+        # BitmapNode(size=4 count=2.0 bitmap=0b110000 id=10b8ea5c0):
+        #     None:
+        #         BitmapNode(size=4 count=2.0
+        #                    bitmap=0b1000000000000000000001000 id=10b8ea518):
+        #             <Key name:A hash:100>: 'a'
+        #             None:
+        #                 BitmapNode(size=2 count=1.0 bitmap=0b10
+        #                            id=10b8ea4a8):
+        #                     None:
+        #                         BitmapNode(size=4 count=2.0
+        #                                    bitmap=0b100000001000
+        #                                    id=10b8ea4e0):
+        #                             None:
+        #                                 CollisionNode(size=4 id=10b8ea470):
+        #                                     <Key name:C hash:100100>: 'c'
+        #                                     <Key name:D hash:100100>: 'd'
+        #                             <Key name:E hash:362244>: 'e'
+        #     <Key name:B hash:101>: 'b'
+
+    def test_map_stress(self):
+        COLLECTION_SIZE = 7000
+        TEST_ITERS_EVERY = 647
+        CRASH_HASH_EVERY = 97
+        CRASH_EQ_EVERY = 11
+        RUN_XTIMES = 3
+
+        for _ in range(RUN_XTIMES):
+            h = self.Map()
+            d = dict()
+
+            for i in range(COLLECTION_SIZE):
+                key = KeyStr(i)
+
+                if not (i % CRASH_HASH_EVERY):
+                    with HashKeyCrasher(error_on_hash=True):
+                        with pytest.raises(HashingError):
+                            h.set(key, i)
+
+                h = h.set(key, i)
+
+                if not (i % CRASH_EQ_EVERY):
+                    with HashKeyCrasher(error_on_eq=True):
+                        with pytest.raises(EqError):
+                            h.get(KeyStr(i))  # really trigger __eq__
+
+                d[key] = i
+                assert len(d) == len(h)
+
+                if not (i % TEST_ITERS_EVERY):
+                    assert set(h.items()) == set(d.items())
+                    assert len(h.items()) == len(d.items())
+
+            assert len(h) == COLLECTION_SIZE
+
+            for key in range(COLLECTION_SIZE):
+                assert h.get(KeyStr(key), 'not found') == key
+
+            keys_to_delete = list(range(COLLECTION_SIZE))
+            random.shuffle(keys_to_delete)
+            for iter_i, i in enumerate(keys_to_delete):
+                key = KeyStr(i)
+
+                if not (iter_i % CRASH_HASH_EVERY):
+                    with HashKeyCrasher(error_on_hash=True):
+                        with pytest.raises(HashingError):
+                            h.delete(key)
+
+                if not (iter_i % CRASH_EQ_EVERY):
+                    with HashKeyCrasher(error_on_eq=True):
+                        with pytest.raises(EqError):
+                            h.delete(KeyStr(i))
+
+                h = h.delete(key)
+                assert h.get(key, 'not found') == 'not found'
+                del d[key]
+                assert len(d) == len(h)
+
+                if iter_i == COLLECTION_SIZE // 2:
+                    hm = h
+                    dm = d.copy()
+
+                if not (iter_i % TEST_ITERS_EVERY):
+                    assert set(h.keys()) == set(d.keys())
+                    assert len(h.keys()) == len(d.keys())
+
+            assert len(d) == 0
+            assert len(h) == 0
+
+            # ============
+
+            for key in dm:
+                assert hm.get(str(key)) == dm[key]
+            assert len(dm) == len(hm)
+
+            for i, key in enumerate(keys_to_delete):
+                if str(key) in dm:
+                    hm = hm.delete(str(key))
+                    dm.pop(str(key))
+                assert hm.get(str(key), 'not found') == 'not found'
+                assert len(d) == len(h)
+
+                if not (i % TEST_ITERS_EVERY):
+                    assert set(h.values()) == set(d.values())
+                    assert len(h.values()) == len(d.values())
+
+            assert len(d) == 0
+            assert len(h) == 0
+            assert list(h.items()) == []
+
+    def test_map_delete_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(102, 'C')
+        D = HashKey(103, 'D')
+        E = HashKey(104, 'E')
+        Z = HashKey(-100, 'Z')
+
+        Er = HashKey(103, 'Er', error_on_eq_to=D)
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        orig_len = len(h)
+
+        # BitmapNode(size=10 bitmap=0b111110000 id=0x10eadc618):
+        #     <Key name:A hash:100>: 'a'
+        #     <Key name:B hash:101>: 'b'
+        #     <Key name:C hash:102>: 'c'
+        #     <Key name:D hash:103>: 'd'
+        #     <Key name:E hash:104>: 'e'
+
+        h = h.delete(C)
+        assert len(h) == orig_len - 1
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h.delete(Er)
+
+        h = h.delete(D)
+        assert len(h) == orig_len - 2
+
+        with pytest.raises(KeyError) as ex:
+            h.delete(Z)
+        assert ex.value.args[0] is Z
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 3
+
+        assert h.get(A, 42) == 42
+        assert h.get(B) == 'b'
+        assert h.get(E) == 'e'
+
+    def test_map_delete_2(self):
+        A = HashKey(100, 'A')
+        B = HashKey(201001, 'B')
+        C = HashKey(101001, 'C')
+        BLike = HashKey(201001, 'B-like')
+        D = HashKey(103, 'D')
+        E = HashKey(104, 'E')
+        Z = HashKey(-100, 'Z')
+
+        Er = HashKey(201001, 'Er', error_on_eq_to=B)
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        h = h.set(B, 'b')  # trigger branch in BitmapNode.assoc
+
+        with pytest.raises(KeyError):
+            h.delete(BLike)    # trigger branch in BitmapNode.without
+
+        orig_len = len(h)
+
+        # BitmapNode(size=8 bitmap=0b1110010000):
+        #     <Key name:A hash:100>: 'a'
+        #     <Key name:D hash:103>: 'd'
+        #     <Key name:E hash:104>: 'e'
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b100000000001000000000):
+        #             <Key name:B hash:201001>: 'b'
+        #             <Key name:C hash:101001>: 'c'
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h.delete(Er)
+
+        with pytest.raises(KeyError) as ex:
+            h.delete(Z)
+        assert ex.value.args[0] is Z
+        assert len(h) == orig_len
+
+        h = h.delete(C)
+        assert len(h) == orig_len - 1
+
+        h = h.delete(B)
+        assert len(h) == orig_len - 2
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 3
+
+        assert h.get(D) == 'd'
+        assert h.get(E) == 'e'
+
+        with pytest.raises(KeyError):
+            h = h.delete(A)
+        with pytest.raises(KeyError):
+            h = h.delete(B)
+        h = h.delete(D)
+        h = h.delete(E)
+        assert len(h) == 0
+
+    def test_map_delete_3(self):
+        A = HashKey(0b00000000001100100, 'A')
+        B = HashKey(0b00000000001100101, 'B')
+
+        C = HashKey(0b11000011100000100, 'C')
+        D = HashKey(0b11000011100000100, 'D')
+        X = HashKey(0b01000011100000100, 'Z')
+        Y = HashKey(0b11000011100000100, 'Y')
+
+        E = HashKey(0b00000000001101000, 'E')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        assert len(h) == 5
+        h = h.set(C, 'c')  # trigger branch in CollisionNode.assoc
+        assert len(h) == 5
+
+        orig_len = len(h)
+
+        with pytest.raises(KeyError):
+            h.delete(X)
+        with pytest.raises(KeyError):
+            h.delete(Y)
+
+        # BitmapNode(size=6 bitmap=0b100110000):
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b1000000000000000000001000):
+        #             <Key name:A hash:100>: 'a'
+        #             NULL:
+        #                 CollisionNode(size=4 id=0x108572410):
+        #                     <Key name:C hash:100100>: 'c'
+        #                     <Key name:D hash:100100>: 'd'
+        #     <Key name:B hash:101>: 'b'
+        #     <Key name:E hash:104>: 'e'
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 1
+
+        h = h.delete(E)
+        assert len(h) == orig_len - 2
+
+        assert h.get(C) == 'c'
+        assert h.get(B) == 'b'
+
+        h2 = h.delete(C)
+        assert len(h2) == orig_len - 3
+
+        h2 = h.delete(D)
+        assert len(h2) == orig_len - 3
+
+        assert len(h) == orig_len - 2
+
+    def test_map_delete_4(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+
+        orig_len = len(h)
+
+        # BitmapNode(size=4 bitmap=0b110000):
+        #     NULL:
+        #         BitmapNode(size=4 bitmap=0b1000000000000000000001000):
+        #             <Key name:A hash:100>: 'a'
+        #             NULL:
+        #                 CollisionNode(size=6 id=0x10515ef30):
+        #                     <Key name:C hash:100100>: 'c'
+        #                     <Key name:D hash:100100>: 'd'
+        #                     <Key name:E hash:100100>: 'e'
+        #     <Key name:B hash:101>: 'b'
+
+        h = h.delete(D)
+        assert len(h) == orig_len - 1
+
+        h = h.delete(E)
+        assert len(h) == orig_len - 2
+
+        h = h.delete(C)
+        assert len(h) == orig_len - 3
+
+        h = h.delete(A)
+        assert len(h) == orig_len - 4
+
+        h = h.delete(B)
+        assert len(h) == 0
+
+    def test_map_delete_5(self):
+        h = self.Map()
+
+        keys = []
+        for i in range(17):
+            key = HashKey(i, str(i))
+            keys.append(key)
+            h = h.set(key, 'val-{}'.format(i))
+
+        collision_key16 = HashKey(16, '18')
+        h = h.set(collision_key16, 'collision')
+
+        # ArrayNode(id=0x10f8b9318):
+        #     0::
+        #     BitmapNode(size=2 count=1 bitmap=0b1):
+        #         <Key name:0 hash:0>: 'val-0'
+        #
+        # ... 14 more BitmapNodes ...
+        #
+        #     15::
+        #     BitmapNode(size=2 count=1 bitmap=0b1):
+        #         <Key name:15 hash:15>: 'val-15'
+        #
+        #     16::
+        #     BitmapNode(size=2 count=1 bitmap=0b1):
+        #         NULL:
+        #             CollisionNode(size=4 id=0x10f2f5af8):
+        #                 <Key name:16 hash:16>: 'val-16'
+        #                 <Key name:18 hash:16>: 'collision'
+
+        assert len(h) == 18
+
+        h = h.delete(keys[2])
+        assert len(h) == 17
+
+        h = h.delete(collision_key16)
+        assert len(h) == 16
+        h = h.delete(keys[16])
+        assert len(h) == 15
+
+        h = h.delete(keys[1])
+        assert len(h) == 14
+        with pytest.raises(KeyError) as ex:
+            h.delete(keys[1])
+        assert ex.value.args[0] is keys[1]
+        assert len(h) == 14
+
+        for key in keys:
+            if key in h:
+                h = h.delete(key)
+        assert len(h) == 0
+
+    def test_map_delete_6(self):
+        h = self.Map()
+        h = h.set(1, 1)
+        h = h.delete(1)
+        assert len(h) == 0
+        assert h == self.Map()
+
+    def test_map_items_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(201001, 'B')
+        C = HashKey(101001, 'C')
+        D = HashKey(103, 'D')
+        E = HashKey(104, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        it = h.items()
+        assert set(list(it)) == \
+            {(A, 'a'), (B, 'b'), (C, 'c'), (D, 'd'), (E, 'e'), (F, 'f')}
+
+    def test_map_items_2(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        it = h.items()
+        assert set(list(it)) == \
+            {(A, 'a'), (B, 'b'), (C, 'c'), (D, 'd'), (E, 'e'), (F, 'f')}
+
+    def test_map_items_3(self):
+        h = self.Map()
+        assert len(h.items()) == 0
+        assert list(h.items()) == []
+
+    def test_map_items_4(self):
+        h = self.Map(a=1, b=2, c=3)
+        k = h.items()
+        assert set(k) == {('a', 1), ('b', 2), ('c', 3)}
+        assert set(k) == {('a', 1), ('b', 2), ('c', 3)}
+
+    def test_map_keys_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        assert set(list(h.keys())) == {A, B, C, D, E, F}
+        assert set(list(h)) == {A, B, C, D, E, F}
+
+    def test_map_keys_2(self):
+        h = self.Map(a=1, b=2, c=3)
+        k = h.keys()
+        assert set(k) == {'a', 'b', 'c'}
+        assert set(k) == {'a', 'b', 'c'}
+
+    def test_map_values_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(100100, 'E')
+        F = HashKey(110, 'F')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(B, 'b')
+        h = h.set(C, 'c')
+        h = h.set(D, 'd')
+        h = h.set(E, 'e')
+        h = h.set(F, 'f')
+
+        assert set(list(h.values())) == {'a', 'b', 'c', 'd', 'e', 'f'}
+
+    def test_map_values_2(self):
+        h = self.Map(a=1, b=2, c=3)
+        k = h.values()
+        assert set(k) == {1, 2, 3}
+        assert set(k) == {1, 2, 3}
+
+    def test_map_eq_1(self):
+        A = HashKey(100, 'A')
+        B = HashKey(101, 'B')
+        C = HashKey(100100, 'C')
+        D = HashKey(100100, 'D')
+        E = HashKey(120, 'E')
+
+        h1 = self.Map()
+        h1 = h1.set(A, 'a')
+        h1 = h1.set(B, 'b')
+        h1 = h1.set(C, 'c')
+        h1 = h1.set(D, 'd')
+
+        h2 = self.Map()
+        h2 = h2.set(A, 'a')
+
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(B, 'b')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(C, 'c')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(D, 'd2')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(D, 'd')
+        assert h1 == h2
+        assert not (h1 != h2)
+
+        h2 = h2.set(E, 'e')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.delete(D)
+        assert not (h1 == h2)
+        assert h1 != h2
+
+        h2 = h2.set(E, 'd')
+        assert not (h1 == h2)
+        assert h1 != h2
+
+    def test_map_eq_2(self):
+        A = HashKey(100, 'A')
+        Er = HashKey(100, 'Er', error_on_eq_to=A)
+
+        h1 = self.Map()
+        h1 = h1.set(A, 'a')
+
+        h2 = self.Map()
+        h2 = h2.set(Er, 'a')
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h1 == h2
+
+        with pytest.raises(ValueError, match='cannot compare'):
+            h1 != h2
+
+    def test_map_eq_3(self):
+        assert self.Map() != 1
+
+    def test_map_gc_1(self):
+        A = HashKey(100, 'A')
+
+        h = self.Map()
+        h = h.set(0, 0)  # empty Map node is memoized in _map.c
+        ref = weakref.ref(h)
+
+        a = []
+        a.append(a)
+        a.append(h)
+        b = []
+        a.append(b)
+        b.append(a)
+        h = h.set(A, b)
+
+        del h, a, b
+
+        gc.collect()
+        gc.collect()
+        gc.collect()
+
+        assert ref() is None
+
+    def test_map_gc_2(self):
+        A = HashKey(100, 'A')
+
+        h = self.Map()
+        h = h.set(A, 'a')
+        h = h.set(A, h)
+
+        ref = weakref.ref(h)
+        hi = iter(h.items())
+        next(hi)
+
+        del h, hi
+
+        gc.collect()
+        gc.collect()
+        gc.collect()
+
+        assert ref() is None
+
+    def test_map_in_1(self):
+        A = HashKey(100, 'A')
+        AA = HashKey(100, 'A')
+
+        B = HashKey(101, 'B')
+
+        h = self.Map()
+        h = h.set(A, 1)
+
+        assert A in h
+        assert not (B in h)
+
+        with pytest.raises(EqError):
+            with HashKeyCrasher(error_on_eq=True):
+                AA in h
+
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                AA in h
+
+    def test_map_getitem_1(self):
+        A = HashKey(100, 'A')
+        AA = HashKey(100, 'A')
+
+        B = HashKey(101, 'B')
+
+        h = self.Map()
+        h = h.set(A, 1)
+
+        assert h[A] == 1
+        assert h[AA] == 1
+
+        with pytest.raises(KeyError):
+            h[B]
+
+        with pytest.raises(EqError):
+            with HashKeyCrasher(error_on_eq=True):
+                h[AA]
+
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                h[AA]
+
+    def test_repr_1(self):
+        h = self.Map()
+        assert repr(h).startswith('<immutables.Map({}) at 0x')
+
+        h = h.set(1, 2).set(2, 3).set(3, 4)
+        assert repr(h).startswith(
+            '<immutables.Map({1: 2, 2: 3, 3: 4}) at 0x')
+
+    def test_repr_2(self):
+        h = self.Map()
+        A = HashKey(100, 'A')
+
+        with pytest.raises(ReprError):
+            with HashKeyCrasher(error_on_repr=True):
+                repr(h.set(1, 2).set(A, 3).set(3, 4))
+
+        with pytest.raises(ReprError):
+            with HashKeyCrasher(error_on_repr=True):
+                repr(h.set(1, 2).set(2, A).set(3, 4))
+
+    def test_repr_3(self):
+        class Key:
+            def __init__(self):
+                self.val = None
+
+            def __hash__(self):
+                return 123
+
+            def __repr__(self):
+                return repr(self.val)
+
+        h = self.Map()
+        k = Key()
+        h = h.set(k, 1)
+        k.val = h
+
+        assert repr(h).startswith(
+            '<immutables.Map({{...}: 1}) at 0x')
+
+    def test_hash_1(self):
+        h = self.Map()
+        assert hash(h) != -1
+        assert hash(h) == hash(h)
+
+        h = h.set(1, 2).set('a', 'b')
+        assert hash(h) != -1
+        assert hash(h) == hash(h)
+
+        assert hash(h.set(1, 2).set('a', 'b')) == \
+            hash(h.set('a', 'b').set(1, 2))
+
+    def test_hash_2(self):
+        h = self.Map()
+        A = HashKey(100, 'A')
+
+        m = h.set(1, 2).set(A, 3).set(3, 4)
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                hash(m)
+
+        m = h.set(1, 2).set(2, A).set(3, 4)
+        with pytest.raises(HashingError):
+            with HashKeyCrasher(error_on_hash=True):
+                hash(m)
+
+    def test_abc_1(self):
+        assert issubclass(self.Map, collections.abc.Mapping)
+
+    def test_map_mut_1(self):
+        h = self.Map()
+        h = h.set('a', 1)
+
+        hm1 = h.mutate()
+        hm2 = h.mutate()
+
+        assert not isinstance(hm1, self.Map)
+
+        assert hm1 is not hm2
+        assert hm1['a'] == 1
+        assert hm2['a'] == 1
+
+        hm1.set('b', 2)
+        hm1.set('c', 3)
+
+        hm2.set('x', 100)
+        hm2.set('a', 1000)
+
+        assert hm1['a'] == 1
+        assert hm1.get('x', -1) == -1
+
+        assert hm2['a'] == 1000
+        assert 'x' in hm2
+
+        h1 = hm1.finish()
+        h2 = hm2.finish()
+
+        assert isinstance(h1, self.Map)
+
+        assert dict(h.items()) == {'a': 1}
+        assert dict(h1.items()) == {'a': 1, 'b': 2, 'c': 3}
+        assert dict(h2.items()) == {'a': 1000, 'x': 100}
+
+    def test_map_mut_2(self):
+        h = self.Map()
+        h = h.set('a', 1)
+
+        hm1 = h.mutate()
+        hm1.set('a', 2)
+        hm1.set('a', 3)
+        hm1.set('a', 4)
+        h2 = hm1.finish()
+
+        assert dict(h.items()) == {'a': 1}
+        assert dict(h2.items()) == {'a': 4}
+
+    def test_map_mut_3(self):
+        h = self.Map()
+        h = h.set('a', 1)
+        hm1 = h.mutate()
+
+        assert repr(hm1).startswith(
+            "<immutables.MapMutation({'a': 1})")
+
+        with pytest.raises(TypeError, match='unhashable type'):
+            hash(hm1)
+
+    def test_map_mut_4(self):
+        h = self.Map()
+        h = h.set('a', 1)
+        h = h.set('b', 2)
+
+        hm1 = h.mutate()
+        hm2 = h.mutate()
+
+        assert hm1 == hm2
+
+        hm1.set('a', 10)
+        assert hm1 != hm2
+
+        hm2.set('a', 10)
+        assert hm1 == hm2
+
+        assert hm2.pop('a') == 10
+        assert hm1 != hm2
+
+    def test_map_mut_5(self):
+        h = self.Map({'a': 1, 'b': 2}, z=100)
+        assert isinstance(h, self.Map)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+        h2 = h.update(z=200, y=-1)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+        assert dict(h2.items()) == {'a': 1, 'b': 2, 'z': 200, 'y': -1}
+
+        h3 = h2.update([(1, 2), (3, 4)])
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+        assert dict(h2.items()) == {'a': 1, 'b': 2, 'z': 200, 'y': -1}
+        assert dict(h3.items()) == \
+                         {'a': 1, 'b': 2, 'z': 200, 'y': -1, 1: 2, 3: 4}
+
+        h4 = h3.update()
+        assert h4 is h3
+
+        h5 = h4.update(self.Map({'zzz': 'yyz'}))
+
+        assert dict(h5.items()) == \
+                         {'a': 1, 'b': 2, 'z': 200, 'y': -1, 1: 2, 3: 4,
+                          'zzz': 'yyz'}
+
+    def test_map_mut_6(self):
+        h = self.Map({'a': 1, 'b': 2}, z=100)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+        with pytest.raises(TypeError, match='not iterable'):
+            h.update(1)
+
+        with pytest.raises(ValueError, match='map update sequence element'):
+            h.update([(1, 2), (3, 4, 5)])
+
+        with pytest.raises(TypeError, match='cannot convert map update'):
+            h.update([(1, 2), 1])
+
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+    def test_map_mut_7(self):
+        key = HashKey(123, 'aaa')
+
+        h = self.Map({'a': 1, 'b': 2}, z=100)
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+        upd = {key: 1}
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                h.update(upd)
+
+        upd = self.Map({key: 'zzz'})
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                h.update(upd)
+
+        upd = [(1, 2), (key, 'zzz')]
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                h.update(upd)
+
+        assert dict(h.items()) == {'a': 1, 'b': 2, 'z': 100}
+
+    def test_map_mut_8(self):
+        key1 = HashKey(123, 'aaa')
+        key2 = HashKey(123, 'bbb')
+
+        h = self.Map({key1: 123})
+        assert dict(h.items()) == {key1: 123}
+
+        upd = {key2: 1}
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                h.update(upd)
+
+        upd = self.Map({key2: 'zzz'})
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                h.update(upd)
+
+        upd = [(1, 2), (key2, 'zzz')]
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                h.update(upd)
+
+        assert dict(h.items()) == {key1: 123}
+
+    def test_map_mut_9(self):
+        key1 = HashKey(123, 'aaa')
+
+        src = {key1: 123}
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                self.Map(src)
+
+        src = [(1, 2), (key1, 123)]
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                self.Map(src)
+
+    def test_map_mut_10(self):
+        key1 = HashKey(123, 'aaa')
+
+        m = self.Map({key1: 123})
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                del mm[key1]
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                mm.pop(key1, None)
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_hash=True):
+            with pytest.raises(HashingError):
+                mm.set(key1, 123)
+
+    def test_map_mut_11(self):
+        m = self.Map({'a': 1, 'b': 2})
+
+        mm = m.mutate()
+        assert mm.pop('a', 1) == 1
+        assert mm.finish() == self.Map({'b': 2})
+
+        mm = m.mutate()
+        assert mm.pop('b', 1) == 2
+        assert mm.finish() == self.Map({'a': 1})
+
+        mm = m.mutate()
+        assert mm.pop('b', 1) == 2
+        del mm['a']
+        assert mm.finish() == self.Map()
+
+    def test_map_mut_12(self):
+        m = self.Map({'a': 1, 'b': 2})
+
+        mm = m.mutate()
+        mm.finish()
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm.pop('a')
+
+        with pytest.raises(ValueError, match='has been finished'):
+            del mm['a']
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm.set('a', 'b')
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm['a'] = 'b'
+
+        with pytest.raises(ValueError, match='has been finished'):
+            mm.update(a='b')
+
+    def test_map_mut_13(self):
+        key1 = HashKey(123, 'aaa')
+        key2 = HashKey(123, 'aaa')
+
+        m = self.Map({key1: 123})
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                del mm[key2]
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                mm.pop(key2, None)
+
+        mm = m.mutate()
+        with HashKeyCrasher(error_on_eq=True):
+            with pytest.raises(EqError):
+                mm.set(key2, 123)
+
+    def test_map_mut_14(self):
+        m = self.Map(a=1, b=2)
+
+        with m.mutate() as mm:
+            mm['z'] = 100
+            del mm['a']
+
+        assert mm.finish() == self.Map(z=100, b=2)
+
+    def test_map_mut_15(self):
+        m = self.Map(a=1, b=2)
+
+        with pytest.raises(ZeroDivisionError):
+            with m.mutate() as mm:
+                mm['z'] = 100
+                del mm['a']
+                1 / 0
+
+        assert mm.finish() == self.Map(z=100, b=2)
+        assert m == self.Map(a=1, b=2)
+
+    def test_map_mut_16(self):
+        m = self.Map(a=1, b=2)
+        hash(m)
+
+        m2 = self.Map(m)
+        m3 = self.Map(m, c=3)
+
+        assert m == m2
+        assert len(m) == len(m2)
+        assert hash(m) == hash(m2)
+
+        assert m is not m2
+        assert m3 == self.Map(a=1, b=2, c=3)
+
+    def test_map_mut_17(self):
+        m = self.Map(a=1)
+        with m.mutate() as mm:
+            with pytest.raises(TypeError, match='cannot create Maps from MapMutations'):
+                self.Map(mm)
+
+    def test_map_mut_18(self):
+        m = self.Map(a=1, b=2)
+        with m.mutate() as mm:
+            mm.update(self.Map(x=1), z=2)
+            mm.update(c=3)
+            mm.update({'n': 100, 'a': 20})
+            m2 = mm.finish()
+
+        expected = self.Map(
+            {'b': 2, 'c': 3, 'n': 100, 'z': 2, 'x': 1, 'a': 20})
+
+        assert len(m2) == 6
+        assert m2 == expected
+        assert m == self.Map(a=1, b=2)
+
+    def test_map_mut_19(self):
+        m = self.Map(a=1, b=2)
+        m2 = m.update({'a': 20})
+        assert len(m2) == 2
+
+    def test_map_mut_stress(self):
+        COLLECTION_SIZE = 7000
+        TEST_ITERS_EVERY = 647
+        RUN_XTIMES = 3
+
+        for _ in range(RUN_XTIMES):
+            h = self.Map()
+            d = dict()
+
+            for i in range(COLLECTION_SIZE // TEST_ITERS_EVERY):
+
+                hm = h.mutate()
+                for j in range(TEST_ITERS_EVERY):
+                    key = random.randint(1, 100000)
+                    key = HashKey(key % 271, str(key))
+
+                    hm.set(key, key)
+                    d[key] = key
+
+                    assert len(hm) == len(d)
+
+                h2 = hm.finish()
+                assert dict(h2.items()) == d
+                h = h2
+
+            assert dict(h.items()) == d
+            assert len(h) == len(d)
+
+            it = iter(tuple(d.keys()))
+            for i in range(COLLECTION_SIZE // TEST_ITERS_EVERY):
+
+                hm = h.mutate()
+                for j in range(TEST_ITERS_EVERY):
+                    try:
+                        key = next(it)
+                    except StopIteration:
+                        break
+
+                    del d[key]
+                    del hm[key]
+
+                    assert len(hm) == len(d)
+
+                h2 = hm.finish()
+                assert dict(h2.items()) == d
+                h = h2
+
+            assert dict(h.items()) == d
+            assert len(h) == len(d)
+
+    def test_map_pickle(self):
+        h = self.Map(a=1, b=2)
+        for proto in range(pickle.HIGHEST_PROTOCOL):
+            p = pickle.dumps(h, proto)
+            uh = pickle.loads(p)
+
+            assert isinstance(uh, self.Map)
+            assert h == uh
+
+        with pytest.raises(TypeError, match="can('t|not) pickle"):
+            pickle.dumps(h.mutate())
+
+    def test_map_is_subscriptable(self):
+        assert self.Map[int, str] is self.Map
+
+class TestPyMap(BaseMapTest):
+    Map = Map
diff --git a/lib-python/3/datetime.py b/lib-python/3/datetime.py
--- a/lib-python/3/datetime.py
+++ b/lib-python/3/datetime.py
@@ -1544,9 +1544,13 @@
             self.__setstate(year, month)
             self._hashcode = -1
             return self
-        year, month, day = _check_date_fields(year, month, day)
-        hour, minute, second, microsecond, fold = _check_time_fields(
-            hour, minute, second, microsecond, fold)
+        elif isinstance(year, tuple) and len(year) == 7:
+            # Internal operation - numbers guaranteed to be valid
+            year, month, day, hour, minute, second, microsecond = year
+        else:
+            year, month, day = _check_date_fields(year, month, day)
+            hour, minute, second, microsecond, fold = _check_time_fields(
+                hour, minute, second, microsecond, fold)
         _check_tzinfo_arg(tzinfo)
         self = dateinterop.__new__(cls)
         self._year = int(year)
@@ -2035,20 +2039,18 @@
         "Add a datetime and a timedelta."
         if not isinstance(other, timedelta):
             return NotImplemented
-        delta = timedelta(self.toordinal(),
-                          hours=self._hour,
-                          minutes=self._minute,
-                          seconds=self._second,
-                          microseconds=self._microsecond)
-        delta += other
-        hour, rem = divmod(delta.seconds, 3600)
-        minute, second = divmod(rem, 60)
-        if 0 < delta.days <= _MAXORDINAL:
-            return datetime.combine(date.fromordinal(delta.days),
-                                    time(hour, minute, second,
-                                         delta.microseconds,
-                                         tzinfo=self._tzinfo))
-        raise OverflowError("result out of range")
+
+        result = _normalize_datetime(
+            self._year,
+            self._month,
+            self._day + other.days,
+            self._hour,
+            self._minute,
+            self._second + other.seconds,
+            self._microsecond + other.microseconds,
+        )
+
+        return datetime(result, tzinfo=self._tzinfo)
 
     __radd__ = __add__
 
@@ -2145,6 +2147,65 @@
 datetime.resolution = timedelta(microseconds=1)
 
 
+def _normalize_pair(hi, lo, factor):
+    if not 0 <= lo <= factor-1:
+        inc, lo = divmod(lo, factor)
+        hi += inc
+    return hi, lo
+
+
+def _normalize_datetime(y, m, d, hh, mm, ss, us):
+    # Normalize all the inputs, and store the normalized values.
+    ss, us = _normalize_pair(ss, us, 1000000)
+    mm, ss = _normalize_pair(mm, ss, 60)
+    hh, mm = _normalize_pair(hh, mm, 60)
+    d, hh = _normalize_pair(d, hh, 24)
+    y, m, d = _normalize_date(y, m, d)
+    return y, m, d, hh, mm, ss, us
+
+
+def _normalize_date(year, month, day):
+    # That was easy.  Now it gets muddy:  the proper range for day
+    # can't be determined without knowing the correct month and year,
+    # but if day is, e.g., plus or minus a million, the current month
+    # and year values make no sense (and may also be out of bounds
+    # themselves).
+    # Saying 12 months == 1 year should be non-controversial.
+    if not 1 <= month <= 12:
+        year, month = _normalize_pair(year, month-1, 12)
+        month += 1
+        assert 1 <= month <= 12
+
+    # Now only day can be out of bounds (year may also be out of bounds
+    # for a datetime object, but we don't care about that here).
+    # If day is out of bounds, what to do is arguable, but at least the
+    # method here is principled and explainable.
+    dim = _days_in_month(year, month)
+    if not 1 <= day <= dim:
+        # Move day-1 days from the first of the month.  First try to
+        # get off cheap if we're only one day out of range (adjustments
+        # for timezone alone can't be worse than that).
+        if day == 0:    # move back a day
+            month -= 1
+            if month > 0:
+                day = _days_in_month(year, month)
+            else:
+                year, month, day = year-1, 12, 31
+        elif day == dim + 1:    # move forward a day
+            month += 1
+            day = 1
+            if month > 12:
+                month = 1
+                year += 1
+        else:
+            ordinal = _ymd2ord(year, month, 1) + (day - 1)
+            year, month, day = _ord2ymd(ordinal)
+
+    if not MINYEAR <= year <= MAXYEAR:
+        raise OverflowError("date value out of range")
+    return year, month, day
+
+
 def _isoweek1monday(year):
     # Helper to calculate the day number of the Monday starting week 1
     # XXX This could be done more efficiently
diff --git a/lib-python/3/importlib/_bootstrap.py b/lib-python/3/importlib/_bootstrap.py
--- a/lib-python/3/importlib/_bootstrap.py
+++ b/lib-python/3/importlib/_bootstrap.py
@@ -67,6 +67,7 @@
         # Deadlock avoidance for concurrent circular imports.
         me = _thread.get_ident()
         tid = self.owner
+        count = 0
         while True:
             lock = _blocking_on.get(tid)
             if lock is None:
@@ -74,6 +75,14 @@
             tid = lock.owner
             if tid == me:
                 return True
+            # workaround for https://bugs.python.org/issue38091:
+            # instead of looping here forever, eventually return False.
+            # Unsure if this will cause real deadlocks to go undetected,
+            # but at least it doesn't cause *this* logic here to
+            # deadlock when there is otherwise no deadlock!
+            count += 1
+            if count >= 100:
+                return False
 
     def acquire(self):
         """
diff --git a/lib-python/3/sysconfig.py b/lib-python/3/sysconfig.py
--- a/lib-python/3/sysconfig.py
+++ b/lib-python/3/sysconfig.py
@@ -451,6 +451,10 @@
     vars['EXE'] = '.exe'
     vars['VERSION'] = _PY_VERSION_SHORT_NO_DOT
     vars['BINDIR'] = os.path.dirname(_safe_realpath(sys.executable))
+    # pypy: give us control over the ABI tag in a wheel name
+    import _imp
+    so_ext = _imp.extension_suffixes()[0]
+    vars['SOABI']= '-'.join(so_ext.split('.')[1].split('-')[:2])
 
 #
 # public APIs
diff --git a/lib-python/3/test/test_asyncio/test_events.py b/lib-python/3/test/test_asyncio/test_events.py
--- a/lib-python/3/test/test_asyncio/test_events.py
+++ b/lib-python/3/test/test_asyncio/test_events.py
@@ -943,9 +943,14 @@
         server = self.loop.run_until_complete(f)
         self.assertEqual(len(server.sockets), 1)
         sock = server.sockets[0]
-        self.assertFalse(
-            sock.getsockopt(
-                socket.SOL_SOCKET, socket.SO_REUSEPORT))
+        try:
+            self.assertFalse(
+                sock.getsockopt(
+                    socket.SOL_SOCKET, socket.SO_REUSEPORT))
+        except OSError:
+            # SO_REUSEPORT is not actually supported, bail!
+            server.close()
+            return
         server.close()
 
         test_utils.run_briefly(self.loop)
diff --git a/lib-python/3/test/test_context.py b/lib-python/3/test/test_context.py
--- a/lib-python/3/test/test_context.py
+++ b/lib-python/3/test/test_context.py
@@ -24,7 +24,7 @@
 
 class ContextTest(unittest.TestCase):
     def test_context_var_new_1(self):
-        with self.assertRaisesRegex(TypeError, 'takes exactly 1'):
+        with self.assertRaises(TypeError):
             contextvars.ContextVar()
 
         with self.assertRaisesRegex(TypeError, 'must be a str'):
@@ -76,11 +76,11 @@
                 pass
 
     def test_context_new_1(self):
-        with self.assertRaisesRegex(TypeError, 'any arguments'):
+        with self.assertRaises(TypeError):
             contextvars.Context(1)
-        with self.assertRaisesRegex(TypeError, 'any arguments'):
+        with self.assertRaises(TypeError):
             contextvars.Context(1, a=1)
-        with self.assertRaisesRegex(TypeError, 'any arguments'):
+        with self.assertRaises(TypeError):
             contextvars.Context(a=1)
         contextvars.Context(**{})
 
diff --git a/lib-python/3/test/test_dis.py b/lib-python/3/test/test_dis.py
--- a/lib-python/3/test/test_dis.py
+++ b/lib-python/3/test/test_dis.py
@@ -272,7 +272,7 @@
              20 RETURN_VALUE
 """
 
-# XXX: change for PyPy?
+# changed for PyPy
 dis_traceback = """\
 %3d           0 SETUP_EXCEPT            12 (to 14)
 
@@ -830,9 +830,9 @@
 # End fodder for opinfo generation tests
 expected_outer_line = 1
 _line_offset = outer.__code__.co_firstlineno - 1
-code_object_f = outer.__code__.co_consts[3]
+code_object_f = outer.__code__.co_consts[2]
 expected_f_line = code_object_f.co_firstlineno - _line_offset
-code_object_inner = code_object_f.co_consts[3]
+code_object_inner = code_object_f.co_consts[2]
 expected_inner_line = code_object_inner.co_firstlineno - _line_offset
 expected_jumpy_line = 1
 
@@ -857,22 +857,22 @@
 
 Instruction = dis.Instruction
 expected_opinfo_outer = [
-  Instruction(opname='LOAD_CONST', opcode=100, arg=8, argval=(3, 4), argrepr='(3, 4)', offset=0, starts_line=2, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=(3, 4), argrepr='(3, 4)', offset=0, starts_line=2, is_jump_target=False),
   Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='a', argrepr='a', offset=2, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='b', argrepr='b', offset=4, starts_line=None, is_jump_target=False),
   Instruction(opname='BUILD_TUPLE', opcode=102, arg=2, argval=2, argrepr='', offset=6, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_f, argrepr=repr(code_object_f), offset=8, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f', argrepr="'outer.<locals>.f'", offset=10, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=code_object_f, argrepr=repr(code_object_f), offset=8, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval='outer.<locals>.f', argrepr="'outer.<locals>.f'", offset=10, starts_line=None, is_jump_target=False),
   Instruction(opname='MAKE_FUNCTION', opcode=132, arg=9, argval=9, argrepr='', offset=12, starts_line=None, is_jump_target=False),
   Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='f', argrepr='f', offset=14, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=16, starts_line=7, is_jump_target=False),
   Instruction(opname='LOAD_DEREF', opcode=136, arg=0, argval='a', argrepr='a', offset=18, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_DEREF', opcode=136, arg=1, argval='b', argrepr='b', offset=20, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval='', argrepr="''", offset=22, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval=1, argrepr='1', offset=24, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='', argrepr="''", offset=22, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=1, argrepr='1', offset=24, starts_line=None, is_jump_target=False),
   Instruction(opname='BUILD_LIST', opcode=103, arg=0, argval=0, argrepr='', offset=26, starts_line=None, is_jump_target=False),
   Instruction(opname='BUILD_MAP', opcode=105, arg=0, argval=0, argrepr='', offset=28, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=7, argval='Hello world!', argrepr="'Hello world!'", offset=30, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=6, argval='Hello world!', argrepr="'Hello world!'", offset=30, starts_line=None, is_jump_target=False),
   Instruction(opname='CALL_FUNCTION', opcode=131, arg=7, argval=7, argrepr='', offset=32, starts_line=None, is_jump_target=False),
   Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=34, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_FAST', opcode=124, arg=2, argval='f', argrepr='f', offset=36, starts_line=8, is_jump_target=False),
@@ -880,14 +880,14 @@
 ]
 
 expected_opinfo_f = [
-  Instruction(opname='LOAD_CONST', opcode=100, arg=5, argval=(5, 6), argrepr='(5, 6)', offset=0, starts_line=3, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval=(5, 6), argrepr='(5, 6)', offset=0, starts_line=3, is_jump_target=False),
   Instruction(opname='LOAD_CLOSURE', opcode=135, arg=2, argval='a', argrepr='a', offset=2, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_CLOSURE', opcode=135, arg=3, argval='b', argrepr='b', offset=4, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_CLOSURE', opcode=135, arg=0, argval='c', argrepr='c', offset=6, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_CLOSURE', opcode=135, arg=1, argval='d', argrepr='d', offset=8, starts_line=None, is_jump_target=False),
   Instruction(opname='BUILD_TUPLE', opcode=102, arg=4, argval=4, argrepr='', offset=10, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval=code_object_inner, argrepr=repr(code_object_inner), offset=12, starts_line=None, is_jump_target=False),
-  Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='outer.<locals>.f.<locals>.inner', argrepr="'outer.<locals>.f.<locals>.inner'", offset=14, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=code_object_inner, argrepr=repr(code_object_inner), offset=12, starts_line=None, is_jump_target=False),
+  Instruction(opname='LOAD_CONST', opcode=100, arg=3, argval='outer.<locals>.f.<locals>.inner', argrepr="'outer.<locals>.f.<locals>.inner'", offset=14, starts_line=None, is_jump_target=False),
   Instruction(opname='MAKE_FUNCTION', opcode=132, arg=9, argval=9, argrepr='', offset=16, starts_line=None, is_jump_target=False),
   Instruction(opname='STORE_FAST', opcode=125, arg=2, argval='inner', argrepr='inner', offset=18, starts_line=None, is_jump_target=False),
   Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=20, starts_line=5, is_jump_target=False),
diff --git a/lib-python/3/test/test_extcall.py b/lib-python/3/test/test_extcall.py
--- a/lib-python/3/test/test_extcall.py
+++ b/lib-python/3/test/test_extcall.py
@@ -57,7 +57,7 @@
     Traceback (most recent call last):
         ...
     TypeError: ...got multiple values for keyword argument 'a'
-    >>> f(1, 2, a=3, **{'a': 4}, **{'a': 5})
+    >>> f(1, 2, a=3, **{'a': 4}, **{'a': 5})  #doctest: +ELLIPSIS
     Traceback (most recent call last):
         ...
     TypeError: ...got multiple values for keyword argument 'a'
@@ -254,20 +254,21 @@
       ...
     TypeError: h() argument after * must be an iterable, not function
 
-    >>> h(*[1], *h)
+    >>> h(*[1], *h)  #doctest: +ELLIPSIS
     Traceback (most recent call last):
       ...
-    TypeError: h() argument after * must be an iterable, not function
+    TypeError: ...
 
     >>> dir(*h)
     Traceback (most recent call last):
       ...
     TypeError: dir() argument after * must be an iterable, not function
 
-    >>> None(*h)
+    >>> None(**h)  #doctest: +ELLIPSIS
     Traceback (most recent call last):
       ...
-    TypeError: ...argument after * must be an iterable, not function
+    TypeError: ... object argument after ** must be a mapping, \
+not function
 
     >>> h(**h)
     Traceback (most recent call last):
@@ -289,35 +290,20 @@
       ...
     TypeError: h() argument after ** must be a mapping, not list
 
-    >>> h(**{'a': 1}, **h)
+    >>> h(**{'a': 1}, **h)  #doctest: +ELLIPSIS
     Traceback (most recent call last):
       ...
-    TypeError: h() argument after ** must be a mapping, not function
+    TypeError: ...argument after ** must be a mapping, not function
 
-    >>> h(**{'a': 1}, **[])
+    >>> h(**{'a': 1}, **[])  #doctest: +ELLIPSIS
     Traceback (most recent call last):
       ...
-    TypeError: h() argument after ** must be a mapping, not list
+    TypeError: ...argument after ** must be a mapping, not list
 
     >>> dir(**h)
     Traceback (most recent call last):
       ...
-    TypeError: ...argument after * must be an iterable, not function
-
-    >>> None(*h)                               #doctest: +ELLIPSIS
-    Traceback (most recent call last):
-      ...
-    TypeError: ...argument after * must be an iterable, not function
-
-    >>> h(**h)                                 #doctest: +ELLIPSIS
-    Traceback (most recent call last):
-      ...
-    TypeError: ...argument after ** must be a mapping, not function
-
-    >>> dir(**h)                               #doctest: +ELLIPSIS
-    Traceback (most recent call last):
-      ...
-    TypeError: ...argument after ** must be a mapping, not function
+    TypeError: dir() argument after ** must be a mapping, not function
 
     >>> None(**h)                              #doctest: +ELLIPSIS
     Traceback (most recent call last):
diff --git a/lib-python/3/test/test_flufl.py b/lib-python/3/test/test_flufl.py
--- a/lib-python/3/test/test_flufl.py
+++ b/lib-python/3/test/test_flufl.py
@@ -15,7 +15,7 @@
         self.assertEqual(cm.exception.text, '2 != 3\n')
         self.assertEqual(cm.exception.filename, '<FLUFL test>')
         self.assertEqual(cm.exception.lineno, 2)
-        self.assertEqual(cm.exception.offset, 4)
+        self.assertEqual(cm.exception.offset, 2)  # changed in PyPy
 
     def test_guido_as_bdfl(self):
         code = '2 {0} 3'
@@ -26,7 +26,7 @@
         self.assertEqual(cm.exception.text, '2 <> 3\n')
         self.assertEqual(cm.exception.filename, '<FLUFL test>')
         self.assertEqual(cm.exception.lineno, 1)
-        self.assertEqual(cm.exception.offset, 4)
+        self.assertEqual(cm.exception.offset, 2)  # changed in PyPy
 
 
 if __name__ == '__main__':
diff --git a/lib-python/3/test/test_import/__init__.py b/lib-python/3/test/test_import/__init__.py
--- a/lib-python/3/test/test_import/__init__.py
+++ b/lib-python/3/test/test_import/__init__.py
@@ -414,16 +414,22 @@
                 os.does_not_exist
 
     def test_concurrency(self):
+        def delay_has_deadlock(frame, event, arg):
+            if event == 'call' and frame.f_code.co_name == 'has_deadlock':
+                time.sleep(0.05)
+
         sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'data'))
         try:
             exc = None
             def run():
+                sys.settrace(delay_has_deadlock)
                 event.wait()
                 try:
                     import package
                 except BaseException as e:
                     nonlocal exc
                     exc = e
+                sys.settrace(None)
 
             for i in range(10):
                 event = threading.Event()
diff --git a/lib_pypy/_cffi_ssl/_stdssl/__init__.py b/lib_pypy/_cffi_ssl/_stdssl/__init__.py
--- a/lib_pypy/_cffi_ssl/_stdssl/__init__.py
+++ b/lib_pypy/_cffi_ssl/_stdssl/__init__.py
@@ -1,4 +1,5 @@
 import sys
+import os
 import time
 import _thread
 import weakref
@@ -10,7 +11,7 @@
     import os
     msg = "\n\nThe _ssl cffi module either doesn't exist or is incompatible with your machine's shared libraries.\n" + \
           "If you have a compiler installed, you can try to rebuild it by running:\n" + \
-          "cd %s\n" % os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + \
+          "cd %s\n" % os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + \
           "%s _ssl_build.py\n" % sys.executable
     raise ImportError(str(e) + msg)
 
@@ -83,6 +84,11 @@
 OP_NO_SSLv2 = lib.SSL_OP_NO_SSLv2
 OP_NO_SSLv3 = lib.SSL_OP_NO_SSLv3
 OP_NO_TLSv1_3 = lib.SSL_OP_NO_TLSv1_3
+if OPENSSL_VERSION_INFO > (1, 1, 0, 0, 0):
+    # OP_ENABLE_MIDDLEBOX_COMPAT = lib.SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+    # XXX should be conditionally compiled into lib
+    OP_ENABLE_MIDDLEBOX_COMPAT = 0x00100000
+
 
 
 SSL_CLIENT = 0
@@ -289,6 +295,20 @@
             mode |= lib.SSL_MODE_AUTO_RETRY
         lib.SSL_set_mode(ssl, mode)
 
+        if HAS_TLSv1_3:
+            if sslctx._post_handshake_auth:
+                if socket_type == SSL_SERVER:
+                    # bpo-37428: OpenSSL does not ignore SSL_VERIFY_POST_HANDSHAKE.
+                    # Set SSL_VERIFY_POST_HANDSHAKE flag only for server sockets and
+                    # only in combination with SSL_VERIFY_PEER flag.
+                    mode = lib.SSL_CTX_get_verify_mode(lib.SSL_get_SSL_CTX(self.ssl))
+                    if (mode & lib.SSL_VERIFY_PEER):
+                        verify_cb = lib.SSL_get_verify_callback(self.ssl)
+                        mode |= lib.SSL_VERIFY_POST_HANDSHAKE
+                        lib.SSL_set_verify(ssl, mode, verify_cb)
+                else:
+                    lib.SSL_set_post_handshake_auth(ssl, 1)
+
         if HAS_SNI and self.server_hostname:
             name = _str_to_ffi_buffer(self.server_hostname)
             lib.SSL_set_tlsext_host_name(ssl, name)
@@ -711,6 +731,15 @@
         else:
             return None


More information about the pypy-commit mailing list