Coverage for Users/vladimirpavlov/PycharmProjects/parameterizable/tests/test_guarded_init_metaclass_helpers.py: 98%

1 import pytest

2 from dataclasses import dataclass

3 from 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)

12

13 def 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")

19

20 # Test valid state (tuple)

21 _validate_pickle_state_integrity(({}, {}), "TestClass")

22

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")

26

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")

30

31 def test_parse_pickle_state():

32 """Test parsing of various pickle state formats."""

33 # None

34 assert _parse_pickle_state(None, "C") == (None, None)

35

36 # Dict

37 assert _parse_pickle_state({"a": 1}, "C") == ({"a": 1}, None)

38

39 # Tuple (dict, dict)

40 assert _parse_pickle_state(({"a": 1}, {"b": 2}), "C") == ({"a": 1}, {"b": 2})

41

42 # Tuple (dict, None)

43 assert _parse_pickle_state(({"a": 1}, None), "C") == ({"a": 1}, None)

44

45 # Tuple (None, dict)

46 assert _parse_pickle_state((None, {"b": 2}), "C") == (None, {"b": 2})

47

48 # Invalid states

49 with pytest.raises(RuntimeError, match="Unsupported pickle state"):

50 _parse_pickle_state("invalid", "C")

51

52 with pytest.raises(RuntimeError, match="Unsupported pickle state"):

53 _parse_pickle_state((1, 2), "C")

54

55 with pytest.raises(RuntimeError, match="Unsupported pickle state"):

56 _parse_pickle_state((1, 2, 3), "C")

57

58 def test_restore_dict_state():

59 """Test restoring state into __dict__."""

60 class Obj:

61 pass

62

63 obj = Obj()

64 # By default Obj has __dict__

65 _restore_dict_state(obj, {"x": 10}, "Obj")

66 assert obj.x == 10

67

68 class SlotsObj:

69 __slots__ = ["x"]

70 def __init__(self):

71 self.x = 0

72

73 slots_obj = SlotsObj()

74 with pytest.raises(RuntimeError, match="instance has no __dict__"):

75 _restore_dict_state(slots_obj, {"x": 10}, "SlotsObj")

76

77 def 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

84

85 obj = SlotsObj()

86 _restore_slots_state(obj, {"x": 1, "y": 2})

87 assert obj.x == 1

88 assert obj.y == 2

89

90 # It assumes attributes are valid, if not setattr raises AttributeError usually.

91 with pytest.raises(AttributeError):

92 _restore_slots_state(obj, {"z": 3})

93

94 def 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

101

102 obj = Hooked()

103 _invoke_post_setstate_hook(obj)

104 assert obj.called

105

106 class NoHook:

107 pass

108

109 _invoke_post_setstate_hook(NoHook()) # Should do nothing

110

111 class BadHook:

112 __post_setstate__ = "not callable"

113

114 with pytest.raises(TypeError, match="__post_setstate__ must be callable"):

115 _invoke_post_setstate_hook(BadHook())

116

117 class FailingHook:

118 def __post_setstate__(self):

119 raise ValueError("oops")

120

121 with pytest.raises(ValueError, match="Error in __post_setstate__: oops"):

122 _invoke_post_setstate_hook(FailingHook())

123

124 def 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

133

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)

138

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")

147

148 def test_raise_if_dataclass():

149 """Test detection of dataclasses."""

150 @dataclass

151 class DC:

152 x: int

153

154 with pytest.raises(TypeError, match="GuardedInitMeta cannot be used with dataclass"):

155 _raise_if_dataclass(DC)

156

157 class Normal:

158 pass

159

160 _raise_if_dataclass(Normal) # Should not raise