mirror of
https://github.com/esphome/esphome.git
synced 2026-10-03 09:46:03 +00:00
Merge branch 'esp8266-native-ninja-emission' into esp8266-arduino-toolchain
This commit is contained in:
@@ -442,7 +442,6 @@ def test_generate_ld_scripts_testing_mode(tmp_path: Path) -> None:
|
||||
(paths.framework / "tools" / "sdk" / "ld" / "eagle.flash.4m.ld").write_text(
|
||||
"MEMORY\n{\n"
|
||||
" dram0_0_seg : org = 0x3FFE8000, len = 0x14000\n"
|
||||
" iram1_0_seg : org = 0x40100000, len = 0x8000\n"
|
||||
" irom0_0_seg : org = 0x40201010, len = 0xfeff0\n"
|
||||
"}\n"
|
||||
)
|
||||
@@ -677,3 +676,67 @@ def test_ninja_path_escaping() -> None:
|
||||
"""Build-statement paths and command-line paths escape differently."""
|
||||
assert arduino8266._e("a b:$c") == "a$ b$:$$c"
|
||||
assert arduino8266._q("/a b/$x") == '"/a b/$$x"'
|
||||
|
||||
|
||||
def test_write_project_asm_excludes_non_define_user_flags(tmp_path: Path) -> None:
|
||||
"""The ASPPCOM command under PlatformIO never sees CCFLAGS, so only -D/-I user flags
|
||||
reach assembly compiles."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags("-DUSER_KNOB=1", "-Wno-volatile")
|
||||
content = _write_ninja(paths)
|
||||
asflags = next(line for line in content.splitlines() if line.startswith("asflags"))
|
||||
assert "-DUSER_KNOB=1" in asflags
|
||||
assert "-Wno-volatile" not in asflags
|
||||
cxxflags = next(
|
||||
line for line in content.splitlines() if line.startswith("cxxflags")
|
||||
)
|
||||
assert "-Wno-volatile" in cxxflags
|
||||
|
||||
|
||||
def test_write_project_returns_changed(tmp_path: Path) -> None:
|
||||
"""The documented contract: True when build.ninja changed, False on an
|
||||
identical regeneration (pins byte-stable output too)."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags()
|
||||
src = CORE.relative_src_path()
|
||||
(src / "esphome" / "components" / "esp8266").mkdir(parents=True, exist_ok=True)
|
||||
(src / "main.cpp").write_text("")
|
||||
with (
|
||||
patch.object(arduino8266, "generate_ld_scripts"),
|
||||
patch("esphome.arduino.library.resolve_libraries", return_value=[]),
|
||||
patch("esphome.arduino8266.framework.ccache_path", return_value=None),
|
||||
):
|
||||
assert arduino8266.write_project(paths) is True
|
||||
assert arduino8266.write_project(paths) is False
|
||||
|
||||
|
||||
def test_write_project_missing_src_dir_raises(tmp_path: Path) -> None:
|
||||
"""A missing generated source tree is its own error, not an install one."""
|
||||
paths = _make_framework(tmp_path)
|
||||
_set_flags()
|
||||
with (
|
||||
patch.object(arduino8266, "generate_ld_scripts"),
|
||||
patch("esphome.arduino.library.resolve_libraries", return_value=[]),
|
||||
patch.object(
|
||||
arduino8266.CORE, "relative_src_path", return_value=tmp_path / "nope"
|
||||
),
|
||||
pytest.raises(EsphomeError, match="source directory"),
|
||||
):
|
||||
arduino8266.write_project(paths)
|
||||
|
||||
|
||||
def test_build_config_custom_mmu_without_knob_warns(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Custom MMU sizes without the CUSTOM knob keep the default layout and
|
||||
warn, as the PlatformIO builder does."""
|
||||
_set_flags("-DMMU_IRAM_SIZE=0xC000")
|
||||
config = _resolve_build_config(_flag_defines())
|
||||
assert config.mmu_defines == ["MMU_IRAM_SIZE=0x8000", "MMU_ICACHE_SIZE=0x8000"]
|
||||
assert "Detected custom MMU flags" in caplog.text
|
||||
|
||||
|
||||
def test_flag_defines_lexes_quoted_single_tokens() -> None:
|
||||
"""A quoted single-token define reads the same as on the compile line."""
|
||||
_set_flags('-DMMU_SEC_HEAP="0x40108000"')
|
||||
assert _flag_defines()["MMU_SEC_HEAP"] == "MMU_SEC_HEAP=0x40108000"
|
||||
|
||||
@@ -451,3 +451,17 @@ def test_load_or_build_idedata_never_caches_bad_compiler(tmp_path: Path) -> None
|
||||
)
|
||||
assert data["cxx_path"] == "/usr/bin/python3"
|
||||
assert not cache.exists()
|
||||
|
||||
|
||||
def test_load_or_build_idedata_cache_hit_skips_rebuild(tmp_path: Path) -> None:
|
||||
"""A valid cache newer than the compile DB is served without re-parsing."""
|
||||
compile_commands = _write_compile_commands(tmp_path)
|
||||
cache = tmp_path / "c.json"
|
||||
cache.write_text(json.dumps({"cc_path": "/tools/gcc", "cached": True}))
|
||||
os.utime(cache, (compile_commands.stat().st_mtime + 5,) * 2)
|
||||
with patch.object(idedata, "idedata_from_build") as mock_build:
|
||||
data = idedata.load_or_build_idedata(
|
||||
compile_commands, tmp_path / "f.elf", cache
|
||||
)
|
||||
mock_build.assert_not_called()
|
||||
assert data["cached"] is True
|
||||
|
||||
@@ -48,3 +48,31 @@ def test_find_ninja_missing_everywhere(tmp_path: Path) -> None:
|
||||
pytest.raises(EsphomeError, match="ninja not found"),
|
||||
):
|
||||
ninja_helper.find_ninja()
|
||||
|
||||
|
||||
def test_escape_ninja_specials() -> None:
|
||||
assert ninja_helper.escape("a b:c$d") == "a$ b$:c$$d"
|
||||
|
||||
|
||||
def test_quote_arg_windows_argv_rule() -> None:
|
||||
# Backslash runs double only before a quote (subprocess.list2cmdline rule)
|
||||
assert ninja_helper.quote_arg('-DX=a\\"b c') == '"-DX=a\\\\\\"b c"'
|
||||
assert ninja_helper.quote_arg("a b\\") == '"a b\\\\"'
|
||||
|
||||
|
||||
def test_shell_token_quotes_only_when_needed() -> None:
|
||||
assert ninja_helper.shell_token("-Os") == "-Os"
|
||||
assert ninja_helper.shell_token("-DP=C:\\x y") == '"-DP=C:\\x y"'
|
||||
assert ninja_helper.shell_token("plain", force=True) == '"plain"'
|
||||
|
||||
|
||||
def test_shell_token_quotes_shell_metacharacters() -> None:
|
||||
"""Tokens like -DMASK=(1<<3) must not reach /bin/sh -c bare."""
|
||||
assert ninja_helper.shell_token("-DMASK=(1<<3)") == '"-DMASK=(1<<3)"'
|
||||
assert ninja_helper.shell_token("-DX=a;b") == '"-DX=a;b"'
|
||||
assert ninja_helper.shell_token("-DX=$HOME") == '"-DX=$$HOME"'
|
||||
|
||||
|
||||
def test_quote_path_force_quotes() -> None:
|
||||
assert ninja_helper.quote_path(Path("a b")) == '"a b"'
|
||||
assert ninja_helper.quote_path("simple") == '"simple"'
|
||||
|
||||
@@ -49,7 +49,8 @@ def test_relocate_ratetable_inserts_after_data_start() -> None:
|
||||
patched = relocate_ratetable(_COMMON_LD_SNIPPET)
|
||||
assert RATETABLE_RULE in patched
|
||||
# Inserted after the .data section's anchor, not the .dport0.data one
|
||||
assert patched.index("_data_start = ABSOLUTE(.);") < patched.index(RATETABLE_RULE)
|
||||
# (whose closing brace bounds the decoy block)
|
||||
assert RATETABLE_RULE not in patched[: patched.index("} >dport0_0_seg")]
|
||||
assert patched.index(RATETABLE_RULE) < patched.index("*(.data)")
|
||||
# Idempotent on an already-patched script
|
||||
assert relocate_ratetable(patched) == patched
|
||||
@@ -106,14 +107,20 @@ def test_board_build_covers_every_board() -> None:
|
||||
assert set(BOARDS) <= set(ESP8266_BOARD_BUILD)
|
||||
|
||||
|
||||
def test_surgery_fingerprint_tracks_inputs() -> None:
|
||||
"""The fingerprint changes with any behavioral input, so linker-script
|
||||
caches stamped with it self-invalidate on surgery edits."""
|
||||
from unittest.mock import patch
|
||||
def test_surgery_fingerprint_covers_module_source() -> None:
|
||||
"""The fingerprint hashes the module source, so any surgery edit
|
||||
invalidates linker-script caches stamped with it."""
|
||||
import hashlib
|
||||
import inspect
|
||||
|
||||
from esphome.components.esp8266 import build_surgery
|
||||
|
||||
base = build_surgery.surgery_fingerprint()
|
||||
assert base == build_surgery.surgery_fingerprint()
|
||||
with patch.object(build_surgery, "_TESTING_SEGMENT_SIZES", {"iram1_0_seg": "0x1"}):
|
||||
assert build_surgery.surgery_fingerprint() != base
|
||||
expected = hashlib.sha256(inspect.getsource(build_surgery).encode()).hexdigest()
|
||||
assert build_surgery.surgery_fingerprint() == expected
|
||||
|
||||
|
||||
def test_testing_memory_patches_present_but_unselected_raises() -> None:
|
||||
"""A known segment left off the caller's list must fail, not silently
|
||||
keep its real memory limit."""
|
||||
with pytest.raises(RuntimeError, match="not selected"):
|
||||
apply_testing_memory_patches(_FLASH_LD_SNIPPET, ("dram0_0_seg",))
|
||||
|
||||
@@ -103,7 +103,7 @@ def test_ccache_path_explicit_skips_probe(monkeypatch: pytest.MonkeyPatch) -> No
|
||||
monkeypatch.setenv("ESPHOME_CCACHE_ENABLE", "1")
|
||||
with (
|
||||
patch("shutil.which", return_value="/usr/bin/ccache"),
|
||||
patch("esphome.framework_helpers._ccache_runs", side_effect=AssertionError),
|
||||
patch("esphome.build_helpers.ccache._ccache_runs", side_effect=AssertionError),
|
||||
):
|
||||
assert framework.ccache_path() == "/usr/bin/ccache"
|
||||
|
||||
|
||||
@@ -25,10 +25,10 @@ from esphome.platformio.library import (
|
||||
GitSource,
|
||||
URLSource,
|
||||
_node_key,
|
||||
_parse_library_json,
|
||||
_resolve_registry_version,
|
||||
collect_filtered_files,
|
||||
normalize_dependencies,
|
||||
parse_library_json,
|
||||
parse_library_properties,
|
||||
split_list_by_condition,
|
||||
)
|
||||
@@ -368,11 +368,11 @@ def test_generate_idf_component_yml_missing_path_raises(tmp_component):
|
||||
generate_idf_component_yml(tmp_component)
|
||||
|
||||
|
||||
def test_parse_library_json(tmp_path):
|
||||
def testparse_library_json(tmp_path):
|
||||
f = tmp_path / "library.json"
|
||||
f.write_text(json.dumps({"name": "test"}))
|
||||
|
||||
result = _parse_library_json(f)
|
||||
result = parse_library_json(f)
|
||||
assert result["name"] == "test"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user