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

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) 

12 

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

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 

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

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

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 

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 

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 

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 

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 

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 

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