2 Copyright (c) 2019 Intel Corporation
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
17 from math import inf, nan
18 from pathlib import Path
19 from unittest.mock import ANY
22 from accuracy_checker.config.config_validator import (
31 from tests.common import mock_filesystem
34 class TestStringField:
35 def test_expects_string(self):
36 string_field = StringField()
38 with pytest.raises(ConfigError):
39 string_field.validate(b"foo")
40 with pytest.raises(ConfigError):
41 string_field.validate({})
42 with pytest.raises(ConfigError):
43 string_field.validate(42)
45 string_field.validate("foo")
47 def test_choices(self):
48 string_field = StringField(choices=['foo', 'bar'])
50 with pytest.raises(ConfigError):
51 string_field.validate('baz')
53 string_field.validate('bar')
55 def test_case_sensitive(self):
56 string_field = StringField(choices=['foo', 'bar'], case_sensitive=False)
58 string_field.validate('foo')
59 string_field.validate('FOO')
61 string_field = StringField(choices=['foo', 'bar'], case_sensitive=True)
63 string_field.validate('foo')
64 with pytest.raises(ConfigError):
65 string_field.validate('FOO')
68 string_field = StringField(regex=r'foo\d*')
70 string_field.validate('foo')
71 string_field.validate('foo42')
73 with pytest.raises(ConfigError):
74 string_field.validate('baz')
76 def test_custom_exception(self, mocker):
77 stub = mocker.stub(name='custom_on_error')
78 string_field = StringField(choices=['foo'], on_error=stub)
80 with pytest.raises(ConfigError):
81 string_field.validate('bar', 'foo')
82 stub.assert_called_once_with('bar', 'foo', ANY)
84 def test_custom_validator(self, mocker):
85 stub = mocker.stub(name='custom_validator')
86 string_field = StringField(choices=['foo'], additional_validator=stub)
88 string_field.validate('foo', 'baz')
89 stub.assert_called_once_with('foo', 'baz')
92 class TestNumberField:
93 def test_expects_number(self):
94 number_field = NumberField(floats=True)
96 number_field.validate(1.0)
97 with pytest.raises(ConfigError):
98 number_field.validate("foo")
99 with pytest.raises(ConfigError):
100 number_field.validate({})
101 with pytest.raises(ConfigError):
102 number_field.validate([])
104 number_field = NumberField(floats=False)
105 number_field.validate(1)
106 with pytest.raises(ConfigError):
107 number_field.validate(1.0)
110 number_field = NumberField(allow_nan=True)
111 number_field.validate(nan)
113 number_field = NumberField(allow_nan=False)
114 with pytest.raises(ConfigError):
115 number_field.validate(nan)
117 def test_infinity(self):
118 number_field = NumberField(allow_inf=True)
119 number_field.validate(inf)
121 number_field = NumberField(allow_inf=False)
122 with pytest.raises(ConfigError):
123 number_field.validate(inf)
125 def test_ranges(self):
126 number_field = NumberField(min_value=0, max_value=5)
128 number_field.validate(0)
129 number_field.validate(1)
130 number_field.validate(2)
132 with pytest.raises(ConfigError):
133 number_field.validate(-1)
134 with pytest.raises(ConfigError):
135 number_field.validate(7)
139 def test_expects_dict(self):
140 dict_field = DictField()
142 dict_field.validate({})
143 with pytest.raises(ConfigError):
144 dict_field.validate("foo")
145 with pytest.raises(ConfigError):
146 dict_field.validate(42)
147 with pytest.raises(ConfigError):
148 dict_field.validate([])
150 def test_validates_keys(self):
151 dict_field = DictField()
152 dict_field.validate({'foo': 42, 1: 'bar'})
154 dict_field = DictField(key_type=str)
155 dict_field.validate({'foo': 42, 'bar': 'bar'})
156 with pytest.raises(ConfigError):
157 dict_field.validate({'foo': 42, 1: 'bar'})
159 dict_field = DictField(key_type=StringField(choices=['foo', 'bar']))
160 dict_field.validate({'foo': 42, 'bar': 42})
161 with pytest.raises(ConfigError):
162 dict_field.validate({'foo': 42, 1: 'bar'})
163 with pytest.raises(ConfigError):
164 dict_field.validate({'foo': 42, 'baz': 42})
166 def test_validates_values(self):
167 dict_field = DictField()
168 dict_field.validate({'foo': 42, 1: 'bar'})
170 dict_field = DictField(value_type=str)
171 dict_field.validate({'foo': 'foo', 1: 'bar'})
172 with pytest.raises(ConfigError):
173 dict_field.validate({'foo': 42, 1: 2})
175 dict_field = DictField(value_type=StringField(choices=['foo', 'bar']))
176 dict_field.validate({1: 'foo', 'bar': 'bar'})
177 with pytest.raises(ConfigError):
178 dict_field.validate({1: 'foo', 2: 3})
179 with pytest.raises(ConfigError):
180 dict_field.validate({1: 'foo', 2: 'baz'})
182 def test_converts_basic_types(self):
183 dict_field = DictField(value_type=str)
184 assert isinstance(dict_field.value_type, StringField)
186 dict_field = DictField(value_type=int)
187 assert isinstance(dict_field.value_type, NumberField)
188 assert dict_field.value_type.floats is False
190 dict_field = DictField(value_type=float)
191 assert isinstance(dict_field.value_type, NumberField)
192 assert dict_field.value_type.floats is True
194 dict_field = DictField(value_type=list)
195 assert isinstance(dict_field.value_type, ListField)
197 dict_field = DictField(value_type=dict)
198 assert isinstance(dict_field.value_type, DictField)
200 dict_field = DictField(value_type=Path)
201 assert isinstance(dict_field.value_type, PathField)
203 def test_empty(self):
204 dict_field = DictField()
205 dict_field.validate({})
207 dict_field = DictField(allow_empty=False)
208 with pytest.raises(ConfigError):
209 dict_field.validate({})
213 def test_expects_list(self):
214 list_field = ListField()
216 list_field.validate([])
217 with pytest.raises(ConfigError):
218 list_field.validate("foo")
219 with pytest.raises(ConfigError):
220 list_field.validate(42)
221 with pytest.raises(ConfigError):
222 list_field.validate({})
224 def test_validates_values(self):
225 list_field = ListField()
226 list_field.validate(['foo', 42])
228 list_field = ListField(value_type=str)
229 list_field.validate(['foo', 'bar'])
230 with pytest.raises(ConfigError):
231 list_field.validate(['foo', 42])
233 list_field = ListField(value_type=StringField(choices=['foo', 'bar']))
234 list_field.validate(['foo', 'bar'])
235 with pytest.raises(ConfigError):
236 list_field.validate(['foo', 42])
237 with pytest.raises(ConfigError):
238 list_field.validate(['foo', 'bar', 'baz'])
240 def test_empty(self):
241 list_field = ListField()
242 list_field.validate([])
244 list_field = ListField(allow_empty=False)
245 with pytest.raises(ConfigError):
246 list_field.validate([])
250 @pytest.mark.usefixtures('mock_path_exists')
251 def test_expects_path_like(self):
252 path_field = PathField()
253 path_field.validate('foo/bar')
254 path_field.validate('/home/user')
255 path_field.validate(Path('foo/bar'))
257 with pytest.raises(ConfigError):
258 path_field.validate(42)
259 with pytest.raises(ConfigError):
260 path_field.validate({})
261 with pytest.raises(ConfigError):
262 path_field.validate([])
264 def test_path_is_checked(self):
265 with mock_filesystem(['foo/bar']) as prefix:
266 prefix_path = Path(prefix)
267 file_field = PathField(is_directory=False)
268 with pytest.raises(ConfigError):
269 file_field.validate(prefix_path / 'foo')
270 file_field.validate(prefix_path / 'foo' / 'bar')
272 dir_field = PathField(is_directory=True)
273 dir_field.validate(prefix_path / 'foo')
275 with pytest.raises(ConfigError):
276 dir_field.validate(prefix_path / 'foo' / 'bar')
279 class TestConfigValidator:
280 def test_compound(self):
281 class SampleValidator(ConfigValidator):
282 foo = StringField(choices=['foo'])
285 sample_validator = SampleValidator('Sample')
286 sample_validator.validate({'foo': 'foo', 'bar': 1})
288 with pytest.raises(ConfigError):
289 sample_validator.validate({'foo': 'foo'})
290 with pytest.raises(ConfigError):
291 sample_validator.validate({'foo': 'bar', 'bar': 1})
293 def test_optional_fields(self):
294 class SampleValidatorNoOptionals(ConfigValidator):
295 foo = StringField(choices=['foo'])
296 bar = NumberField(optional=False)
298 sample_validator = SampleValidatorNoOptionals('Sample')
299 sample_validator.validate({'foo': 'foo', 'bar': 1})
300 with pytest.raises(ConfigError):
301 sample_validator.validate({'foo': 'bar'})
303 class SampleValidatorWithOptionals(ConfigValidator):
304 foo = StringField(choices=['foo'])
305 bar = NumberField(optional=True)
307 sample_validator = SampleValidatorWithOptionals('Sample')
308 sample_validator.validate({'foo': 'foo', 'bar': 1})
309 sample_validator.validate({'foo': 'foo'})
311 def test_extra_fields__warn_on_extra(self):
312 class SampleValidatorWarnOnExtra(ConfigValidator):
313 foo = StringField(choices=['foo'])
315 sample_validator = SampleValidatorWarnOnExtra(
316 'Sample', on_extra_argument=ConfigValidator.WARN_ON_EXTRA_ARGUMENT
319 with pytest.warns(UserWarning):
320 sample_validator.validate({'foo': 'foo', 'bar': 'bar'})
322 def test_extra_fields__error_on_extra(self):
323 class SampleValidatorErrorOnExtra(ConfigValidator):
324 foo = StringField(choices=['foo'])
326 sample_validator = SampleValidatorErrorOnExtra(
327 'Sample', on_extra_argument=ConfigValidator.ERROR_ON_EXTRA_ARGUMENT)
329 with pytest.raises(ConfigError):
330 sample_validator.validate({'foo': 'bar', 'bar': 'bar'})
332 def test_extra_fields__ignore_extra(self):
333 class SampleValidatorIgnoresExtra(ConfigValidator):
334 foo = StringField(choices=['foo'])
336 sample_validator = SampleValidatorIgnoresExtra(
337 'Sample', on_extra_argument=ConfigValidator.IGNORE_ON_EXTRA_ARGUMENT)
339 sample_validator.validate({'foo': 'foo', 'bar': 'bar'})
341 def test_custom_exception(self, mocker):
342 class SampleValidator(ConfigValidator):
343 foo = StringField(choices=['foo'])
345 stub = mocker.stub(name='custom_on_error')
346 sample_validator = SampleValidator('Sample', on_error=stub)
347 sample_validator.validate({})
348 stub.assert_called_once_with(ANY, 'Sample', ANY)
350 def test_custom_validator(self, mocker):
351 class SampleValidator(ConfigValidator):
352 foo = StringField(choices=['foo'])
354 stub = mocker.stub(name='custom_validator')
355 sample_validator = SampleValidator('Sample', additional_validator=stub)
356 entry = {'foo': 'foo'}
357 sample_validator.validate(entry)
358 stub.assert_called_once_with(entry, 'Sample')
360 def test_nested(self):
361 class InnerValidator(ConfigValidator):
362 foo = StringField(choices=['foo'])
364 class OuterValidator(ConfigValidator):
365 bar = ListField(InnerValidator('Inner'))
367 outer_validator = OuterValidator('Outer', on_extra_argument=ConfigValidator.ERROR_ON_EXTRA_ARGUMENT)
369 outer_validator.validate({'bar': [{'foo': 'foo'}, {'foo': 'foo'}]})
371 def test_inheritance(self):
372 class ParentValidator(ConfigValidator):
373 foo = StringField(choices=['foo'])
375 class DerivedValidator(ParentValidator):
376 bar = StringField(choices=['bar'])
378 derived_validator = DerivedValidator('Derived', on_extra_argument=ConfigValidator.ERROR_ON_EXTRA_ARGUMENT)
379 derived_validator.validate({'foo': 'foo', 'bar': 'bar'})