mirror of
https://github.com/esphome/esphome.git
synced 2026-05-27 11:56:11 +08:00
[cli] Add config-hash command (#15548)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
@@ -1415,6 +1415,15 @@ def command_config(args: ArgsProtocol, config: ConfigType) -> int | None:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def command_config_hash(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||||
|
# generating code might modify config, so it must be done in order to generate
|
||||||
|
# a hash that will match what was generated when compiling and then running
|
||||||
|
# on the device
|
||||||
|
generate_cpp_contents(config)
|
||||||
|
safe_print(f"0x{CORE.config_hash:08x}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def command_vscode(args: ArgsProtocol) -> int | None:
|
def command_vscode(args: ArgsProtocol) -> int | None:
|
||||||
from esphome import vscode
|
from esphome import vscode
|
||||||
|
|
||||||
@@ -1950,6 +1959,7 @@ PRE_CONFIG_ACTIONS = {
|
|||||||
|
|
||||||
POST_CONFIG_ACTIONS = {
|
POST_CONFIG_ACTIONS = {
|
||||||
"config": command_config,
|
"config": command_config,
|
||||||
|
"config-hash": command_config_hash,
|
||||||
"compile": command_compile,
|
"compile": command_compile,
|
||||||
"upload": command_upload,
|
"upload": command_upload,
|
||||||
"logs": command_logs,
|
"logs": command_logs,
|
||||||
@@ -2063,6 +2073,13 @@ def parse_args(argv):
|
|||||||
"--show-secrets", help="Show secrets in output.", action="store_true"
|
"--show-secrets", help="Show secrets in output.", action="store_true"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser_config_hash = subparsers.add_parser(
|
||||||
|
"config-hash", help="Calculate the hash of the configuration."
|
||||||
|
)
|
||||||
|
parser_config_hash.add_argument(
|
||||||
|
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||||
|
)
|
||||||
|
|
||||||
parser_compile = subparsers.add_parser(
|
parser_compile = subparsers.add_parser(
|
||||||
"compile", help="Read the configuration and compile a program."
|
"compile", help="Read the configuration and compile a program."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from esphome.__main__ import (
|
|||||||
command_analyze_memory,
|
command_analyze_memory,
|
||||||
command_bundle,
|
command_bundle,
|
||||||
command_clean_all,
|
command_clean_all,
|
||||||
|
command_config_hash,
|
||||||
command_rename,
|
command_rename,
|
||||||
command_run,
|
command_run,
|
||||||
command_update_all,
|
command_update_all,
|
||||||
@@ -3439,6 +3440,33 @@ def test_command_wizard(tmp_path: Path) -> None:
|
|||||||
mock_wizard.assert_called_once_with(config_file)
|
mock_wizard.assert_called_once_with(config_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_command_config_hash(
|
||||||
|
tmp_path: Path,
|
||||||
|
capfd: CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""command_config_hash runs codegen then prints CORE.config_hash.
|
||||||
|
|
||||||
|
The printed format must match `0x{config_hash:08x}` used by
|
||||||
|
generate_build_info_data_cpp so the value can be compared byte-for-byte
|
||||||
|
against the ESPHOME_CONFIG_HASH embedded in firmware.
|
||||||
|
"""
|
||||||
|
setup_core(tmp_path=tmp_path, config={"esphome": {"name": "test"}})
|
||||||
|
args = MockArgs()
|
||||||
|
|
||||||
|
# generate_cpp_contents requires real components to be loaded; mock it out
|
||||||
|
# so this test isolates the command's output contract. The command must
|
||||||
|
# still call it (codegen can mutate config, which affects the hash).
|
||||||
|
with patch("esphome.__main__.generate_cpp_contents") as mock_generate:
|
||||||
|
result = command_config_hash(args, CORE.config)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
mock_generate.assert_called_once_with(CORE.config)
|
||||||
|
|
||||||
|
output = strip_ansi_codes(capfd.readouterr().out).strip()
|
||||||
|
assert re.fullmatch(r"0x[0-9a-f]{8}", output)
|
||||||
|
assert output == f"0x{CORE.config_hash:08x}"
|
||||||
|
|
||||||
|
|
||||||
def test_command_rename_invalid_characters(
|
def test_command_rename_invalid_characters(
|
||||||
tmp_path: Path, capfd: CaptureFixture[str]
|
tmp_path: Path, capfd: CaptureFixture[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user