diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/pyrsistent/py3/tests/class_test.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/pyrsistent/py3/tests/class_test.py')
-rw-r--r-- | contrib/python/pyrsistent/py3/tests/class_test.py | 474 |
1 files changed, 474 insertions, 0 deletions
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 |