Files
esphome/tests/unit_tests/test_platformio_runner.py
T

94 lines
2.9 KiB
Python

"""Tests for esphome.platformio.runner."""
from __future__ import annotations
from collections.abc import Callable
import io
import sys
from types import ModuleType
import pytest
from esphome.platformio import runner
def _prepare_main(
monkeypatch: pytest.MonkeyPatch, pio_main: Callable[[], int]
) -> io.BytesIO:
"""Point ``runner.main()`` at a fake PlatformIO with a fake stdout.
The real ``main`` patches PlatformIO internals and then hands control to
it; both are stubbed out so only the stream wrapping is exercised. The
fake stdout is block buffered like a pipe, so the caller can see what
actually left the wrapper.
"""
buf = io.BytesIO()
stream = io.TextIOWrapper(buf, encoding="utf-8", newline="\n", line_buffering=False)
monkeypatch.setattr(sys, "argv", ["pio", "run"])
monkeypatch.setattr(sys, "stdout", stream)
monkeypatch.setattr(sys, "stderr", stream)
monkeypatch.setattr(runner, "patch_structhash", lambda: None)
monkeypatch.setattr(runner, "patch_file_downloader", lambda: None)
platformio = ModuleType("platformio")
platformio_main = ModuleType("platformio.__main__")
platformio_main.main = pio_main # type: ignore[attr-defined]
platformio.__main__ = platformio_main # type: ignore[attr-defined]
monkeypatch.setitem(sys.modules, "platformio", platformio)
monkeypatch.setitem(sys.modules, "platformio.__main__", platformio_main)
return buf
def test_main_drains_a_partial_line_on_a_clean_run(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A build ending mid line still shows that line."""
def pio_main() -> int:
print("Linking .pioenvs/firmware.elf\n", end="")
print("Building took 12.4 seconds", end="")
return 0
buf = _prepare_main(monkeypatch, pio_main)
assert runner.main() == 0
assert buf.getvalue().decode("utf-8") == (
"Linking .pioenvs/firmware.elf\nBuilding took 12.4 seconds\n"
)
def test_main_drains_when_platformio_exits_early(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Leaving through ``sys.exit`` still drains, because it runs in a finally."""
def pio_main() -> int:
print("*** [.pioenvs/firmware.elf] Error 1", end="")
sys.exit(1)
buf = _prepare_main(monkeypatch, pio_main)
with pytest.raises(SystemExit) as excinfo:
runner.main()
assert excinfo.value.code == 1
assert buf.getvalue().decode("utf-8") == "*** [.pioenvs/firmware.elf] Error 1\n"
def test_main_still_filters_a_drained_partial_line(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Releasing a held line does not smuggle noise past the filter."""
def pio_main() -> int:
# Matches FILTER_PLATFORMIO_LINES, and arrives without a terminator.
print("Verbose mode can be enabled via `-v, --verbose` option", end="")
return 0
buf = _prepare_main(monkeypatch, pio_main)
assert runner.main() == 0
assert buf.getvalue() == b""