aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pyrsistent/py3/tests
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/pyrsistent/py3/tests
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/pyrsistent/py3/tests')
-rw-r--r--contrib/python/pyrsistent/py3/tests/bag_test.py150
-rw-r--r--contrib/python/pyrsistent/py3/tests/checked_map_test.py152
-rw-r--r--contrib/python/pyrsistent/py3/tests/checked_set_test.py85
-rw-r--r--contrib/python/pyrsistent/py3/tests/checked_vector_test.py213
-rw-r--r--contrib/python/pyrsistent/py3/tests/class_test.py474
-rw-r--r--contrib/python/pyrsistent/py3/tests/deque_test.py293
-rw-r--r--contrib/python/pyrsistent/py3/tests/field_test.py23
-rw-r--r--contrib/python/pyrsistent/py3/tests/freeze_test.py174
-rw-r--r--contrib/python/pyrsistent/py3/tests/hypothesis_vector_test.py304
-rw-r--r--contrib/python/pyrsistent/py3/tests/immutable_object_test.py67
-rw-r--r--contrib/python/pyrsistent/py3/tests/list_test.py209
-rw-r--r--contrib/python/pyrsistent/py3/tests/map_test.py551
-rw-r--r--contrib/python/pyrsistent/py3/tests/memory_profiling.py48
-rw-r--r--contrib/python/pyrsistent/py3/tests/record_test.py878
-rw-r--r--contrib/python/pyrsistent/py3/tests/regression_test.py30
-rw-r--r--contrib/python/pyrsistent/py3/tests/set_test.py181
-rw-r--r--contrib/python/pyrsistent/py3/tests/toolz_test.py6
-rw-r--r--contrib/python/pyrsistent/py3/tests/transform_test.py122
-rw-r--r--contrib/python/pyrsistent/py3/tests/vector_test.py934
-rw-r--r--contrib/python/pyrsistent/py3/tests/ya.make27
20 files changed, 4921 insertions, 0 deletions
diff --git a/contrib/python/pyrsistent/py3/tests/bag_test.py b/contrib/python/pyrsistent/py3/tests/bag_test.py
new file mode 100644
index 0000000000..fb80603108
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/bag_test.py
@@ -0,0 +1,150 @@
+import pytest
+
+from pyrsistent import b, pbag
+
+
+def test_literalish_works():
+ assert b(1, 2) == pbag([1, 2])
+
+def test_empty_bag():
+ """
+ creating an empty pbag returns a singleton.
+
+ Note that this should NOT be relied upon in application code.
+ """
+ assert b() is b()
+
+def test_supports_hash():
+ assert hash(b(1, 2)) == hash(b(2, 1))
+
+def test_hash_in_dict():
+ assert {b(1,2,3,3): "hello"}[b(3,3,2,1)] == "hello"
+
+def test_empty_truthiness():
+ assert b(1)
+ assert not b()
+
+
+def test_repr_empty():
+ assert repr(b()) == 'pbag([])'
+
+def test_repr_elements():
+ assert repr(b(1, 2)) in ('pbag([1, 2])', 'pbag([2, 1])')
+
+
+def test_add_empty():
+ assert b().add(1) == b(1)
+
+def test_remove_final():
+ assert b().add(1).remove(1) == b()
+
+def test_remove_nonfinal():
+ assert b().add(1).add(1).remove(1) == b(1)
+
+def test_remove_nonexistent():
+ with pytest.raises(KeyError) as excinfo:
+ b().remove(1)
+ assert str(excinfo.exconly()) == 'KeyError: 1'
+
+
+def test_eq_empty():
+ assert b() == b()
+
+def test_neq():
+ assert b(1) != b()
+
+def test_eq_same_order():
+ assert b(1, 2, 1) == b(1, 2, 1)
+
+def test_eq_different_order():
+ assert b(2, 1, 2) == b(1, 2, 2)
+
+
+def test_count_non_existent():
+ assert b().count(1) == 0
+
+def test_count_unique():
+ assert b(1).count(1) == 1
+
+def test_count_duplicate():
+ assert b(1, 1).count(1) == 2
+
+
+def test_length_empty():
+ assert len(b()) == 0
+
+def test_length_unique():
+ assert len(b(1)) == 1
+
+def test_length_duplicates():
+ assert len(b(1, 1)) == 2
+
+def test_length_multiple_elements():
+ assert len(b(1, 1, 2, 3)) == 4
+
+
+def test_iter_duplicates():
+ assert list(b(1, 1)) == [1, 1]
+
+def test_iter_multiple_elements():
+ assert list(b(1, 2, 2)) in ([1, 2, 2], [2, 2, 1])
+
+def test_contains():
+ assert 1 in b(1)
+
+def test_not_contains():
+ assert 1 not in b(2)
+
+def test_add():
+ assert b(3, 3, 3, 2, 2, 1) + b(4, 3, 2, 1) == b(4,
+ 3, 3, 3, 3,
+ 2, 2, 2,
+ 1, 1)
+
+def test_sub():
+ assert b(1, 2, 3, 3) - b(3, 4) == b(1, 2, 3)
+
+def test_or():
+ assert b(1, 2, 2, 3, 3, 3) | b(1, 2, 3, 4, 4) == b(1,
+ 2, 2,
+ 3, 3, 3,
+ 4, 4)
+
+def test_and():
+ assert b(1, 2, 2, 3, 3, 3) & b(2, 3, 3, 4) == b(2, 3, 3)
+
+
+def test_pbag_is_unorderable():
+ with pytest.raises(TypeError):
+ _ = b(1) < b(2) # type: ignore
+
+ with pytest.raises(TypeError):
+ _ = b(1) <= b(2) # type: ignore
+
+ with pytest.raises(TypeError):
+ _ = b(1) > b(2) # type: ignore
+
+ with pytest.raises(TypeError):
+ _ = b(1) >= b(2) # type: ignore
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(b(1))
+
+
+def test_update():
+ assert pbag([1, 2, 2]).update([3, 3, 4]) == pbag([1, 2, 2, 3, 3, 4])
+
+
+def test_update_no_elements():
+ b = pbag([1, 2, 2])
+ assert b.update([]) is b
+
+
+def test_iterable():
+ """
+ PBags can be created from iterables even though they can't be len() hinted.
+ """
+
+ assert pbag(iter("a")) == pbag(iter("a"))
diff --git a/contrib/python/pyrsistent/py3/tests/checked_map_test.py b/contrib/python/pyrsistent/py3/tests/checked_map_test.py
new file mode 100644
index 0000000000..b0ffbceecf
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/checked_map_test.py
@@ -0,0 +1,152 @@
+import pickle
+import pytest
+from pyrsistent import CheckedPMap, InvariantException, PMap, CheckedType, CheckedPSet, CheckedPVector, \
+ CheckedKeyTypeError, CheckedValueTypeError
+
+
+class FloatToIntMap(CheckedPMap):
+ __key_type__ = float
+ __value_type__ = int
+ __invariant__ = lambda key, value: (int(key) == value, 'Invalid mapping')
+
+def test_instantiate():
+ x = FloatToIntMap({1.25: 1, 2.5: 2})
+
+ assert dict(x.items()) == {1.25: 1, 2.5: 2}
+ assert isinstance(x, FloatToIntMap)
+ assert isinstance(x, PMap)
+ assert isinstance(x, CheckedType)
+
+def test_instantiate_empty():
+ x = FloatToIntMap()
+
+ assert dict(x.items()) == {}
+ assert isinstance(x, FloatToIntMap)
+
+def test_set():
+ x = FloatToIntMap()
+ x2 = x.set(1.0, 1)
+
+ assert x2[1.0] == 1
+ assert isinstance(x2, FloatToIntMap)
+
+def test_invalid_key_type():
+ with pytest.raises(CheckedKeyTypeError):
+ FloatToIntMap({1: 1})
+
+def test_invalid_value_type():
+ with pytest.raises(CheckedValueTypeError):
+ FloatToIntMap({1.0: 1.0})
+
+def test_breaking_invariant():
+ try:
+ FloatToIntMap({1.5: 2})
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Invalid mapping',)
+
+def test_repr():
+ x = FloatToIntMap({1.25: 1})
+
+ assert str(x) == 'FloatToIntMap({1.25: 1})'
+
+def test_default_serialization():
+ x = FloatToIntMap({1.25: 1, 2.5: 2})
+
+ assert x.serialize() == {1.25: 1, 2.5: 2}
+
+class StringFloatToIntMap(FloatToIntMap):
+ @staticmethod
+ def __serializer__(format, key, value):
+ return format.format(key), format.format(value)
+
+def test_custom_serialization():
+ x = StringFloatToIntMap({1.25: 1, 2.5: 2})
+
+ assert x.serialize("{0}") == {"1.25": "1", "2.5": "2"}
+
+class FloatSet(CheckedPSet):
+ __type__ = float
+
+class IntToFloatSetMap(CheckedPMap):
+ __key_type__ = int
+ __value_type__ = FloatSet
+
+
+def test_multi_level_serialization():
+ x = IntToFloatSetMap.create({1: [1.25, 1.50], 2: [2.5, 2.75]})
+
+ assert str(x) == "IntToFloatSetMap({1: FloatSet([1.5, 1.25]), 2: FloatSet([2.75, 2.5])})"
+
+ sx = x.serialize()
+ assert sx == {1: set([1.5, 1.25]), 2: set([2.75, 2.5])}
+ assert isinstance(sx[1], set)
+
+def test_create_non_checked_types():
+ assert FloatToIntMap.create({1.25: 1, 2.5: 2}) == FloatToIntMap({1.25: 1, 2.5: 2})
+
+def test_create_checked_types():
+ class IntSet(CheckedPSet):
+ __type__ = int
+
+ class FloatVector(CheckedPVector):
+ __type__ = float
+
+ class IntSetToFloatVectorMap(CheckedPMap):
+ __key_type__ = IntSet
+ __value_type__ = FloatVector
+
+ x = IntSetToFloatVectorMap.create({frozenset([1, 2]): [1.25, 2.5]})
+
+ assert str(x) == "IntSetToFloatVectorMap({IntSet([1, 2]): FloatVector([1.25, 2.5])})"
+
+def test_evolver_returns_same_instance_when_no_updates():
+ x = FloatToIntMap({1.25: 1, 2.25: 2})
+
+ assert x.evolver().persistent() is x
+
+def test_map_with_no_types_or_invariants():
+ class NoCheckPMap(CheckedPMap):
+ pass
+
+ x = NoCheckPMap({1: 2, 3: 4})
+ assert x[1] == 2
+ assert x[3] == 4
+
+
+def test_pickling():
+ x = FloatToIntMap({1.25: 1, 2.5: 2})
+ y = pickle.loads(pickle.dumps(x, -1))
+
+ assert x == y
+ assert isinstance(y, FloatToIntMap)
+
+
+class FloatVector(CheckedPVector):
+ __type__ = float
+
+
+class VectorToSetMap(CheckedPMap):
+ __key_type__ = '__tests__.checked_map_test.FloatVector'
+ __value_type__ = '__tests__.checked_map_test.FloatSet'
+
+
+def test_type_check_with_string_specification():
+ content = [1.5, 2.0]
+ vec = FloatVector(content)
+ sett = FloatSet(content)
+ map = VectorToSetMap({vec: sett})
+
+ assert map[vec] == sett
+
+
+def test_type_creation_with_string_specification():
+ content = (1.5, 2.0)
+ map = VectorToSetMap.create({content: content})
+
+ assert map[FloatVector(content)] == set(content)
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(VectorToSetMap({}))
diff --git a/contrib/python/pyrsistent/py3/tests/checked_set_test.py b/contrib/python/pyrsistent/py3/tests/checked_set_test.py
new file mode 100644
index 0000000000..f0be4963e2
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/checked_set_test.py
@@ -0,0 +1,85 @@
+import pickle
+import pytest
+from pyrsistent import CheckedPSet, PSet, InvariantException, CheckedType, CheckedPVector, CheckedValueTypeError
+
+
+class Naturals(CheckedPSet):
+ __type__ = int
+ __invariant__ = lambda value: (value >= 0, 'Negative value')
+
+def test_instantiate():
+ x = Naturals([1, 2, 3, 3])
+
+ assert list(x) == [1, 2, 3]
+ assert isinstance(x, Naturals)
+ assert isinstance(x, PSet)
+ assert isinstance(x, CheckedType)
+
+def test_add():
+ x = Naturals()
+ x2 = x.add(1)
+
+ assert list(x2) == [1]
+ assert isinstance(x2, Naturals)
+
+def test_invalid_type():
+ with pytest.raises(CheckedValueTypeError):
+ Naturals([1, 2.0])
+
+def test_breaking_invariant():
+ try:
+ Naturals([1, -1])
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Negative value',)
+
+def test_repr():
+ x = Naturals([1, 2])
+
+ assert str(x) == 'Naturals([1, 2])'
+
+def test_default_serialization():
+ x = Naturals([1, 2])
+
+ assert x.serialize() == set([1, 2])
+
+class StringNaturals(Naturals):
+ @staticmethod
+ def __serializer__(format, value):
+ return format.format(value)
+
+def test_custom_serialization():
+ x = StringNaturals([1, 2])
+
+ assert x.serialize("{0}") == set(["1", "2"])
+
+class NaturalsVector(CheckedPVector):
+ __type__ = Naturals
+
+def test_multi_level_serialization():
+ x = NaturalsVector.create([[1, 2], [3, 4]])
+
+ assert str(x) == "NaturalsVector([Naturals([1, 2]), Naturals([3, 4])])"
+
+ sx = x.serialize()
+ assert sx == [set([1, 2]), set([3, 4])]
+ assert isinstance(sx[0], set)
+
+def test_create():
+ assert Naturals.create([1, 2]) == Naturals([1, 2])
+
+def test_evolver_returns_same_instance_when_no_updates():
+ x = Naturals([1, 2])
+ assert x.evolver().persistent() is x
+
+def test_pickling():
+ x = Naturals([1, 2])
+ y = pickle.loads(pickle.dumps(x, -1))
+
+ assert x == y
+ assert isinstance(y, Naturals)
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(Naturals([1, 2])) \ No newline at end of file
diff --git a/contrib/python/pyrsistent/py3/tests/checked_vector_test.py b/contrib/python/pyrsistent/py3/tests/checked_vector_test.py
new file mode 100644
index 0000000000..b2e3d43cd6
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/checked_vector_test.py
@@ -0,0 +1,213 @@
+import datetime
+import pickle
+import pytest
+from pyrsistent import CheckedPVector, InvariantException, optional, CheckedValueTypeError, PVector
+
+
+class Naturals(CheckedPVector):
+ __type__ = int
+ __invariant__ = lambda value: (value >= 0, 'Negative value')
+
+def test_instantiate():
+ x = Naturals([1, 2, 3])
+
+ assert list(x) == [1, 2, 3]
+ assert isinstance(x, Naturals)
+ assert isinstance(x, PVector)
+
+def test_append():
+ x = Naturals()
+ x2 = x.append(1)
+
+ assert list(x2) == [1]
+ assert isinstance(x2, Naturals)
+
+def test_extend():
+ x = Naturals()
+ x2 = x.extend([1])
+
+ assert list(x2) == [1]
+ assert isinstance(x2, Naturals)
+
+def test_set():
+ x = Naturals([1, 2])
+ x2 = x.set(1, 3)
+
+ assert list(x2) == [1, 3]
+ assert isinstance(x2, Naturals)
+
+
+def test_invalid_type():
+ try:
+ Naturals([1, 2.0])
+ assert False
+ except CheckedValueTypeError as e:
+ assert e.expected_types == (int,)
+ assert e.actual_type is float
+ assert e.actual_value == 2.0
+ assert e.source_class is Naturals
+
+ x = Naturals([1, 2])
+ with pytest.raises(TypeError):
+ x.append(3.0)
+
+ with pytest.raises(TypeError):
+ x.extend([3, 4.0])
+
+ with pytest.raises(TypeError):
+ x.set(1, 2.0)
+
+ with pytest.raises(TypeError):
+ x.evolver()[1] = 2.0
+
+def test_breaking_invariant():
+ try:
+ Naturals([1, -1])
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Negative value',)
+
+ x = Naturals([1, 2])
+ try:
+ x.append(-1)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Negative value',)
+
+ try:
+ x.extend([-1])
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Negative value',)
+
+ try:
+ x.set(1, -1)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Negative value',)
+
+def test_create_base_case():
+ x = Naturals.create([1, 2, 3])
+
+ assert isinstance(x, Naturals)
+ assert x == Naturals([1, 2, 3])
+
+def test_create_with_instance_of_checked_pvector_returns_the_argument():
+ x = Naturals([1, 2, 3])
+
+ assert Naturals.create(x) is x
+
+class OptionalNaturals(CheckedPVector):
+ __type__ = optional(int)
+ __invariant__ = lambda value: (value is None or value >= 0, 'Negative value')
+
+def test_multiple_allowed_types():
+ assert list(OptionalNaturals([1, None, 3])) == [1, None, 3]
+
+class NaturalsVector(CheckedPVector):
+ __type__ = optional(Naturals)
+
+def test_create_of_nested_structure():
+ assert NaturalsVector([Naturals([1, 2]), Naturals([3, 4]), None]) ==\
+ NaturalsVector.create([[1, 2], [3, 4], None])
+
+def test_serialize_default_case():
+ v = CheckedPVector([1, 2, 3])
+ assert v.serialize() == [1, 2, 3]
+
+class Dates(CheckedPVector):
+ __type__ = datetime.date
+
+ @staticmethod
+ def __serializer__(format, d):
+ return d.strftime(format)
+
+def test_serialize_custom_serializer():
+ d = datetime.date
+ v = Dates([d(2015, 2, 2), d(2015, 2, 3)])
+ assert v.serialize(format='%Y-%m-%d') == ['2015-02-02', '2015-02-03']
+
+def test_type_information_is_inherited():
+ class MultiDates(Dates):
+ __type__ = int
+
+ MultiDates([datetime.date(2015, 2, 4), 5])
+
+ with pytest.raises(TypeError):
+ MultiDates([5.0])
+
+def test_invariants_are_inherited():
+ class LimitNaturals(Naturals):
+ __invariant__ = lambda value: (value < 10, 'Too big')
+
+ try:
+ LimitNaturals([10, -1])
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('Too big', 'Negative value')
+
+def test_invariant_must_be_callable():
+ with pytest.raises(TypeError):
+ class InvalidInvariant(CheckedPVector):
+ __invariant__ = 1
+
+def test_type_spec_must_be_type():
+ with pytest.raises(TypeError):
+ class InvalidType(CheckedPVector):
+ __type__ = 1
+
+def test_repr():
+ x = Naturals([1, 2])
+
+ assert str(x) == 'Naturals([1, 2])'
+
+def test_evolver_returns_same_instance_when_no_updates():
+ x = Naturals([1, 2])
+ assert x.evolver().persistent() is x
+
+def test_pickling():
+ x = Naturals([1, 2])
+ y = pickle.loads(pickle.dumps(x, -1))
+
+ assert x == y
+ assert isinstance(y, Naturals)
+
+def test_multiple_optional_types():
+ class Numbers(CheckedPVector):
+ __type__ = optional(int, float)
+
+ numbers = Numbers([1, 2.5, None])
+ assert numbers.serialize() == [1, 2.5, None]
+
+ with pytest.raises(TypeError):
+ numbers.append('foo')
+
+
+class NaturalsVectorStr(CheckedPVector):
+ __type__ = '__tests__.checked_vector_test.Naturals'
+
+
+def test_check_with_string_specification():
+ naturals_list = [Naturals([1, 2]), Naturals([3, 4])]
+ nv = NaturalsVectorStr(naturals_list)
+ assert nv == naturals_list
+
+
+def test_create_with_string_specification():
+ naturals_list = [[1, 2], [3, 4]]
+ nv = NaturalsVectorStr.create(naturals_list)
+ assert nv == naturals_list
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(Naturals([]))
+
+
+def test_create_with_generator_iterator():
+ # See issue #97
+ class Numbers(CheckedPVector):
+ __type__ = int
+
+ n = Numbers(i for i in [1, 2, 3])
+ assert n == Numbers([1, 2, 3]) \ No newline at end of file
diff --git a/contrib/python/pyrsistent/py3/tests/class_test.py b/contrib/python/pyrsistent/py3/tests/class_test.py
new file mode 100644
index 0000000000..5e953965d5
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/class_test.py
@@ -0,0 +1,474 @@
+from collections.abc import Hashable
+import math
+import pickle
+import pytest
+import uuid
+from pyrsistent import (
+ field, InvariantException, PClass, optional, CheckedPVector,
+ pmap_field, pset_field, pvector_field)
+
+
+class Point(PClass):
+ x = field(type=int, mandatory=True, invariant=lambda x: (x >= 0, 'X negative'))
+ y = field(type=int, serializer=lambda formatter, y: formatter(y))
+ z = field(type=int, initial=0)
+
+
+class Hierarchy(PClass):
+ point = field(type=Point)
+
+
+class TypedContainerObj(PClass):
+ map = pmap_field(str, str)
+ set = pset_field(str)
+ vec = pvector_field(str)
+
+
+class UniqueThing(PClass):
+ id = field(type=uuid.UUID, factory=uuid.UUID)
+ x = field(type=int)
+
+
+def test_create_ignore_extra():
+ p = Point.create({'x': 5, 'y': 10, 'z': 15, 'a': 0}, ignore_extra=True)
+ assert p.x == 5
+ assert p.y == 10
+ assert p.z == 15
+ assert isinstance(p, Point)
+
+
+def test_create_ignore_extra_false():
+ with pytest.raises(AttributeError):
+ _ = Point.create({'x': 5, 'y': 10, 'z': 15, 'a': 0})
+
+
+def test_create_ignore_extra_true():
+ h = Hierarchy.create(
+ {'point': {'x': 5, 'y': 10, 'z': 15, 'extra_field_0': 'extra_data_0'}, 'extra_field_1': 'extra_data_1'},
+ ignore_extra=True)
+ assert isinstance(h, Hierarchy)
+
+
+def test_evolve_pclass_instance():
+ p = Point(x=1, y=2)
+ p2 = p.set(x=p.x+2)
+
+ # Original remains
+ assert p.x == 1
+ assert p.y == 2
+
+ # Evolved object updated
+ assert p2.x == 3
+ assert p2.y == 2
+
+ p3 = p2.set('x', 4)
+ assert p3.x == 4
+ assert p3.y == 2
+
+
+def test_direct_assignment_not_possible():
+ p = Point(x=1, y=2)
+
+ with pytest.raises(AttributeError):
+ p.x = 1
+
+ with pytest.raises(AttributeError):
+ setattr(p, 'x', 1)
+
+
+def test_direct_delete_not_possible():
+ p = Point(x=1, y=2)
+ with pytest.raises(AttributeError):
+ del p.x
+
+ with pytest.raises(AttributeError):
+ delattr(p, 'x')
+
+
+def test_cannot_construct_with_undeclared_fields():
+ with pytest.raises(AttributeError):
+ Point(x=1, p=5)
+
+
+def test_cannot_construct_with_wrong_type():
+ with pytest.raises(TypeError):
+ Point(x='a')
+
+
+def test_cannot_construct_without_mandatory_fields():
+ try:
+ Point(y=1)
+ assert False
+ except InvariantException as e:
+ assert "[Point.x]" in str(e)
+
+
+def test_field_invariant_must_hold():
+ try:
+ Point(x=-1)
+ assert False
+ except InvariantException as e:
+ assert "X negative" in str(e)
+
+
+def test_initial_value_set_when_not_present_in_arguments():
+ p = Point(x=1, y=2)
+
+ assert p.z == 0
+
+
+class Line(PClass):
+ p1 = field(type=Point)
+ p2 = field(type=Point)
+
+
+def test_can_create_nested_structures_from_dict_and_serialize_back_to_dict():
+ source = dict(p1=dict(x=1, y=2, z=3), p2=dict(x=10, y=20, z=30))
+ l = Line.create(source)
+
+ assert l.p1.x == 1
+ assert l.p1.y == 2
+ assert l.p1.z == 3
+ assert l.p2.x == 10
+ assert l.p2.y == 20
+ assert l.p2.z == 30
+
+ assert l.serialize(format=lambda val: val) == source
+
+
+def test_can_serialize_with_custom_serializer():
+ p = Point(x=1, y=1, z=1)
+
+ assert p.serialize(format=lambda v: v + 17) == {'x': 1, 'y': 18, 'z': 1}
+
+
+def test_implements_proper_equality_based_on_equality_of_fields():
+ p1 = Point(x=1, y=2)
+ p2 = Point(x=3)
+ p3 = Point(x=1, y=2)
+
+ assert p1 == p3
+ assert not p1 != p3
+ assert p1 != p2
+ assert not p1 == p2
+
+
+def test_is_hashable():
+ p1 = Point(x=1, y=2)
+ p2 = Point(x=3, y=2)
+
+ d = {p1: 'A point', p2: 'Another point'}
+
+ p1_like = Point(x=1, y=2)
+ p2_like = Point(x=3, y=2)
+
+ assert isinstance(p1, Hashable)
+ assert d[p1_like] == 'A point'
+ assert d[p2_like] == 'Another point'
+ assert Point(x=10) not in d
+
+
+def test_supports_nested_transformation():
+ l1 = Line(p1=Point(x=2, y=1), p2=Point(x=20, y=10))
+
+ l2 = l1.transform(['p1', 'x'], 3)
+
+ assert l1.p1.x == 2
+
+ assert l2.p1.x == 3
+ assert l2.p1.y == 1
+ assert l2.p2.x == 20
+ assert l2.p2.y == 10
+
+
+def test_repr():
+ class ARecord(PClass):
+ a = field()
+ b = field()
+
+ assert repr(ARecord(a=1, b=2)) in ('ARecord(a=1, b=2)', 'ARecord(b=2, a=1)')
+
+
+def test_global_invariant_check():
+ class UnitCirclePoint(PClass):
+ __invariant__ = lambda cp: (0.99 < math.sqrt(cp.x*cp.x + cp.y*cp.y) < 1.01,
+ "Point not on unit circle")
+ x = field(type=float)
+ y = field(type=float)
+
+ UnitCirclePoint(x=1.0, y=0.0)
+
+ with pytest.raises(InvariantException):
+ UnitCirclePoint(x=1.0, y=1.0)
+
+
+def test_supports_pickling():
+ p1 = Point(x=2, y=1)
+ p2 = pickle.loads(pickle.dumps(p1, -1))
+
+ assert p1 == p2
+ assert isinstance(p2, Point)
+
+
+def test_supports_pickling_with_typed_container_fields():
+ obj = TypedContainerObj(map={'foo': 'bar'}, set=['hello', 'there'], vec=['a', 'b'])
+ obj2 = pickle.loads(pickle.dumps(obj))
+ assert obj == obj2
+
+
+def test_can_remove_optional_member():
+ p1 = Point(x=1, y=2)
+ p2 = p1.remove('y')
+
+ assert p2 == Point(x=1)
+
+
+def test_cannot_remove_mandatory_member():
+ p1 = Point(x=1, y=2)
+
+ with pytest.raises(InvariantException):
+ p1.remove('x')
+
+
+def test_cannot_remove_non_existing_member():
+ p1 = Point(x=1)
+
+ with pytest.raises(AttributeError):
+ p1.remove('y')
+
+
+def test_evolver_without_evolution_returns_original_instance():
+ p1 = Point(x=1)
+ e = p1.evolver()
+
+ assert e.persistent() is p1
+
+
+def test_evolver_with_evolution_to_same_element_returns_original_instance():
+ p1 = Point(x=1)
+ e = p1.evolver()
+ e.set('x', p1.x)
+
+ assert e.persistent() is p1
+
+
+def test_evolver_supports_chained_set_and_remove():
+ p1 = Point(x=1, y=2)
+
+ assert p1.evolver().set('x', 3).remove('y').persistent() == Point(x=3)
+
+
+def test_evolver_supports_dot_notation_for_setting_and_getting_elements():
+ e = Point(x=1, y=2).evolver()
+
+ e.x = 3
+ assert e.x == 3
+ assert e.persistent() == Point(x=3, y=2)
+
+
+class Numbers(CheckedPVector):
+ __type__ = int
+
+
+class LinkedList(PClass):
+ value = field(type='__tests__.class_test.Numbers')
+ next = field(type=optional('__tests__.class_test.LinkedList'))
+
+
+def test_string_as_type_specifier():
+ l = LinkedList(value=[1, 2], next=LinkedList(value=[3, 4], next=None))
+
+ assert isinstance(l.value, Numbers)
+ assert list(l.value) == [1, 2]
+ assert l.next.next is None
+
+
+def test_multiple_invariants_on_field():
+ # If the invariant returns a list of tests the results of running those tests will be
+ # a tuple containing result data of all failing tests.
+
+ class MultiInvariantField(PClass):
+ one = field(type=int, invariant=lambda x: ((False, 'one_one'),
+ (False, 'one_two'),
+ (True, 'one_three')))
+ two = field(invariant=lambda x: (False, 'two_one'))
+
+ try:
+ MultiInvariantField(one=1, two=2)
+ assert False
+ except InvariantException as e:
+ assert set(e.invariant_errors) == set([('one_one', 'one_two'), 'two_one'])
+
+
+def test_multiple_global_invariants():
+ class MultiInvariantGlobal(PClass):
+ __invariant__ = lambda self: ((False, 'x'), (False, 'y'))
+ one = field()
+
+ try:
+ MultiInvariantGlobal(one=1)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == (('x', 'y'),)
+
+
+def test_inherited_global_invariants():
+ class Distant(object):
+ def __invariant__(self):
+ return [(self.distant, "distant")]
+
+ class Nearby(Distant):
+ def __invariant__(self):
+ return [(self.nearby, "nearby")]
+
+ class MultipleInvariantGlobal(Nearby, PClass):
+ distant = field()
+ nearby = field()
+
+ try:
+ MultipleInvariantGlobal(distant=False, nearby=False)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == (("nearby",), ("distant",),)
+
+
+def test_diamond_inherited_global_invariants():
+ counter = []
+ class Base(object):
+ def __invariant__(self):
+ counter.append(None)
+ return [(False, "base")]
+
+ class Left(Base):
+ pass
+
+ class Right(Base):
+ pass
+
+ class SingleInvariantGlobal(Left, Right, PClass):
+ pass
+
+ try:
+ SingleInvariantGlobal()
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == (("base",),)
+ assert counter == [None]
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(Point(x=1, y=2))
+
+
+def test_supports_weakref_with_multi_level_inheritance():
+ import weakref
+
+ class PPoint(Point):
+ a = field()
+
+ weakref.ref(PPoint(x=1, y=2))
+
+
+def test_supports_lazy_initial_value_for_field():
+ class MyClass(PClass):
+ a = field(int, initial=lambda: 2)
+
+ assert MyClass() == MyClass(a=2)
+
+
+def test_type_checks_lazy_initial_value_for_field():
+ class MyClass(PClass):
+ a = field(int, initial=lambda: "a")
+
+ with pytest.raises(TypeError):
+ MyClass()
+
+
+def test_invariant_checks_lazy_initial_value_for_field():
+ class MyClass(PClass):
+ a = field(int, invariant=lambda x: (x < 5, "Too large"), initial=lambda: 10)
+
+ with pytest.raises(InvariantException):
+ MyClass()
+
+
+def test_invariant_checks_static_initial_value():
+ class MyClass(PClass):
+ a = field(int, invariant=lambda x: (x < 5, "Too large"), initial=10)
+
+ with pytest.raises(InvariantException):
+ MyClass()
+
+
+def test_lazy_invariant_message():
+ class MyClass(PClass):
+ a = field(int, invariant=lambda x: (x < 5, lambda: "{x} is too large".format(x=x)))
+
+ try:
+ MyClass(a=5)
+ assert False
+ except InvariantException as e:
+ assert '5 is too large' in e.invariant_errors
+
+
+def test_enum_key_type():
+ import enum
+ class Foo(enum.Enum):
+ Bar = 1
+ Baz = 2
+
+ # This currently fails because the enum is iterable
+ class MyClass1(PClass):
+ f = pmap_field(key_type=Foo, value_type=int)
+
+ MyClass1()
+
+ # This is OK since it's wrapped in a tuple
+ class MyClass2(PClass):
+ f = pmap_field(key_type=(Foo,), value_type=int)
+
+ MyClass2()
+
+
+def test_pickle_with_one_way_factory():
+ thing = UniqueThing(id='25544626-86da-4bce-b6b6-9186c0804d64')
+ assert pickle.loads(pickle.dumps(thing)) == thing
+
+
+def test_evolver_with_one_way_factory():
+ thing = UniqueThing(id='cc65249a-56fe-4995-8719-ea02e124b234')
+ ev = thing.evolver()
+ ev.x = 5 # necessary to prevent persistent() returning the original
+ assert ev.persistent() == UniqueThing(id=str(thing.id), x=5)
+
+
+def test_set_doesnt_trigger_other_factories():
+ thing = UniqueThing(id='b413b280-de76-4e28-a8e3-5470ca83ea2c')
+ thing.set(x=5)
+
+
+def test_set_does_trigger_factories():
+ class SquaredPoint(PClass):
+ x = field(factory=lambda x: x ** 2)
+ y = field()
+
+ sp = SquaredPoint(x=3, y=10)
+ assert (sp.x, sp.y) == (9, 10)
+
+ sp2 = sp.set(x=4)
+ assert (sp2.x, sp2.y) == (16, 10)
+
+
+def test_value_can_be_overridden_in_subclass_new():
+ class X(PClass):
+ y = pvector_field(int)
+
+ def __new__(cls, **kwargs):
+ items = kwargs.get('y', None)
+ if items is None:
+ kwargs['y'] = ()
+ return super(X, cls).__new__(cls, **kwargs)
+
+ a = X(y=[])
+ b = a.set(y=None)
+ assert a == b
diff --git a/contrib/python/pyrsistent/py3/tests/deque_test.py b/contrib/python/pyrsistent/py3/tests/deque_test.py
new file mode 100644
index 0000000000..7798a75583
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/deque_test.py
@@ -0,0 +1,293 @@
+import pickle
+import pytest
+from pyrsistent import pdeque, dq
+
+
+def test_basic_right_and_left():
+ x = pdeque([1, 2])
+
+ assert x.right == 2
+ assert x.left == 1
+ assert len(x) == 2
+
+
+def test_construction_with_maxlen():
+ assert pdeque([1, 2, 3, 4], maxlen=2) == pdeque([3, 4])
+ assert pdeque([1, 2, 3, 4], maxlen=4) == pdeque([1, 2, 3, 4])
+ assert pdeque([], maxlen=2) == pdeque()
+
+
+def test_construction_with_invalid_maxlen():
+ with pytest.raises(TypeError):
+ pdeque([], maxlen='foo')
+
+ with pytest.raises(ValueError):
+ pdeque([], maxlen=-3)
+
+
+def test_pop():
+ x = pdeque([1, 2, 3, 4]).pop()
+ assert x.right == 3
+ assert x.left == 1
+
+ x = x.pop()
+ assert x.right == 2
+ assert x.left == 1
+
+ x = x.pop()
+ assert x.right == 1
+ assert x.left == 1
+
+ x = x.pop()
+ assert x == pdeque()
+
+ x = pdeque([1, 2]).pop()
+ assert x == pdeque([1])
+
+ x = x.pop()
+ assert x == pdeque()
+
+ assert pdeque().append(1).pop() == pdeque()
+ assert pdeque().appendleft(1).pop() == pdeque()
+
+
+def test_pop_multiple():
+ assert pdeque([1, 2, 3, 4]).pop(3) == pdeque([1])
+ assert pdeque([1, 2]).pop(3) == pdeque()
+
+
+def test_pop_with_negative_index():
+ assert pdeque([1, 2, 3]).pop(-1) == pdeque([1, 2, 3]).popleft(1)
+ assert pdeque([1, 2, 3]).popleft(-1) == pdeque([1, 2, 3]).pop(1)
+
+
+def test_popleft():
+ x = pdeque([1, 2, 3, 4]).popleft()
+ assert x.left == 2
+ assert x.right == 4
+
+ x = x.popleft()
+ assert x.left == 3
+ assert x.right == 4
+
+ x = x.popleft()
+ assert x.right == 4
+ assert x.left == 4
+
+ x = x.popleft()
+ assert x == pdeque()
+
+ x = pdeque([1, 2]).popleft()
+ assert x == pdeque([2])
+
+ x = x.popleft()
+ assert x == pdeque()
+
+ assert pdeque().append(1).popleft() == pdeque()
+ assert pdeque().appendleft(1).popleft() == pdeque()
+
+
+def test_popleft_multiple():
+ assert pdeque([1, 2, 3, 4]).popleft(3) == pdeque([4])
+
+
+def test_left_on_empty_deque():
+ with pytest.raises(IndexError):
+ pdeque().left
+
+
+def test_right_on_empty_deque():
+ with pytest.raises(IndexError):
+ pdeque().right
+
+
+def test_pop_empty_deque_returns_empty_deque():
+ # The other option is to throw an index error, this is what feels best for now though
+ assert pdeque().pop() == pdeque()
+ assert pdeque().popleft() == pdeque()
+
+
+def test_str():
+ assert str(pdeque([1, 2, 3])) == 'pdeque([1, 2, 3])'
+ assert str(pdeque([])) == 'pdeque([])'
+ assert str(pdeque([1, 2], maxlen=4)) == 'pdeque([1, 2], maxlen=4)'
+
+
+def test_append():
+ assert pdeque([1, 2]).append(3).append(4) == pdeque([1, 2, 3, 4])
+
+
+def test_append_with_maxlen():
+ assert pdeque([1, 2], maxlen=2).append(3).append(4) == pdeque([3, 4])
+ assert pdeque([1, 2], maxlen=3).append(3).append(4) == pdeque([2, 3, 4])
+ assert pdeque([], maxlen=0).append(1) == pdeque()
+
+
+def test_appendleft():
+ assert pdeque([2, 1]).appendleft(3).appendleft(4) == pdeque([4, 3, 2, 1])
+
+
+def test_appendleft_with_maxlen():
+ assert pdeque([2, 1], maxlen=2).appendleft(3).appendleft(4) == pdeque([4, 3])
+ assert pdeque([2, 1], maxlen=3).appendleft(3).appendleft(4) == pdeque([4, 3, 2])
+ assert pdeque([], maxlen=0).appendleft(1) == pdeque()
+
+
+def test_extend():
+ assert pdeque([1, 2]).extend([3, 4]) == pdeque([1, 2, 3, 4])
+
+
+def test_extend_with_maxlen():
+ assert pdeque([1, 2], maxlen=3).extend([3, 4]) == pdeque([2, 3, 4])
+ assert pdeque([1, 2], maxlen=2).extend([3, 4]) == pdeque([3, 4])
+ assert pdeque([], maxlen=2).extend([1, 2]) == pdeque([1, 2])
+ assert pdeque([], maxlen=0).extend([1, 2]) == pdeque([])
+
+
+def test_extendleft():
+ assert pdeque([2, 1]).extendleft([3, 4]) == pdeque([4, 3, 2, 1])
+
+
+def test_extendleft_with_maxlen():
+ assert pdeque([1, 2], maxlen=3).extendleft([3, 4]) == pdeque([4, 3, 1])
+ assert pdeque([1, 2], maxlen=2).extendleft([3, 4]) == pdeque([4, 3])
+ assert pdeque([], maxlen=2).extendleft([1, 2]) == pdeque([2, 1])
+ assert pdeque([], maxlen=0).extendleft([1, 2]) == pdeque([])
+
+
+def test_count():
+ x = pdeque([1, 2, 3, 2, 1])
+ assert x.count(1) == 2
+ assert x.count(2) == 2
+
+
+def test_remove():
+ assert pdeque([1, 2, 3, 4]).remove(2) == pdeque([1, 3, 4])
+ assert pdeque([1, 2, 3, 4]).remove(4) == pdeque([1, 2, 3])
+
+ # Right list must be reversed before removing element
+ assert pdeque([1, 2, 3, 3, 4, 5, 4, 6]).remove(4) == pdeque([1, 2, 3, 3, 5, 4, 6])
+
+
+def test_remove_element_missing():
+ with pytest.raises(ValueError):
+ pdeque().remove(2)
+
+ with pytest.raises(ValueError):
+ pdeque([1, 2, 3]).remove(4)
+
+
+def test_reverse():
+ assert pdeque([1, 2, 3, 4]).reverse() == pdeque([4, 3, 2, 1])
+ assert pdeque().reverse() == pdeque()
+
+
+def test_rotate_right():
+ assert pdeque([1, 2, 3, 4, 5]).rotate(2) == pdeque([4, 5, 1, 2, 3])
+ assert pdeque([1, 2]).rotate(0) == pdeque([1, 2])
+ assert pdeque().rotate(2) == pdeque()
+
+
+def test_rotate_left():
+ assert pdeque([1, 2, 3, 4, 5]).rotate(-2) == pdeque([3, 4, 5, 1, 2])
+ assert pdeque().rotate(-2) == pdeque()
+
+
+def test_set_maxlen():
+ x = pdeque([], maxlen=4)
+ assert x.maxlen == 4
+
+ with pytest.raises(AttributeError):
+ x.maxlen = 5
+
+
+def test_comparison():
+ small = pdeque([1, 2])
+ large = pdeque([1, 2, 3])
+
+ assert small < large
+ assert large > small
+ assert not small > large
+ assert not large < small
+ assert large != small
+
+ # Not equal to other types
+ assert small != [1, 2]
+
+
+def test_pickling():
+ input = pdeque([1, 2, 3], maxlen=5)
+ output = pickle.loads(pickle.dumps(input, -1))
+
+ assert output == input
+ assert output.maxlen == input.maxlen
+
+
+def test_indexing():
+ assert pdeque([1, 2, 3])[0] == 1
+ assert pdeque([1, 2, 3])[1] == 2
+ assert pdeque([1, 2, 3])[2] == 3
+ assert pdeque([1, 2, 3])[-1] == 3
+ assert pdeque([1, 2, 3])[-2] == 2
+ assert pdeque([1, 2, 3])[-3] == 1
+
+
+def test_one_element_indexing():
+ assert pdeque([2])[0] == 2
+ assert pdeque([2])[-1] == 2
+
+
+def test_empty_indexing():
+ with pytest.raises(IndexError):
+ assert pdeque([])[0] == 1
+
+
+def test_indexing_out_of_range():
+ with pytest.raises(IndexError):
+ pdeque([1, 2, 3])[-4]
+
+ with pytest.raises(IndexError):
+ pdeque([1, 2, 3])[3]
+
+ with pytest.raises(IndexError):
+ pdeque([2])[-2]
+
+
+def test_indexing_invalid_type():
+ with pytest.raises(TypeError) as e:
+ pdeque([1, 2, 3])['foo']
+
+ assert 'cannot be interpreted' in str(e.value)
+
+
+def test_slicing():
+ assert pdeque([1, 2, 3])[1:2] == pdeque([2])
+ assert pdeque([1, 2, 3])[2:1] == pdeque([])
+ assert pdeque([1, 2, 3])[-2:-1] == pdeque([2])
+ assert pdeque([1, 2, 3])[::2] == pdeque([1, 3])
+
+
+def test_hashing():
+ assert hash(pdeque([1, 2, 3])) == hash(pdeque().append(1).append(2).append(3))
+
+
+def test_index():
+ assert pdeque([1, 2, 3]).index(3) == 2
+
+
+def test_literalish():
+ assert dq(1, 2, 3) == pdeque([1, 2, 3])
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(dq(1, 2))
+
+
+def test_iterable():
+ """
+ PDeques can be created from iterables even though they can't be len()
+ hinted.
+ """
+
+ assert pdeque(iter("a")) == pdeque(iter("a"))
diff --git a/contrib/python/pyrsistent/py3/tests/field_test.py b/contrib/python/pyrsistent/py3/tests/field_test.py
new file mode 100644
index 0000000000..176b64cc6b
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/field_test.py
@@ -0,0 +1,23 @@
+from enum import Enum
+
+from pyrsistent import field, pvector_field
+
+
+class ExampleEnum(Enum):
+ x = 1
+ y = 2
+
+
+def test_enum():
+ f = field(type=ExampleEnum)
+
+ assert ExampleEnum in f.type
+ assert len(f.type) == 1
+
+
+# This is meant to exercise `_seq_field`.
+def test_pvector_field_enum_type():
+ f = pvector_field(ExampleEnum)
+
+ assert len(f.type) == 1
+ assert ExampleEnum is list(f.type)[0].__type__
diff --git a/contrib/python/pyrsistent/py3/tests/freeze_test.py b/contrib/python/pyrsistent/py3/tests/freeze_test.py
new file mode 100644
index 0000000000..158cf5d872
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/freeze_test.py
@@ -0,0 +1,174 @@
+"""Tests for freeze and thaw."""
+import collections
+from pyrsistent import v, m, s, freeze, thaw, PRecord, field, mutant
+
+
+## Freeze (standard)
+
+def test_freeze_basic():
+ assert freeze(1) == 1
+ assert freeze('foo') == 'foo'
+
+def test_freeze_list():
+ assert freeze([1, 2]) == v(1, 2)
+
+def test_freeze_dict():
+ result = freeze({'a': 'b'})
+ assert result == m(a='b')
+ assert type(freeze({'a': 'b'})) is type(m())
+
+def test_freeze_defaultdict():
+ test_dict = collections.defaultdict(dict)
+ test_dict['a'] = 'b'
+ result = freeze(test_dict)
+ assert result == m(a='b')
+ assert type(freeze({'a': 'b'})) is type(m())
+
+def test_freeze_set():
+ result = freeze(set([1, 2, 3]))
+ assert result == s(1, 2, 3)
+ assert type(result) is type(s())
+
+def test_freeze_recurse_in_dictionary_values():
+ result = freeze({'a': [1]})
+ assert result == m(a=v(1))
+ assert type(result['a']) is type(v())
+
+def test_freeze_recurse_in_defaultdict_values():
+ test_dict = collections.defaultdict(dict)
+ test_dict['a'] = [1]
+ result = freeze(test_dict)
+ assert result == m(a=v(1))
+ assert type(result['a']) is type(v())
+
+def test_freeze_recurse_in_pmap_values():
+ input = {'a': m(b={'c': 1})}
+ result = freeze(input)
+ # PMap and PVector are == to their mutable equivalents
+ assert result == input
+ assert type(result) is type(m())
+ assert type(result['a']['b']) is type(m())
+
+def test_freeze_recurse_in_lists():
+ result = freeze(['a', {'b': 3}])
+ assert result == v('a', m(b=3))
+ assert type(result[1]) is type(m())
+
+def test_freeze_recurse_in_pvectors():
+ input = [1, v(2, [3])]
+ result = freeze(input)
+ # PMap and PVector are == to their mutable equivalents
+ assert result == input
+ assert type(result) is type(v())
+ assert type(result[1][1]) is type(v())
+
+def test_freeze_recurse_in_tuples():
+ """Values in tuples are recursively frozen."""
+ result = freeze(('a', {}))
+ assert result == ('a', m())
+ assert type(result[1]) is type(m())
+
+
+## Freeze (weak)
+
+def test_freeze_nonstrict_no_recurse_in_pmap_values():
+ input = {'a': m(b={'c': 1})}
+ result = freeze(input, strict=False)
+ # PMap and PVector are == to their mutable equivalents
+ assert result == input
+ assert type(result) is type(m())
+ assert type(result['a']['b']) is dict
+
+def test_freeze_nonstrict_no_recurse_in_pvectors():
+ input = [1, v(2, [3])]
+ result = freeze(input, strict=False)
+ # PMap and PVector are == to their mutable equivalents
+ assert result == input
+ assert type(result) is type(v())
+ assert type(result[1][1]) is list
+
+
+## Thaw
+
+def test_thaw_basic():
+ assert thaw(1) == 1
+ assert thaw('foo') == 'foo'
+
+def test_thaw_list():
+ result = thaw(v(1, 2))
+ assert result == [1, 2]
+ assert type(result) is list
+
+def test_thaw_dict():
+ result = thaw(m(a='b'))
+ assert result == {'a': 'b'}
+ assert type(result) is dict
+
+def test_thaw_set():
+ result = thaw(s(1, 2))
+ assert result == set([1, 2])
+ assert type(result) is set
+
+def test_thaw_recurse_in_mapping_values():
+ result = thaw(m(a=v(1)))
+ assert result == {'a': [1]}
+ assert type(result['a']) is list
+
+def test_thaw_recurse_in_dict_values():
+ result = thaw({'a': v(1, m(b=2))})
+ assert result == {'a': [1, {'b': 2}]}
+ assert type(result['a']) is list
+ assert type(result['a'][1]) is dict
+
+def test_thaw_recurse_in_vectors():
+ result = thaw(v('a', m(b=3)))
+ assert result == ['a', {'b': 3}]
+ assert type(result[1]) is dict
+
+def test_thaw_recurse_in_lists():
+ result = thaw(v(['a', m(b=1), v(2)]))
+ assert result == [['a', {'b': 1}, [2]]]
+ assert type(result[0]) is list
+ assert type(result[0][1]) is dict
+
+def test_thaw_recurse_in_tuples():
+ result = thaw(('a', m()))
+ assert result == ('a', {})
+ assert type(result[1]) is dict
+
+def test_thaw_can_handle_subclasses_of_persistent_base_types():
+ class R(PRecord):
+ x = field()
+
+ result = thaw(R(x=1))
+ assert result == {'x': 1}
+ assert type(result) is dict
+
+
+## Thaw (weak)
+
+def test_thaw_non_strict_no_recurse_in_dict_values():
+ result = thaw({'a': v(1, m(b=2))}, strict=False)
+ assert result == {'a': [1, {'b': 2}]}
+ assert type(result['a']) is type(v())
+ assert type(result['a'][1]) is type(m())
+
+def test_thaw_non_strict_no_recurse_in_lists():
+ result = thaw(v(['a', m(b=1), v(2)]), strict=False)
+ assert result == [['a', {'b': 1}, [2]]]
+ assert type(result[0][1]) is type(m())
+
+def test_mutant_decorator():
+ @mutant
+ def fn(a_list, a_dict):
+ assert a_list == v(1, 2, 3)
+ assert isinstance(a_dict, type(m()))
+ assert a_dict == {'a': 5}
+
+ return [1, 2, 3], {'a': 3}
+
+ pv, pm = fn([1, 2, 3], a_dict={'a': 5})
+
+ assert pv == v(1, 2, 3)
+ assert pm == m(a=3)
+ assert isinstance(pm, type(m()))
diff --git a/contrib/python/pyrsistent/py3/tests/hypothesis_vector_test.py b/contrib/python/pyrsistent/py3/tests/hypothesis_vector_test.py
new file mode 100644
index 0000000000..73e82abf0b
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/hypothesis_vector_test.py
@@ -0,0 +1,304 @@
+"""
+Hypothesis-based tests for pvector.
+"""
+
+import gc
+
+from collections.abc import Iterable
+from functools import wraps
+from pyrsistent import PClass, field
+
+from pytest import fixture
+
+from pyrsistent import pvector, discard
+
+from hypothesis import strategies as st, assume
+from hypothesis.stateful import RuleBasedStateMachine, Bundle, rule
+
+
+class RefCountTracker:
+ """
+ An object that might catch reference count errors sometimes.
+ """
+ def __init__(self):
+ self.id = id(self)
+
+ def __repr__(self):
+ return "<%s>" % (self.id,)
+
+ def __del__(self):
+ # If self is a dangling memory reference this check might fail. Or
+ # segfault :)
+ if self.id != id(self):
+ raise RuntimeError()
+
+
+@fixture(scope="module")
+def gc_when_done(request):
+ request.addfinalizer(gc.collect)
+
+
+def test_setup(gc_when_done):
+ """
+ Ensure we GC when tests finish.
+ """
+
+
+# Pairs of a list and corresponding pvector:
+PVectorAndLists = st.lists(st.builds(RefCountTracker)).map(
+ lambda l: (l, pvector(l)))
+
+
+def verify_inputs_unmodified(original):
+ """
+ Decorator that asserts that the wrapped function does not modify its
+ inputs.
+ """
+ def to_tuples(pairs):
+ return [(tuple(l), tuple(pv)) for (l, pv) in pairs]
+
+ @wraps(original)
+ def wrapper(self, **kwargs):
+ inputs = [k for k in kwargs.values() if isinstance(k, Iterable)]
+ tuple_inputs = to_tuples(inputs)
+ try:
+ return original(self, **kwargs)
+ finally:
+ # Ensure inputs were unmodified:
+ assert to_tuples(inputs) == tuple_inputs
+ return wrapper
+
+
+def assert_equal(l, pv):
+ assert l == pv
+ assert len(l) == len(pv)
+ length = len(l)
+ for i in range(length):
+ assert l[i] == pv[i]
+ for i in range(length):
+ for j in range(i, length):
+ assert l[i:j] == pv[i:j]
+ assert l == list(iter(pv))
+
+
+class PVectorBuilder(RuleBasedStateMachine):
+ """
+ Build a list and matching pvector step-by-step.
+
+ In each step in the state machine we do same operation on a list and
+ on a pvector, and then when we're done we compare the two.
+ """
+ sequences = Bundle("sequences")
+
+ @rule(target=sequences, start=PVectorAndLists)
+ def initial_value(self, start):
+ """
+ Some initial values generated by a hypothesis strategy.
+ """
+ return start
+
+ @rule(target=sequences, former=sequences)
+ @verify_inputs_unmodified
+ def append(self, former):
+ """
+ Append an item to the pair of sequences.
+ """
+ l, pv = former
+ obj = RefCountTracker()
+ l2 = l[:]
+ l2.append(obj)
+ return l2, pv.append(obj)
+
+ @rule(target=sequences, start=sequences, end=sequences)
+ @verify_inputs_unmodified
+ def extend(self, start, end):
+ """
+ Extend a pair of sequences with another pair of sequences.
+ """
+ l, pv = start
+ l2, pv2 = end
+ # compare() has O(N**2) behavior, so don't want too-large lists:
+ assume(len(l) + len(l2) < 50)
+ l3 = l[:]
+ l3.extend(l2)
+ return l3, pv.extend(pv2)
+
+ @rule(target=sequences, former=sequences, data=st.data())
+ @verify_inputs_unmodified
+ def remove(self, former, data):
+ """
+ Remove an item from the sequences.
+ """
+ l, pv = former
+ assume(l)
+ l2 = l[:]
+ i = data.draw(st.sampled_from(range(len(l))))
+ del l2[i]
+ return l2, pv.delete(i)
+
+ @rule(target=sequences, former=sequences, data=st.data())
+ @verify_inputs_unmodified
+ def set(self, former, data):
+ """
+ Overwrite an item in the sequence.
+ """
+ l, pv = former
+ assume(l)
+ l2 = l[:]
+ i = data.draw(st.sampled_from(range(len(l))))
+ obj = RefCountTracker()
+ l2[i] = obj
+ return l2, pv.set(i, obj)
+
+ @rule(target=sequences, former=sequences, data=st.data())
+ @verify_inputs_unmodified
+ def transform_set(self, former, data):
+ """
+ Transform the sequence by setting value.
+ """
+ l, pv = former
+ assume(l)
+ l2 = l[:]
+ i = data.draw(st.sampled_from(range(len(l))))
+ obj = RefCountTracker()
+ l2[i] = obj
+ return l2, pv.transform([i], obj)
+
+ @rule(target=sequences, former=sequences, data=st.data())
+ @verify_inputs_unmodified
+ def transform_discard(self, former, data):
+ """
+ Transform the sequence by discarding a value.
+ """
+ l, pv = former
+ assume(l)
+ l2 = l[:]
+ i = data.draw(st.sampled_from(range(len(l))))
+ del l2[i]
+ return l2, pv.transform([i], discard)
+
+ @rule(target=sequences, former=sequences, data=st.data())
+ @verify_inputs_unmodified
+ def subset(self, former, data):
+ """
+ A subset of the previous sequence.
+ """
+ l, pv = former
+ assume(l)
+ i = data.draw(st.sampled_from(range(len(l))))
+ j = data.draw(st.sampled_from(range(len(l))))
+ return l[i:j], pv[i:j]
+
+ @rule(pair=sequences)
+ @verify_inputs_unmodified
+ def compare(self, pair):
+ """
+ The list and pvector must match.
+ """
+ l, pv = pair
+ # compare() has O(N**2) behavior, so don't want too-large lists:
+ assume(len(l) < 50)
+ assert_equal(l, pv)
+
+
+PVectorBuilderTests = PVectorBuilder.TestCase
+
+
+class EvolverItem(PClass):
+ original_list = field()
+ original_pvector = field()
+ current_list = field()
+ current_evolver = field()
+
+
+class PVectorEvolverBuilder(RuleBasedStateMachine):
+ """
+ Build a list and matching pvector evolver step-by-step.
+
+ In each step in the state machine we do same operation on a list and
+ on a pvector evolver, and then when we're done we compare the two.
+ """
+ sequences = Bundle("evolver_sequences")
+
+ @rule(target=sequences, start=PVectorAndLists)
+ def initial_value(self, start):
+ """
+ Some initial values generated by a hypothesis strategy.
+ """
+ l, pv = start
+ return EvolverItem(original_list=l,
+ original_pvector=pv,
+ current_list=l[:],
+ current_evolver=pv.evolver())
+
+ @rule(item=sequences)
+ def append(self, item):
+ """
+ Append an item to the pair of sequences.
+ """
+ obj = RefCountTracker()
+ item.current_list.append(obj)
+ item.current_evolver.append(obj)
+
+ @rule(start=sequences, end=sequences)
+ def extend(self, start, end):
+ """
+ Extend a pair of sequences with another pair of sequences.
+ """
+ # compare() has O(N**2) behavior, so don't want too-large lists:
+ assume(len(start.current_list) + len(end.current_list) < 50)
+ start.current_evolver.extend(end.current_list)
+ start.current_list.extend(end.current_list)
+
+ @rule(item=sequences, data=st.data())
+ def delete(self, item, data):
+ """
+ Remove an item from the sequences.
+ """
+ assume(item.current_list)
+ i = data.draw(st.sampled_from(range(len(item.current_list))))
+ del item.current_list[i]
+ del item.current_evolver[i]
+
+ @rule(item=sequences, data=st.data())
+ def setitem(self, item, data):
+ """
+ Overwrite an item in the sequence using ``__setitem__``.
+ """
+ assume(item.current_list)
+ i = data.draw(st.sampled_from(range(len(item.current_list))))
+ obj = RefCountTracker()
+ item.current_list[i] = obj
+ item.current_evolver[i] = obj
+
+ @rule(item=sequences, data=st.data())
+ def set(self, item, data):
+ """
+ Overwrite an item in the sequence using ``set``.
+ """
+ assume(item.current_list)
+ i = data.draw(st.sampled_from(range(len(item.current_list))))
+ obj = RefCountTracker()
+ item.current_list[i] = obj
+ item.current_evolver.set(i, obj)
+
+ @rule(item=sequences)
+ def compare(self, item):
+ """
+ The list and pvector evolver must match.
+ """
+ item.current_evolver.is_dirty()
+ # compare() has O(N**2) behavior, so don't want too-large lists:
+ assume(len(item.current_list) < 50)
+ # original object unmodified
+ assert item.original_list == item.original_pvector
+ # evolver matches:
+ for i in range(len(item.current_evolver)):
+ assert item.current_list[i] == item.current_evolver[i]
+ # persistent version matches
+ assert_equal(item.current_list, item.current_evolver.persistent())
+ # original object still unmodified
+ assert item.original_list == item.original_pvector
+
+
+PVectorEvolverBuilderTests = PVectorEvolverBuilder.TestCase
diff --git a/contrib/python/pyrsistent/py3/tests/immutable_object_test.py b/contrib/python/pyrsistent/py3/tests/immutable_object_test.py
new file mode 100644
index 0000000000..11ff513cbc
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/immutable_object_test.py
@@ -0,0 +1,67 @@
+import pytest
+from pyrsistent import immutable
+
+class Empty(immutable(verbose=True)):
+ pass
+
+
+class Single(immutable('x')):
+ pass
+
+
+class FrozenMember(immutable('x, y_')):
+ pass
+
+
+class DerivedWithNew(immutable(['x', 'y'])):
+ def __new__(cls, x, y):
+ return super(DerivedWithNew, cls).__new__(cls, x, y)
+
+
+def test_instantiate_object_with_no_members():
+ t = Empty()
+ t2 = t.set()
+
+ assert t is t2
+
+
+def test_assign_non_existing_attribute():
+ t = Empty()
+
+ with pytest.raises(AttributeError):
+ t.set(a=1)
+
+
+def test_basic_instantiation():
+ t = Single(17)
+
+ assert t.x == 17
+ assert str(t) == 'Single(x=17)'
+
+
+def test_cannot_modify_member():
+ t = Single(17)
+
+ with pytest.raises(AttributeError):
+ t.x = 18
+
+def test_basic_replace():
+ t = Single(17)
+ t2 = t.set(x=18)
+
+ assert t.x == 17
+ assert t2.x == 18
+
+
+def test_cannot_replace_frozen_member():
+ t = FrozenMember(17, 18)
+
+ with pytest.raises(AttributeError):
+ t.set(y_=18)
+
+
+def test_derived_class_with_new():
+ d = DerivedWithNew(1, 2)
+ d2 = d.set(x=3)
+
+ assert d2.x == 3
diff --git a/contrib/python/pyrsistent/py3/tests/list_test.py b/contrib/python/pyrsistent/py3/tests/list_test.py
new file mode 100644
index 0000000000..ccbd83ba97
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/list_test.py
@@ -0,0 +1,209 @@
+import pickle
+import pytest
+from pyrsistent import plist, l
+
+
+def test_literalish_works():
+ assert l(1, 2, 3) == plist([1, 2, 3])
+
+
+def test_first_and_rest():
+ pl = plist([1, 2])
+ assert pl.first == 1
+ assert pl.rest.first == 2
+ assert pl.rest.rest is plist()
+
+
+def test_instantiate_large_list():
+ assert plist(range(1000)).first == 0
+
+
+def test_iteration():
+ assert list(plist()) == []
+ assert list(plist([1, 2, 3])) == [1, 2, 3]
+
+
+def test_cons():
+ assert plist([1, 2, 3]).cons(0) == plist([0, 1, 2, 3])
+
+
+def test_cons_empty_list():
+ assert plist().cons(0) == plist([0])
+
+
+def test_truthiness():
+ assert plist([1])
+ assert not plist()
+
+
+def test_len():
+ assert len(plist([1, 2, 3])) == 3
+ assert len(plist()) == 0
+
+
+def test_first_illegal_on_empty_list():
+ with pytest.raises(AttributeError):
+ plist().first
+
+
+def test_rest_return_self_on_empty_list():
+ assert plist().rest is plist()
+
+
+def test_reverse():
+ assert plist([1, 2, 3]).reverse() == plist([3, 2, 1])
+ assert reversed(plist([1, 2, 3])) == plist([3, 2, 1])
+
+ assert plist().reverse() == plist()
+ assert reversed(plist()) == plist()
+
+
+def test_inequality():
+ assert plist([1, 2]) != plist([1, 3])
+ assert plist([1, 2]) != plist([1, 2, 3])
+ assert plist() != plist([1, 2, 3])
+
+
+def test_repr():
+ assert str(plist()) == "plist([])"
+ assert str(plist([1, 2, 3])) == "plist([1, 2, 3])"
+
+
+def test_indexing():
+ assert plist([1, 2, 3])[2] == 3
+ assert plist([1, 2, 3])[-1] == 3
+
+
+def test_indexing_on_empty_list():
+ with pytest.raises(IndexError):
+ plist()[0]
+
+
+def test_index_out_of_range():
+ with pytest.raises(IndexError):
+ plist([1, 2])[2]
+
+ with pytest.raises(IndexError):
+ plist([1, 2])[-3]
+
+def test_index_invalid_type():
+ with pytest.raises(TypeError) as e:
+ plist([1, 2, 3])['foo'] # type: ignore
+
+ assert 'cannot be interpreted' in str(e.value)
+
+
+def test_slicing_take():
+ assert plist([1, 2, 3])[:2] == plist([1, 2])
+
+
+def test_slicing_take_out_of_range():
+ assert plist([1, 2, 3])[:20] == plist([1, 2, 3])
+
+
+def test_slicing_drop():
+ li = plist([1, 2, 3])
+ assert li[1:] is li.rest
+
+
+def test_slicing_drop_out_of_range():
+ assert plist([1, 2, 3])[3:] is plist()
+
+
+def test_contains():
+ assert 2 in plist([1, 2, 3])
+ assert 4 not in plist([1, 2, 3])
+ assert 1 not in plist()
+
+
+def test_count():
+ assert plist([1, 2, 1]).count(1) == 2
+ assert plist().count(1) == 0
+
+
+def test_index():
+ assert plist([1, 2, 3]).index(3) == 2
+
+
+def test_index_item_not_found():
+ with pytest.raises(ValueError):
+ plist().index(3)
+
+ with pytest.raises(ValueError):
+ plist([1, 2]).index(3)
+
+
+def test_pickling_empty_list():
+ assert pickle.loads(pickle.dumps(plist(), -1)) == plist()
+
+
+def test_pickling_non_empty_list():
+ assert pickle.loads(pickle.dumps(plist([1, 2, 3]), -1)) == plist([1, 2, 3])
+
+
+def test_comparison():
+ assert plist([1, 2]) < plist([1, 2, 3])
+ assert plist([2, 1]) > plist([1, 2, 3])
+ assert plist() < plist([1])
+ assert plist([1]) > plist()
+
+
+def test_comparison_with_other_type():
+ assert plist() != []
+
+
+def test_hashing():
+ assert hash(plist([1, 2])) == hash(plist([1, 2]))
+ assert hash(plist([1, 2])) != hash(plist([2, 1]))
+
+
+def test_split():
+ left_list, right_list = plist([1, 2, 3, 4, 5]).split(3)
+ assert left_list == plist([1, 2, 3])
+ assert right_list == plist([4, 5])
+
+
+def test_split_no_split_occurred():
+ x = plist([1, 2])
+ left_list, right_list = x.split(2)
+ assert left_list is x
+ assert right_list is plist()
+
+
+def test_split_empty_list():
+ left_list, right_list = plist().split(2)
+ assert left_list == plist()
+ assert right_list == plist()
+
+
+def test_remove():
+ assert plist([1, 2, 3, 2]).remove(2) == plist([1, 3, 2])
+ assert plist([1, 2, 3]).remove(1) == plist([2, 3])
+ assert plist([1, 2, 3]).remove(3) == plist([1, 2])
+
+
+def test_remove_missing_element():
+ with pytest.raises(ValueError):
+ plist([1, 2]).remove(3)
+
+ with pytest.raises(ValueError):
+ plist().remove(2)
+
+
+def test_mcons():
+ assert plist([1, 2]).mcons([3, 4]) == plist([4, 3, 1, 2])
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(plist())
+ weakref.ref(plist([1, 2]))
+
+
+def test_iterable():
+ """
+ PLists can be created from iterables even though they can't be len()
+ hinted.
+ """
+
+ assert plist(iter("a")) == plist(iter("a"))
diff --git a/contrib/python/pyrsistent/py3/tests/map_test.py b/contrib/python/pyrsistent/py3/tests/map_test.py
new file mode 100644
index 0000000000..ae2317b233
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/map_test.py
@@ -0,0 +1,551 @@
+from collections import namedtuple
+from collections.abc import Mapping, Hashable
+from operator import add
+import pytest
+from pyrsistent import pmap, m
+import pickle
+
+
+def test_instance_of_hashable():
+ assert isinstance(m(), Hashable)
+
+
+def test_instance_of_map():
+ assert isinstance(m(), Mapping)
+
+
+def test_literalish_works():
+ assert m() is pmap()
+ assert m(a=1, b=2) == pmap({'a': 1, 'b': 2})
+
+
+def test_empty_initialization():
+ a_map = pmap()
+ assert len(a_map) == 0
+
+
+def test_initialization_with_one_element():
+ the_map = pmap({'a': 2})
+ assert len(the_map) == 1
+ assert the_map['a'] == 2
+ assert the_map.a == 2
+ assert 'a' in the_map
+
+ assert the_map is the_map.discard('b')
+
+ empty_map = the_map.remove('a')
+ assert len(empty_map) == 0
+ assert 'a' not in empty_map
+
+
+def test_get_non_existing_raises_key_error():
+ m1 = m()
+ with pytest.raises(KeyError) as error:
+ m1['foo']
+
+ assert str(error.value) == "'foo'"
+
+
+def test_remove_non_existing_element_raises_key_error():
+ m1 = m(a=1)
+
+ with pytest.raises(KeyError) as error:
+ m1.remove('b')
+
+ assert str(error.value) == "'b'"
+
+
+def test_various_iterations():
+ assert {'a', 'b'} == set(m(a=1, b=2))
+ assert ['a', 'b'] == sorted(m(a=1, b=2).keys())
+
+ assert {1, 2} == set(m(a=1, b=2).itervalues())
+ assert [1, 2] == sorted(m(a=1, b=2).values())
+
+ assert {('a', 1), ('b', 2)} == set(m(a=1, b=2).iteritems())
+ assert {('a', 1), ('b', 2)} == set(m(a=1, b=2).items())
+
+ pm = pmap({k: k for k in range(100)})
+ assert len(pm) == len(pm.keys())
+ assert len(pm) == len(pm.values())
+ assert len(pm) == len(pm.items())
+ ks = pm.keys()
+ assert all(k in pm for k in ks)
+ assert all(k in ks for k in ks)
+ us = pm.items()
+ assert all(pm[k] == v for (k, v) in us)
+ vs = pm.values()
+ assert all(v in vs for v in vs)
+
+
+def test_initialization_with_two_elements():
+ map1 = pmap({'a': 2, 'b': 3})
+ assert len(map1) == 2
+ assert map1['a'] == 2
+ assert map1['b'] == 3
+
+ map2 = map1.remove('a')
+ assert 'a' not in map2
+ assert map2['b'] == 3
+
+
+def test_initialization_with_many_elements():
+ init_dict = dict([(str(x), x) for x in range(1700)])
+ the_map = pmap(init_dict)
+
+ assert len(the_map) == 1700
+ assert the_map['16'] == 16
+ assert the_map['1699'] == 1699
+ assert the_map.set('256', 256) is the_map
+
+ new_map = the_map.remove('1600')
+ assert len(new_map) == 1699
+ assert '1600' not in new_map
+ assert new_map['1601'] == 1601
+
+ # Some NOP properties
+ assert new_map.discard('18888') is new_map
+ assert '19999' not in new_map
+ assert new_map['1500'] == 1500
+ assert new_map.set('1500', new_map['1500']) is new_map
+
+
+def test_access_non_existing_element():
+ map1 = pmap()
+ assert len(map1) == 0
+
+ map2 = map1.set('1', 1)
+ assert '1' not in map1
+ assert map2['1'] == 1
+ assert '2' not in map2
+
+
+def test_overwrite_existing_element():
+ map1 = pmap({'a': 2})
+ map2 = map1.set('a', 3)
+
+ assert len(map2) == 1
+ assert map2['a'] == 3
+
+
+def test_hash():
+ x = m(a=1, b=2, c=3)
+ y = m(a=1, b=2, c=3)
+
+ assert hash(x) == hash(y)
+
+
+def test_same_hash_when_content_the_same_but_underlying_vector_size_differs():
+ x = pmap(dict((x, x) for x in range(1000)))
+ y = pmap({10: 10, 200: 200, 700: 700})
+
+ for z in x:
+ if z not in y:
+ x = x.remove(z)
+
+ assert x == y
+ assert hash(x) == hash(y)
+
+
+class HashabilityControlled(object):
+ hashable = True
+
+ def __hash__(self):
+ if self.hashable:
+ return 4 # Proven random
+ raise ValueError("I am not currently hashable.")
+
+
+def test_map_does_not_hash_values_on_second_hash_invocation():
+ hashable = HashabilityControlled()
+ x = pmap(dict(el=hashable))
+ hash(x)
+ hashable.hashable = False
+ hash(x)
+
+
+def test_equal():
+ x = m(a=1, b=2, c=3)
+ y = m(a=1, b=2, c=3)
+
+ assert x == y
+ assert not (x != y)
+
+ assert y == x
+ assert not (y != x)
+
+
+def test_equal_to_dict():
+ x = m(a=1, b=2, c=3)
+ y = dict(a=1, b=2, c=3)
+
+ assert x == y
+ assert not (x != y)
+
+ assert y == x
+ assert not (y != x)
+
+
+def test_equal_with_different_bucket_sizes():
+ x = pmap({'a': 1, 'b': 2}, 50)
+ y = pmap({'a': 1, 'b': 2}, 10)
+
+ assert x == y
+ assert not (x != y)
+
+ assert y == x
+ assert not (y != x)
+
+
+def test_equal_with_different_insertion_order():
+ x = pmap([(i, i) for i in range(50)], 10)
+ y = pmap([(i, i) for i in range(49, -1, -1)], 10)
+
+ assert x == y
+ assert not (x != y)
+
+ assert y == x
+ assert not (y != x)
+
+
+def test_not_equal():
+ x = m(a=1, b=2, c=3)
+ y = m(a=1, b=2)
+
+ assert x != y
+ assert not (x == y)
+
+ assert y != x
+ assert not (y == x)
+
+
+def test_not_equal_to_dict():
+ x = m(a=1, b=2, c=3)
+ y = dict(a=1, b=2, d=4)
+
+ assert x != y
+ assert not (x == y)
+
+ assert y != x
+ assert not (y == x)
+
+
+def test_update_with_multiple_arguments():
+ # If same value is present in multiple sources, the rightmost is used.
+ x = m(a=1, b=2, c=3)
+ y = x.update(m(b=4, c=5), {'c': 6})
+
+ assert y == m(a=1, b=4, c=6)
+
+
+def test_update_one_argument():
+ x = m(a=1)
+
+ assert x.update(m(b=2)) == m(a=1, b=2)
+
+
+def test_update_no_arguments():
+ x = m(a=1)
+
+ assert x.update() is x
+
+
+def test_addition():
+ assert m(x=1, y=2) + m(y=3, z=4) == m(x=1, y=3, z=4)
+
+
+def test_union_operator():
+ assert m(x=1, y=2) | m(y=3, z=4) == m(x=1, y=3, z=4)
+
+
+def test_transform_base_case():
+ # Works as set when called with only one key
+ x = m(a=1, b=2)
+
+ assert x.transform(['a'], 3) == m(a=3, b=2)
+
+
+def test_transform_nested_maps():
+ x = m(a=1, b=m(c=3, d=m(e=6, f=7)))
+
+ assert x.transform(['b', 'd', 'e'], 999) == m(a=1, b=m(c=3, d=m(e=999, f=7)))
+
+
+def test_transform_levels_missing():
+ x = m(a=1, b=m(c=3))
+
+ assert x.transform(['b', 'd', 'e'], 999) == m(a=1, b=m(c=3, d=m(e=999)))
+
+
+class HashDummy(object):
+ def __hash__(self):
+ return 6528039219058920 # Hash of '33'
+
+ def __eq__(self, other):
+ return self is other
+
+
+def test_hash_collision_is_correctly_resolved():
+ dummy1 = HashDummy()
+ dummy2 = HashDummy()
+ dummy3 = HashDummy()
+ dummy4 = HashDummy()
+
+ map1 = pmap({dummy1: 1, dummy2: 2, dummy3: 3})
+ assert map1[dummy1] == 1
+ assert map1[dummy2] == 2
+ assert map1[dummy3] == 3
+ assert dummy4 not in map1
+
+ keys = set()
+ values = set()
+ for k, v in map1.iteritems():
+ keys.add(k)
+ values.add(v)
+
+ assert keys == {dummy1, dummy2, dummy3}
+ assert values == {1, 2, 3}
+
+ map2 = map1.set(dummy1, 11)
+ assert map2[dummy1] == 11
+
+ # Re-use existing structure when inserted element is the same
+ assert map2.set(dummy1, 11) is map2
+
+ map3 = map1.set('a', 22)
+ assert map3['a'] == 22
+ assert map3[dummy3] == 3
+
+ # Remove elements
+ map4 = map1.discard(dummy2)
+ assert len(map4) == 2
+ assert map4[dummy1] == 1
+ assert dummy2 not in map4
+ assert map4[dummy3] == 3
+
+ assert map1.discard(dummy4) is map1
+
+ # Empty map handling
+ empty_map = map4.remove(dummy1).remove(dummy3)
+ assert len(empty_map) == 0
+ assert empty_map.discard(dummy1) is empty_map
+
+
+def test_bitmap_indexed_iteration():
+ a_map = pmap({'a': 2, 'b': 1})
+ keys = set()
+ values = set()
+
+ count = 0
+ for k, v in a_map.iteritems():
+ count += 1
+ keys.add(k)
+ values.add(v)
+
+ assert count == 2
+ assert keys == {'a', 'b'}
+ assert values == {2, 1}
+
+
+def test_iteration_with_many_elements():
+ values = list(range(0, 2000))
+ keys = [str(x) for x in values]
+ init_dict = dict(zip(keys, values))
+
+ hash_dummy1 = HashDummy()
+ hash_dummy2 = HashDummy()
+
+ # Throw in a couple of hash collision nodes to tests
+ # those properly as well
+ init_dict[hash_dummy1] = 12345
+ init_dict[hash_dummy2] = 54321
+ a_map = pmap(init_dict)
+
+ actual_values = set()
+ actual_keys = set()
+
+ for k, v in a_map.iteritems():
+ actual_values.add(v)
+ actual_keys.add(k)
+
+ assert actual_keys == set(keys + [hash_dummy1, hash_dummy2])
+ assert actual_values == set(values + [12345, 54321])
+
+
+def test_str():
+ assert str(pmap({1: 2, 3: 4})) == "pmap({1: 2, 3: 4})"
+
+
+def test_empty_truthiness():
+ assert m(a=1)
+ assert not m()
+
+
+def test_update_with():
+ assert m(a=1).update_with(add, m(a=2, b=4)) == m(a=3, b=4)
+ assert m(a=1).update_with(lambda l, r: l, m(a=2, b=4)) == m(a=1, b=4)
+
+ def map_add(l, r):
+ return dict(list(l.items()) + list(r.items()))
+
+ assert m(a={'c': 3}).update_with(map_add, m(a={'d': 4})) == m(a={'c': 3, 'd': 4})
+
+
+def test_pickling_empty_map():
+ assert pickle.loads(pickle.dumps(m(), -1)) == m()
+
+
+def test_pickling_non_empty_map():
+ assert pickle.loads(pickle.dumps(m(a=1, b=2), -1)) == m(a=1, b=2)
+
+
+def test_set_with_relocation():
+ x = pmap({'a': 1000}, pre_size=1)
+ x = x.set('b', 3000)
+ x = x.set('c', 4000)
+ x = x.set('d', 5000)
+ x = x.set('d', 6000)
+
+ assert len(x) == 4
+ assert x == pmap({'a': 1000, 'b': 3000, 'c': 4000, 'd': 6000})
+
+
+def test_evolver_simple_update():
+ x = m(a=1000, b=2000)
+ e = x.evolver()
+ e['b'] = 3000
+
+ assert e['b'] == 3000
+ assert e.persistent()['b'] == 3000
+ assert x['b'] == 2000
+
+
+def test_evolver_update_with_relocation():
+ x = pmap({'a': 1000}, pre_size=1)
+ e = x.evolver()
+ e['b'] = 3000
+ e['c'] = 4000
+ e['d'] = 5000
+ e['d'] = 6000
+
+ assert len(e) == 4
+ assert e.persistent() == pmap({'a': 1000, 'b': 3000, 'c': 4000, 'd': 6000})
+
+
+def test_evolver_set_with_reallocation_edge_case():
+ # Demonstrates a bug in evolver that also affects updates. Under certain
+ # circumstances, the result of `x.update(y)` will **not** have all the
+ # keys from `y`.
+ foo = object()
+ x = pmap({'a': foo}, pre_size=1)
+ e = x.evolver()
+ e['b'] = 3000
+ # Bug is triggered when we do a reallocation and the new value is
+ # identical to the old one.
+ e['a'] = foo
+
+ y = e.persistent()
+ assert 'b' in y
+ assert y is e.persistent()
+
+
+def test_evolver_remove_element():
+ e = m(a=1000, b=2000).evolver()
+ assert 'a' in e
+
+ del e['a']
+ assert 'a' not in e
+
+
+def test_evolver_remove_element_not_present():
+ e = m(a=1000, b=2000).evolver()
+
+ with pytest.raises(KeyError) as error:
+ del e['c']
+
+ assert str(error.value) == "'c'"
+
+
+def test_copy_returns_reference_to_self():
+ m1 = m(a=10)
+ assert m1.copy() is m1
+
+
+def test_dot_access_of_non_existing_element_raises_attribute_error():
+ m1 = m(a=10)
+
+ with pytest.raises(AttributeError) as error:
+ m1.b
+
+ error_message = str(error.value)
+
+ assert "'b'" in error_message
+ assert type(m1).__name__ in error_message
+
+
+def test_pmap_unorderable():
+ with pytest.raises(TypeError):
+ _ = m(a=1) < m(b=2)
+
+ with pytest.raises(TypeError):
+ _ = m(a=1) <= m(b=2)
+
+ with pytest.raises(TypeError):
+ _ = m(a=1) > m(b=2)
+
+ with pytest.raises(TypeError):
+ _ = m(a=1) >= m(b=2)
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(m(a=1))
+
+
+def test_insert_and_get_many_elements():
+ # This test case triggers reallocation of the underlying bucket structure.
+ a_map = m()
+ for x in range(1000):
+ a_map = a_map.set(str(x), x)
+
+ assert len(a_map) == 1000
+ for x in range(1000):
+ assert a_map[str(x)] == x, x
+
+
+def test_iterable():
+ """
+ PMaps can be created from iterables even though they can't be len() hinted.
+ """
+
+ assert pmap(iter([("a", "b")])) == pmap([("a", "b")])
+
+
+class BrokenPerson(namedtuple('Person', 'name')):
+ def __eq__(self, other):
+ return self.__class__ == other.__class__ and self.name == other.name
+
+ def __hash__(self):
+ return hash(self.name)
+
+
+class BrokenItem(namedtuple('Item', 'name')):
+ def __eq__(self, other):
+ return self.__class__ == other.__class__ and self.name == other.name
+
+ def __hash__(self):
+ return hash(self.name)
+
+
+def test_pmap_removal_with_broken_classes_deriving_from_namedtuple():
+ """
+ The two classes above implement __eq__ but also would need to implement __ne__ to compare
+ consistently. See issue https://github.com/tobgu/pyrsistent/issues/268 for details.
+ """
+ s = pmap({BrokenPerson('X'): 2, BrokenItem('X'): 3})
+ s = s.remove(BrokenPerson('X'))
+
+ # Both items are removed due to how they are compared for inequality
+ assert BrokenPerson('X') not in s
+ assert BrokenItem('X') in s
+ assert len(s) == 1
diff --git a/contrib/python/pyrsistent/py3/tests/memory_profiling.py b/contrib/python/pyrsistent/py3/tests/memory_profiling.py
new file mode 100644
index 0000000000..69036520cd
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/memory_profiling.py
@@ -0,0 +1,48 @@
+"""
+Script to try do detect any memory leaks that may be lurking in the C implementation of the PVector.
+"""
+import inspect
+import sys
+import time
+import memory_profiler
+import vector_test
+from pyrsistent import pvector
+
+try:
+ import pvectorc
+except ImportError:
+ print("No C implementation of PVector available, terminating")
+ sys.exit()
+
+
+PROFILING_DURATION = 2.0
+
+
+def run_function(fn):
+ stop = time.time() + PROFILING_DURATION
+ while time.time() < stop:
+ fn(pvector)
+
+
+def detect_memory_leak(samples):
+ # Do not allow a memory usage difference larger than 5% between the beginning and the end.
+ # Skip the first samples to get rid of the build up period and the last sample since it seems
+ # a little less precise
+ return abs(1 - (sum(samples[5:8]) / sum(samples[-4:-1]))) > 0.05
+
+
+def profile_tests():
+ test_functions = [fn for fn in inspect.getmembers(vector_test, inspect.isfunction)
+ if fn[0].startswith('test_')]
+
+ for name, fn in test_functions:
+ # There are a couple of tests that are not run for the C implementation, skip those
+ fn_args = inspect.getfullargspec(fn)[0]
+ if 'pvector' in fn_args:
+ print('Executing %s' % name)
+ result = memory_profiler.memory_usage((run_function, (fn,), {}), interval=.1)
+ assert not detect_memory_leak(result), (name, result)
+
+
+if __name__ == "__main__":
+ profile_tests() \ No newline at end of file
diff --git a/contrib/python/pyrsistent/py3/tests/record_test.py b/contrib/python/pyrsistent/py3/tests/record_test.py
new file mode 100644
index 0000000000..95fc55b8f1
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/record_test.py
@@ -0,0 +1,878 @@
+import pickle
+import datetime
+import pytest
+import uuid
+from pyrsistent import (
+ PRecord, field, InvariantException, ny, pset, PSet, CheckedPVector,
+ PTypeError, pset_field, pvector_field, pmap_field, pmap, PMap,
+ pvector, PVector, v, m)
+
+
+class ARecord(PRecord):
+ x = field(type=(int, float))
+ y = field()
+
+
+class Hierarchy(PRecord):
+ point1 = field(ARecord)
+ point2 = field(ARecord)
+ points = pvector_field(ARecord)
+
+
+class RecordContainingContainers(PRecord):
+ map = pmap_field(str, str)
+ vec = pvector_field(str)
+ set = pset_field(str)
+
+
+class UniqueThing(PRecord):
+ id = field(type=uuid.UUID, factory=uuid.UUID)
+
+
+class Something(object):
+ pass
+
+class Another(object):
+ pass
+
+def test_create_ignore_extra_true():
+ h = Hierarchy.create(
+ {'point1': {'x': 1, 'y': 'foo', 'extra_field_0': 'extra_data_0'},
+ 'point2': {'x': 1, 'y': 'foo', 'extra_field_1': 'extra_data_1'},
+ 'extra_field_2': 'extra_data_2',
+ }, ignore_extra=True
+ )
+ assert h
+
+
+def test_create_ignore_extra_true_sequence_hierarchy():
+ h = Hierarchy.create(
+ {'point1': {'x': 1, 'y': 'foo', 'extra_field_0': 'extra_data_0'},
+ 'point2': {'x': 1, 'y': 'foo', 'extra_field_1': 'extra_data_1'},
+ 'points': [{'x': 1, 'y': 'foo', 'extra_field_2': 'extra_data_2'},
+ {'x': 1, 'y': 'foo', 'extra_field_3': 'extra_data_3'}],
+ 'extra_field____': 'extra_data_2',
+ }, ignore_extra=True
+ )
+ assert h
+
+
+def test_ignore_extra_for_pvector_field():
+ class HierarchyA(PRecord):
+ points = pvector_field(ARecord, optional=False)
+
+ class HierarchyB(PRecord):
+ points = pvector_field(ARecord, optional=True)
+
+ point_object = {'x': 1, 'y': 'foo', 'extra_field': 69}
+
+ h = HierarchyA.create({'points': [point_object]}, ignore_extra=True)
+ assert h
+ h = HierarchyB.create({'points': [point_object]}, ignore_extra=True)
+ assert h
+
+
+def test_create():
+ r = ARecord(x=1, y='foo')
+ assert r.x == 1
+ assert r.y == 'foo'
+ assert isinstance(r, ARecord)
+
+
+def test_create_ignore_extra():
+ r = ARecord.create({'x': 1, 'y': 'foo', 'z': None}, ignore_extra=True)
+ assert r.x == 1
+ assert r.y == 'foo'
+ assert isinstance(r, ARecord)
+
+
+def test_create_ignore_extra_false():
+ with pytest.raises(AttributeError):
+ _ = ARecord.create({'x': 1, 'y': 'foo', 'z': None})
+
+
+def test_correct_assignment():
+ r = ARecord(x=1, y='foo')
+ r2 = r.set('x', 2.0)
+ r3 = r2.set('y', 'bar')
+
+ assert r2 == {'x': 2.0, 'y': 'foo'}
+ assert r3 == {'x': 2.0, 'y': 'bar'}
+ assert isinstance(r3, ARecord)
+
+
+def test_direct_assignment_not_possible():
+ with pytest.raises(AttributeError):
+ ARecord().x = 1
+
+
+def test_cannot_assign_undeclared_fields():
+ with pytest.raises(AttributeError):
+ ARecord().set('z', 5)
+
+
+def test_cannot_assign_wrong_type_to_fields():
+ try:
+ ARecord().set('x', 'foo')
+ assert False
+ except PTypeError as e:
+ assert e.source_class == ARecord
+ assert e.field == 'x'
+ assert e.expected_types == set([int, float])
+ assert e.actual_type is type('foo')
+
+
+def test_cannot_construct_with_undeclared_fields():
+ with pytest.raises(AttributeError):
+ ARecord(z=5)
+
+
+def test_cannot_construct_with_fields_of_wrong_type():
+ with pytest.raises(TypeError):
+ ARecord(x='foo')
+
+
+def test_support_record_inheritance():
+ class BRecord(ARecord):
+ z = field()
+
+ r = BRecord(x=1, y='foo', z='bar')
+
+ assert isinstance(r, BRecord)
+ assert isinstance(r, ARecord)
+ assert r == {'x': 1, 'y': 'foo', 'z': 'bar'}
+
+
+def test_single_type_spec():
+ class A(PRecord):
+ x = field(type=int)
+
+ r = A(x=1)
+ assert r.x == 1
+
+ with pytest.raises(TypeError):
+ r.set('x', 'foo')
+
+
+def test_remove():
+ r = ARecord(x=1, y='foo')
+ r2 = r.remove('y')
+
+ assert isinstance(r2, ARecord)
+ assert r2 == {'x': 1}
+
+
+def test_remove_non_existing_member():
+ r = ARecord(x=1, y='foo')
+
+ with pytest.raises(KeyError):
+ r.remove('z')
+
+
+def test_field_invariant_must_hold():
+ class BRecord(PRecord):
+ x = field(invariant=lambda x: (x > 1, 'x too small'))
+ y = field(mandatory=True)
+
+ try:
+ BRecord(x=1)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('x too small',)
+ assert e.missing_fields == ('BRecord.y',)
+
+
+def test_global_invariant_must_hold():
+ class BRecord(PRecord):
+ __invariant__ = lambda r: (r.x <= r.y, 'y smaller than x')
+ x = field()
+ y = field()
+
+ BRecord(x=1, y=2)
+
+ try:
+ BRecord(x=2, y=1)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('y smaller than x',)
+ assert e.missing_fields == ()
+
+
+def test_set_multiple_fields():
+ a = ARecord(x=1, y='foo')
+ b = a.set(x=2, y='bar')
+
+ assert b == {'x': 2, 'y': 'bar'}
+
+
+def test_initial_value():
+ class BRecord(PRecord):
+ x = field(initial=1)
+ y = field(initial=2)
+
+ a = BRecord()
+ assert a.x == 1
+ assert a.y == 2
+
+
+def test_enum_field():
+ try:
+ from enum import Enum
+ except ImportError:
+ return # Enum not supported in this environment
+
+ class ExampleEnum(Enum):
+ x = 1
+ y = 2
+
+ class RecordContainingEnum(PRecord):
+ enum_field = field(type=ExampleEnum)
+
+ r = RecordContainingEnum(enum_field=ExampleEnum.x)
+ assert r.enum_field == ExampleEnum.x
+
+def test_type_specification_must_be_a_type():
+ with pytest.raises(TypeError):
+ class BRecord(PRecord):
+ x = field(type=1)
+
+
+def test_initial_must_be_of_correct_type():
+ with pytest.raises(TypeError):
+ class BRecord(PRecord):
+ x = field(type=int, initial='foo')
+
+
+def test_invariant_must_be_callable():
+ with pytest.raises(TypeError):
+ class BRecord(PRecord):
+ x = field(invariant='foo') # type: ignore
+
+
+def test_global_invariants_are_inherited():
+ class BRecord(PRecord):
+ __invariant__ = lambda r: (r.x % r.y == 0, 'modulo')
+ x = field()
+ y = field()
+
+ class CRecord(BRecord):
+ __invariant__ = lambda r: (r.x > r.y, 'size')
+
+ try:
+ CRecord(x=5, y=3)
+ assert False
+ except InvariantException as e:
+ assert e.invariant_errors == ('modulo',)
+
+
+def test_global_invariants_must_be_callable():
+ with pytest.raises(TypeError):
+ class CRecord(PRecord):
+ __invariant__ = 1
+
+
+def test_repr():
+ r = ARecord(x=1, y=2)
+ assert repr(r) == 'ARecord(x=1, y=2)' or repr(r) == 'ARecord(y=2, x=1)'
+
+
+def test_factory():
+ class BRecord(PRecord):
+ x = field(type=int, factory=int)
+
+ assert BRecord(x=2.5) == {'x': 2}
+
+
+def test_factory_must_be_callable():
+ with pytest.raises(TypeError):
+ class BRecord(PRecord):
+ x = field(type=int, factory=1) # type: ignore
+
+
+def test_nested_record_construction():
+ class BRecord(PRecord):
+ x = field(int, factory=int)
+
+ class CRecord(PRecord):
+ a = field()
+ b = field(type=BRecord)
+
+ r = CRecord.create({'a': 'foo', 'b': {'x': '5'}})
+ assert isinstance(r, CRecord)
+ assert isinstance(r.b, BRecord)
+ assert r == {'a': 'foo', 'b': {'x': 5}}
+
+
+def test_pickling():
+ x = ARecord(x=2.0, y='bar')
+ y = pickle.loads(pickle.dumps(x, -1))
+
+ assert x == y
+ assert isinstance(y, ARecord)
+
+def test_supports_pickling_with_typed_container_fields():
+ obj = RecordContainingContainers(
+ map={'foo': 'bar'}, set=['hello', 'there'], vec=['a', 'b'])
+ obj2 = pickle.loads(pickle.dumps(obj))
+ assert obj == obj2
+
+def test_all_invariant_errors_reported():
+ class BRecord(PRecord):
+ x = field(factory=int, invariant=lambda x: (x >= 0, 'x negative'))
+ y = field(mandatory=True)
+
+ class CRecord(PRecord):
+ a = field(invariant=lambda x: (x != 0, 'a zero'))
+ b = field(type=BRecord)
+
+ try:
+ CRecord.create({'a': 0, 'b': {'x': -5}})
+ assert False
+ except InvariantException as e:
+ assert set(e.invariant_errors) == set(['x negative', 'a zero'])
+ assert e.missing_fields == ('BRecord.y',)
+
+
+def test_precord_factory_method_is_idempotent():
+ class BRecord(PRecord):
+ x = field()
+ y = field()
+
+ r = BRecord(x=1, y=2)
+ assert BRecord.create(r) is r
+
+
+def test_serialize():
+ class BRecord(PRecord):
+ d = field(type=datetime.date,
+ factory=lambda d: datetime.datetime.strptime(d, "%d%m%Y").date(),
+ serializer=lambda format, d: d.strftime('%Y-%m-%d') if format == 'ISO' else d.strftime('%d%m%Y'))
+
+ assert BRecord(d='14012015').serialize('ISO') == {'d': '2015-01-14'}
+ assert BRecord(d='14012015').serialize('other') == {'d': '14012015'}
+
+
+def test_nested_serialize():
+ class BRecord(PRecord):
+ d = field(serializer=lambda format, d: format)
+
+ class CRecord(PRecord):
+ b = field()
+
+ serialized = CRecord(b=BRecord(d='foo')).serialize('bar')
+
+ assert serialized == {'b': {'d': 'bar'}}
+ assert isinstance(serialized, dict)
+
+
+def test_serializer_must_be_callable():
+ with pytest.raises(TypeError):
+ class CRecord(PRecord):
+ x = field(serializer=1) # type: ignore
+
+
+def test_transform_without_update_returns_same_precord():
+ r = ARecord(x=2.0, y='bar')
+ assert r.transform([ny], lambda x: x) is r
+
+
+class Application(PRecord):
+ name = field(type=str)
+ image = field(type=str)
+
+
+class ApplicationVector(CheckedPVector):
+ __type__ = Application
+
+
+class Node(PRecord):
+ applications = field(type=ApplicationVector)
+
+
+def test_nested_create_serialize():
+ node = Node(applications=[Application(name='myapp', image='myimage'),
+ Application(name='b', image='c')])
+
+ node2 = Node.create({'applications': [{'name': 'myapp', 'image': 'myimage'},
+ {'name': 'b', 'image': 'c'}]})
+
+ assert node == node2
+
+ serialized = node.serialize()
+ restored = Node.create(serialized)
+
+ assert restored == node
+
+
+def test_pset_field_initial_value():
+ """
+ ``pset_field`` results in initial value that is empty.
+ """
+ class Record(PRecord):
+ value = pset_field(int)
+ assert Record() == Record(value=[])
+
+def test_pset_field_custom_initial():
+ """
+ A custom initial value can be passed in.
+ """
+ class Record(PRecord):
+ value = pset_field(int, initial=(1, 2))
+ assert Record() == Record(value=[1, 2])
+
+def test_pset_field_factory():
+ """
+ ``pset_field`` has a factory that creates a ``PSet``.
+ """
+ class Record(PRecord):
+ value = pset_field(int)
+ record = Record(value=[1, 2])
+ assert isinstance(record.value, PSet)
+
+def test_pset_field_checked_set():
+ """
+ ``pset_field`` results in a set that enforces its type.
+ """
+ class Record(PRecord):
+ value = pset_field(int)
+ record = Record(value=[1, 2])
+ with pytest.raises(TypeError):
+ record.value.add("hello") # type: ignore
+
+def test_pset_field_checked_vector_multiple_types():
+ """
+ ``pset_field`` results in a vector that enforces its types.
+ """
+ class Record(PRecord):
+ value = pset_field((int, str))
+ record = Record(value=[1, 2, "hello"])
+ with pytest.raises(TypeError):
+ record.value.add(object())
+
+def test_pset_field_type():
+ """
+ ``pset_field`` enforces its type.
+ """
+ class Record(PRecord):
+ value = pset_field(int)
+ record = Record()
+ with pytest.raises(TypeError):
+ record.set("value", None)
+
+def test_pset_field_mandatory():
+ """
+ ``pset_field`` is a mandatory field.
+ """
+ class Record(PRecord):
+ value = pset_field(int)
+ record = Record(value=[1])
+ with pytest.raises(InvariantException):
+ record.remove("value")
+
+def test_pset_field_default_non_optional():
+ """
+ By default ``pset_field`` is non-optional, i.e. does not allow
+ ``None``.
+ """
+ class Record(PRecord):
+ value = pset_field(int)
+ with pytest.raises(TypeError):
+ Record(value=None)
+
+def test_pset_field_explicit_non_optional():
+ """
+ If ``optional`` argument is ``False`` then ``pset_field`` is
+ non-optional, i.e. does not allow ``None``.
+ """
+ class Record(PRecord):
+ value = pset_field(int, optional=False)
+ with pytest.raises(TypeError):
+ Record(value=None)
+
+def test_pset_field_optional():
+ """
+ If ``optional`` argument is true, ``None`` is acceptable alternative
+ to a set.
+ """
+ class Record(PRecord):
+ value = pset_field(int, optional=True)
+ assert ((Record(value=[1, 2]).value, Record(value=None).value) ==
+ (pset([1, 2]), None))
+
+def test_pset_field_name():
+ """
+ The created set class name is based on the type of items in the set.
+ """
+ class Record(PRecord):
+ value = pset_field(Something)
+ value2 = pset_field(int)
+ assert ((Record().value.__class__.__name__,
+ Record().value2.__class__.__name__) ==
+ ("SomethingPSet", "IntPSet"))
+
+def test_pset_multiple_types_field_name():
+ """
+ The created set class name is based on the multiple given types of
+ items in the set.
+ """
+ class Record(PRecord):
+ value = pset_field((Something, int))
+
+ assert (Record().value.__class__.__name__ ==
+ "SomethingIntPSet")
+
+def test_pset_field_name_string_type():
+ """
+ The created set class name is based on the type of items specified by name
+ """
+ class Record(PRecord):
+ value = pset_field("record_test.Something")
+ assert Record().value.__class__.__name__ == "SomethingPSet"
+
+
+def test_pset_multiple_string_types_field_name():
+ """
+ The created set class name is based on the multiple given types of
+ items in the set specified by name
+ """
+ class Record(PRecord):
+ value = pset_field(("record_test.Something", "record_test.Another"))
+
+ assert Record().value.__class__.__name__ == "SomethingAnotherPSet"
+
+def test_pvector_field_initial_value():
+ """
+ ``pvector_field`` results in initial value that is empty.
+ """
+ class Record(PRecord):
+ value = pvector_field(int)
+ assert Record() == Record(value=[])
+
+def test_pvector_field_custom_initial():
+ """
+ A custom initial value can be passed in.
+ """
+ class Record(PRecord):
+ value = pvector_field(int, initial=(1, 2))
+ assert Record() == Record(value=[1, 2])
+
+def test_pvector_field_factory():
+ """
+ ``pvector_field`` has a factory that creates a ``PVector``.
+ """
+ class Record(PRecord):
+ value = pvector_field(int)
+ record = Record(value=[1, 2])
+ assert isinstance(record.value, PVector)
+
+def test_pvector_field_checked_vector():
+ """
+ ``pvector_field`` results in a vector that enforces its type.
+ """
+ class Record(PRecord):
+ value = pvector_field(int)
+ record = Record(value=[1, 2])
+ with pytest.raises(TypeError):
+ record.value.append("hello") # type: ignore
+
+def test_pvector_field_checked_vector_multiple_types():
+ """
+ ``pvector_field`` results in a vector that enforces its types.
+ """
+ class Record(PRecord):
+ value = pvector_field((int, str))
+ record = Record(value=[1, 2, "hello"])
+ with pytest.raises(TypeError):
+ record.value.append(object())
+
+def test_pvector_field_type():
+ """
+ ``pvector_field`` enforces its type.
+ """
+ class Record(PRecord):
+ value = pvector_field(int)
+ record = Record()
+ with pytest.raises(TypeError):
+ record.set("value", None)
+
+def test_pvector_field_mandatory():
+ """
+ ``pvector_field`` is a mandatory field.
+ """
+ class Record(PRecord):
+ value = pvector_field(int)
+ record = Record(value=[1])
+ with pytest.raises(InvariantException):
+ record.remove("value")
+
+def test_pvector_field_default_non_optional():
+ """
+ By default ``pvector_field`` is non-optional, i.e. does not allow
+ ``None``.
+ """
+ class Record(PRecord):
+ value = pvector_field(int)
+ with pytest.raises(TypeError):
+ Record(value=None)
+
+def test_pvector_field_explicit_non_optional():
+ """
+ If ``optional`` argument is ``False`` then ``pvector_field`` is
+ non-optional, i.e. does not allow ``None``.
+ """
+ class Record(PRecord):
+ value = pvector_field(int, optional=False)
+ with pytest.raises(TypeError):
+ Record(value=None)
+
+def test_pvector_field_optional():
+ """
+ If ``optional`` argument is true, ``None`` is acceptable alternative
+ to a sequence.
+ """
+ class Record(PRecord):
+ value = pvector_field(int, optional=True)
+ assert ((Record(value=[1, 2]).value, Record(value=None).value) ==
+ (pvector([1, 2]), None))
+
+def test_pvector_field_name():
+ """
+ The created set class name is based on the type of items in the set.
+ """
+ class Record(PRecord):
+ value = pvector_field(Something)
+ value2 = pvector_field(int)
+ assert ((Record().value.__class__.__name__,
+ Record().value2.__class__.__name__) ==
+ ("SomethingPVector", "IntPVector"))
+
+def test_pvector_multiple_types_field_name():
+ """
+ The created vector class name is based on the multiple given types of
+ items in the vector.
+ """
+ class Record(PRecord):
+ value = pvector_field((Something, int))
+
+ assert (Record().value.__class__.__name__ ==
+ "SomethingIntPVector")
+
+def test_pvector_field_name_string_type():
+ """
+ The created set class name is based on the type of items in the set
+ specified by name.
+ """
+ class Record(PRecord):
+ value = pvector_field("record_test.Something")
+ assert Record().value.__class__.__name__ == "SomethingPVector"
+
+def test_pvector_multiple_string_types_field_name():
+ """
+ The created vector class name is based on the multiple given types of
+ items in the vector.
+ """
+ class Record(PRecord):
+ value = pvector_field(("record_test.Something", "record_test.Another"))
+
+ assert Record().value.__class__.__name__ == "SomethingAnotherPVector"
+
+def test_pvector_field_create_from_nested_serialized_data():
+ class Foo(PRecord):
+ foo = field(type=str)
+
+ class Bar(PRecord):
+ bar = pvector_field(Foo)
+
+ data = Bar(bar=v(Foo(foo="foo")))
+ Bar.create(data.serialize()) == data
+
+def test_pmap_field_initial_value():
+ """
+ ``pmap_field`` results in initial value that is empty.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, int)
+ assert Record() == Record(value={})
+
+def test_pmap_field_factory():
+ """
+ ``pmap_field`` has a factory that creates a ``PMap``.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, int)
+ record = Record(value={1: 1234})
+ assert isinstance(record.value, PMap)
+
+def test_pmap_field_checked_map_key():
+ """
+ ``pmap_field`` results in a map that enforces its key type.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, type(None))
+ record = Record(value={1: None})
+ with pytest.raises(TypeError):
+ record.value.set("hello", None) # type: ignore
+
+def test_pmap_field_checked_map_value():
+ """
+ ``pmap_field`` results in a map that enforces its value type.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, type(None))
+ record = Record(value={1: None})
+ with pytest.raises(TypeError):
+ record.value.set(2, 4) # type: ignore
+
+def test_pmap_field_checked_map_key_multiple_types():
+ """
+ ``pmap_field`` results in a map that enforces its key types.
+ """
+ class Record(PRecord):
+ value = pmap_field((int, str), type(None))
+ record = Record(value={1: None, "hello": None})
+ with pytest.raises(TypeError):
+ record.value.set(object(), None)
+
+def test_pmap_field_checked_map_value_multiple_types():
+ """
+ ``pmap_field`` results in a map that enforces its value types.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, (str, type(None)))
+ record = Record(value={1: None, 3: "hello"})
+ with pytest.raises(TypeError):
+ record.value.set(2, 4)
+
+def test_pmap_field_mandatory():
+ """
+ ``pmap_field`` is a mandatory field.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, int)
+ record = Record()
+ with pytest.raises(InvariantException):
+ record.remove("value")
+
+def test_pmap_field_default_non_optional():
+ """
+ By default ``pmap_field`` is non-optional, i.e. does not allow
+ ``None``.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, int)
+ # Ought to be TypeError, but pyrsistent doesn't quite allow that:
+ with pytest.raises(AttributeError):
+ Record(value=None)
+
+def test_pmap_field_explicit_non_optional():
+ """
+ If ``optional`` argument is ``False`` then ``pmap_field`` is
+ non-optional, i.e. does not allow ``None``.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, int, optional=False)
+ # Ought to be TypeError, but pyrsistent doesn't quite allow that:
+ with pytest.raises(AttributeError):
+ Record(value=None)
+
+def test_pmap_field_optional():
+ """
+ If ``optional`` argument is true, ``None`` is acceptable alternative
+ to a set.
+ """
+ class Record(PRecord):
+ value = pmap_field(int, int, optional=True)
+ assert (Record(value={1: 2}).value, Record(value=None).value) == \
+ (pmap({1: 2}), None)
+
+def test_pmap_field_name():
+ """
+ The created map class name is based on the types of items in the map.
+ """
+ class Record(PRecord):
+ value = pmap_field(Something, Another)
+ value2 = pmap_field(int, float)
+ assert ((Record().value.__class__.__name__,
+ Record().value2.__class__.__name__) ==
+ ("SomethingToAnotherPMap", "IntToFloatPMap"))
+
+def test_pmap_field_name_multiple_types():
+ """
+ The created map class name is based on the types of items in the map,
+ including when there are multiple supported types.
+ """
+ class Record(PRecord):
+ value = pmap_field((Something, Another), int)
+ value2 = pmap_field(str, (int, float))
+ assert ((Record().value.__class__.__name__,
+ Record().value2.__class__.__name__) ==
+ ("SomethingAnotherToIntPMap", "StrToIntFloatPMap"))
+
+def test_pmap_field_name_string_type():
+ """
+ The created map class name is based on the types of items in the map
+ specified by name.
+ """
+ class Record(PRecord):
+ value = pmap_field("record_test.Something", "record_test.Another")
+ assert Record().value.__class__.__name__ == "SomethingToAnotherPMap"
+
+def test_pmap_field_name_multiple_string_types():
+ """
+ The created map class name is based on the types of items in the map,
+ including when there are multiple supported types.
+ """
+ class Record(PRecord):
+ value = pmap_field(("record_test.Something", "record_test.Another"), int)
+ value2 = pmap_field(str, ("record_test.Something", "record_test.Another"))
+ assert ((Record().value.__class__.__name__,
+ Record().value2.__class__.__name__) ==
+ ("SomethingAnotherToIntPMap", "StrToSomethingAnotherPMap"))
+
+def test_pmap_field_invariant():
+ """
+ The ``invariant`` parameter is passed through to ``field``.
+ """
+ class Record(PRecord):
+ value = pmap_field(
+ int, int,
+ invariant=(
+ lambda pmap: (len(pmap) == 1, "Exactly one item required.")
+ )
+ )
+ with pytest.raises(InvariantException):
+ Record(value={})
+ with pytest.raises(InvariantException):
+ Record(value={1: 2, 3: 4})
+ assert Record(value={1: 2}).value == {1: 2}
+
+
+def test_pmap_field_create_from_nested_serialized_data():
+ class Foo(PRecord):
+ foo = field(type=str)
+
+ class Bar(PRecord):
+ bar = pmap_field(str, Foo)
+
+ data = Bar(bar=m(foo_key=Foo(foo="foo")))
+ Bar.create(data.serialize()) == data
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(ARecord(x=1, y=2))
+
+
+def test_supports_lazy_initial_value_for_field():
+ class MyRecord(PRecord):
+ a = field(int, initial=lambda: 2)
+
+ assert MyRecord() == MyRecord(a=2)
+
+
+def test_pickle_with_one_way_factory():
+ """
+ A field factory isn't called when restoring from pickle.
+ """
+ thing = UniqueThing(id='25544626-86da-4bce-b6b6-9186c0804d64')
+ assert thing == pickle.loads(pickle.dumps(thing))
diff --git a/contrib/python/pyrsistent/py3/tests/regression_test.py b/contrib/python/pyrsistent/py3/tests/regression_test.py
new file mode 100644
index 0000000000..f8c1133834
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/regression_test.py
@@ -0,0 +1,30 @@
+from pyrsistent import pmap
+import random
+
+import gc
+
+
+def test_segfault_issue_52():
+ threshold = None
+ if hasattr(gc, 'get_threshold'):
+ # PyPy is lacking these functions
+ threshold = gc.get_threshold()
+ gc.set_threshold(1, 1, 1) # fail fast
+
+ v = [pmap()]
+
+ def step():
+ depth = random.randint(1, 10)
+ path = random.sample(range(100000), depth)
+ v[0] = v[0].transform(path, "foo")
+
+ for i in range(1000): # usually crashes after 10-20 steps
+ while True:
+ try:
+ step()
+ break
+ except AttributeError: # evolver on string
+ continue
+
+ if threshold:
+ gc.set_threshold(*threshold)
diff --git a/contrib/python/pyrsistent/py3/tests/set_test.py b/contrib/python/pyrsistent/py3/tests/set_test.py
new file mode 100644
index 0000000000..f605ee0d5e
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/set_test.py
@@ -0,0 +1,181 @@
+from pyrsistent import pset, s
+import pytest
+import pickle
+
+def test_key_is_tuple():
+ with pytest.raises(KeyError):
+ pset().remove((1,1))
+
+def test_literalish_works():
+ assert s() is pset()
+ assert s(1, 2) == pset([1, 2])
+
+
+def test_supports_hash():
+ assert hash(s(1, 2)) == hash(s(1, 2))
+
+
+def test_empty_truthiness():
+ assert s(1)
+ assert not s()
+
+
+def test_contains_elements_that_it_was_initialized_with():
+ initial = [1, 2, 3]
+ s = pset(initial)
+
+ assert set(s) == set(initial)
+ assert len(s) == len(set(initial))
+
+
+def test_is_immutable():
+ s1 = pset([1])
+ s2 = s1.add(2)
+
+ assert s1 == pset([1])
+ assert s2 == pset([1, 2])
+
+ s3 = s2.remove(1)
+ assert s2 == pset([1, 2])
+ assert s3 == pset([2])
+
+
+def test_remove_when_not_present():
+ s1 = s(1, 2, 3)
+ with pytest.raises(KeyError):
+ s1.remove(4)
+
+
+def test_discard():
+ s1 = s(1, 2, 3)
+ assert s1.discard(3) == s(1, 2)
+ assert s1.discard(4) is s1
+
+
+def test_is_iterable():
+ assert sum(pset([1, 2, 3])) == 6
+
+
+def test_contains():
+ s = pset([1, 2, 3])
+
+ assert 2 in s
+ assert 4 not in s
+
+
+def test_supports_set_operations():
+ s1 = pset([1, 2, 3])
+ s2 = pset([3, 4, 5])
+
+ assert s1 | s2 == s(1, 2, 3, 4, 5)
+ assert s1.union(s2) == s1 | s2
+
+ assert s1 & s2 == s(3)
+ assert s1.intersection(s2) == s1 & s2
+
+ assert s1 - s2 == s(1, 2)
+ assert s1.difference(s2) == s1 - s2
+
+ assert s1 ^ s2 == s(1, 2, 4, 5)
+ assert s1.symmetric_difference(s2) == s1 ^ s2
+
+
+def test_supports_set_comparisons():
+ s1 = s(1, 2, 3)
+ s3 = s(1, 2)
+ s4 = s(1, 2, 3)
+
+ assert s(1, 2, 3, 3, 5) == s(1, 2, 3, 5)
+ assert s1 != s3
+
+ assert s3 < s1
+ assert s3 <= s1
+ assert s3 <= s4
+
+ assert s1 > s3
+ assert s1 >= s3
+ assert s4 >= s3
+
+
+def test_str():
+ rep = str(pset([1, 2, 3]))
+ assert rep == "pset([1, 2, 3])"
+
+
+def test_is_disjoint():
+ s1 = pset([1, 2, 3])
+ s2 = pset([3, 4, 5])
+ s3 = pset([4, 5])
+
+ assert not s1.isdisjoint(s2)
+ assert s1.isdisjoint(s3)
+
+
+def test_evolver_simple_add():
+ x = s(1, 2, 3)
+ e = x.evolver()
+ assert not e.is_dirty()
+
+ e.add(4)
+ assert e.is_dirty()
+
+ x2 = e.persistent()
+ assert not e.is_dirty()
+ assert x2 == s(1, 2, 3, 4)
+ assert x == s(1, 2, 3)
+
+def test_evolver_simple_remove():
+ x = s(1, 2, 3)
+ e = x.evolver()
+ e.remove(2)
+
+ x2 = e.persistent()
+ assert x2 == s(1, 3)
+ assert x == s(1, 2, 3)
+
+
+def test_evolver_no_update_produces_same_pset():
+ x = s(1, 2, 3)
+ e = x.evolver()
+ assert e.persistent() is x
+
+
+def test_evolver_len():
+ x = s(1, 2, 3)
+ e = x.evolver()
+ assert len(e) == 3
+
+
+def test_copy_returns_reference_to_self():
+ s1 = s(10)
+ assert s1.copy() is s1
+
+
+def test_pickling_empty_set():
+ assert pickle.loads(pickle.dumps(s(), -1)) == s()
+
+
+def test_pickling_non_empty_map():
+ assert pickle.loads(pickle.dumps(s(1, 2), -1)) == s(1, 2)
+
+
+def test_supports_weakref():
+ import weakref
+ weakref.ref(s(1))
+
+
+def test_update():
+ assert s(1, 2, 3).update([3, 4, 4, 5]) == s(1, 2, 3, 4, 5)
+
+
+def test_update_no_elements():
+ s1 = s(1, 2)
+ assert s1.update([]) is s1
+
+
+def test_iterable():
+ """
+ PSets can be created from iterables even though they can't be len() hinted.
+ """
+
+ assert pset(iter("a")) == pset(iter("a"))
diff --git a/contrib/python/pyrsistent/py3/tests/toolz_test.py b/contrib/python/pyrsistent/py3/tests/toolz_test.py
new file mode 100644
index 0000000000..d145704b86
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/toolz_test.py
@@ -0,0 +1,6 @@
+from pyrsistent import get_in, m, v
+
+
+def test_get_in():
+ # This is not an extensive test. The doctest covers that fairly good though.
+ get_in(m(a=v(1, 2, 3)), ['m', 1]) == 2
diff --git a/contrib/python/pyrsistent/py3/tests/transform_test.py b/contrib/python/pyrsistent/py3/tests/transform_test.py
new file mode 100644
index 0000000000..d133d14f65
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/transform_test.py
@@ -0,0 +1,122 @@
+from pyrsistent import freeze, inc, discard, rex, ny, field, PClass, pmap
+
+
+def test_callable_command():
+ m = freeze({'foo': {'bar': {'baz': 1}}})
+ assert m.transform(['foo', 'bar', 'baz'], inc) == {'foo': {'bar': {'baz': 2}}}
+
+
+def test_predicate():
+ m = freeze({'foo': {'bar': {'baz': 1}, 'qux': {'baz': 1}}})
+ assert m.transform(['foo', lambda x: x.startswith('b'), 'baz'], inc) == {'foo': {'bar': {'baz': 2}, 'qux': {'baz': 1}}}
+
+
+def test_broken_predicate():
+ broken_predicates = [
+ lambda: None,
+ lambda a, b, c: None,
+ lambda a, b, c, d=None: None,
+ lambda *args: None,
+ lambda **kwargs: None,
+ ]
+ for pred in broken_predicates:
+ try:
+ freeze({}).transform([pred], None)
+ assert False
+ except ValueError as e:
+ assert str(e) == "callable in transform path must take 1 or 2 arguments"
+
+
+def test_key_value_predicate():
+ m = freeze({
+ 'foo': 1,
+ 'bar': 2,
+ })
+ assert m.transform([
+ lambda k, v: (k, v) == ('foo', 1),
+ ], lambda v: v * 3) == {"foo": 3, "bar": 2}
+
+
+def test_remove():
+ m = freeze({'foo': {'bar': {'baz': 1}}})
+ assert m.transform(['foo', 'bar', 'baz'], discard) == {'foo': {'bar': {}}}
+
+
+def test_remove_pvector():
+ m = freeze({'foo': [1, 2, 3]})
+ assert m.transform(['foo', 1], discard) == {'foo': [1, 3]}
+
+
+def test_remove_pclass():
+ class MyClass(PClass):
+ a = field()
+ b = field()
+
+ m = freeze({'foo': MyClass(a=1, b=2)})
+ assert m.transform(['foo', 'b'], discard) == {'foo': MyClass(a=1)}
+
+
+def test_predicate_no_match():
+ m = freeze({'foo': {'bar': {'baz': 1}}})
+ assert m.transform(['foo', lambda x: x.startswith('c'), 'baz'], inc) == m
+
+
+def test_rex_predicate():
+ m = freeze({'foo': {'bar': {'baz': 1},
+ 'bof': {'baz': 1}}})
+ assert m.transform(['foo', rex('^bo.*'), 'baz'], inc) == {'foo': {'bar': {'baz': 1},
+ 'bof': {'baz': 2}}}
+
+
+def test_rex_with_non_string_key():
+ m = freeze({'foo': 1, 5: 2})
+ assert m.transform([rex(".*")], 5) == {'foo': 5, 5: 2}
+
+
+def test_ny_predicated_matches_any_key():
+ m = freeze({'foo': 1, 5: 2})
+ assert m.transform([ny], 5) == {'foo': 5, 5: 5}
+
+
+def test_new_elements_created_when_missing():
+ m = freeze({})
+ assert m.transform(['foo', 'bar', 'baz'], 7) == {'foo': {'bar': {'baz': 7}}}
+
+
+def test_mixed_vector_and_map():
+ m = freeze({'foo': [1, 2, 3]})
+ assert m.transform(['foo', 1], 5) == freeze({'foo': [1, 5, 3]})
+
+
+def test_vector_predicate_callable_command():
+ v = freeze([1, 2, 3, 4, 5])
+ assert v.transform([lambda i: 0 < i < 4], inc) == freeze(freeze([1, 3, 4, 5, 5]))
+
+
+def test_vector_insert_map_one_step_beyond_end():
+ v = freeze([1, 2])
+ assert v.transform([2, 'foo'], 3) == freeze([1, 2, {'foo': 3}])
+
+
+def test_multiple_transformations():
+ v = freeze([1, 2])
+ assert v.transform([2, 'foo'], 3, [2, 'foo'], inc) == freeze([1, 2, {'foo': 4}])
+
+
+def test_no_transformation_returns_the_same_structure():
+ v = freeze([{'foo': 1}, {'bar': 2}])
+ assert v.transform([ny, ny], lambda x: x) is v
+
+
+def test_discard_multiple_elements_in_pvector():
+ assert freeze([0, 1, 2, 3, 4]).transform([lambda i: i % 2], discard) == freeze([0, 2, 4])
+
+
+def test_transform_insert_empty_pmap():
+ m = pmap().transform(['123'], pmap())
+ assert m == pmap({'123': pmap()})
+
+
+def test_discard_does_not_insert_nodes():
+ m = freeze({}).transform(['foo', 'bar'], discard)
+ assert m == pmap({})
diff --git a/contrib/python/pyrsistent/py3/tests/vector_test.py b/contrib/python/pyrsistent/py3/tests/vector_test.py
new file mode 100644
index 0000000000..e5c4bf69c3
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/vector_test.py
@@ -0,0 +1,934 @@
+from collections.abc import Hashable, Sequence
+import os
+import pickle
+import pytest
+
+from pyrsistent._pvector import python_pvector
+
+
+@pytest.fixture(scope='session', params=['pyrsistent._pvector', 'pvectorc'])
+def pvector(request):
+ if request.param == 'pvectorc' and os.environ.get('PYRSISTENT_NO_C_EXTENSION'):
+ pytest.skip('Configured to not run tests for C extension')
+
+ m = pytest.importorskip(request.param)
+ if request.param == 'pyrsistent._pvector':
+ return m.python_pvector
+ return m.pvector
+
+
+def test_literalish_works():
+ from pyrsistent import pvector, v
+ assert v() is pvector()
+ assert v(1, 2) == pvector([1, 2])
+
+
+def test_empty_initialization(pvector):
+ seq = pvector()
+ assert len(seq) == 0
+
+ with pytest.raises(IndexError) as error:
+ x = seq[0]
+ assert str(error.value) == 'Index out of range: 0'
+
+
+def test_initialization_with_one_element(pvector):
+ seq = pvector([3])
+ assert len(seq) == 1
+ assert seq[0] == 3
+
+
+def test_append_works_and_does_not_affect_original_within_tail(pvector):
+ seq1 = pvector([3])
+ seq2 = seq1.append(2)
+
+ assert len(seq1) == 1
+ assert seq1[0] == 3
+
+ assert len(seq2) == 2
+ assert seq2[0] == 3
+ assert seq2[1] == 2
+
+
+def test_append_works_and_does_not_affect_original_outside_tail(pvector):
+ original = pvector([])
+ seq = original
+
+ for x in range(33):
+ seq = seq.append(x)
+
+ assert len(seq) == 33
+ assert seq[0] == 0
+ assert seq[31] == 31
+ assert seq[32] == 32
+
+ assert len(original) == 0
+
+
+def test_append_when_root_overflows(pvector):
+ seq = pvector([])
+
+ for x in range(32 * 33):
+ seq = seq.append(x)
+
+ seq = seq.append(10001)
+
+ for i in range(32 * 33):
+ assert seq[i] == i
+
+ assert seq[32 * 33] == 10001
+
+
+def test_multi_level_sequence(pvector):
+ seq = pvector(range(8000))
+ seq2 = seq.append(11)
+
+ assert seq[5] == 5
+ assert seq2[7373] == 7373
+ assert seq2[8000] == 11
+
+
+def test_multi_level_sequence_from_iterator(pvector):
+ seq = pvector(iter(range(8000)))
+ seq2 = seq.append(11)
+
+ assert seq[5] == 5
+ assert seq2[7373] == 7373
+ assert seq2[8000] == 11
+
+
+def test_random_insert_within_tail(pvector):
+ seq = pvector([1, 2, 3])
+
+ seq2 = seq.set(1, 4)
+
+ assert seq2[1] == 4
+ assert seq[1] == 2
+
+
+def test_random_insert_outside_tail(pvector):
+ seq = pvector(range(20000))
+
+ seq2 = seq.set(19000, 4)
+
+ assert seq2[19000] == 4
+ assert seq[19000] == 19000
+
+
+def test_insert_beyond_end(pvector):
+ seq = pvector(range(2))
+ seq2 = seq.set(2, 50)
+ assert seq2[2] == 50
+
+ with pytest.raises(IndexError) as error:
+ seq2.set(19, 4)
+
+ assert str(error.value) == 'Index out of range: 19'
+
+
+def test_insert_with_index_from_the_end(pvector):
+ x = pvector([1, 2, 3, 4])
+
+ assert x.set(-2, 5) == pvector([1, 2, 5, 4])
+
+
+def test_insert_with_too_negative_index(pvector):
+ x = pvector([1, 2, 3, 4])
+
+ with pytest.raises(IndexError):
+ x.set(-5, 17)
+
+
+def test_iteration(pvector):
+ y = 0
+ seq = pvector(range(2000))
+ for x in seq:
+ assert x == y
+ y += 1
+
+ assert y == 2000
+
+
+def test_zero_extend(pvector):
+ the_list = []
+ seq = pvector()
+ seq2 = seq.extend(the_list)
+ assert seq == seq2
+
+
+def test_short_extend(pvector):
+ # Extend within tail length
+ the_list = [1, 2]
+ seq = pvector()
+ seq2 = seq.extend(the_list)
+
+ assert len(seq2) == len(the_list)
+ assert seq2[0] == the_list[0]
+ assert seq2[1] == the_list[1]
+
+
+def test_long_extend(pvector):
+ # Multi level extend
+ seq = pvector()
+ length = 2137
+
+ # Extend from scratch
+ seq2 = seq.extend(range(length))
+ assert len(seq2) == length
+ for i in range(length):
+ assert seq2[i] == i
+
+ # Extend already filled vector
+ seq3 = seq2.extend(range(length, length + 5))
+ assert len(seq3) == length + 5
+ for i in range(length + 5):
+ assert seq3[i] == i
+
+ # Check that the original vector is still intact
+ assert len(seq2) == length
+ for i in range(length):
+ assert seq2[i] == i
+
+
+def test_slicing_zero_length_range(pvector):
+ seq = pvector(range(10))
+ seq2 = seq[2:2]
+
+ assert len(seq2) == 0
+
+
+def test_slicing_range(pvector):
+ seq = pvector(range(10))
+ seq2 = seq[2:4]
+
+ assert list(seq2) == [2, 3]
+
+
+def test_slice_identity(pvector):
+ # Pvector is immutable, no need to make a copy!
+ seq = pvector(range(10))
+
+ assert seq is seq[::]
+
+
+def test_slicing_range_with_step(pvector):
+ seq = pvector(range(100))
+ seq2 = seq[2:12:3]
+
+ assert list(seq2) == [2, 5, 8, 11]
+
+
+def test_slicing_no_range_but_step(pvector):
+ seq = pvector(range(10))
+ seq2 = seq[::2]
+
+ assert list(seq2) == [0, 2, 4, 6, 8]
+
+
+def test_slicing_reverse(pvector):
+ seq = pvector(range(10))
+ seq2 = seq[::-1]
+
+ assert seq2[0] == 9
+ assert seq2[1] == 8
+ assert len(seq2) == 10
+
+ seq3 = seq[-3: -7: -1]
+ assert seq3[0] == 7
+ assert seq3[3] == 4
+ assert len(seq3) == 4
+
+
+def test_delete_index(pvector):
+ seq = pvector([1, 2, 3])
+ assert seq.delete(0) == pvector([2, 3])
+ assert seq.delete(1) == pvector([1, 3])
+ assert seq.delete(2) == pvector([1, 2])
+ assert seq.delete(-1) == pvector([1, 2])
+ assert seq.delete(-2) == pvector([1, 3])
+ assert seq.delete(-3) == pvector([2, 3])
+
+
+def test_delete_index_out_of_bounds(pvector):
+ with pytest.raises(IndexError):
+ pvector([]).delete(0)
+ with pytest.raises(IndexError):
+ pvector([]).delete(-1)
+
+
+def test_delete_index_malformed(pvector):
+ with pytest.raises(TypeError):
+ pvector([]).delete('a')
+
+
+def test_delete_slice(pvector):
+ seq = pvector(range(5))
+ assert seq.delete(1, 4) == pvector([0, 4])
+ assert seq.delete(4, 1) == seq
+ assert seq.delete(0, 1) == pvector([1, 2, 3, 4])
+ assert seq.delete(6, 8) == seq
+ assert seq.delete(-1, 1) == seq
+ assert seq.delete(1, -1) == pvector([0, 4])
+
+
+def test_remove(pvector):
+ seq = pvector(range(5))
+ assert seq.remove(3) == pvector([0, 1, 2, 4])
+
+
+def test_remove_first_only(pvector):
+ seq = pvector([1, 2, 3, 2, 1])
+ assert seq.remove(2) == pvector([1, 3, 2, 1])
+
+
+def test_remove_index_out_of_bounds(pvector):
+ seq = pvector(range(5))
+ with pytest.raises(ValueError) as err:
+ seq.remove(5)
+ assert 'not in' in str(err.value)
+
+
+def test_addition(pvector):
+ v = pvector([1, 2]) + pvector([3, 4])
+
+ assert list(v) == [1, 2, 3, 4]
+
+
+def test_sorted(pvector):
+ seq = pvector([5, 2, 3, 1])
+ assert [1, 2, 3, 5] == sorted(seq)
+
+
+def test_boolean_conversion(pvector):
+ assert not bool(pvector())
+ assert bool(pvector([1]))
+
+
+def test_access_with_negative_index(pvector):
+ seq = pvector([1, 2, 3, 4])
+
+ assert seq[-1] == 4
+ assert seq[-4] == 1
+
+
+def test_index_error_positive(pvector):
+ with pytest.raises(IndexError):
+ pvector([1, 2, 3])[3]
+
+
+def test_index_error_negative(pvector):
+ with pytest.raises(IndexError):
+ pvector([1, 2, 3])[-4]
+
+
+def test_is_sequence(pvector):
+ assert isinstance(pvector(), Sequence)
+
+
+def test_empty_repr(pvector):
+ assert str(pvector()) == "pvector([])"
+
+
+def test_non_empty_repr(pvector):
+ v = pvector([1, 2, 3])
+ assert str(v) == "pvector([1, 2, 3])"
+
+ # There's some state that needs to be reset between calls in the native version,
+ # test that multiple invocations work.
+ assert str(v) == "pvector([1, 2, 3])"
+
+
+def test_repr_when_contained_object_contains_reference_to_self(pvector):
+ x = [1, 2, 3]
+ v = pvector([1, 2, x])
+ x.append(v)
+ assert str(v) == 'pvector([1, 2, [1, 2, 3, pvector([1, 2, [...]])]])'
+
+ # Run a GC to provoke any potential misbehavior
+ import gc
+ gc.collect()
+
+
+def test_is_hashable(pvector):
+
+ v = pvector([1, 2, 3])
+ v2 = pvector([1, 2, 3])
+
+ assert hash(v) == hash(v2)
+ assert isinstance(pvector(), Hashable)
+
+
+def test_refuses_to_hash_when_members_are_unhashable(pvector):
+ v = pvector([1, 2, [1, 2]])
+
+ with pytest.raises(TypeError):
+ hash(v)
+
+
+def test_compare_same_vectors(pvector):
+ v = pvector([1, 2])
+ assert v == v
+ assert pvector() == pvector()
+
+
+def test_compare_with_other_type_of_object(pvector):
+ assert pvector([1, 2]) != 'foo'
+
+
+def test_compare_equal_vectors(pvector):
+ v1 = pvector([1, 2])
+ v2 = pvector([1, 2])
+ assert v1 == v2
+ assert v1 >= v2
+ assert v1 <= v2
+
+
+def test_compare_different_vectors_same_size(pvector):
+ v1 = pvector([1, 2])
+ v2 = pvector([1, 3])
+ assert v1 != v2
+
+
+def test_compare_different_vectors_different_sizes(pvector):
+ v1 = pvector([1, 2])
+ v2 = pvector([1, 2, 3])
+ assert v1 != v2
+
+
+def test_compare_lt_gt(pvector):
+ v1 = pvector([1, 2])
+ v2 = pvector([1, 2, 3])
+ assert v1 < v2
+ assert v2 > v1
+
+
+def test_repeat(pvector):
+ v = pvector([1, 2])
+ assert 5 * pvector() is pvector()
+ assert v is 1 * v
+ assert 0 * v is pvector()
+ assert 2 * pvector([1, 2]) == pvector([1, 2, 1, 2])
+ assert -3 * pvector([1, 2]) is pvector()
+
+
+def test_transform_zero_key_length(pvector):
+ x = pvector([1, 2])
+
+ assert x.transform([], 3) == 3
+
+
+def test_transform_base_case(pvector):
+ x = pvector([1, 2])
+
+ assert x.transform([1], 3) == pvector([1, 3])
+
+
+def test_transform_nested_vectors(pvector):
+ x = pvector([1, 2, pvector([3, 4]), 5])
+
+ assert x.transform([2, 0], 999) == pvector([1, 2, pvector([999, 4]), 5])
+
+
+def test_transform_when_appending(pvector):
+ from pyrsistent import m
+ x = pvector([1, 2])
+
+ assert x.transform([2, 'd'], 999) == pvector([1, 2, m(d=999)])
+
+
+def test_transform_index_error_out_range(pvector):
+ x = pvector([1, 2, pvector([3, 4]), 5])
+
+ with pytest.raises(IndexError):
+ x.transform([2, 10], 999)
+
+
+def test_transform_index_error_wrong_type(pvector):
+ x = pvector([1, 2, pvector([3, 4]), 5])
+
+ with pytest.raises(TypeError):
+ x.transform([2, 'foo'], 999)
+
+
+def test_transform_non_setable_type(pvector):
+ x = pvector([1, 2, 5])
+
+ with pytest.raises(TypeError):
+ x.transform([2, 3], 999)
+
+
+def test_reverse(pvector):
+ x = pvector([1, 2, 5])
+
+ assert list(reversed(x)) == [5, 2, 1]
+
+
+def test_contains(pvector):
+ x = pvector([1, 2, 5])
+
+ assert 2 in x
+ assert 3 not in x
+
+
+def test_index(pvector):
+ x = pvector([1, 2, 5])
+
+ assert x.index(5) == 2
+
+
+def test_index_not_found(pvector):
+ x = pvector([1, 2, 5])
+
+ with pytest.raises(ValueError):
+ x.index(7)
+
+
+def test_index_not_found_with_limits(pvector):
+ x = pvector([1, 2, 5, 1])
+
+ with pytest.raises(ValueError):
+ x.index(1, 1, 3)
+
+
+def test_count(pvector):
+ x = pvector([1, 2, 5, 1])
+
+ assert x.count(1) == 2
+ assert x.count(4) == 0
+
+
+def test_empty_truthiness(pvector):
+ assert pvector([1])
+ assert not pvector([])
+
+
+def test_pickling_empty_vector(pvector):
+ assert pickle.loads(pickle.dumps(pvector(), -1)) == pvector()
+
+
+def test_pickling_non_empty_vector(pvector):
+ assert pickle.loads(pickle.dumps(pvector([1, 'a']), -1)) == pvector([1, 'a'])
+
+
+def test_mset_basic_assignments(pvector):
+ v1 = pvector(range(2000))
+ v2 = v1.mset(1, -1, 505, -505, 1998, -1998)
+
+ # Original not changed
+ assert v1[1] == 1
+ assert v1[505] == 505
+ assert v1[1998] == 1998
+
+ # Other updated
+ assert v2[1] == -1
+ assert v2[505] == -505
+ assert v2[1998] == -1998
+
+
+def test_mset_odd_number_of_arguments(pvector):
+ v = pvector([0, 1])
+
+ with pytest.raises(TypeError):
+ v.mset(0, 10, 1)
+
+
+def test_mset_index_out_of_range(pvector):
+ v = pvector([0, 1])
+
+ with pytest.raises(IndexError):
+ v.mset(3, 10)
+
+
+def test_evolver_no_update(pvector):
+ # This is mostly a test against memory leaks in the C implementation
+ v = pvector(range(40))
+
+ assert v.evolver().persistent() == v
+
+
+def test_evolver_deallocate_dirty_evolver(pvector):
+ # Ref count handling in native implementation
+ v = pvector(range(3220))
+ e = v.evolver()
+ e[10] = -10
+ e[3220] = -3220
+
+
+def test_evolver_simple_update_in_tree(pvector):
+ v = pvector(range(35))
+ e = v.evolver()
+ e[10] = -10
+
+ assert e[10] == -10
+ assert e.persistent()[10] == -10
+
+
+def test_evolver_set_out_of_range(pvector):
+ v = pvector([0])
+ e = v.evolver()
+ with pytest.raises(IndexError) as error:
+ e[10] = 1
+ assert str(error.value) == "Index out of range: 10"
+
+def test_evolver_multi_level_multi_update_in_tree(pvector):
+ # This test is mostly to detect memory/ref count issues in the native implementation
+ v = pvector(range(3500))
+ e = v.evolver()
+
+ # Update differs between first and second time since the
+ # corresponding node will be marked as dirty the first time only.
+ e[10] = -10
+ e[11] = -11
+ e[10] = -1000
+
+ # Update in neighbour node
+ e[50] = -50
+ e[50] = -5000
+
+ # Update in node in other half of vector
+ e[3000] = -3000
+ e[3000] = -30000
+
+ # Before freezing
+ assert e[10] == -1000
+ assert e[11] == -11
+ assert e[50] == -5000
+ assert e[3000] == -30000
+
+ # Run a GC to provoke any potential misbehavior
+ import gc
+ gc.collect()
+
+ v2 = e.persistent()
+ assert v2[10] == -1000
+ assert v2[50] == -5000
+ assert v2[3000] == -30000
+
+ # Run a GC to provoke any potential misbehavior
+ gc.collect()
+
+ # After freezing
+ assert e[10] == -1000
+ assert e[11] == -11
+ assert e[50] == -5000
+ assert e[3000] == -30000
+
+ # Original stays the same
+ assert v[10] == 10
+ assert v[50] == 50
+ assert v[3000] == 3000
+
+
+def test_evolver_simple_update_in_tail(pvector):
+ v = pvector(range(35))
+ e = v.evolver()
+ e[33] = -33
+
+ assert e[33] == -33
+ assert e.persistent()[33] == -33
+ assert v[33] == 33
+
+
+def test_evolver_simple_update_just_outside_vector(pvector):
+ v = pvector()
+ e = v.evolver()
+ e[0] = 1
+
+ assert e[0] == 1
+ assert e.persistent()[0] == 1
+ assert len(v) == 0
+
+
+def test_evolver_append(pvector):
+ v = pvector()
+ e = v.evolver()
+ e.append(1000)
+ assert e[0] == 1000
+
+ e[0] = 2000
+ assert e[0] == 2000
+ assert list(e.persistent()) == [2000]
+ assert list(v) == []
+
+
+def test_evolver_extend(pvector):
+ v = pvector([1000])
+ e = v.evolver()
+ e.extend([2000, 3000])
+ e[2] = 20000
+
+ assert list(e.persistent()) == [1000, 2000, 20000]
+ assert list(v) == [1000]
+
+
+def test_evolver_assign_and_read_with_negative_indices(pvector):
+ v = pvector([1, 2, 3])
+ e = v.evolver()
+ e[-1] = 4
+ e.extend([11, 12, 13])
+ e[-1] = 33
+
+ assert e[-1] == 33
+ assert list(e.persistent()) == [1, 2, 4, 11, 12, 33]
+
+
+def test_evolver_non_integral_access(pvector):
+ e = pvector([1]).evolver()
+
+ with pytest.raises(TypeError):
+ x = e['foo']
+
+
+def test_evolver_non_integral_assignment(pvector):
+ e = pvector([1]).evolver()
+
+ with pytest.raises(TypeError):
+ e['foo'] = 1
+
+
+def test_evolver_out_of_bounds_access(pvector):
+ e = pvector([1]).evolver()
+
+ with pytest.raises(IndexError):
+ x = e[1]
+
+
+def test_evolver_out_of_bounds_assignment(pvector):
+ e = pvector([1]).evolver()
+
+ with pytest.raises(IndexError):
+ e[2] = 1
+
+
+def test_no_dependencies_between_evolvers_from_the_same_pvector(pvector):
+ original_list = list(range(40))
+ v = pvector(original_list)
+ e1 = v.evolver()
+ e2 = v.evolver()
+
+ e1.extend([1, 2, 3])
+ e1[2] = 20
+ e1[35] = 350
+
+ e2.extend([-1, -2, -3])
+ e2[2] = -20
+ e2[35] = -350
+
+ e1_expected = original_list + [1, 2, 3]
+ e1_expected[2] = 20
+ e1_expected[35] = 350
+ assert list(e1.persistent()) == e1_expected
+
+ e2_expected = original_list + [-1, -2, -3]
+ e2_expected[2] = -20
+ e2_expected[35] = -350
+ assert list(e2.persistent()) == e2_expected
+
+
+def test_pvectors_produced_from_the_same_evolver_do_not_interfere(pvector):
+ original_list = list(range(40))
+ v = pvector(original_list)
+ e = v.evolver()
+
+ e.extend([1, 2, 3])
+ e[2] = 20
+ e[35] = 350
+
+ v1 = e.persistent()
+ v1_expected = original_list + [1, 2, 3]
+ v1_expected[2] = 20
+ v1_expected[35] = 350
+
+ e.extend([-1, -2, -3])
+ e[3] = -30
+ e[36] = -360
+
+ v2 = e.persistent()
+ v2_expected = v1_expected + [-1, -2, -3]
+ v2_expected[3] = -30
+ v2_expected[36] = -360
+
+ assert list(v1) == v1_expected
+ assert list(v2) == v2_expected
+
+
+def test_evolver_len(pvector):
+ e = pvector([1, 2, 3]).evolver()
+ e.extend([4, 5])
+
+ assert len(e) == 5
+
+
+def test_evolver_is_dirty(pvector):
+ e = pvector([1, 2, 3]).evolver()
+ assert not e.is_dirty()
+
+ e.append(4)
+ assert e.is_dirty
+
+ e.persistent()
+ assert not e.is_dirty()
+
+ e[2] = 2000
+ assert e.is_dirty
+
+ e.persistent()
+ assert not e.is_dirty()
+
+
+def test_vector_insert_one_step_beyond_end(pvector):
+ # This test exists to get the transform functionality under memory
+ # leak supervision. Most of the transformation tests are in test_transform.py.
+ v = pvector([1, 2])
+ assert v.transform([2], 3) == pvector([1, 2, 3])
+
+
+def test_evolver_with_no_updates_returns_same_pvector(pvector):
+ v = pvector([1, 2])
+ assert v.evolver().persistent() is v
+
+
+def test_evolver_returns_itself_on_evolving_operations(pvector):
+ # Does this to be able to chain operations
+ v = pvector([1, 2])
+ assert v.evolver().append(3).extend([4, 5]).set(1, 6).persistent() == pvector([1, 6, 3, 4, 5])
+
+
+def test_evolver_delete_by_index(pvector):
+ e = pvector([1, 2, 3]).evolver()
+
+ del e[0]
+
+ assert e.persistent() == python_pvector([2, 3])
+ assert e.append(4).persistent() == python_pvector([2, 3, 4])
+
+
+def test_evolver_delete_function_by_index(pvector):
+ e = pvector([1, 2, 3]).evolver()
+
+ assert e.delete(1).persistent() == python_pvector([1, 3])
+
+
+def test_evolver_delete_function_by_index_multiple_times(pvector):
+ SIZE = 40
+ e = pvector(range(SIZE)).evolver()
+ for i in range(SIZE):
+ assert e[0] == i
+ assert list(e.persistent()) == list(range(i, SIZE))
+ del e[0]
+
+ assert e.persistent() == list()
+
+
+def test_evolver_delete_function_invalid_index(pvector):
+ e = pvector([1, 2]).evolver()
+
+ with pytest.raises(TypeError):
+ del e["e"]
+
+
+def test_delete_of_non_existing_element(pvector):
+ e = pvector([1, 2]).evolver()
+
+ with pytest.raises(IndexError):
+ del e[2]
+
+ del e[0]
+ del e[0]
+
+ with pytest.raises(IndexError):
+ del e[0]
+
+ assert e.persistent() == pvector()
+
+
+def test_append_followed_by_delete(pvector):
+ e = pvector([1, 2]).evolver()
+
+ e.append(3)
+
+ del e[2]
+
+
+def test_evolver_set_followed_by_delete(pvector):
+ evolver = pvector([1, 2]).evolver()
+ evolver[1] = 3
+
+ assert [evolver[i] for i in range(len(evolver))] == [1, 3]
+
+ del evolver[0]
+
+ assert evolver.persistent() == pvector([3])
+
+
+def test_compare_with_list(pvector):
+ v = pvector([1, 2, 3])
+
+ assert v == [1, 2, 3]
+ assert v != [1, 2]
+ assert v > [1, 2]
+ assert v < [2, 2]
+ assert [1, 2] < v
+ assert v <= [1, 2, 3]
+ assert v <= [1, 2, 4]
+ assert v >= [1, 2, 3]
+ assert v >= [1, 2]
+
+
+def test_compare_with_non_iterable(pvector):
+ assert pvector([1, 2, 3]) != 5
+ assert not (pvector([1, 2, 3]) == 5)
+
+
+def test_python_no_c_extension_with_environment_variable():
+ from importlib import reload as reload_module
+ import pyrsistent._pvector
+ import pyrsistent
+ import os
+
+ os.environ['PYRSISTENT_NO_C_EXTENSION'] = 'TRUE'
+
+ reload_module(pyrsistent._pvector)
+ reload_module(pyrsistent)
+
+ assert type(pyrsistent.pvector()) is pyrsistent._pvector.PythonPVector
+
+ del os.environ['PYRSISTENT_NO_C_EXTENSION']
+
+ reload_module(pyrsistent._pvector)
+ reload_module(pyrsistent)
+
+
+def test_supports_weakref(pvector):
+ import weakref
+ weakref.ref(pvector())
+
+def test_get_evolver_referents(pvector):
+ """The C implementation of the evolver should expose the original PVector
+ to the gc only once.
+ """
+ if pvector.__module__ == 'pyrsistent._pvector':
+ pytest.skip("This test only applies to pvectorc")
+ import gc
+ v = pvector([1, 2, 3])
+ e = v.evolver()
+ assert len([x for x in gc.get_referents(e) if x is v]) == 1
+
+
+def test_failing_repr(pvector):
+ # See https://github.com/tobgu/pyrsistent/issues/84
+ class A(object):
+ def __repr__(self):
+ raise ValueError('oh no!')
+
+ with pytest.raises(ValueError):
+ repr(pvector([A()]))
+
+
+def test_iterable(pvector):
+ """
+ PVectors can be created from iterables even though they can't be len()
+ hinted.
+ """
+
+ assert pvector(iter("a")) == pvector(iter("a"))
diff --git a/contrib/python/pyrsistent/py3/tests/ya.make b/contrib/python/pyrsistent/py3/tests/ya.make
new file mode 100644
index 0000000000..8bb63ae559
--- /dev/null
+++ b/contrib/python/pyrsistent/py3/tests/ya.make
@@ -0,0 +1,27 @@
+PY3TEST()
+
+PEERDIR(
+ contrib/python/pyrsistent
+)
+
+TEST_SRCS(
+ bag_test.py
+ checked_map_test.py
+ checked_set_test.py
+ checked_vector_test.py
+ class_test.py
+ deque_test.py
+ field_test.py
+ freeze_test.py
+ immutable_object_test.py
+ list_test.py
+ map_test.py
+ record_test.py
+ regression_test.py
+ set_test.py
+ toolz_test.py
+)
+
+NO_LINT()
+
+END()