Skip to content

Commit c632b0e

Browse files
authored
Unittests for esphome python code (esphome#931)
1 parent 714d28a commit c632b0e

16 files changed

+1212
-0
lines changed

.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
omit = esphome/components/*

esphome/core.py

+3
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ def __str__(self):
143143
return f'{self.total_days}d'
144144
return '0s'
145145

146+
def __repr__(self):
147+
return f"TimePeriod<{self.total_microseconds}>"
148+
146149
@property
147150
def total_microseconds(self):
148151
return self.total_milliseconds * 1000 + (self.microseconds or 0)

pytest.ini

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
addopts =
3+
--cov=esphome
4+
--cov-branch

requirements_test.txt

+6
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ pylint==2.4.4 ; python_version>"3"
1616
flake8==3.7.9
1717
pillow
1818
pexpect
19+
20+
# Unit tests
21+
pytest==5.3.2
22+
pytest-cov==2.8.1
23+
pytest-mock==1.13.0
24+
hypothesis==4.57.0

script/fulltest

+1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ set -x
99
script/ci-custom.py
1010
script/lint-python
1111
script/lint-cpp
12+
script/unit_test
1213
script/test

script/unit_test

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
cd "$(dirname "$0")/.."
6+
7+
set -x
8+
9+
pytest tests/unit_tests

tests/unit_tests/conftest.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""
2+
ESPHome Unittests
3+
~~~~~~~~~~~~~~~~~
4+
5+
Configuration file for unit tests.
6+
7+
If adding unit tests ensure that they are fast. Slower integration tests should
8+
not be part of a unit test suite.
9+
10+
"""
11+
import sys
12+
import pytest
13+
14+
from pathlib import Path
15+
16+
17+
here = Path(__file__).parent
18+
19+
# Configure location of package root
20+
package_root = here.parent.parent
21+
sys.path.insert(0, package_root.as_posix())
22+
23+
24+
@pytest.fixture
25+
def fixture_path() -> Path:
26+
"""
27+
Location of all fixture files.
28+
"""
29+
return here / "fixtures"
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A files are unique.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
All b files match.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
All b files match.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
C files are unique.

tests/unit_tests/strategies.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Text
2+
3+
import hypothesis.strategies._internal.core as st
4+
from hypothesis.strategies._internal.strategies import SearchStrategy
5+
6+
7+
@st.defines_strategy_with_reusable_values
8+
def mac_addr_strings():
9+
# type: () -> SearchStrategy[Text]
10+
"""A strategy for MAC address strings.
11+
12+
This consists of six strings representing integers [0..255],
13+
without zero-padding, joined by dots.
14+
"""
15+
return st.builds("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}".format, *(6 * [st.integers(0, 255)]))
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import pytest
2+
import string
3+
4+
from hypothesis import given, example
5+
from hypothesis.strategies import one_of, text, integers, booleans, builds
6+
7+
from esphome import config_validation
8+
from esphome.config_validation import Invalid
9+
from esphome.core import Lambda, HexInt
10+
11+
12+
def test_check_not_tamplatable__invalid():
13+
with pytest.raises(Invalid, match="This option is not templatable!"):
14+
config_validation.check_not_templatable(Lambda(""))
15+
16+
17+
@given(one_of(
18+
booleans(),
19+
integers(),
20+
text(alphabet=string.ascii_letters + string.digits)),
21+
)
22+
def test_alphanumeric__valid(value):
23+
actual = config_validation.alphanumeric(value)
24+
25+
assert actual == str(value)
26+
27+
28+
@given(value=text(alphabet=string.ascii_lowercase + string.digits + "_"))
29+
def test_valid_name__valid(value):
30+
actual = config_validation.valid_name(value)
31+
32+
assert actual == value
33+
34+
35+
@pytest.mark.parametrize("value", (
36+
"foo bar", "FooBar", "foo::bar"
37+
))
38+
def test_valid_name__invalid(value):
39+
with pytest.raises(Invalid):
40+
config_validation.valid_name(value)
41+
42+
43+
@given(one_of(integers(), text()))
44+
def test_string__valid(value):
45+
actual = config_validation.string(value)
46+
47+
assert actual == str(value)
48+
49+
50+
@pytest.mark.parametrize("value", (
51+
{}, [], True, False, None
52+
))
53+
def test_string__invalid(value):
54+
with pytest.raises(Invalid):
55+
config_validation.string(value)
56+
57+
58+
@given(text())
59+
def test_strict_string__valid(value):
60+
actual = config_validation.string_strict(value)
61+
62+
assert actual == value
63+
64+
65+
@pytest.mark.parametrize("value", (None, 123))
66+
def test_string_string__invalid(value):
67+
with pytest.raises(Invalid, match="Must be string, got"):
68+
config_validation.string_strict(value)
69+
70+
71+
@given(builds(lambda v: "mdi:" + v, text()))
72+
@example("")
73+
def test_icon__valid(value):
74+
actual = config_validation.icon(value)
75+
76+
assert actual == value
77+
78+
79+
def test_icon__invalid():
80+
with pytest.raises(Invalid, match="Icons should start with prefix"):
81+
config_validation.icon("foo")
82+
83+
84+
@pytest.mark.parametrize("value", (
85+
"True", "YES", "on", "enAblE", True
86+
))
87+
def test_boolean__valid_true(value):
88+
assert config_validation.boolean(value) is True
89+
90+
91+
@pytest.mark.parametrize("value", (
92+
"False", "NO", "off", "disAblE", False
93+
))
94+
def test_boolean__valid_false(value):
95+
assert config_validation.boolean(value) is False
96+
97+
98+
@pytest.mark.parametrize("value", (
99+
None, 1, 0, "foo"
100+
))
101+
def test_boolean__invalid(value):
102+
with pytest.raises(Invalid, match="Expected boolean value"):
103+
config_validation.boolean(value)
104+
105+
106+
# TODO: ensure_list
107+
@given(integers())
108+
def hex_int__valid(value):
109+
actual = config_validation.hex_int(value)
110+
111+
assert isinstance(actual, HexInt)
112+
assert actual == value
113+

0 commit comments

Comments
 (0)