Coverage for src/extratools_core/testtools.py: 99%

77 statements  

« prev     ^ index     » next       coverage.py v7.8.1, created at 2025-06-24 04:41 -0700

1from collections.abc import Callable, Mapping, MutableMapping 

2from typing import Any 

3 

4from .typing import SearchableMapping 

5 

6 

7def expect_exception(error_cls: type[Exception], f: Callable[[], Any]) -> None: 

8 try: 

9 f() 

10 except error_cls: 

11 return 

12 

13 raise AssertionError 

14 

15 

16def is_proper_mapping[KT, VT]( 

17 cls: type[Mapping[KT, VT]] | Callable[[], Mapping[KT, VT]], 

18 *, 

19 key_cls: type[KT] | Callable[[], KT], 

20 value_cls: type[VT] | Callable[[], VT], 

21) -> None: 

22 m: Mapping[KT, VT] = cls() 

23 

24 assert isinstance(m, Mapping) 

25 

26 assert len(m) >= 0 

27 

28 assert list(zip(m.keys(), m.values(), strict=True)) == list(m.items()) 

29 

30 key: KT 

31 value: VT 

32 for key, value in m.items(): 

33 assert key in m 

34 assert m[key] == value 

35 assert m.get(key) == value 

36 

37 key = key_cls() 

38 value = value_cls() 

39 

40 assert key not in m 

41 expect_exception(KeyError, lambda: m[key]) 

42 assert m.get(key) is None 

43 assert m.get(key, value) == value 

44 

45 

46def is_proper_searchable_mapping[KT, VT, FT]( 

47 cls: type[SearchableMapping[KT, VT]] | Callable[[], SearchableMapping[KT, VT]], 

48 *, 

49 filter_cls: type[FT] | Callable[[], FT], 

50 keys_func: Callable[[FT], list[KT]], 

51) -> None: 

52 m: SearchableMapping[KT, VT] = cls() 

53 

54 assert isinstance(m, SearchableMapping) 

55 

56 assert list(m) == list(m.search()) 

57 

58 filter_body = filter_cls() 

59 

60 assert set(m.search(filter_body)) <= set(keys_func(filter_body)) 

61 

62 

63def is_proper_mutable_mapping[KT, VT]( 

64 cls: type[MutableMapping[KT, VT]] | Callable[[], MutableMapping[KT, VT]], 

65 *, 

66 key_cls: type[KT] | Callable[[], KT], 

67 value_cls: type[VT] | Callable[[], VT], 

68) -> None: 

69 m: MutableMapping[KT, VT] = cls() 

70 

71 assert isinstance(m, MutableMapping) 

72 

73 assert len(m) >= 0 

74 

75 m.clear() 

76 assert len(m) == 0 

77 

78 assert list(m.keys()) == [] 

79 assert list(m.values()) == [] 

80 assert list(m.items()) == [] 

81 

82 key: KT = key_cls() 

83 value: VT = value_cls() 

84 assert key not in m 

85 

86 m[key] = value 

87 assert key in m 

88 assert len(m) == 1 

89 assert m[key] == value 

90 assert m.get(key) == value 

91 

92 assert list(m.keys()) == [key] 

93 assert list(m.values()) == [value] 

94 assert list(m.items()) == [(key, value)] 

95 

96 # No duplication 

97 m[key] = value 

98 assert len(m) == 1 

99 

100 del m[key] 

101 assert key not in m 

102 assert len(m) == 0 

103 expect_exception(KeyError, lambda: m[key]) 

104 assert m.get(key) is None 

105 assert m.get(key, value) == value 

106 

107 assert m.setdefault(key, value) == value 

108 assert key in m 

109 assert len(m) == 1 

110 assert m[key] == value 

111 

112 assert m.pop(key) == value 

113 assert key not in m 

114 assert len(m) == 0 

115 # `pop`` is special here that it would raise `KeyError` if `default` is not specified. 

116 expect_exception(KeyError, lambda: m.pop(key)) 

117 assert m.pop(key, None) is None 

118 assert m.pop(key, value) == value 

119 

120 m.update([(key, value)]) 

121 assert len(m) == 1 

122 

123 assert list(zip(m.keys(), m.values(), strict=True)) == list(m.items()) 

124 

125 for key, value in m.items(): 

126 assert key in m 

127 assert m[key] == value 

128 assert m.get(key) == value 

129 

130 m.clear() 

131 assert len(m) == 0