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
« 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"""
9import json5 as json
10import pytest
12from mlos_bench.tunables.tunable import Tunable
13from mlos_bench.tunables.tunable_groups import TunableGroups
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 )
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
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
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"})
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})
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"})
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"})
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"})
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]
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
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
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
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
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
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
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
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]
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]
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
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
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`).
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
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`).
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