mirror of
https://github.com/esphome/esphome.git
synced 2026-10-02 17:30:22 +00:00
Merge branch 'pch-strict-ci' into host-pch
This commit is contained in:
@@ -1240,8 +1240,11 @@ def test_prepare_pch_probe_rejection_latches_and_degrades(
|
||||
|
||||
def rejecting(cmd, **kwargs):
|
||||
if "-fsyntax-only" in cmd:
|
||||
if "-include" not in cmd:
|
||||
# Baseline without the pch passes: the pch is to blame
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 0, "", "warning: esphome_pch.h.gch: had text segment "
|
||||
cmd, 1, "", "error: esphome_pch.h.gch: had text segment "
|
||||
)
|
||||
gch.write_bytes(b"gch")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
@@ -1289,8 +1292,10 @@ def test_prepare_pch_strict_reprobes_cached_gch(
|
||||
|
||||
def reject(cmd, **kwargs):
|
||||
assert "-fsyntax-only" in cmd, "cached path must not recompile"
|
||||
if "-include" not in cmd:
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 0, "", "warning: esphome_pch.h.gch: had text segment "
|
||||
cmd, 1, "", "error: esphome_pch.h.gch: had text segment "
|
||||
)
|
||||
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
|
||||
@@ -1304,3 +1309,78 @@ def test_prepare_pch_strict_reprobes_cached_gch(
|
||||
# Per-process rejection may not reproduce: the cached path must not
|
||||
# latch the pch off for later non-strict builds
|
||||
assert not (dev / "build" / "esphome_pch.h.gch.failed").exists()
|
||||
|
||||
|
||||
def test_prepare_pch_unexpected_command_shape_degrades(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""A tail the probe slice cannot trust discards and degrades."""
|
||||
from esphome.build_gen.espidf import prepare_pch
|
||||
from esphome.core import EsphomeError
|
||||
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
|
||||
dev = _make_pch_device(tmp_path, "dev_sh")
|
||||
CORE.build_path = dev
|
||||
gch = dev / "build" / "esphome_pch.h.gch"
|
||||
|
||||
def ok(cmd, **kwargs):
|
||||
gch.write_bytes(b"gch")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
|
||||
with (
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch("esphome.build_helpers.pch.subprocess.run", side_effect=ok),
|
||||
patch(
|
||||
"esphome.build_helpers.pch.pch_compile_command",
|
||||
return_value=(
|
||||
["g++", "-DX=1", "-c", "x", "-o", "y", "extra"],
|
||||
dev / "build",
|
||||
),
|
||||
),
|
||||
pytest.raises(EsphomeError, match="command shape"),
|
||||
):
|
||||
prepare_pch()
|
||||
assert not gch.exists()
|
||||
|
||||
# Non-strict: same shape problem degrades without raising
|
||||
monkeypatch.delenv("ESPHOME_PCH_STRICT")
|
||||
with (
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch("esphome.build_helpers.pch.subprocess.run", side_effect=ok),
|
||||
patch(
|
||||
"esphome.build_helpers.pch.pch_compile_command",
|
||||
return_value=(
|
||||
["g++", "-DX=1", "-c", "x", "-o", "y", "extra"],
|
||||
dev / "build",
|
||||
),
|
||||
),
|
||||
):
|
||||
prepare_pch()
|
||||
assert not gch.exists()
|
||||
|
||||
|
||||
def test_prepare_pch_probe_baseline_spawn_failure_is_transient(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""A baseline that cannot spawn is environmental: no marker."""
|
||||
from esphome.build_gen.espidf import prepare_pch
|
||||
|
||||
dev = _make_pch_device(tmp_path, "dev_bs")
|
||||
CORE.build_path = dev
|
||||
gch = dev / "build" / "esphome_pch.h.gch"
|
||||
|
||||
def flaky(cmd, **kwargs):
|
||||
if "-fsyntax-only" in cmd:
|
||||
if "-include" in cmd:
|
||||
return subprocess.CompletedProcess(cmd, 1, "", "boom")
|
||||
raise OSError("baseline spawn failed")
|
||||
gch.write_bytes(b"gch")
|
||||
return subprocess.CompletedProcess(cmd, 0, "", "")
|
||||
|
||||
with (
|
||||
patch.object(CORE, "name", "test"),
|
||||
patch("esphome.build_helpers.pch.subprocess.run", side_effect=flaky),
|
||||
):
|
||||
prepare_pch()
|
||||
assert not gch.exists()
|
||||
assert not (dev / "build" / "esphome_pch.h.gch.failed").exists()
|
||||
|
||||
@@ -253,3 +253,14 @@ def test_pch_extra_scripts_strict_raises_when_disabled(
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "1")
|
||||
with pytest.raises(EsphomeError, match="disabled"):
|
||||
pch.pch_extra_scripts()
|
||||
|
||||
|
||||
def test_pch_strict_rejects_unrecognized_values(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""A typo must not silently disable the gate."""
|
||||
from esphome.core import EsphomeError
|
||||
|
||||
monkeypatch.setenv("ESPHOME_PCH_STRICT", "yolo")
|
||||
with pytest.raises(EsphomeError, match="Unrecognized ESPHOME_PCH_STRICT"):
|
||||
pch.pch_strict()
|
||||
|
||||
@@ -100,12 +100,14 @@ def _fake_cxx(
|
||||
body += f"echo {fail_msg or 'boom'} >&2\nexit 1\n"
|
||||
else:
|
||||
# Only the c++-header compile has a -o; the load probe has none
|
||||
body += 'out=""; prev=""; mf=0; dep=0\nfor a in "$@"; do [ "$prev" = "-o" ] && out="$a"; prev="$a"; [ "$a" = "-MF" ] && mf=1; case "$a" in -M|-MM|-MD|-MMD) dep=1;; esac; done\n'
|
||||
body += 'out=""; prev=""; mf=0; dep=0; inc=0\nfor a in "$@"; do [ "$prev" = "-o" ] && out="$a"; prev="$a"; [ "$a" = "-MF" ] && mf=1; [ "$a" = "-include" ] && inc=1; case "$a" in -M|-MM|-MD|-MMD) dep=1;; esac; done\n'
|
||||
# Real cc1plus rejects -MF without a dependency flag
|
||||
body += 'if [ "$mf" = 1 ] && [ "$dep" = 0 ]; then echo "cc1plus: error: to generate dependencies you must specify either \x27-M\x27 or \x27-MM\x27" >&2; exit 1; fi\n'
|
||||
body += '[ -n "$out" ] && echo gch > "$out"\n'
|
||||
if reject_pch:
|
||||
body += 'case " $* " in *c++-header*) ;; *) echo "warning: esphome_pch.h.gch: had text segment at different address" >&2;; esac\n'
|
||||
# -Werror=invalid-pch makes rejection a nonzero exit; the
|
||||
# baseline (no -include) still passes
|
||||
body += 'case " $* " in *c++-header*) ;; *) if [ "$inc" = 1 ]; then echo "error: esphome_pch.h.gch: had text segment at different address" >&2; exit 1; fi;; esac\n'
|
||||
body += f'case " $* " in *c++-header*) exit 0;; *) exit {probe_exit};; esac\n'
|
||||
cxx.write_text("#!/bin/sh\n" + body)
|
||||
cxx.chmod(cxx.stat().st_mode | stat.S_IEXEC)
|
||||
@@ -293,12 +295,14 @@ def test_pch_script_spawn_failure_is_transient(
|
||||
assert "did not run" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_pch_script_probe_nonzero_exit_falls_back(tmp_path: Path) -> None:
|
||||
"""A probe failure whose stderr never mentions .gch must still count."""
|
||||
def test_pch_script_probe_environment_failure_does_not_latch(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""Probe AND baseline failing is environmental: no marker, retry."""
|
||||
scons_env = _run_script(tmp_path, probe_exit=1)
|
||||
proj = tmp_path / "dev"
|
||||
assert not (proj / "esphome_pch.h.gch").exists()
|
||||
assert (proj / "esphome_pch.h.gch.failed").is_file()
|
||||
assert not (proj / "esphome_pch.h.gch.failed").exists()
|
||||
assert scons_env.prepended == []
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user