mirror of
https://github.com/esphome/esphome.git
synced 2025-10-30 06:33:51 +00:00
Merge branch 'dev' into api_size_limits
This commit is contained in:
9
tests/components/lm75b/common.yaml
Normal file
9
tests/components/lm75b/common.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
i2c:
|
||||
- id: i2c_lm75b
|
||||
scl: ${scl_pin}
|
||||
sda: ${sda_pin}
|
||||
|
||||
sensor:
|
||||
- platform: lm75b
|
||||
name: LM75B Temperature
|
||||
update_interval: 30s
|
||||
5
tests/components/lm75b/test.esp32-ard.yaml
Normal file
5
tests/components/lm75b/test.esp32-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO15
|
||||
sda_pin: GPIO13
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/lm75b/test.esp32-c3-ard.yaml
Normal file
5
tests/components/lm75b/test.esp32-c3-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/lm75b/test.esp32-c3-idf.yaml
Normal file
5
tests/components/lm75b/test.esp32-c3-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/lm75b/test.esp32-idf.yaml
Normal file
5
tests/components/lm75b/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO15
|
||||
sda_pin: GPIO13
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/lm75b/test.esp8266-ard.yaml
Normal file
5
tests/components/lm75b/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/lm75b/test.rp2040-ard.yaml
Normal file
5
tests/components/lm75b/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -17,5 +17,7 @@ sensor:
|
||||
temperature:
|
||||
name: QMC5883L Temperature
|
||||
range: 800uT
|
||||
data_rate: 200Hz
|
||||
oversampling: 256x
|
||||
update_interval: 15s
|
||||
drdy_pin: ${drdy_pin}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
drdy_pin: GPIO18
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
drdy_pin: GPIO6
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
drdy_pin: GPIO6
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO16
|
||||
sda_pin: GPIO17
|
||||
drdy_pin: GPIO18
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
drdy_pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
substitutions:
|
||||
scl_pin: GPIO5
|
||||
sda_pin: GPIO4
|
||||
drdy_pin: GPIO2
|
||||
|
||||
<<: !include common.yaml
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
substitutions:
|
||||
pin: GPIO2
|
||||
clock_resolution: "2000000"
|
||||
carrier_duty_percent: "25"
|
||||
carrier_frequency: "30000"
|
||||
filter_symbols: "2"
|
||||
receive_symbols: "4"
|
||||
rmt_symbols: "64"
|
||||
|
||||
@@ -44,37 +44,53 @@ def test_get_clang_tidy_version_from_requirements(
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_calculate_clang_tidy_hash() -> None:
|
||||
"""Test calculating hash from all configuration sources."""
|
||||
def test_calculate_clang_tidy_hash_with_sdkconfig(tmp_path: Path) -> None:
|
||||
"""Test calculating hash from all configuration sources including sdkconfig.defaults."""
|
||||
clang_tidy_content = b"Checks: '-*,readability-*'\n"
|
||||
requirements_version = "clang-tidy==18.1.5"
|
||||
platformio_content = b"[env:esp32]\nplatform = espressif32\n"
|
||||
sdkconfig_content = b"CONFIG_AUTOSTART_ARDUINO=y\n"
|
||||
requirements_content = "clang-tidy==18.1.5\n"
|
||||
|
||||
# Create temporary files
|
||||
(tmp_path / ".clang-tidy").write_bytes(clang_tidy_content)
|
||||
(tmp_path / "platformio.ini").write_bytes(platformio_content)
|
||||
(tmp_path / "sdkconfig.defaults").write_bytes(sdkconfig_content)
|
||||
(tmp_path / "requirements_dev.txt").write_text(requirements_content)
|
||||
|
||||
# Expected hash calculation
|
||||
expected_hasher = hashlib.sha256()
|
||||
expected_hasher.update(clang_tidy_content)
|
||||
expected_hasher.update(requirements_version.encode())
|
||||
expected_hasher.update(platformio_content)
|
||||
expected_hasher.update(sdkconfig_content)
|
||||
expected_hash = expected_hasher.hexdigest()
|
||||
|
||||
# Mock the dependencies
|
||||
with (
|
||||
patch("clang_tidy_hash.read_file_bytes") as mock_read_bytes,
|
||||
patch(
|
||||
"clang_tidy_hash.get_clang_tidy_version_from_requirements",
|
||||
return_value=requirements_version,
|
||||
),
|
||||
):
|
||||
# Set up mock to return different content based on the file being read
|
||||
def read_file_mock(path: Path) -> bytes:
|
||||
if ".clang-tidy" in str(path):
|
||||
return clang_tidy_content
|
||||
if "platformio.ini" in str(path):
|
||||
return platformio_content
|
||||
return b""
|
||||
result = clang_tidy_hash.calculate_clang_tidy_hash(repo_root=tmp_path)
|
||||
|
||||
mock_read_bytes.side_effect = read_file_mock
|
||||
result = clang_tidy_hash.calculate_clang_tidy_hash()
|
||||
assert result == expected_hash
|
||||
|
||||
|
||||
def test_calculate_clang_tidy_hash_without_sdkconfig(tmp_path: Path) -> None:
|
||||
"""Test calculating hash without sdkconfig.defaults file."""
|
||||
clang_tidy_content = b"Checks: '-*,readability-*'\n"
|
||||
requirements_version = "clang-tidy==18.1.5"
|
||||
platformio_content = b"[env:esp32]\nplatform = espressif32\n"
|
||||
requirements_content = "clang-tidy==18.1.5\n"
|
||||
|
||||
# Create temporary files (without sdkconfig.defaults)
|
||||
(tmp_path / ".clang-tidy").write_bytes(clang_tidy_content)
|
||||
(tmp_path / "platformio.ini").write_bytes(platformio_content)
|
||||
(tmp_path / "requirements_dev.txt").write_text(requirements_content)
|
||||
|
||||
# Expected hash calculation (no sdkconfig)
|
||||
expected_hasher = hashlib.sha256()
|
||||
expected_hasher.update(clang_tidy_content)
|
||||
expected_hasher.update(requirements_version.encode())
|
||||
expected_hasher.update(platformio_content)
|
||||
expected_hash = expected_hasher.hexdigest()
|
||||
|
||||
result = clang_tidy_hash.calculate_clang_tidy_hash(repo_root=tmp_path)
|
||||
|
||||
assert result == expected_hash
|
||||
|
||||
@@ -85,67 +101,63 @@ def test_read_stored_hash_exists(tmp_path: Path) -> None:
|
||||
hash_file = tmp_path / ".clang-tidy.hash"
|
||||
hash_file.write_text(f"{stored_hash}\n")
|
||||
|
||||
with (
|
||||
patch("clang_tidy_hash.Path") as mock_path_class,
|
||||
patch("clang_tidy_hash.read_file_lines", return_value=[f"{stored_hash}\n"]),
|
||||
):
|
||||
# Mock the path calculation and exists check
|
||||
mock_hash_file = Mock()
|
||||
mock_hash_file.exists.return_value = True
|
||||
mock_path_class.return_value.parent.parent.__truediv__.return_value = (
|
||||
mock_hash_file
|
||||
)
|
||||
|
||||
result = clang_tidy_hash.read_stored_hash()
|
||||
result = clang_tidy_hash.read_stored_hash(repo_root=tmp_path)
|
||||
|
||||
assert result == stored_hash
|
||||
|
||||
|
||||
def test_read_stored_hash_not_exists() -> None:
|
||||
def test_read_stored_hash_not_exists(tmp_path: Path) -> None:
|
||||
"""Test reading hash when file doesn't exist."""
|
||||
with patch("clang_tidy_hash.Path") as mock_path_class:
|
||||
# Mock the path calculation and exists check
|
||||
mock_hash_file = Mock()
|
||||
mock_hash_file.exists.return_value = False
|
||||
mock_path_class.return_value.parent.parent.__truediv__.return_value = (
|
||||
mock_hash_file
|
||||
)
|
||||
|
||||
result = clang_tidy_hash.read_stored_hash()
|
||||
result = clang_tidy_hash.read_stored_hash(repo_root=tmp_path)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_write_hash() -> None:
|
||||
def test_write_hash(tmp_path: Path) -> None:
|
||||
"""Test writing hash to file."""
|
||||
hash_value = "abc123def456"
|
||||
hash_file = tmp_path / ".clang-tidy.hash"
|
||||
|
||||
with patch("clang_tidy_hash.write_file_content") as mock_write:
|
||||
clang_tidy_hash.write_hash(hash_value)
|
||||
clang_tidy_hash.write_hash(hash_value, repo_root=tmp_path)
|
||||
|
||||
# Verify write_file_content was called with correct parameters
|
||||
mock_write.assert_called_once()
|
||||
args = mock_write.call_args[0]
|
||||
assert str(args[0]).endswith(".clang-tidy.hash")
|
||||
assert args[1] == hash_value.strip() + "\n"
|
||||
assert hash_file.exists()
|
||||
assert hash_file.read_text() == hash_value.strip() + "\n"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("args", "current_hash", "stored_hash", "expected_exit"),
|
||||
("args", "current_hash", "stored_hash", "hash_file_in_changed", "expected_exit"),
|
||||
[
|
||||
(["--check"], "abc123", "abc123", 1), # Hashes match, no scan needed
|
||||
(["--check"], "abc123", "def456", 0), # Hashes differ, scan needed
|
||||
(["--check"], "abc123", None, 0), # No stored hash, scan needed
|
||||
(["--check"], "abc123", "abc123", False, 1), # Hashes match, no scan needed
|
||||
(["--check"], "abc123", "def456", False, 0), # Hashes differ, scan needed
|
||||
(["--check"], "abc123", None, False, 0), # No stored hash, scan needed
|
||||
(
|
||||
["--check"],
|
||||
"abc123",
|
||||
"abc123",
|
||||
True,
|
||||
0,
|
||||
), # Hash file updated in PR, scan needed
|
||||
],
|
||||
)
|
||||
def test_main_check_mode(
|
||||
args: list[str], current_hash: str, stored_hash: str | None, expected_exit: int
|
||||
args: list[str],
|
||||
current_hash: str,
|
||||
stored_hash: str | None,
|
||||
hash_file_in_changed: bool,
|
||||
expected_exit: int,
|
||||
) -> None:
|
||||
"""Test main function in check mode."""
|
||||
changed = [".clang-tidy.hash"] if hash_file_in_changed else []
|
||||
|
||||
# Create a mock module that can be imported
|
||||
mock_helpers = Mock()
|
||||
mock_helpers.changed_files = Mock(return_value=changed)
|
||||
|
||||
with (
|
||||
patch("sys.argv", ["clang_tidy_hash.py"] + args),
|
||||
patch("clang_tidy_hash.calculate_clang_tidy_hash", return_value=current_hash),
|
||||
patch("clang_tidy_hash.read_stored_hash", return_value=stored_hash),
|
||||
patch.dict("sys.modules", {"helpers": mock_helpers}),
|
||||
pytest.raises(SystemExit) as exc_info,
|
||||
):
|
||||
clang_tidy_hash.main()
|
||||
|
||||
@@ -101,3 +101,10 @@ def mock_get_idedata() -> Generator[Mock, None, None]:
|
||||
"""Mock get_idedata for platformio_api."""
|
||||
with patch("esphome.platformio_api.get_idedata") as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_component() -> Generator[Mock, None, None]:
|
||||
"""Mock get_component for config module."""
|
||||
with patch("esphome.config.get_component") as mock:
|
||||
yield mock
|
||||
|
||||
10
tests/unit_tests/fixtures/auto_load_dynamic.yaml
Normal file
10
tests/unit_tests/fixtures/auto_load_dynamic.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
esphome:
|
||||
name: test-device
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
# Test component with dynamic AUTO_LOAD
|
||||
test_component:
|
||||
enable_logger: true
|
||||
enable_api: false
|
||||
8
tests/unit_tests/fixtures/auto_load_static.yaml
Normal file
8
tests/unit_tests/fixtures/auto_load_static.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
esphome:
|
||||
name: test-device
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
# Test component with static AUTO_LOAD
|
||||
test_component:
|
||||
131
tests/unit_tests/test_config_auto_load.py
Normal file
131
tests/unit_tests/test_config_auto_load.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""Tests for AUTO_LOAD functionality including dynamic AUTO_LOAD."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config, config_validation as cv, yaml_util
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fixtures_dir() -> Path:
|
||||
"""Get the fixtures directory."""
|
||||
return Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def default_component() -> Mock:
|
||||
"""Create a default mock component for unmocked components."""
|
||||
return Mock(
|
||||
auto_load=[],
|
||||
is_platform_component=False,
|
||||
is_platform=False,
|
||||
multi_conf=False,
|
||||
multi_conf_no_default=False,
|
||||
dependencies=[],
|
||||
conflicts_with=[],
|
||||
config_schema=cv.Schema({}, extra=cv.ALLOW_EXTRA),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def static_auto_load_component() -> Mock:
|
||||
"""Create a mock component with static AUTO_LOAD."""
|
||||
return Mock(
|
||||
auto_load=["logger"],
|
||||
is_platform_component=False,
|
||||
is_platform=False,
|
||||
multi_conf=False,
|
||||
multi_conf_no_default=False,
|
||||
dependencies=[],
|
||||
conflicts_with=[],
|
||||
config_schema=cv.Schema({}, extra=cv.ALLOW_EXTRA),
|
||||
)
|
||||
|
||||
|
||||
def test_static_auto_load_adds_components(
|
||||
mock_get_component: Mock,
|
||||
fixtures_dir: Path,
|
||||
static_auto_load_component: Mock,
|
||||
default_component: Mock,
|
||||
) -> None:
|
||||
"""Test that static AUTO_LOAD triggers loading of specified components."""
|
||||
CORE.config_path = fixtures_dir / "auto_load_static.yaml"
|
||||
|
||||
config_file = fixtures_dir / "auto_load_static.yaml"
|
||||
raw_config = yaml_util.load_yaml(config_file)
|
||||
|
||||
component_mocks = {"test_component": static_auto_load_component}
|
||||
mock_get_component.side_effect = lambda name: component_mocks.get(
|
||||
name, default_component
|
||||
)
|
||||
|
||||
result = config.validate_config(raw_config, {})
|
||||
|
||||
# Check for validation errors
|
||||
assert not result.errors, f"Validation errors: {result.errors}"
|
||||
|
||||
# Logger should have been auto-loaded by test_component
|
||||
assert "logger" in result
|
||||
assert "test_component" in result
|
||||
|
||||
|
||||
def test_dynamic_auto_load_with_config_param(
|
||||
mock_get_component: Mock,
|
||||
fixtures_dir: Path,
|
||||
default_component: Mock,
|
||||
) -> None:
|
||||
"""Test that dynamic AUTO_LOAD evaluates based on configuration."""
|
||||
CORE.config_path = fixtures_dir / "auto_load_dynamic.yaml"
|
||||
|
||||
config_file = fixtures_dir / "auto_load_dynamic.yaml"
|
||||
raw_config = yaml_util.load_yaml(config_file)
|
||||
|
||||
# Track if auto_load was called with config
|
||||
auto_load_calls = []
|
||||
|
||||
def dynamic_auto_load(conf: dict[str, Any]) -> list[str]:
|
||||
"""Dynamically load components based on config."""
|
||||
auto_load_calls.append(conf)
|
||||
component_map = {
|
||||
"enable_logger": "logger",
|
||||
"enable_api": "api",
|
||||
}
|
||||
return [comp for key, comp in component_map.items() if conf.get(key)]
|
||||
|
||||
dynamic_component = Mock(
|
||||
auto_load=dynamic_auto_load,
|
||||
is_platform_component=False,
|
||||
is_platform=False,
|
||||
multi_conf=False,
|
||||
multi_conf_no_default=False,
|
||||
dependencies=[],
|
||||
conflicts_with=[],
|
||||
config_schema=cv.Schema({}, extra=cv.ALLOW_EXTRA),
|
||||
)
|
||||
|
||||
component_mocks = {"test_component": dynamic_component}
|
||||
mock_get_component.side_effect = lambda name: component_mocks.get(
|
||||
name, default_component
|
||||
)
|
||||
|
||||
result = config.validate_config(raw_config, {})
|
||||
|
||||
# Check for validation errors
|
||||
assert not result.errors, f"Validation errors: {result.errors}"
|
||||
|
||||
# Verify auto_load was called with the validated config
|
||||
assert len(auto_load_calls) == 1, "auto_load should be called exactly once"
|
||||
assert auto_load_calls[0].get("enable_logger") is True
|
||||
assert auto_load_calls[0].get("enable_api") is False
|
||||
|
||||
# Only logger should be auto-loaded (enable_logger=true in YAML)
|
||||
assert "logger" in result, (
|
||||
f"Logger not found in result. Result keys: {list(result.keys())}"
|
||||
)
|
||||
# API should NOT be auto-loaded (enable_api=false in YAML)
|
||||
assert "api" not in result
|
||||
assert "test_component" in result
|
||||
@@ -10,13 +10,6 @@ from esphome import config, yaml_util
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_component() -> Generator[Mock, None, None]:
|
||||
"""Fixture for mocking get_component."""
|
||||
with patch("esphome.config.get_component") as mock_get_component:
|
||||
yield mock_get_component
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_platform() -> Generator[Mock, None, None]:
|
||||
"""Fixture for mocking get_platform."""
|
||||
|
||||
Reference in New Issue
Block a user