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

86 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-06 00:35 +0000

1# 

2# Copyright (c) Microsoft Corporation. 

3# Licensed under the MIT License. 

4# 

5""" 

6Unit tests for assigning values to the individual parameters within tunable groups. 

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

18 Make sure that bulk assignment fails for parameters 

19 that don't exist in the TunableGroups object. 

20 """ 

21 with pytest.raises(KeyError): 

22 tunable_groups.assign({ 

23 "vmSize": "Standard_B2ms", 

24 "idle": "mwait", 

25 "UnknownParam_1": 1, 

26 "UnknownParam_2": "invalid-value" 

27 }) 

28 

29 

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

31 """ 

32 Regular assignment for categorical tunable. 

33 """ 

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

35 tunable_categorical.value = "Standard_B4ms" 

36 assert not tunable_categorical.is_special 

37 

38 

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

40 """ 

41 Check parameter validation for categorical tunables. 

42 """ 

43 with pytest.raises(ValueError): 

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

45 

46 

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

48 """ 

49 Check parameter out-of-range validation for numerical tunables. 

50 """ 

51 with pytest.raises(ValueError): 

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

53 

54 

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

56 """ 

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

58 """ 

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

60 

61 

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

63 """ 

64 Check the range when assigning to an integer tunable. 

65 """ 

66 with pytest.raises(ValueError): 

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

68 

69 

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

71 """ 

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

73 """ 

74 with pytest.raises(ValueError): 

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

76 

77 

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

79 """ 

80 Check str to int coercion. 

81 """ 

82 with pytest.raises(ValueError): 

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

84 

85 

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

87 """ 

88 Check numerical value assignment. 

89 """ 

90 tunable_int.numerical_value = 10.0 

91 assert tunable_int.numerical_value == 10 

92 assert not tunable_int.is_special 

93 

94 

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

96 """ 

97 Check numerical value assignment. 

98 """ 

99 tunable_float.numerical_value = 0.1 

100 assert tunable_float.numerical_value == 0.1 

101 assert not tunable_float.is_special 

102 

103 

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

105 """ 

106 Check str to int coercion. 

107 """ 

108 tunable_int.value = "10" 

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

110 assert not tunable_int.is_special 

111 

112 

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

114 """ 

115 Check str to float coercion. 

116 """ 

117 tunable_float.value = "0.5" 

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

119 assert not tunable_float.is_special 

120 

121 

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

123 """ 

124 Check float to int coercion. 

125 """ 

126 tunable_int.value = 10.0 

127 assert tunable_int.value == 10 

128 assert not tunable_int.is_special 

129 

130 

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

132 """ 

133 Check the invalid float to int coercion. 

134 """ 

135 with pytest.raises(ValueError): 

136 tunable_int.value = 10.1 

137 

138 

139def test_tunable_assign_null_to_categorical() -> None: 

140 """ 

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

142 """ 

143 json_config = """ 

144 { 

145 "name": "categorical_test", 

146 "type": "categorical", 

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

148 "default": "foo" 

149 } 

150 """ 

151 config = json.loads(json_config) 

152 categorical_tunable = Tunable(name='categorical_test', config=config) 

153 assert categorical_tunable 

154 assert categorical_tunable.category == "foo" 

155 categorical_tunable.value = None 

156 assert categorical_tunable.value is None 

157 assert categorical_tunable.value != 'None' 

158 assert categorical_tunable.category is None 

159 

160 

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

162 """ 

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

164 """ 

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

166 tunable_int.value = None 

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

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

169 

170 

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

172 """ 

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

174 """ 

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

176 tunable_float.value = None 

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

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

179 

180 

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

182 """ 

183 Check the assignment of a special value outside of the range (but declared `special`). 

184 """ 

185 tunable_int.numerical_value = -1 

186 assert tunable_int.numerical_value == -1 

187 assert tunable_int.is_special 

188 

189 

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

191 """ 

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

193 """ 

194 with pytest.raises(ValueError): 

195 tunable_int.numerical_value = -2 

196 

197 

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

199 """ 

200 Check the assignment of a special value outside of the range (but declared `special`). 

201 Check coercion from float to int. 

202 """ 

203 tunable_int.numerical_value = -1.0 

204 assert tunable_int.numerical_value == -1 

205 assert tunable_int.is_special 

206 

207 

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

209 """ 

210 Check the assignment of a special value outside of the range (but declared `special`). 

211 Check coercion from string to int. 

212 """ 

213 tunable_int.value = "-1" 

214 assert tunable_int.numerical_value == -1 

215 assert tunable_int.is_special