Merge branch 'esp8266-native-ninja-emission' into esp8266-arduino-toolchain

This commit is contained in:
J. Nick Koston
2026-08-23 13:57:24 -05:00
13 changed files with 329 additions and 146 deletions
+22 -26
View File
@@ -1,5 +1,4 @@
from collections.abc import Generator
import errno
import io
import logging
import os
@@ -178,37 +177,34 @@ def _run_probe_on_pty(
output = b""
deadline = time.monotonic() + 60
try:
try:
proc = subprocess.Popen(
_probe_command(fixture_path),
stdout=follower,
stderr=follower if stderr_to_pty else subprocess.PIPE,
stdin=follower,
env=probe_env,
)
finally:
os.close(follower)
while True:
timeout = deadline - time.monotonic()
if timeout <= 0 or not select.select([controller], [], [], timeout)[0]:
pytest.fail(f"pty probe produced no EOF in time; got {output!r}")
try:
chunk = os.read(controller, 1024)
except OSError as err:
# macOS raises EIO once the child closes its end of the pty;
# anything else is a real failure, not end-of-stream.
if err.errno != errno.EIO:
raise
break
if not chunk:
break
proc = subprocess.Popen(
_probe_command(fixture_path),
stdout=follower,
stderr=follower if stderr_to_pty else subprocess.PIPE,
stdin=follower,
env=probe_env,
)
# The parent keeps the follower open until the child has exited and
# the controller is drained: macOS discards buffered pty output once
# the last follower closes, so closing it early loses the probe's
# output whenever the child finishes before the first read.
while proc.poll() is None:
if time.monotonic() > deadline:
pytest.fail(f"pty probe did not exit in time; got {output!r}")
if select.select([controller], [], [], 0.01)[0]:
output += os.read(controller, 4096)
# Everything the child wrote is already buffered, so drain without waiting.
while select.select([controller], [], [], 0)[0] and (
chunk := os.read(controller, 4096)
):
output += chunk
stderr_text = ""
if proc.stderr is not None:
stderr_text = proc.stderr.read().decode(errors="replace")
proc.stderr.close()
assert proc.wait(60) == 0, stderr_text
assert proc.returncode == 0, stderr_text
finally:
os.close(follower)
os.close(controller)
if proc is not None and proc.poll() is None:
proc.kill()
@@ -471,6 +471,32 @@ def test_spaced_linkflag_survives_relexing(tmp_path) -> None:
]
def test_env_attribute_access_warns_without_call(caplog) -> None:
"""hasattr()/truthiness on an unsupported method is diagnosable; dunder
protocol probes stay silent."""
env = _FakeSConsEnv(
board_mcu="esp8266", pio_env="esphome_esp8266", pio_platform="espressif8266"
)
assert env.GetProjectOption
assert caplog.text.count("env.GetProjectOption is not supported") == 1
assert not hasattr(env, "__deepcopy__")
assert "__deepcopy__" not in caplog.text
def test_env_unmodelled_subscript_degrades_one_branch(caplog) -> None:
"""env[...] on an unmodelled var returns '' instead of KeyError
discarding the whole capture."""
env = _FakeSConsEnv(
board_mcu="esp8266", pio_env="esphome_esp8266", pio_platform="espressif8266"
)
assert env["PIOFRAMEWORK"] == ""
assert env["PIOFRAMEWORK"] == ""
assert caplog.text.count("env['PIOFRAMEWORK'] is not modelled") == 1
assert env["BOARD_MCU"] == "esp8266"
env.Append(LIBS=["still_captured"])
assert env.result.libs == ["still_captured"]
def test_uncaptured_append_key_warns_once(caplog) -> None:
"""A loop of Appends to the same uncaptured key warns once."""