mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 07:58:09 +01:00
Unittests for esphome python code (#931)
This commit is contained in:
parent
714d28a61a
commit
c632b0e1d4
16 changed files with 1212 additions and 0 deletions
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 f'{self.total_days}d'
|
||||||
return '0s'
|
return '0s'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"TimePeriod<{self.total_microseconds}>"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_microseconds(self):
|
def total_microseconds(self):
|
||||||
return self.total_milliseconds * 1000 + (self.microseconds or 0)
|
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
|
flake8==3.7.9
|
||||||
pillow
|
pillow
|
||||||
pexpect
|
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/ci-custom.py
|
||||||
script/lint-python
|
script/lint-python
|
||||||
script/lint-cpp
|
script/lint-cpp
|
||||||
|
script/unit_test
|
||||||
script/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)
|
Loading…
Reference in a new issue