diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 1319786841..0649e15f5f 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -104,7 +104,7 @@ def alphanumeric(value): raise Invalid("string value is None") value = str(value) if not value.isalnum(): - raise Invalid("string value is not alphanumeric") + raise Invalid(f"{value} is not alphanumeric") return value diff --git a/esphome/core.py b/esphome/core.py index 7bbaf9c54c..dfb37555e1 100644 --- a/esphome/core.py +++ b/esphome/core.py @@ -24,15 +24,18 @@ class EsphomeError(Exception): class HexInt(int): def __str__(self): - if 0 <= self <= 255: - return f"0x{self:02X}" - return f"0x{self:X}" + value = self + sign = "-" if value < 0 else "" + value = abs(value) + if 0 <= value <= 255: + return f"{sign}0x{value:02X}" + return f"{sign}0x{value:X}" class IPAddress: def __init__(self, *args): if len(args) != 4: - raise ValueError("IPAddress must consist up 4 items") + raise ValueError("IPAddress must consist of 4 items") self.args = args def __str__(self): diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py index a19330062e..ced75af105 100644 --- a/tests/unit_tests/test_config_validation.py +++ b/tests/unit_tests/test_config_validation.py @@ -9,22 +9,24 @@ from esphome.config_validation import Invalid from esphome.core import Lambda, HexInt -def test_check_not_tamplatable__invalid(): +def test_check_not_templatable__invalid(): with pytest.raises(Invalid, match="This option is not templatable!"): config_validation.check_not_templatable(Lambda("")) -@given(one_of( - booleans(), - integers(), - text(alphabet=string.ascii_letters + string.digits)), -) +@pytest.mark.parametrize("value", ("foo", 1, "D12", False)) def test_alphanumeric__valid(value): actual = config_validation.alphanumeric(value) assert actual == str(value) +@pytest.mark.parametrize("value", ("£23", "Foo!")) +def test_alphanumeric__invalid(value): + with pytest.raises(Invalid): + actual = config_validation.alphanumeric(value) + + @given(value=text(alphabet=string.ascii_lowercase + string.digits + "_")) def test_valid_name__valid(value): actual = config_validation.valid_name(value) @@ -110,4 +112,3 @@ def hex_int__valid(value): assert isinstance(actual, HexInt) assert actual == value - diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index e48286ae51..3c0a96149d 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -7,12 +7,12 @@ from esphome import helpers @pytest.mark.parametrize("preferred_string, current_strings, expected", ( - ("foo", [], "foo"), - # TODO: Should this actually start at 1? - ("foo", ["foo"], "foo_2"), - ("foo", ("foo",), "foo_2"), - ("foo", ("foo", "foo_2"), "foo_3"), - ("foo", ("foo", "foo_2", "foo_2"), "foo_3"), + ("foo", [], "foo"), + # TODO: Should this actually start at 1? + ("foo", ["foo"], "foo_2"), + ("foo", ("foo",), "foo_2"), + ("foo", ("foo", "foo_2"), "foo_3"), + ("foo", ("foo", "foo_2", "foo_2"), "foo_3"), )) def test_ensure_unique_string(preferred_string, current_strings, expected): actual = helpers.ensure_unique_string(preferred_string, current_strings) @@ -21,9 +21,9 @@ def test_ensure_unique_string(preferred_string, current_strings, expected): @pytest.mark.parametrize("text, expected", ( - ("foo", "foo"), - ("foo\nbar", "foo\nbar"), - ("foo\nbar\neek", "foo\n bar\neek"), + ("foo", "foo"), + ("foo\nbar", "foo\nbar"), + ("foo\nbar\neek", "foo\n bar\neek"), )) def test_indent_all_but_first_and_last(text, expected): actual = helpers.indent_all_but_first_and_last(text) @@ -32,9 +32,9 @@ def test_indent_all_but_first_and_last(text, expected): @pytest.mark.parametrize("text, expected", ( - ("foo", [" foo"]), - ("foo\nbar", [" foo", " bar"]), - ("foo\nbar\neek", [" foo", " bar", " eek"]), + ("foo", [" foo"]), + ("foo\nbar", [" foo", " bar"]), + ("foo\nbar\neek", [" foo", " bar", " eek"]), )) def test_indent_list(text, expected): actual = helpers.indent_list(text) @@ -43,9 +43,9 @@ def test_indent_list(text, expected): @pytest.mark.parametrize("text, expected", ( - ("foo", " foo"), - ("foo\nbar", " foo\n bar"), - ("foo\nbar\neek", " foo\n bar\n eek"), + ("foo", " foo"), + ("foo\nbar", " foo\n bar"), + ("foo\nbar\neek", " foo\n bar\n eek"), )) def test_indent(text, expected): actual = helpers.indent(text) @@ -54,11 +54,11 @@ def test_indent(text, expected): @pytest.mark.parametrize("string, expected", ( - ("foo", '"foo"'), - ("foo\nbar", '"foo\\012bar"'), - ("foo\\bar", '"foo\\134bar"'), - ('foo "bar"', '"foo \\042bar\\042"'), - ('foo 🐍', '"foo \\360\\237\\220\\215"'), + ("foo", '"foo"'), + ("foo\nbar", '"foo\\012bar"'), + ("foo\\bar", '"foo\\134bar"'), + ('foo "bar"', '"foo \\042bar\\042"'), + ('foo 🐍', '"foo \\360\\237\\220\\215"'), )) def test_cpp_string_escape(string, expected): actual = helpers.cpp_string_escape(string) @@ -83,11 +83,11 @@ def test_is_ip_address__valid(value): @pytest.mark.parametrize("var, value, default, expected", ( - ("FOO", None, False, False), - ("FOO", None, True, True), - ("FOO", "", False, False), - ("FOO", "Yes", False, True), - ("FOO", "123", False, True), + ("FOO", None, False, False), + ("FOO", None, True, True), + ("FOO", "", False, False), + ("FOO", "Yes", False, True), + ("FOO", "123", False, True), )) def test_get_bool_env(monkeypatch, var, value, default, expected): if value is None: @@ -101,8 +101,8 @@ def test_get_bool_env(monkeypatch, var, value, default, expected): @pytest.mark.parametrize("value, expected", ( - (None, False), - ("Yes", True) + (None, False), + ("Yes", True) )) def test_is_hassio(monkeypatch, value, expected): if value is None: @@ -121,7 +121,7 @@ def test_walk_files(fixture_path): actual = list(helpers.walk_files(path)) # Ensure paths start with the root - assert all(p.startswith(path.as_posix()) for p in actual) + assert all(p.startswith(str(path)) for p in actual) class Test_write_file_if_changed: @@ -186,18 +186,18 @@ class Test_copy_file_if_changed: @pytest.mark.parametrize("file1, file2, expected", ( - # Same file - ("file-a.txt", "file-a.txt", True), - # Different files, different size - ("file-a.txt", "file-b_1.txt", False), - # Different files, same size - ("file-a.txt", "file-c.txt", False), - # Same files - ("file-b_1.txt", "file-b_2.txt", True), - # Not a file - ("file-a.txt", "", False), - # File doesn't exist - ("file-a.txt", "file-d.txt", False), + # Same file + ("file-a.txt", "file-a.txt", True), + # Different files, different size + ("file-a.txt", "file-b_1.txt", False), + # Different files, same size + ("file-a.txt", "file-c.txt", False), + # Same files + ("file-b_1.txt", "file-b_2.txt", True), + # Not a file + ("file-a.txt", "", False), + # File doesn't exist + ("file-a.txt", "file-d.txt", False), )) def test_file_compare(fixture_path, file1, file2, expected): path1 = fixture_path / "helpers" / file1