[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