mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Unittests for esphome python code (#931)
This commit is contained in:
		
							
								
								
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.coveragerc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| [run] | ||||
| omit = esphome/components/* | ||||
| @@ -143,6 +143,9 @@ class TimePeriod: | ||||
|             return f'{self.total_days}d' | ||||
|         return '0s' | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return f"TimePeriod<{self.total_microseconds}>" | ||||
|  | ||||
|     @property | ||||
|     def total_microseconds(self): | ||||
|         return self.total_milliseconds * 1000 + (self.microseconds or 0) | ||||
|   | ||||
							
								
								
									
										4
									
								
								pytest.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								pytest.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| [pytest] | ||||
| addopts = | ||||
|     --cov=esphome | ||||
|     --cov-branch | ||||
| @@ -16,3 +16,9 @@ pylint==2.4.4 ; python_version>"3" | ||||
| flake8==3.7.9 | ||||
| pillow | ||||
| pexpect | ||||
|  | ||||
| # Unit tests | ||||
| pytest==5.3.2 | ||||
| pytest-cov==2.8.1 | ||||
| pytest-mock==1.13.0 | ||||
| hypothesis==4.57.0 | ||||
|   | ||||
| @@ -9,4 +9,5 @@ set -x | ||||
| script/ci-custom.py | ||||
| script/lint-python | ||||
| script/lint-cpp | ||||
| script/unit_test | ||||
| script/test | ||||
|   | ||||
							
								
								
									
										9
									
								
								script/unit_test
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								script/unit_test
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| set -e | ||||
|  | ||||
| cd "$(dirname "$0")/.." | ||||
|  | ||||
| set -x | ||||
|  | ||||
| pytest tests/unit_tests | ||||
							
								
								
									
										30
									
								
								tests/unit_tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								tests/unit_tests/conftest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| """ | ||||
| ESPHome Unittests | ||||
| ~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Configuration file for unit tests. | ||||
|  | ||||
| If adding unit tests ensure that they are fast. Slower integration tests should | ||||
| not be part of a unit test suite. | ||||
|  | ||||
| """ | ||||
| import sys | ||||
| import pytest | ||||
|  | ||||
| from pathlib import Path | ||||
|  | ||||
|  | ||||
| here = Path(__file__).parent | ||||
|  | ||||
| # Configure location of package root | ||||
| package_root = here.parent.parent | ||||
| sys.path.insert(0, package_root.as_posix()) | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def fixture_path() -> Path: | ||||
|     """ | ||||
|     Location of all fixture files. | ||||
|     """ | ||||
|     return here / "fixtures" | ||||
|  | ||||
							
								
								
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-a.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-a.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| A files are unique. | ||||
							
								
								
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-b_1.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-b_1.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| All b files match. | ||||
							
								
								
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-b_2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-b_2.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| All b files match. | ||||
							
								
								
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-c.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tests/unit_tests/fixtures/helpers/file-c.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| C files are unique. | ||||
							
								
								
									
										15
									
								
								tests/unit_tests/strategies.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tests/unit_tests/strategies.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| from typing import Text | ||||
|  | ||||
| import hypothesis.strategies._internal.core as st | ||||
| from hypothesis.strategies._internal.strategies import SearchStrategy | ||||
|  | ||||
|  | ||||
| @st.defines_strategy_with_reusable_values | ||||
| def mac_addr_strings(): | ||||
|     # type: () -> SearchStrategy[Text] | ||||
|     """A strategy for MAC address strings. | ||||
|  | ||||
|     This consists of six strings representing integers [0..255], | ||||
|     without zero-padding, joined by dots. | ||||
|     """ | ||||
|     return st.builds("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}".format, *(6 * [st.integers(0, 255)])) | ||||
							
								
								
									
										113
									
								
								tests/unit_tests/test_config_validation.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								tests/unit_tests/test_config_validation.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | ||||
| import pytest | ||||
| import string | ||||
|  | ||||
| from hypothesis import given, example | ||||
| from hypothesis.strategies import one_of, text, integers, booleans, builds | ||||
|  | ||||
| from esphome import config_validation | ||||
| from esphome.config_validation import Invalid | ||||
| from esphome.core import Lambda, HexInt | ||||
|  | ||||
|  | ||||
| def test_check_not_tamplatable__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)), | ||||
| ) | ||||
| def test_alphanumeric__valid(value): | ||||
|     actual = config_validation.alphanumeric(value) | ||||
|  | ||||
|     assert actual == str(value) | ||||
|  | ||||
|  | ||||
| @given(value=text(alphabet=string.ascii_lowercase + string.digits + "_")) | ||||
| def test_valid_name__valid(value): | ||||
|     actual = config_validation.valid_name(value) | ||||
|  | ||||
|     assert actual == value | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     "foo bar", "FooBar", "foo::bar" | ||||
| )) | ||||
| def test_valid_name__invalid(value): | ||||
|     with pytest.raises(Invalid): | ||||
|         config_validation.valid_name(value) | ||||
|  | ||||
|  | ||||
| @given(one_of(integers(), text())) | ||||
| def test_string__valid(value): | ||||
|     actual = config_validation.string(value) | ||||
|  | ||||
|     assert actual == str(value) | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     {}, [], True, False, None | ||||
| )) | ||||
| def test_string__invalid(value): | ||||
|     with pytest.raises(Invalid): | ||||
|         config_validation.string(value) | ||||
|  | ||||
|  | ||||
| @given(text()) | ||||
| def test_strict_string__valid(value): | ||||
|     actual = config_validation.string_strict(value) | ||||
|  | ||||
|     assert actual == value | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", (None, 123)) | ||||
| def test_string_string__invalid(value): | ||||
|     with pytest.raises(Invalid, match="Must be string, got"): | ||||
|         config_validation.string_strict(value) | ||||
|  | ||||
|  | ||||
| @given(builds(lambda v: "mdi:" + v, text())) | ||||
| @example("") | ||||
| def test_icon__valid(value): | ||||
|     actual = config_validation.icon(value) | ||||
|  | ||||
|     assert actual == value | ||||
|  | ||||
|  | ||||
| def test_icon__invalid(): | ||||
|     with pytest.raises(Invalid, match="Icons should start with prefix"): | ||||
|         config_validation.icon("foo") | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     "True", "YES", "on", "enAblE", True | ||||
| )) | ||||
| def test_boolean__valid_true(value): | ||||
|     assert config_validation.boolean(value) is True | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     "False", "NO", "off", "disAblE", False | ||||
| )) | ||||
| def test_boolean__valid_false(value): | ||||
|     assert config_validation.boolean(value) is False | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     None, 1, 0, "foo" | ||||
| )) | ||||
| def test_boolean__invalid(value): | ||||
|     with pytest.raises(Invalid, match="Expected boolean value"): | ||||
|         config_validation.boolean(value) | ||||
|  | ||||
|  | ||||
| # TODO: ensure_list | ||||
| @given(integers()) | ||||
| def hex_int__valid(value): | ||||
|     actual = config_validation.hex_int(value) | ||||
|  | ||||
|     assert isinstance(actual, HexInt) | ||||
|     assert actual == value | ||||
|      | ||||
							
								
								
									
										491
									
								
								tests/unit_tests/test_core.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										491
									
								
								tests/unit_tests/test_core.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,491 @@ | ||||
| import pytest | ||||
|  | ||||
| from hypothesis import given | ||||
| from hypothesis.provisional import ip4_addr_strings | ||||
| from strategies import mac_addr_strings | ||||
|  | ||||
| from esphome import core, const | ||||
|  | ||||
|  | ||||
| class TestHexInt: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             (1, "0x01"), | ||||
|             (255, "0xFF"), | ||||
|             (128, "0x80"), | ||||
|             (256, "0x100"), | ||||
|             (-1, "-0x01"),  # TODO: this currently fails | ||||
|     )) | ||||
|     def test_str(self, value, expected): | ||||
|         target = core.HexInt(value) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|  | ||||
| class TestIPAddress: | ||||
|     @given(value=ip4_addr_strings()) | ||||
|     def test_init__valid(self, value): | ||||
|         core.IPAddress(*value.split(".")) | ||||
|  | ||||
|     @pytest.mark.parametrize("value", ("127.0.0", "localhost", "")) | ||||
|     def test_init__invalid(self, value): | ||||
|         with pytest.raises(ValueError, match="IPAddress must consist of 4 items"): | ||||
|             core.IPAddress(*value.split(".")) | ||||
|  | ||||
|     @given(value=ip4_addr_strings()) | ||||
|     def test_str(self, value): | ||||
|         target = core.IPAddress(*value.split(".")) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == value | ||||
|  | ||||
|  | ||||
| class TestMACAddress: | ||||
|     @given(value=mac_addr_strings()) | ||||
|     def test_init__valid(self, value): | ||||
|         core.MACAddress(*value.split(":")) | ||||
|  | ||||
|     @pytest.mark.parametrize("value", ("1:2:3:4:5", "localhost", "")) | ||||
|     def test_init__invalid(self, value): | ||||
|         with pytest.raises(ValueError, match="MAC Address must consist of 6 items"): | ||||
|             core.MACAddress(*value.split(":")) | ||||
|  | ||||
|     @given(value=mac_addr_strings()) | ||||
|     def test_str(self, value): | ||||
|         target = core.MACAddress(*(int(v, 16) for v in value.split(":"))) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == value | ||||
|  | ||||
|     def test_as_hex(self): | ||||
|         target = core.MACAddress(0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF) | ||||
|  | ||||
|         actual = target.as_hex | ||||
|  | ||||
|         assert actual.text == "0xDEADBEEF00FFULL" | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     1, 2, -1, 0, 1.0, -1.0, 42.0009, -42.0009 | ||||
| )) | ||||
| def test_is_approximately_integer__in_range(value): | ||||
|     actual = core.is_approximately_integer(value) | ||||
|  | ||||
|     assert actual is True | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value", ( | ||||
|     42.01, -42.01, 1.5 | ||||
| )) | ||||
| def test_is_approximately_integer__not_in_range(value): | ||||
|     actual = core.is_approximately_integer(value) | ||||
|  | ||||
|     assert actual is False | ||||
|  | ||||
|  | ||||
| class TestTimePeriod: | ||||
|     @pytest.mark.parametrize("kwargs, expected", ( | ||||
|             ({}, {}), | ||||
|             ({"microseconds": 1}, {"microseconds": 1}), | ||||
|             ({"microseconds": 1.0001}, {"microseconds": 1}), | ||||
|             ({"milliseconds": 2}, {"milliseconds": 2}), | ||||
|             ({"milliseconds": 2.0001}, {"milliseconds": 2}), | ||||
|             ({"milliseconds": 2.01}, {"milliseconds": 2, "microseconds": 10}), | ||||
|             ({"seconds": 3}, {"seconds": 3}), | ||||
|             ({"seconds": 3.0001}, {"seconds": 3}), | ||||
|             ({"seconds": 3.01}, {"seconds": 3, "milliseconds": 10}), | ||||
|             ({"minutes": 4}, {"minutes": 4}), | ||||
|             ({"minutes": 4.0001}, {"minutes": 4}), | ||||
|             ({"minutes": 4.1}, {"minutes": 4, "seconds": 6}), | ||||
|             ({"hours": 5}, {"hours": 5}), | ||||
|             ({"hours": 5.0001}, {"hours": 5}), | ||||
|             ({"hours": 5.1}, {"hours": 5, "minutes": 6}), | ||||
|             ({"days": 6}, {"days": 6}), | ||||
|             ({"days": 6.0001}, {"days": 6}), | ||||
|             ({"days": 6.1}, {"days": 6, "hours": 2, "minutes": 24}), | ||||
|     )) | ||||
|     def test_init(self, kwargs, expected): | ||||
|         target = core.TimePeriod(**kwargs) | ||||
|  | ||||
|         actual = target.as_dict() | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     def test_init__microseconds_with_fraction(self): | ||||
|         with pytest.raises(ValueError, match="Maximum precision is microseconds"): | ||||
|             core.TimePeriod(microseconds=1.1) | ||||
|  | ||||
|     @pytest.mark.parametrize("kwargs, expected", ( | ||||
|             ({}, "0s"), | ||||
|             ({"microseconds": 1}, "1us"), | ||||
|             ({"microseconds": 1.0001}, "1us"), | ||||
|             ({"milliseconds": 2}, "2ms"), | ||||
|             ({"milliseconds": 2.0001}, "2ms"), | ||||
|             ({"milliseconds": 2.01}, "2010us"), | ||||
|             ({"seconds": 3}, "3s"), | ||||
|             ({"seconds": 3.0001}, "3s"), | ||||
|             ({"seconds": 3.01}, "3010ms"), | ||||
|             ({"minutes": 4}, "4min"), | ||||
|             ({"minutes": 4.0001}, "4min"), | ||||
|             ({"minutes": 4.1}, "246s"), | ||||
|             ({"hours": 5}, "5h"), | ||||
|             ({"hours": 5.0001}, "5h"), | ||||
|             ({"hours": 5.1}, "306min"), | ||||
|             ({"days": 6}, "6d"), | ||||
|             ({"days": 6.0001}, "6d"), | ||||
|             ({"days": 6.1}, "8784min"), | ||||
|     )) | ||||
|     def test_str(self, kwargs, expected): | ||||
|         target = core.TimePeriod(**kwargs) | ||||
|  | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|             ("__eq__", core.TimePeriod(microseconds=900), False), | ||||
|             ("__eq__", core.TimePeriod(milliseconds=1), True), | ||||
|             ("__eq__", core.TimePeriod(microseconds=1100), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|  | ||||
|             ("__ne__", core.TimePeriod(microseconds=900), True), | ||||
|             ("__ne__", core.TimePeriod(milliseconds=1), False), | ||||
|             ("__ne__", core.TimePeriod(microseconds=1100), True), | ||||
|             ("__ne__", 1000, NotImplemented), | ||||
|             ("__ne__", "1000", NotImplemented), | ||||
|             ("__ne__", True, NotImplemented), | ||||
|             ("__ne__", object(), NotImplemented), | ||||
|             ("__ne__", None, NotImplemented), | ||||
|  | ||||
|             ("__lt__", core.TimePeriod(microseconds=900), False), | ||||
|             ("__lt__", core.TimePeriod(milliseconds=1), False), | ||||
|             ("__lt__", core.TimePeriod(microseconds=1100), True), | ||||
|             ("__lt__", 1000, NotImplemented), | ||||
|             ("__lt__", "1000", NotImplemented), | ||||
|             ("__lt__", True, NotImplemented), | ||||
|             ("__lt__", object(), NotImplemented), | ||||
|             ("__lt__", None, NotImplemented), | ||||
|  | ||||
|             ("__gt__", core.TimePeriod(microseconds=900), True), | ||||
|             ("__gt__", core.TimePeriod(milliseconds=1), False), | ||||
|             ("__gt__", core.TimePeriod(microseconds=1100), False), | ||||
|             ("__gt__", 1000, NotImplemented), | ||||
|             ("__gt__", "1000", NotImplemented), | ||||
|             ("__gt__", True, NotImplemented), | ||||
|             ("__gt__", object(), NotImplemented), | ||||
|             ("__gt__", None, NotImplemented), | ||||
|  | ||||
|             ("__le__", core.TimePeriod(microseconds=900), False), | ||||
|             ("__le__", core.TimePeriod(milliseconds=1), True), | ||||
|             ("__le__", core.TimePeriod(microseconds=1100), True), | ||||
|             ("__le__", 1000, NotImplemented), | ||||
|             ("__le__", "1000", NotImplemented), | ||||
|             ("__le__", True, NotImplemented), | ||||
|             ("__le__", object(), NotImplemented), | ||||
|             ("__le__", None, NotImplemented), | ||||
|  | ||||
|             ("__ge__", core.TimePeriod(microseconds=900), True), | ||||
|             ("__ge__", core.TimePeriod(milliseconds=1), True), | ||||
|             ("__ge__", core.TimePeriod(microseconds=1100), False), | ||||
|             ("__ge__", 1000, NotImplemented), | ||||
|             ("__ge__", "1000", NotImplemented), | ||||
|             ("__ge__", True, NotImplemented), | ||||
|             ("__ge__", object(), NotImplemented), | ||||
|             ("__ge__", None, NotImplemented), | ||||
|     )) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.TimePeriod(microseconds=1000) | ||||
|  | ||||
|         actual = getattr(target, comparison)(other) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|  | ||||
| SAMPLE_LAMBDA = """ | ||||
| it.strftime(64, 0, id(my_font), TextAlign::TOP_CENTER, "%H:%M:%S", id(esptime).now()); | ||||
| it.printf(64, 16, id(my_font2), TextAlign::TOP_CENTER, "%.1f°C (%.1f%%)", id( office_tmp ).state, id(office_hmd).state); | ||||
| """ | ||||
|  | ||||
|  | ||||
| class TestLambda: | ||||
|     def test_init__copy_initializer(self): | ||||
|         value = core.Lambda("foo") | ||||
|         target = core.Lambda(value) | ||||
|  | ||||
|         assert str(target) is value.value | ||||
|  | ||||
|     def test_parts(self): | ||||
|         target = core.Lambda(SAMPLE_LAMBDA.strip()) | ||||
|  | ||||
|         # Check cache | ||||
|         assert target._parts is None | ||||
|         actual = target.parts | ||||
|         assert target._parts is actual | ||||
|         assert target.parts is actual | ||||
|  | ||||
|         assert actual == [ | ||||
|             "it.strftime(64, 0, ", | ||||
|             "my_font", | ||||
|             "", | ||||
|             ", TextAlign::TOP_CENTER, \"%H:%M:%S\", ", | ||||
|             "esptime", | ||||
|             ".", | ||||
|             "now());\nit.printf(64, 16, ", | ||||
|             "my_font2", | ||||
|             "", | ||||
|             ", TextAlign::TOP_CENTER, \"%.1f°C (%.1f%%)\", ", | ||||
|             "office_tmp", | ||||
|             ".", | ||||
|             "state, ", | ||||
|             "office_hmd", | ||||
|             ".", | ||||
|             "state);" | ||||
|         ] | ||||
|  | ||||
|     def test_requires_ids(self): | ||||
|         target = core.Lambda(SAMPLE_LAMBDA.strip()) | ||||
|  | ||||
|         # Check cache | ||||
|         assert target._requires_ids is None | ||||
|         actual = target.requires_ids | ||||
|         assert target._requires_ids is actual | ||||
|         assert target.requires_ids is actual | ||||
|  | ||||
|         assert actual == [ | ||||
|             core.ID("my_font"), | ||||
|             core.ID("esptime"), | ||||
|             core.ID("my_font2"), | ||||
|             core.ID("office_tmp"), | ||||
|             core.ID("office_hmd"), | ||||
|         ] | ||||
|  | ||||
|     def test_value_setter(self): | ||||
|         target = core.Lambda("") | ||||
|  | ||||
|         # Populate cache | ||||
|         _ = target.parts | ||||
|         _ = target.requires_ids | ||||
|  | ||||
|         target.value = SAMPLE_LAMBDA | ||||
|  | ||||
|         # Check cache has been cleared | ||||
|         assert target._parts is None | ||||
|         assert target._requires_ids is None | ||||
|  | ||||
|         assert target.value == SAMPLE_LAMBDA | ||||
|  | ||||
|     def test_repr(self): | ||||
|         target = core.Lambda("id(var).value == 1") | ||||
|  | ||||
|         assert repr(target) == "Lambda<id(var).value == 1>" | ||||
|  | ||||
|  | ||||
| class TestID: | ||||
|     @pytest.fixture | ||||
|     def target(self): | ||||
|         return core.ID(None, is_declaration=True, type="binary_sensor::Example") | ||||
|  | ||||
|     @pytest.mark.parametrize("id, is_manual, expected", ( | ||||
|             ("foo", None, True), | ||||
|             (None, None, False), | ||||
|             ("foo", True, True), | ||||
|             ("foo", False, False), | ||||
|             (None, True, True), | ||||
|     )) | ||||
|     def test_init__resolve_is_manual(self, id, is_manual, expected): | ||||
|         target = core.ID(id, is_manual=is_manual) | ||||
|  | ||||
|         assert target.is_manual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("registered_ids, expected", ( | ||||
|             ([], "binary_sensor_example"), | ||||
|             (["binary_sensor_example"], "binary_sensor_example_2"), | ||||
|             (["foo"], "binary_sensor_example"), | ||||
|             (["binary_sensor_example", "foo", "binary_sensor_example_2"], "binary_sensor_example_3"), | ||||
|     )) | ||||
|     def test_resolve(self, target, registered_ids, expected): | ||||
|         actual = target.resolve(registered_ids) | ||||
|  | ||||
|         assert actual == expected | ||||
|         assert str(target) == expected | ||||
|  | ||||
|     def test_copy(self, target): | ||||
|         target.resolve([]) | ||||
|  | ||||
|         actual = target.copy() | ||||
|  | ||||
|         assert actual is not target | ||||
|         assert all(getattr(actual, n) == getattr(target, n) | ||||
|                    for n in ("id", "is_declaration", "type", "is_manual")) | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|             ("__eq__", core.ID(id="foo"), True), | ||||
|             ("__eq__", core.ID(id="bar"), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|     )) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.ID(id="foo") | ||||
|  | ||||
|         actual = getattr(target, comparison)(other) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|  | ||||
| class TestDocumentLocation: | ||||
|     @pytest.fixture | ||||
|     def target(self): | ||||
|         return core.DocumentLocation( | ||||
|             document="foo.txt", | ||||
|             line=10, | ||||
|             column=20, | ||||
|         ) | ||||
|  | ||||
|     def test_str(self, target): | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == "foo.txt 10:20" | ||||
|  | ||||
|  | ||||
| class TestDocumentRange: | ||||
|     @pytest.fixture | ||||
|     def target(self): | ||||
|         return core.DocumentRange( | ||||
|             core.DocumentLocation( | ||||
|                 document="foo.txt", | ||||
|                 line=10, | ||||
|                 column=20, | ||||
|             ), | ||||
|             core.DocumentLocation( | ||||
|                 document="foo.txt", | ||||
|                 line=15, | ||||
|                 column=12, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|     def test_str(self, target): | ||||
|         actual = str(target) | ||||
|  | ||||
|         assert actual == "[foo.txt 10:20 - foo.txt 15:12]" | ||||
|  | ||||
|  | ||||
| class TestDefine: | ||||
|     @pytest.mark.parametrize("name, value, prop, expected", ( | ||||
|             ("ANSWER", None, "as_build_flag", "-DANSWER"), | ||||
|             ("ANSWER", None, "as_macro", "#define ANSWER"), | ||||
|             ("ANSWER", None, "as_tuple", ("ANSWER", None)), | ||||
|             ("ANSWER", 42, "as_build_flag", "-DANSWER=42"), | ||||
|             ("ANSWER", 42, "as_macro", "#define ANSWER 42"), | ||||
|             ("ANSWER", 42, "as_tuple", ("ANSWER", 42)), | ||||
|     )) | ||||
|     def test_properties(self, name, value, prop, expected): | ||||
|         target = core.Define(name, value) | ||||
|  | ||||
|         actual = getattr(target, prop) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|             ("__eq__", core.Define(name="FOO", value=42), True), | ||||
|             ("__eq__", core.Define(name="FOO", value=13), False), | ||||
|             ("__eq__", core.Define(name="FOO"), False), | ||||
|             ("__eq__", core.Define(name="BAR", value=42), False), | ||||
|             ("__eq__", core.Define(name="BAR"), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|     )) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.Define(name="FOO", value=42) | ||||
|  | ||||
|         actual = getattr(target, comparison)(other) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|  | ||||
| class TestLibrary: | ||||
|     @pytest.mark.parametrize("name, value, prop, expected", ( | ||||
|             ("mylib", None, "as_lib_dep", "mylib"), | ||||
|             ("mylib", None, "as_tuple", ("mylib", None)), | ||||
|             ("mylib", "1.2.3", "as_lib_dep", "mylib@1.2.3"), | ||||
|             ("mylib", "1.2.3", "as_tuple", ("mylib", "1.2.3")), | ||||
|     )) | ||||
|     def test_properties(self, name, value, prop, expected): | ||||
|         target = core.Library(name, value) | ||||
|  | ||||
|         actual = getattr(target, prop) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("comparison, other, expected", ( | ||||
|             ("__eq__", core.Library(name="libfoo", version="1.2.3"), True), | ||||
|             ("__eq__", core.Library(name="libfoo", version="1.2.4"), False), | ||||
|             ("__eq__", core.Library(name="libbar", version="1.2.3"), False), | ||||
|             ("__eq__", 1000, NotImplemented), | ||||
|             ("__eq__", "1000", NotImplemented), | ||||
|             ("__eq__", True, NotImplemented), | ||||
|             ("__eq__", object(), NotImplemented), | ||||
|             ("__eq__", None, NotImplemented), | ||||
|     )) | ||||
|     def test_comparison(self, comparison, other, expected): | ||||
|         target = core.Library(name="libfoo", version="1.2.3") | ||||
|  | ||||
|         actual = getattr(target, comparison)(other) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|  | ||||
| class TestEsphomeCore: | ||||
|     @pytest.fixture | ||||
|     def target(self, fixture_path): | ||||
|         target = core.EsphomeCore() | ||||
|         target.build_path = "foo/build" | ||||
|         target.config_path = "foo/config" | ||||
|         return target | ||||
|  | ||||
|     def test_reset(self, target): | ||||
|         """Call reset on target and compare to new instance""" | ||||
|         other = core.EsphomeCore() | ||||
|  | ||||
|         target.reset() | ||||
|  | ||||
|         # TODO: raw_config and config differ, should they? | ||||
|         assert target.__dict__ == other.__dict__ | ||||
|  | ||||
|     def test_address__none(self, target): | ||||
|         assert target.address is None | ||||
|  | ||||
|     def test_address__wifi(self, target): | ||||
|         target.config[const.CONF_WIFI] = {const.CONF_USE_ADDRESS: "1.2.3.4"} | ||||
|         target.config["ethernet"] = {const.CONF_USE_ADDRESS: "4.3.2.1"} | ||||
|  | ||||
|         assert target.address == "1.2.3.4" | ||||
|  | ||||
|     def test_address__ethernet(self, target): | ||||
|         target.config["ethernet"] = {const.CONF_USE_ADDRESS: "4.3.2.1"} | ||||
|  | ||||
|         assert target.address == "4.3.2.1" | ||||
|  | ||||
|     def test_is_esp32(self, target): | ||||
|         target.esp_platform = "ESP32" | ||||
|  | ||||
|         assert target.is_esp32 is True | ||||
|         assert target.is_esp8266 is False | ||||
|  | ||||
|     def test_is_esp8266(self, target): | ||||
|         target.esp_platform = "ESP8266" | ||||
|  | ||||
|         assert target.is_esp32 is False | ||||
|         assert target.is_esp8266 is True | ||||
							
								
								
									
										208
									
								
								tests/unit_tests/test_helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								tests/unit_tests/test_helpers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,208 @@ | ||||
| import pytest | ||||
|  | ||||
| from hypothesis import given | ||||
| from hypothesis.provisional import ip4_addr_strings | ||||
|  | ||||
| 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"), | ||||
| )) | ||||
| def test_ensure_unique_string(preferred_string, current_strings, expected): | ||||
|     actual = helpers.ensure_unique_string(preferred_string, current_strings) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("text, expected", ( | ||||
|         ("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) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("text, expected", ( | ||||
|         ("foo", ["  foo"]), | ||||
|         ("foo\nbar", ["  foo", "  bar"]), | ||||
|         ("foo\nbar\neek", ["  foo", "  bar", "  eek"]), | ||||
| )) | ||||
| def test_indent_list(text, expected): | ||||
|     actual = helpers.indent_list(text) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("text, expected", ( | ||||
|         ("foo", "  foo"), | ||||
|         ("foo\nbar", "  foo\n  bar"), | ||||
|         ("foo\nbar\neek", "  foo\n  bar\n  eek"), | ||||
| )) | ||||
| def test_indent(text, expected): | ||||
|     actual = helpers.indent(text) | ||||
|  | ||||
|     assert actual == 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"'), | ||||
| )) | ||||
| def test_cpp_string_escape(string, expected): | ||||
|     actual = helpers.cpp_string_escape(string) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("host", ( | ||||
|     "127.0.0", "localhost", "127.0.0.b", | ||||
| )) | ||||
| def test_is_ip_address__invalid(host): | ||||
|     actual = helpers.is_ip_address(host) | ||||
|  | ||||
|     assert actual is False | ||||
|  | ||||
|  | ||||
| @given(value=ip4_addr_strings()) | ||||
| def test_is_ip_address__valid(value): | ||||
|     actual = helpers.is_ip_address(value) | ||||
|  | ||||
|     assert actual is True | ||||
|  | ||||
|  | ||||
| @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), | ||||
| )) | ||||
| def test_get_bool_env(monkeypatch, var, value, default, expected): | ||||
|     if value is None: | ||||
|         monkeypatch.delenv(var, raising=False) | ||||
|     else: | ||||
|         monkeypatch.setenv(var, value) | ||||
|  | ||||
|     actual = helpers.get_bool_env(var, default) | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| @pytest.mark.parametrize("value, expected", ( | ||||
|         (None, False), | ||||
|         ("Yes", True) | ||||
| )) | ||||
| def test_is_hassio(monkeypatch, value, expected): | ||||
|     if value is None: | ||||
|         monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False) | ||||
|     else: | ||||
|         monkeypatch.setenv("ESPHOME_IS_HASSIO", value) | ||||
|  | ||||
|     actual = helpers.is_hassio() | ||||
|  | ||||
|     assert actual == expected | ||||
|  | ||||
|  | ||||
| def test_walk_files(fixture_path): | ||||
|     path = fixture_path / "helpers" | ||||
|  | ||||
|     actual = list(helpers.walk_files(path)) | ||||
|  | ||||
|     # Ensure paths start with the root | ||||
|     assert all(p.startswith(path.as_posix()) for p in actual) | ||||
|  | ||||
|  | ||||
| class Test_write_file_if_changed: | ||||
|     def test_src_and_dst_match(self, tmp_path): | ||||
|         text = "A files are unique.\n" | ||||
|         initial = text | ||||
|         dst = tmp_path / "file-a.txt" | ||||
|         dst.write_text(initial) | ||||
|  | ||||
|         helpers.write_file_if_changed(dst, text) | ||||
|  | ||||
|         assert dst.read_text() == text | ||||
|  | ||||
|     def test_src_and_dst_do_not_match(self, tmp_path): | ||||
|         text = "A files are unique.\n" | ||||
|         initial = "B files are unique.\n" | ||||
|         dst = tmp_path / "file-a.txt" | ||||
|         dst.write_text(initial) | ||||
|  | ||||
|         helpers.write_file_if_changed(dst, text) | ||||
|  | ||||
|         assert dst.read_text() == text | ||||
|  | ||||
|     def test_dst_does_not_exist(self, tmp_path): | ||||
|         text = "A files are unique.\n" | ||||
|         dst = tmp_path / "file-a.txt" | ||||
|  | ||||
|         helpers.write_file_if_changed(dst, text) | ||||
|  | ||||
|         assert dst.read_text() == text | ||||
|  | ||||
|  | ||||
| class Test_copy_file_if_changed: | ||||
|     def test_src_and_dst_match(self, tmp_path, fixture_path): | ||||
|         src = fixture_path / "helpers" / "file-a.txt" | ||||
|         initial = fixture_path / "helpers" / "file-a.txt" | ||||
|         dst = tmp_path / "file-a.txt" | ||||
|  | ||||
|         dst.write_text(initial.read_text()) | ||||
|  | ||||
|         helpers.copy_file_if_changed(src, dst) | ||||
|  | ||||
|     def test_src_and_dst_do_not_match(self, tmp_path, fixture_path): | ||||
|         src = fixture_path / "helpers" / "file-a.txt" | ||||
|         initial = fixture_path / "helpers" / "file-c.txt" | ||||
|         dst = tmp_path / "file-a.txt" | ||||
|  | ||||
|         dst.write_text(initial.read_text()) | ||||
|  | ||||
|         helpers.copy_file_if_changed(src, dst) | ||||
|  | ||||
|         assert src.read_text() == dst.read_text() | ||||
|  | ||||
|     def test_dst_does_not_exist(self, tmp_path, fixture_path): | ||||
|         src = fixture_path / "helpers" / "file-a.txt" | ||||
|         dst = tmp_path / "file-a.txt" | ||||
|  | ||||
|         helpers.copy_file_if_changed(src, dst) | ||||
|  | ||||
|         assert dst.exists() | ||||
|         assert src.read_text() == dst.read_text() | ||||
|  | ||||
|  | ||||
| @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), | ||||
| )) | ||||
| def test_file_compare(fixture_path, file1, file2, expected): | ||||
|     path1 = fixture_path / "helpers" / file1 | ||||
|     path2 = fixture_path / "helpers" / file2 | ||||
|  | ||||
|     actual = helpers.file_compare(path1, path2) | ||||
|  | ||||
|     assert actual == expected | ||||
							
								
								
									
										326
									
								
								tests/unit_tests/test_pins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								tests/unit_tests/test_pins.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,326 @@ | ||||
| """ | ||||
| Please Note: | ||||
|  | ||||
| These tests cover the process of identifying information about pins, they do not | ||||
| check if the definition of MCUs and pins is correct. | ||||
|  | ||||
| """ | ||||
| import logging | ||||
|  | ||||
| import pytest | ||||
|  | ||||
| from esphome.config_validation import Invalid | ||||
| from esphome.core import EsphomeCore | ||||
| from esphome import pins | ||||
|  | ||||
|  | ||||
| MOCK_ESP8266_BOARD_ID = "_mock_esp8266" | ||||
| MOCK_ESP8266_PINS = {'X0': 16, 'X1': 5, 'X2': 4, 'LED': 2} | ||||
| MOCK_ESP8266_BOARD_ALIAS_ID = "_mock_esp8266_alias" | ||||
| MOCK_ESP8266_FLASH_SIZE = pins.FLASH_SIZE_2_MB | ||||
|  | ||||
| MOCK_ESP32_BOARD_ID = "_mock_esp32" | ||||
| MOCK_ESP32_PINS = {'Y0': 12, 'Y1': 8, 'Y2': 3, 'LED': 9, "A0": 8} | ||||
| MOCK_ESP32_BOARD_ALIAS_ID = "_mock_esp32_alias" | ||||
|  | ||||
| UNKNOWN_PLATFORM = "STM32" | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def mock_mcu(monkeypatch): | ||||
|     """ | ||||
|     Add a mock MCU into the lists as a stable fixture | ||||
|     """ | ||||
|     pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_PINS | ||||
|     pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] = MOCK_ESP8266_FLASH_SIZE | ||||
|     pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_BOARD_ID | ||||
|     pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] = MOCK_ESP8266_FLASH_SIZE | ||||
|     pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] = MOCK_ESP32_PINS | ||||
|     pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] = MOCK_ESP32_BOARD_ID | ||||
|     yield | ||||
|     del pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ID] | ||||
|     del pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ID] | ||||
|     del pins.ESP8266_BOARD_PINS[MOCK_ESP8266_BOARD_ALIAS_ID] | ||||
|     del pins.ESP8266_FLASH_SIZES[MOCK_ESP8266_BOARD_ALIAS_ID] | ||||
|     del pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ID] | ||||
|     del pins.ESP32_BOARD_PINS[MOCK_ESP32_BOARD_ALIAS_ID] | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def core(monkeypatch, mock_mcu): | ||||
|     core = EsphomeCore() | ||||
|     monkeypatch.setattr(pins, "CORE", core) | ||||
|     return core | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def core_esp8266(core): | ||||
|     core.esp_platform = "ESP8266" | ||||
|     core.board = MOCK_ESP8266_BOARD_ID | ||||
|     return core | ||||
|  | ||||
|  | ||||
| @pytest.fixture | ||||
| def core_esp32(core): | ||||
|     core.esp_platform = "ESP32" | ||||
|     core.board = MOCK_ESP32_BOARD_ID | ||||
|     return core | ||||
|  | ||||
|  | ||||
| class Test_lookup_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X1", 5), | ||||
|             ("MOSI", 13), | ||||
|     )) | ||||
|     def test_valid_esp8266_pin(self, core_esp8266, value, expected): | ||||
|         actual = pins._lookup_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     def test_valid_esp8266_pin_alias(self, core_esp8266): | ||||
|         core_esp8266.board = MOCK_ESP8266_BOARD_ALIAS_ID | ||||
|  | ||||
|         actual = pins._lookup_pin("X2") | ||||
|  | ||||
|         assert actual == 4 | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("Y1", 8), | ||||
|             ("A0", 8), | ||||
|             ("MOSI", 23), | ||||
|     )) | ||||
|     def test_valid_esp32_pin(self, core_esp32, value, expected): | ||||
|         actual = pins._lookup_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.xfail(reason="This may be expected") | ||||
|     def test_valid_32_pin_alias(self, core_esp32): | ||||
|         core_esp32.board = MOCK_ESP32_BOARD_ALIAS_ID | ||||
|  | ||||
|         actual = pins._lookup_pin("Y2") | ||||
|  | ||||
|         assert actual == 3 | ||||
|  | ||||
|     def test_invalid_pin(self, core_esp8266): | ||||
|         with pytest.raises(Invalid, match="Cannot resolve pin name 'X42' for board _mock_esp8266."): | ||||
|             pins._lookup_pin("X42") | ||||
|  | ||||
|     def test_unsupported_platform(self, core): | ||||
|         core.esp_platform = UNKNOWN_PLATFORM | ||||
|  | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             pins._lookup_pin("TX") | ||||
|  | ||||
|  | ||||
| class Test_translate_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             (2, 2), | ||||
|             ("3", 3), | ||||
|             ("GPIO4", 4), | ||||
|             ("TX", 1), | ||||
|             ("Y0", 12), | ||||
|     )) | ||||
|     def test_valid_values(self, core_esp32, value, expected): | ||||
|         actual = pins._translate_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value", ({}, None)) | ||||
|     def test_invalid_values(self, core_esp32, value): | ||||
|         with pytest.raises(Invalid, match="This variable only supports"): | ||||
|             pins._translate_pin(value) | ||||
|  | ||||
|  | ||||
| class Test_validate_gpio_pin: | ||||
|     def test_esp32_valid(self, core_esp32): | ||||
|         actual = pins.validate_gpio_pin("GPIO22") | ||||
|  | ||||
|         assert actual == 22 | ||||
|  | ||||
|     @pytest.mark.parametrize("value, match", ( | ||||
|             (-1, "ESP32: Invalid pin number: -1"), | ||||
|             (40, "ESP32: Invalid pin number: 40"), | ||||
|             (6, "This pin cannot be used on ESP32s and"), | ||||
|             (7, "This pin cannot be used on ESP32s and"), | ||||
|             (8, "This pin cannot be used on ESP32s and"), | ||||
|             (11, "This pin cannot be used on ESP32s and"), | ||||
|             (20, "The pin GPIO20 is not usable on ESP32s"), | ||||
|             (24, "The pin GPIO24 is not usable on ESP32s"), | ||||
|             (28, "The pin GPIO28 is not usable on ESP32s"), | ||||
|             (29, "The pin GPIO29 is not usable on ESP32s"), | ||||
|             (30, "The pin GPIO30 is not usable on ESP32s"), | ||||
|             (31, "The pin GPIO31 is not usable on ESP32s"), | ||||
|     )) | ||||
|     def test_esp32_invalid_pin(self, core_esp32, value, match): | ||||
|         with pytest.raises(Invalid, match=match): | ||||
|             pins.validate_gpio_pin(value) | ||||
|  | ||||
|     @pytest.mark.parametrize("value", (9, 10)) | ||||
|     def test_esp32_warning(self, core_esp32, caplog, value): | ||||
|         caplog.at_level(logging.WARNING) | ||||
|         pins.validate_gpio_pin(value) | ||||
|  | ||||
|         assert len(caplog.messages) == 1 | ||||
|         assert caplog.messages[0].endswith("flash interface in QUAD IO flash mode.") | ||||
|  | ||||
|     def test_esp8266_valid(self, core_esp8266): | ||||
|         actual = pins.validate_gpio_pin("GPIO12") | ||||
|  | ||||
|         assert actual == 12 | ||||
|  | ||||
|     @pytest.mark.parametrize("value, match", ( | ||||
|             (-1, "ESP8266: Invalid pin number: -1"), | ||||
|             (18, "ESP8266: Invalid pin number: 18"), | ||||
|             (6, "This pin cannot be used on ESP8266s and"), | ||||
|             (7, "This pin cannot be used on ESP8266s and"), | ||||
|             (8, "This pin cannot be used on ESP8266s and"), | ||||
|             (11, "This pin cannot be used on ESP8266s and"), | ||||
|     )) | ||||
|     def test_esp8266_invalid_pin(self, core_esp8266, value, match): | ||||
|         with pytest.raises(Invalid, match=match): | ||||
|             pins.validate_gpio_pin(value) | ||||
|  | ||||
|     @pytest.mark.parametrize("value", (9, 10)) | ||||
|     def test_esp8266_warning(self, core_esp8266, caplog, value): | ||||
|         caplog.at_level(logging.WARNING) | ||||
|         pins.validate_gpio_pin(value) | ||||
|  | ||||
|         assert len(caplog.messages) == 1 | ||||
|         assert caplog.messages[0].endswith("flash interface in QUAD IO flash mode.") | ||||
|  | ||||
|     def test_unknown_device(self, core): | ||||
|         core.esp_platform = UNKNOWN_PLATFORM | ||||
|  | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             pins.validate_gpio_pin("0") | ||||
|  | ||||
|  | ||||
| class Test_input_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X0", 16), | ||||
|     )) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.input_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("Y0", 12), | ||||
|             (17, 17), | ||||
|     )) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.input_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value", (17,)) | ||||
|     def test_invalid_esp8266_values(self, core_esp8266, value): | ||||
|         with pytest.raises(Invalid): | ||||
|             pins.input_pin(value) | ||||
|  | ||||
|     def test_unknown_platform(self, core): | ||||
|         core.esp_platform = UNKNOWN_PLATFORM | ||||
|  | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             pins.input_pin(2) | ||||
|  | ||||
|  | ||||
| class Test_input_pullup_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X0", 16), | ||||
|     )) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.input_pullup_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("Y0", 12), | ||||
|             (17, 17), | ||||
|     )) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.input_pullup_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value", (0,)) | ||||
|     def test_invalid_esp8266_values(self, core_esp8266, value): | ||||
|         with pytest.raises(Invalid): | ||||
|             pins.input_pullup_pin(value) | ||||
|  | ||||
|     def test_unknown_platform(self, core): | ||||
|         core.esp_platform = UNKNOWN_PLATFORM | ||||
|  | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             pins.input_pullup_pin(2) | ||||
|  | ||||
|  | ||||
| class Test_output_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("X0", 16), | ||||
|     )) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.output_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             ("Y0", 12), | ||||
|             (17, 17), | ||||
|     )) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.output_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value", (17,)) | ||||
|     def test_invalid_esp8266_values(self, core_esp8266, value): | ||||
|         with pytest.raises(Invalid): | ||||
|             pins.output_pin(value) | ||||
|  | ||||
|     @pytest.mark.parametrize("value", range(34, 40)) | ||||
|     def test_invalid_esp32_values(self, core_esp32, value): | ||||
|         with pytest.raises(Invalid): | ||||
|             pins.output_pin(value) | ||||
|  | ||||
|     def test_unknown_platform(self, core): | ||||
|         core.esp_platform = UNKNOWN_PLATFORM | ||||
|  | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             pins.output_pin(2) | ||||
|  | ||||
|  | ||||
| class Test_analog_pin: | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             (17, 17), | ||||
|     )) | ||||
|     def test_valid_esp8266_values(self, core_esp8266, value, expected): | ||||
|         actual = pins.analog_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value, expected", ( | ||||
|             (32, 32), | ||||
|             (39, 39), | ||||
|     )) | ||||
|     def test_valid_esp32_values(self, core_esp32, value, expected): | ||||
|         actual = pins.analog_pin(value) | ||||
|  | ||||
|         assert actual == expected | ||||
|  | ||||
|     @pytest.mark.parametrize("value", ("X0",)) | ||||
|     def test_invalid_esp8266_values(self, core_esp8266, value): | ||||
|         with pytest.raises(Invalid): | ||||
|             pins.analog_pin(value) | ||||
|  | ||||
|     @pytest.mark.parametrize("value", ("Y0",)) | ||||
|     def test_invalid_esp32_values(self, core_esp32, value): | ||||
|         with pytest.raises(Invalid): | ||||
|             pins.analog_pin(value) | ||||
|  | ||||
|     def test_unknown_platform(self, core): | ||||
|         core.esp_platform = UNKNOWN_PLATFORM | ||||
|  | ||||
|         with pytest.raises(NotImplementedError): | ||||
|             pins.analog_pin(2) | ||||
		Reference in New Issue
	
	Block a user