Coverage for Users / vladimirpavlov / PycharmProjects / parameterizable / tests / test_guarded_init_metaclass_helpers.py: 98%
97 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-01 16:37 -0600
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-01 16:37 -0600
1import pytest
2from dataclasses import dataclass
3from mixinforge.guarded_init_metaclass import (
4 _validate_pickle_state_integrity,
5 _parse_pickle_state,
6 _restore_dict_state,
7 _restore_slots_state,
8 _invoke_post_setstate_hook,
9 _re_raise_with_context,
10 _raise_if_dataclass,
11)
13def test_validate_pickle_state_integrity():
14 """Test validation of pickle state for forbidden _init_finished=True."""
15 # Test valid state (dict)
16 _validate_pickle_state_integrity({}, "TestClass")
17 _validate_pickle_state_integrity({"_init_finished": False}, "TestClass")
18 _validate_pickle_state_integrity(None, "TestClass")
20 # Test valid state (tuple)
21 _validate_pickle_state_integrity(({}, {}), "TestClass")
23 # Test invalid state (dict)
24 with pytest.raises(RuntimeError, match="must not be pickled with _init_finished=True"):
25 _validate_pickle_state_integrity({"_init_finished": True}, "TestClass")
27 # Test invalid state (tuple)
28 with pytest.raises(RuntimeError, match="must not be pickled with _init_finished=True"):
29 _validate_pickle_state_integrity(({"_init_finished": True}, None), "TestClass")
31def test_parse_pickle_state():
32 """Test parsing of various pickle state formats."""
33 # None
34 assert _parse_pickle_state(None, "C") == (None, None)
36 # Dict
37 assert _parse_pickle_state({"a": 1}, "C") == ({"a": 1}, None)
39 # Tuple (dict, dict)
40 assert _parse_pickle_state(({"a": 1}, {"b": 2}), "C") == ({"a": 1}, {"b": 2})
42 # Tuple (dict, None)
43 assert _parse_pickle_state(({"a": 1}, None), "C") == ({"a": 1}, None)
45 # Tuple (None, dict)
46 assert _parse_pickle_state((None, {"b": 2}), "C") == (None, {"b": 2})
48 # Invalid states
49 with pytest.raises(RuntimeError, match="Unsupported pickle state"):
50 _parse_pickle_state("invalid", "C")
52 with pytest.raises(RuntimeError, match="Unsupported pickle state"):
53 _parse_pickle_state((1, 2), "C")
55 with pytest.raises(RuntimeError, match="Unsupported pickle state"):
56 _parse_pickle_state((1, 2, 3), "C")
58def test_restore_dict_state():
59 """Test restoring state into __dict__."""
60 class Obj:
61 pass
63 obj = Obj()
64 # By default Obj has __dict__
65 _restore_dict_state(obj, {"x": 10}, "Obj")
66 assert obj.x == 10
68 class SlotsObj:
69 __slots__ = ["x"]
70 def __init__(self):
71 self.x = 0
73 slots_obj = SlotsObj()
74 with pytest.raises(RuntimeError, match="instance has no __dict__"):
75 _restore_dict_state(slots_obj, {"x": 10}, "SlotsObj")
77def test_restore_slots_state():
78 """Test restoring state into slots."""
79 class SlotsObj:
80 __slots__ = ["x", "y"]
81 def __init__(self):
82 self.x = 0
83 self.y = 0
85 obj = SlotsObj()
86 _restore_slots_state(obj, {"x": 1, "y": 2})
87 assert obj.x == 1
88 assert obj.y == 2
90 # It assumes attributes are valid, if not setattr raises AttributeError usually.
91 with pytest.raises(AttributeError):
92 _restore_slots_state(obj, {"z": 3})
94def test_invoke_post_setstate_hook():
95 """Test invocation of __post_setstate__ hook."""
96 class Hooked:
97 def __init__(self):
98 self.called = False
99 def __post_setstate__(self):
100 self.called = True
102 obj = Hooked()
103 _invoke_post_setstate_hook(obj)
104 assert obj.called
106 class NoHook:
107 pass
109 _invoke_post_setstate_hook(NoHook()) # Should do nothing
111 class BadHook:
112 __post_setstate__ = "not callable"
114 with pytest.raises(TypeError, match="__post_setstate__ must be callable"):
115 _invoke_post_setstate_hook(BadHook())
117 class FailingHook:
118 def __post_setstate__(self):
119 raise ValueError("oops")
121 with pytest.raises(ValueError, match="Error in __post_setstate__: oops"):
122 _invoke_post_setstate_hook(FailingHook())
124def test_re_raise_with_context():
125 """Test exception wrapping logic."""
126 # Exception with standard init
127 try:
128 _re_raise_with_context("MyHook", ValueError("bad value"))
129 except ValueError as e:
130 assert "Error in MyHook: bad value" in str(e)
131 assert isinstance(e, ValueError)
132 assert e.__cause__ is not None
134 # Exception with custom init that might fail with single string
135 class CustomError(Exception):
136 def __init__(self, arg1, arg2):
137 super().__init__(arg1, arg2)
139 try:
140 _re_raise_with_context("MyHook", CustomError("a", "b"))
141 except RuntimeError as e:
142 assert "Error in MyHook" in str(e)
143 assert "CustomError" in str(e)
144 assert e.__cause__ is not None
145 except CustomError:
146 pytest.fail("Should have raised RuntimeError fallback")
148def test_raise_if_dataclass():
149 """Test detection of dataclasses."""
150 @dataclass
151 class DC:
152 x: int
154 with pytest.raises(TypeError, match="GuardedInitMeta cannot be used with dataclass"):
155 _raise_if_dataclass(DC)
157 class Normal:
158 pass
160 _raise_if_dataclass(Normal) # Should not raise