Coverage for mlos_bench/mlos_bench/tests/tunables/tunables_assign_test.py: 100%

103 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 00:44 +0000

1# 

2# Copyright (c) Microsoft Corporation. 

3# Licensed under the MIT License. 

4# 

5"""Unit tests for assigning values to the individual parameters within tunable 

6groups. 

7""" 

8 

9import json5 as json 

10import pytest 

11 

12from mlos_bench.tunables.tunable import Tunable 

13from mlos_bench.tunables.tunable_groups import TunableGroups 

14 

15 

16def test_tunables_assign_unknown_param(tunable_groups: TunableGroups) -> None: 

17 """Make sure that bulk assignment fails for parameters that don't exist in the 

18 TunableGroups object. 

19 """ 

20 with pytest.raises(KeyError): 

21 tunable_groups.assign( 

22 { 

23 "vmSize": "Standard_B2ms", 

24 "idle": "mwait", 

25 "UnknownParam_1": 1, 

26 "UnknownParam_2": "invalid-value", 

27 } 

28 ) 

29 

30 

31def test_tunables_assign_defaults(tunable_groups: TunableGroups) -> None: 

32 """Make sure we can assign the default values using an empty dictionary.""" 

33 tunable_groups_defaults = tunable_groups.copy() 

34 assert tunable_groups.is_defaults() 

35 # Assign the default values. 

36 # Also reset the _is_updated flag to avoid considering that in the comparison. 

37 tunable_groups.assign({}).reset() 

38 assert tunable_groups_defaults == tunable_groups 

39 assert tunable_groups.is_defaults() 

40 new_vm_size = "Standard_B2s" 

41 assert tunable_groups["vmSize"] != new_vm_size 

42 # Change one value. 

43 tunable_groups.assign({"vmSize": new_vm_size}).reset() 

44 assert tunable_groups["vmSize"] == new_vm_size 

45 # Check that the other values are still defaults. 

46 idle_tunable, _ = tunable_groups.get_tunable("idle") 

47 assert idle_tunable.is_default() 

48 assert tunable_groups_defaults != tunable_groups 

49 assert not tunable_groups.is_defaults() 

50 # Reassign defaults. 

51 tunable_groups.assign({}).reset() 

52 assert tunable_groups["vmSize"] != new_vm_size 

53 assert tunable_groups.is_defaults() 

54 assert tunable_groups_defaults == tunable_groups 

55 

56 

57def test_tunables_assign_categorical(tunable_categorical: Tunable) -> None: 

58 """Regular assignment for categorical tunable.""" 

59 # Must be one of: {"Standard_B2s", "Standard_B2ms", "Standard_B4ms"} 

60 tunable_categorical.value = "Standard_B4ms" 

61 assert not tunable_categorical.is_special 

62 

63 

64def test_tunables_assign_invalid_categorical(tunable_groups: TunableGroups) -> None: 

65 """Check parameter validation for categorical tunables.""" 

66 with pytest.raises(ValueError): 

67 tunable_groups.assign({"vmSize": "InvalidSize"}) 

68 

69 

70def test_tunables_assign_invalid_range(tunable_groups: TunableGroups) -> None: 

71 """Check parameter out-of-range validation for numerical tunables.""" 

72 with pytest.raises(ValueError): 

73 tunable_groups.assign({"kernel_sched_migration_cost_ns": -2}) 

74 

75 

76def test_tunables_assign_coerce_str(tunable_groups: TunableGroups) -> None: 

77 """Check the conversion from strings when assigning to an integer parameter.""" 

78 tunable_groups.assign({"kernel_sched_migration_cost_ns": "10000"}) 

79 

80 

81def test_tunables_assign_coerce_str_range_check(tunable_groups: TunableGroups) -> None: 

82 """Check the range when assigning to an integer tunable.""" 

83 with pytest.raises(ValueError): 

84 tunable_groups.assign({"kernel_sched_migration_cost_ns": "5500000"}) 

85 

86 

87def test_tunables_assign_coerce_str_invalid(tunable_groups: TunableGroups) -> None: 

88 """Make sure we fail when assigning an invalid string to an integer tunable.""" 

89 with pytest.raises(ValueError): 

90 tunable_groups.assign({"kernel_sched_migration_cost_ns": "1.1"}) 

91 

92 

93def test_tunable_assign_str_to_numerical(tunable_int: Tunable) -> None: 

94 """Check str to int coercion.""" 

95 with pytest.raises(ValueError): 

96 tunable_int.numerical_value = "foo" # type: ignore[assignment] 

97 

98 

99def test_tunable_assign_int_to_numerical_value(tunable_int: Tunable) -> None: 

100 """Check numerical value assignment.""" 

101 tunable_int.numerical_value = 10.0 

102 assert tunable_int.numerical_value == 10 

103 assert not tunable_int.is_special 

104 

105 

106def test_tunable_assign_float_to_numerical_value(tunable_float: Tunable) -> None: 

107 """Check numerical value assignment.""" 

108 tunable_float.numerical_value = 0.1 

109 assert tunable_float.numerical_value == 0.1 

110 assert not tunable_float.is_special 

111 

112 

113def test_tunable_assign_str_to_int(tunable_int: Tunable) -> None: 

114 """Check str to int coercion.""" 

115 tunable_int.value = "10" 

116 assert tunable_int.value == 10 # type: ignore[comparison-overlap] 

117 assert not tunable_int.is_special 

118 

119 

120def test_tunable_assign_str_to_float(tunable_float: Tunable) -> None: 

121 """Check str to float coercion.""" 

122 tunable_float.value = "0.5" 

123 assert tunable_float.value == 0.5 # type: ignore[comparison-overlap] 

124 assert not tunable_float.is_special 

125 

126 

127def test_tunable_assign_float_to_int(tunable_int: Tunable) -> None: 

128 """Check float to int coercion.""" 

129 tunable_int.value = 10.0 

130 assert tunable_int.value == 10 

131 assert not tunable_int.is_special 

132 

133 

134def test_tunable_assign_float_to_int_fail(tunable_int: Tunable) -> None: 

135 """Check the invalid float to int coercion.""" 

136 with pytest.raises(ValueError): 

137 tunable_int.value = 10.1 

138 

139 

140def test_tunable_assign_null_to_categorical() -> None: 

141 """Checks that we can use null/None in categorical tunables.""" 

142 json_config = """ 

143 { 

144 "name": "categorical_test", 

145 "type": "categorical", 

146 "values": ["foo", null], 

147 "default": "foo" 

148 } 

149 """ 

150 config = json.loads(json_config) 

151 categorical_tunable = Tunable(name="categorical_test", config=config) 

152 assert categorical_tunable 

153 assert categorical_tunable.category == "foo" 

154 categorical_tunable.value = None 

155 assert categorical_tunable.value is None 

156 assert categorical_tunable.value != "None" 

157 assert categorical_tunable.category is None 

158 

159 

160def test_tunable_assign_null_to_int(tunable_int: Tunable) -> None: 

161 """Checks that we can't use null/None in integer tunables.""" 

162 with pytest.raises((TypeError, AssertionError)): 

163 tunable_int.value = None 

164 with pytest.raises((TypeError, AssertionError)): 

165 tunable_int.numerical_value = None # type: ignore[assignment] 

166 

167 

168def test_tunable_assign_null_to_float(tunable_float: Tunable) -> None: 

169 """Checks that we can't use null/None in float tunables.""" 

170 with pytest.raises((TypeError, AssertionError)): 

171 tunable_float.value = None 

172 with pytest.raises((TypeError, AssertionError)): 

173 tunable_float.numerical_value = None # type: ignore[assignment] 

174 

175 

176def test_tunable_assign_special(tunable_int: Tunable) -> None: 

177 """Check the assignment of a special value outside of the range (but declared 

178 `special`). 

179 """ 

180 tunable_int.numerical_value = -1 

181 assert tunable_int.numerical_value == -1 

182 assert tunable_int.is_special 

183 

184 

185def test_tunable_assign_special_fail(tunable_int: Tunable) -> None: 

186 """Assign a value that is neither special nor in range and fail.""" 

187 with pytest.raises(ValueError): 

188 tunable_int.numerical_value = -2 

189 

190 

191def test_tunable_assign_special_with_coercion(tunable_int: Tunable) -> None: 

192 """ 

193 Check the assignment of a special value outside of the range (but declared 

194 `special`). 

195 

196 Check coercion from float to int. 

197 """ 

198 tunable_int.numerical_value = -1.0 

199 assert tunable_int.numerical_value == -1 

200 assert tunable_int.is_special 

201 

202 

203def test_tunable_assign_special_with_coercion_str(tunable_int: Tunable) -> None: 

204 """ 

205 Check the assignment of a special value outside of the range (but declared 

206 `special`). 

207 

208 Check coercion from string to int. 

209 """ 

210 tunable_int.value = "-1" 

211 assert tunable_int.numerical_value == -1 

212 assert tunable_int.is_special