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

85 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-07 01:52 +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_categorical(tunable_categorical: Tunable) -> None: 

32 """Regular assignment for categorical tunable.""" 

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

34 tunable_categorical.value = "Standard_B4ms" 

35 assert not tunable_categorical.is_special 

36 

37 

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

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

40 with pytest.raises(ValueError): 

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

42 

43 

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

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

46 with pytest.raises(ValueError): 

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

48 

49 

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

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

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

53 

54 

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

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

57 with pytest.raises(ValueError): 

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

59 

60 

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

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

63 with pytest.raises(ValueError): 

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

65 

66 

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

68 """Check str to int coercion.""" 

69 with pytest.raises(ValueError): 

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

71 

72 

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

74 """Check numerical value assignment.""" 

75 tunable_int.numerical_value = 10.0 

76 assert tunable_int.numerical_value == 10 

77 assert not tunable_int.is_special 

78 

79 

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

81 """Check numerical value assignment.""" 

82 tunable_float.numerical_value = 0.1 

83 assert tunable_float.numerical_value == 0.1 

84 assert not tunable_float.is_special 

85 

86 

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

88 """Check str to int coercion.""" 

89 tunable_int.value = "10" 

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

91 assert not tunable_int.is_special 

92 

93 

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

95 """Check str to float coercion.""" 

96 tunable_float.value = "0.5" 

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

98 assert not tunable_float.is_special 

99 

100 

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

102 """Check float to int coercion.""" 

103 tunable_int.value = 10.0 

104 assert tunable_int.value == 10 

105 assert not tunable_int.is_special 

106 

107 

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

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

110 with pytest.raises(ValueError): 

111 tunable_int.value = 10.1 

112 

113 

114def test_tunable_assign_null_to_categorical() -> None: 

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

116 json_config = """ 

117 { 

118 "name": "categorical_test", 

119 "type": "categorical", 

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

121 "default": "foo" 

122 } 

123 """ 

124 config = json.loads(json_config) 

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

126 assert categorical_tunable 

127 assert categorical_tunable.category == "foo" 

128 categorical_tunable.value = None 

129 assert categorical_tunable.value is None 

130 assert categorical_tunable.value != "None" 

131 assert categorical_tunable.category is None 

132 

133 

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

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

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

137 tunable_int.value = None 

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

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

140 

141 

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

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

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

145 tunable_float.value = None 

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

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

148 

149 

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

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

152 `special`). 

153 """ 

154 tunable_int.numerical_value = -1 

155 assert tunable_int.numerical_value == -1 

156 assert tunable_int.is_special 

157 

158 

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

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

161 with pytest.raises(ValueError): 

162 tunable_int.numerical_value = -2 

163 

164 

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

166 """ 

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

168 `special`). 

169 

170 Check coercion from float to int. 

171 """ 

172 tunable_int.numerical_value = -1.0 

173 assert tunable_int.numerical_value == -1 

174 assert tunable_int.is_special 

175 

176 

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

178 """ 

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

180 `special`). 

181 

182 Check coercion from string to int. 

183 """ 

184 tunable_int.value = "-1" 

185 assert tunable_int.numerical_value == -1 

186 assert tunable_int.is_special