mirror of
https://github.com/esphome/esphome.git
synced 2025-09-01 10:52:19 +01:00
Tests for CPP Code generation and some Python3 improvements (#961)
* Basic pytest configuration * Added unit_test script that triggers pytest * Changed "fixtures" to fixture_path This is consistent with pytest's tmp_path * Initial unit tests for esphome.helpers * Disabled coverage reporting for esphome/components. Focus initial unittest efforts on the core code. * Migrated some ip_address to hypothesis * Added a hypothesis MAC address strategy * Initial tests for core * Added hypothesis to requirements * Added tests for core classes TestTimePeriod Lambda ID DocumentLocation DocumentRange Define Library * Updated test config so package root is discovered * Setup fixtures and inital tests for pins * Added tests for validate GPIO * Added tests for pin type * Added initial config_validation tests * Added more tests for config_validation * Added comparison unit tests * Added repr to core.TimePeriod. Simplified identifying faults in tests * Fixed inverted gt/lt tests * Some tests for Espcore * Updated syntax for Python3 * Removed usage of kwarg that isn't required * Started writing test cases * Started writing test cases for cpp_generator * Additional docs and more Python3 releated improvements * More test cases for cpp_generator. * Fixed linter errors * Add codegen tests to ensure file API remains stable * Add test cases for cpp_helpers
This commit is contained in:
26
tests/unit_tests/test_codegen.py
Normal file
26
tests/unit_tests/test_codegen.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import pytest
|
||||
|
||||
from esphome import codegen as cg
|
||||
|
||||
|
||||
# Test interface remains the same.
|
||||
@pytest.mark.parametrize("attr", (
|
||||
# from cpp_generator
|
||||
"Expression", "RawExpression", "RawStatement", "TemplateArguments",
|
||||
"StructInitializer", "ArrayInitializer", "safe_exp", "Statement", "LineComment",
|
||||
"progmem_array", "statement", "variable", "Pvariable", "new_Pvariable",
|
||||
"add", "add_global", "add_library", "add_build_flag", "add_define",
|
||||
"get_variable", "get_variable_with_full_id", "process_lambda", "is_template", "templatable", "MockObj",
|
||||
"MockObjClass",
|
||||
# from cpp_helpers
|
||||
"gpio_pin_expression", "register_component", "build_registry_entry",
|
||||
"build_registry_list", "extract_registry_entry_config", "register_parented",
|
||||
"global_ns", "void", "nullptr", "float_", "double", "bool_", "int_", "std_ns", "std_string",
|
||||
"std_vector", "uint8", "uint16", "uint32", "int32", "const_char_ptr", "NAN",
|
||||
"esphome_ns", "App", "Nameable", "Component", "ComponentPtr",
|
||||
# from cpp_types
|
||||
"PollingComponent", "Application", "optional", "arduino_json_ns", "JsonObject",
|
||||
"JsonObjectRef", "JsonObjectConstRef", "Controller", "GPIOPin"
|
||||
))
|
||||
def test_exists(attr):
|
||||
assert hasattr(cg, attr)
|
@@ -459,13 +459,13 @@ class TestEsphomeCore:
|
||||
target.config_path = "foo/config"
|
||||
return target
|
||||
|
||||
@pytest.mark.xfail(reason="raw_config and config differ, should they?")
|
||||
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):
|
||||
|
293
tests/unit_tests/test_cpp_generator.py
Normal file
293
tests/unit_tests/test_cpp_generator.py
Normal file
@@ -0,0 +1,293 @@
|
||||
from typing import Iterator
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import cpp_generator as cg
|
||||
from esphome import cpp_types as ct
|
||||
|
||||
|
||||
class TestExpressions:
|
||||
@pytest.mark.parametrize("target, expected", (
|
||||
(cg.RawExpression("foo && bar"), "foo && bar"),
|
||||
|
||||
(cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'),
|
||||
(cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), 'float *foo = 1'),
|
||||
(cg.AssignmentExpression(ct.float_, "", "foo", 1, None), 'float foo = 1'),
|
||||
|
||||
(cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"),
|
||||
(cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"),
|
||||
|
||||
(cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"),
|
||||
))
|
||||
def test_str__simple(self, target: cg.Expression, expected: str):
|
||||
actual = str(target)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
class TestExpressionList:
|
||||
SAMPLE_ARGS = (1, "2", True, None, None)
|
||||
|
||||
def test_str(self):
|
||||
target = cg.ExpressionList(*self.SAMPLE_ARGS)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == '1, "2", true'
|
||||
|
||||
def test_iter(self):
|
||||
target = cg.ExpressionList(*self.SAMPLE_ARGS)
|
||||
|
||||
actual = iter(target)
|
||||
|
||||
assert isinstance(actual, Iterator)
|
||||
assert len(tuple(actual)) == 3
|
||||
|
||||
|
||||
class TestTemplateArguments:
|
||||
SAMPLE_ARGS = (int, 1, "2", True, None, None)
|
||||
|
||||
def test_str(self):
|
||||
target = cg.TemplateArguments(*self.SAMPLE_ARGS)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == '<int32_t, 1, "2", true>'
|
||||
|
||||
def test_iter(self):
|
||||
target = cg.TemplateArguments(*self.SAMPLE_ARGS)
|
||||
|
||||
actual = iter(target)
|
||||
|
||||
assert isinstance(actual, Iterator)
|
||||
assert len(tuple(actual)) == 4
|
||||
|
||||
|
||||
class TestCallExpression:
|
||||
def test_str__no_template_args(self):
|
||||
target = cg.CallExpression(
|
||||
cg.RawExpression("my_function"),
|
||||
1, "2", False
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == 'my_function(1, "2", false)'
|
||||
|
||||
def test_str__with_template_args(self):
|
||||
target = cg.CallExpression(
|
||||
cg.RawExpression("my_function"),
|
||||
cg.TemplateArguments(int, float),
|
||||
1, "2", False
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == 'my_function<int32_t, float>(1, "2", false)'
|
||||
|
||||
|
||||
class TestStructInitializer:
|
||||
def test_str(self):
|
||||
target = cg.StructInitializer(
|
||||
cg.MockObjClass("foo::MyStruct", parents=()),
|
||||
("state", "on"),
|
||||
("min_length", 1),
|
||||
("max_length", 5),
|
||||
("foo", None),
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == 'foo::MyStruct{\n' \
|
||||
' .state = "on",\n' \
|
||||
' .min_length = 1,\n' \
|
||||
' .max_length = 5,\n' \
|
||||
'}'
|
||||
|
||||
|
||||
class TestArrayInitializer:
|
||||
def test_str__empty(self):
|
||||
target = cg.ArrayInitializer(
|
||||
None, None
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == "{}"
|
||||
|
||||
def test_str__not_multiline(self):
|
||||
target = cg.ArrayInitializer(
|
||||
1, 2, 3, 4
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == "{1, 2, 3, 4}"
|
||||
|
||||
def test_str__multiline(self):
|
||||
target = cg.ArrayInitializer(
|
||||
1, 2, 3, 4, multiline=True
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == "{\n 1,\n 2,\n 3,\n 4,\n}"
|
||||
|
||||
|
||||
class TestParameterListExpression:
|
||||
def test_str(self):
|
||||
target = cg.ParameterListExpression(
|
||||
cg.ParameterExpression(int, "foo"),
|
||||
(float, "bar"),
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == "int32_t foo, float bar"
|
||||
|
||||
|
||||
class TestLambdaExpression:
|
||||
def test_str__no_return(self):
|
||||
target = cg.LambdaExpression(
|
||||
(
|
||||
"if ((foo == 5) && (bar < 10))) {\n",
|
||||
"}",
|
||||
),
|
||||
((int, "foo"), (float, "bar")),
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == (
|
||||
"[=](int32_t foo, float bar) {\n"
|
||||
" if ((foo == 5) && (bar < 10))) {\n"
|
||||
" }\n"
|
||||
"}"
|
||||
)
|
||||
|
||||
def test_str__with_return(self):
|
||||
target = cg.LambdaExpression(
|
||||
("return (foo == 5) && (bar < 10));", ),
|
||||
cg.ParameterListExpression((int, "foo"), (float, "bar")),
|
||||
"=",
|
||||
bool,
|
||||
)
|
||||
|
||||
actual = str(target)
|
||||
|
||||
assert actual == (
|
||||
"[=](int32_t foo, float bar) -> bool {\n"
|
||||
" return (foo == 5) && (bar < 10));\n"
|
||||
"}"
|
||||
)
|
||||
|
||||
|
||||
class TestLiterals:
|
||||
@pytest.mark.parametrize("target, expected", (
|
||||
(cg.StringLiteral("foo"), '"foo"'),
|
||||
|
||||
(cg.IntLiteral(0), "0"),
|
||||
(cg.IntLiteral(42), "42"),
|
||||
(cg.IntLiteral(4304967295), "4304967295ULL"),
|
||||
(cg.IntLiteral(2150483647), "2150483647UL"),
|
||||
(cg.IntLiteral(-2150083647), "-2150083647LL"),
|
||||
|
||||
(cg.BoolLiteral(True), "true"),
|
||||
(cg.BoolLiteral(False), "false"),
|
||||
|
||||
(cg.HexIntLiteral(0), "0x00"),
|
||||
(cg.HexIntLiteral(42), "0x2A"),
|
||||
(cg.HexIntLiteral(682), "0x2AA"),
|
||||
|
||||
(cg.FloatLiteral(0.0), "0.0f"),
|
||||
(cg.FloatLiteral(4.2), "4.2f"),
|
||||
(cg.FloatLiteral(1.23456789), "1.23456789f"),
|
||||
(cg.FloatLiteral(math.nan), "NAN"),
|
||||
))
|
||||
def test_str__simple(self, target: cg.Literal, expected: str):
|
||||
actual = str(target)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
FAKE_ENUM_VALUE = cg.EnumValue()
|
||||
FAKE_ENUM_VALUE.enum_value = "foo"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj, expected_type", (
|
||||
(cg.RawExpression("foo"), cg.RawExpression),
|
||||
(FAKE_ENUM_VALUE, cg.StringLiteral),
|
||||
(True, cg.BoolLiteral),
|
||||
("foo", cg.StringLiteral),
|
||||
(cg.HexInt(42), cg.HexIntLiteral),
|
||||
(42, cg.IntLiteral),
|
||||
(42.1, cg.FloatLiteral),
|
||||
(cg.TimePeriodMicroseconds(microseconds=42), cg.IntLiteral),
|
||||
(cg.TimePeriodMilliseconds(milliseconds=42), cg.IntLiteral),
|
||||
(cg.TimePeriodSeconds(seconds=42), cg.IntLiteral),
|
||||
(cg.TimePeriodMinutes(minutes=42), cg.IntLiteral),
|
||||
((1, 2, 3), cg.ArrayInitializer),
|
||||
([1, 2, 3], cg.ArrayInitializer),
|
||||
))
|
||||
def test_safe_exp__allowed_values(obj, expected_type):
|
||||
actual = cg.safe_exp(obj)
|
||||
|
||||
assert isinstance(actual, expected_type)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj, expected_type", (
|
||||
(bool, ct.bool_),
|
||||
(int, ct.int32),
|
||||
(float, ct.float_),
|
||||
))
|
||||
def test_safe_exp__allowed_types(obj, expected_type):
|
||||
actual = cg.safe_exp(obj)
|
||||
|
||||
assert actual is expected_type
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj, expected_error", (
|
||||
(cg.ID("foo"), "Object foo is an ID."),
|
||||
((x for x in "foo"), r"Object <.*> is a coroutine."),
|
||||
(None, "Object is not an expression"),
|
||||
))
|
||||
def test_safe_exp__invalid_values(obj, expected_error):
|
||||
with pytest.raises(ValueError, match=expected_error):
|
||||
cg.safe_exp(obj)
|
||||
|
||||
|
||||
class TestStatements:
|
||||
@pytest.mark.parametrize("target, expected", (
|
||||
(cg.RawStatement("foo && bar"), "foo && bar"),
|
||||
|
||||
(cg.ExpressionStatement("foo"), '"foo";'),
|
||||
(cg.ExpressionStatement(42), '42;'),
|
||||
|
||||
(cg.LineComment("The point of foo is..."), "// The point of foo is..."),
|
||||
(cg.LineComment("Help help\nI'm being repressed"), "// Help help\n// I'm being repressed"),
|
||||
|
||||
(
|
||||
cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None),
|
||||
'static const uint16_t foo[] PROGMEM = "bar"'
|
||||
)
|
||||
))
|
||||
def test_str__simple(self, target: cg.Statement, expected: str):
|
||||
actual = str(target)
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
# TODO: This method has side effects in CORE
|
||||
# def test_progmem_array():
|
||||
# pass
|
||||
|
||||
|
||||
class TestMockObj:
|
||||
def test_getattr(self):
|
||||
target = cg.MockObj("foo")
|
||||
actual = target.eek
|
||||
assert isinstance(actual, cg.MockObj)
|
||||
assert actual.base == "foo.eek"
|
||||
assert actual.op == "."
|
85
tests/unit_tests/test_cpp_helpers.py
Normal file
85
tests/unit_tests/test_cpp_helpers.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import pytest
|
||||
from mock import Mock
|
||||
|
||||
from esphome import cpp_helpers as ch
|
||||
from esphome import const
|
||||
from esphome.cpp_generator import MockObj
|
||||
|
||||
|
||||
def test_gpio_pin_expression__conf_is_none(monkeypatch):
|
||||
target = ch.gpio_pin_expression(None)
|
||||
|
||||
actual = next(target)
|
||||
|
||||
assert actual is None
|
||||
|
||||
|
||||
def test_gpio_pin_expression__new_pin(monkeypatch):
|
||||
target = ch.gpio_pin_expression({
|
||||
const.CONF_NUMBER: 42,
|
||||
const.CONF_MODE: "input",
|
||||
const.CONF_INVERTED: False
|
||||
})
|
||||
|
||||
actual = next(target)
|
||||
|
||||
assert isinstance(actual, MockObj)
|
||||
|
||||
|
||||
def test_register_component(monkeypatch):
|
||||
var = Mock(base="foo.bar")
|
||||
|
||||
app_mock = Mock(register_component=Mock(return_value=var))
|
||||
monkeypatch.setattr(ch, "App", app_mock)
|
||||
|
||||
core_mock = Mock(component_ids=["foo.bar"])
|
||||
monkeypatch.setattr(ch, "CORE", core_mock)
|
||||
|
||||
add_mock = Mock()
|
||||
monkeypatch.setattr(ch, "add", add_mock)
|
||||
|
||||
target = ch.register_component(var, {})
|
||||
|
||||
actual = next(target)
|
||||
|
||||
assert actual is var
|
||||
add_mock.assert_called_once()
|
||||
app_mock.register_component.assert_called_with(var)
|
||||
assert core_mock.component_ids == []
|
||||
|
||||
|
||||
def test_register_component__no_component_id(monkeypatch):
|
||||
var = Mock(base="foo.eek")
|
||||
|
||||
core_mock = Mock(component_ids=["foo.bar"])
|
||||
monkeypatch.setattr(ch, "CORE", core_mock)
|
||||
|
||||
with pytest.raises(ValueError, match="Component ID foo.eek was not declared to"):
|
||||
target = ch.register_component(var, {})
|
||||
next(target)
|
||||
|
||||
|
||||
def test_register_component__with_setup_priority(monkeypatch):
|
||||
var = Mock(base="foo.bar")
|
||||
|
||||
app_mock = Mock(register_component=Mock(return_value=var))
|
||||
monkeypatch.setattr(ch, "App", app_mock)
|
||||
|
||||
core_mock = Mock(component_ids=["foo.bar"])
|
||||
monkeypatch.setattr(ch, "CORE", core_mock)
|
||||
|
||||
add_mock = Mock()
|
||||
monkeypatch.setattr(ch, "add", add_mock)
|
||||
|
||||
target = ch.register_component(var, {
|
||||
const.CONF_SETUP_PRIORITY: "123",
|
||||
const.CONF_UPDATE_INTERVAL: "456",
|
||||
})
|
||||
|
||||
actual = next(target)
|
||||
|
||||
assert actual is var
|
||||
add_mock.assert_called()
|
||||
assert add_mock.call_count == 3
|
||||
app_mock.register_component.assert_called_with(var)
|
||||
assert core_mock.component_ids == []
|
Reference in New Issue
Block a user