mirror of
https://github.com/esphome/esphome.git
synced 2026-08-24 07:06:20 +00:00
Merge branch 'esp8266-native-build-infra' into esp8266-native-framework-installer
This commit is contained in:
@@ -779,15 +779,25 @@ def run_batch_downloads(
|
||||
collected (list.append is atomic under the GIL) and returned after the
|
||||
bar is done, so the caller's warnings never land on the bar's row; a
|
||||
failed job credits its tracker 0 so the bar can still complete. Ctrl-C
|
||||
drops queued jobs instead of downloading them all before the process
|
||||
can exit; in-flight ones still finish. ``jobs`` must be non-empty.
|
||||
drops queued jobs and aborts in-flight ones at their next progress
|
||||
tick; resumable ``.part`` files keep the bytes already fetched.
|
||||
``jobs`` must be non-empty.
|
||||
"""
|
||||
failures: list[tuple[str, Exception]] = []
|
||||
cancelled = threading.Event()
|
||||
|
||||
def _run(name: str, fetch: Callable[[Callable[[int], None]], None]) -> None:
|
||||
tracker = progress.tracker()
|
||||
|
||||
def checked(done: int) -> None:
|
||||
if cancelled.is_set():
|
||||
raise _BatchDownloadCancelled
|
||||
tracker(done)
|
||||
|
||||
try:
|
||||
fetch(tracker)
|
||||
fetch(checked)
|
||||
except _BatchDownloadCancelled:
|
||||
tracker(0)
|
||||
except Exception as err: # noqa: BLE001 # pylint: disable=broad-exception-caught
|
||||
failures.append((name, err))
|
||||
tracker(0)
|
||||
@@ -796,12 +806,21 @@ def run_batch_downloads(
|
||||
try:
|
||||
for future in [ex.submit(_run, name, fetch) for name, fetch in jobs]:
|
||||
future.result()
|
||||
except BaseException:
|
||||
# Without this the non-daemon workers download to completion before
|
||||
# the interpreter can exit, making Ctrl-C ineffective for minutes
|
||||
cancelled.set()
|
||||
raise
|
||||
finally:
|
||||
ex.shutdown(wait=True, cancel_futures=True)
|
||||
progress.done()
|
||||
return failures
|
||||
|
||||
|
||||
class _BatchDownloadCancelled(Exception):
|
||||
"""Raised inside a download job to abandon it after Ctrl-C."""
|
||||
|
||||
|
||||
class BatchDownloadProgress:
|
||||
"""One progress bar across several concurrent ``download_with_resume`` calls.
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ DEFAULT_BUILD_FLAGS = []
|
||||
# "asm" merges SCons's AS and ASPP sets: all compile as assembler-with-cpp.
|
||||
# The kind values drive the ESP8266 native ninja rules (later in this
|
||||
# chain); existing backends consume only the keys. Note .C/.C++ join the
|
||||
# suffix set here, matching PlatformIO's CXXSUFFIXES.
|
||||
# suffix set here per CXXSUFFIXES; SCons demotes .C to C on
|
||||
# case-insensitive filesystems, we always treat it as C++.
|
||||
SOURCE_KIND_FOR_SUFFIX: dict[str, str] = {
|
||||
".c": "c",
|
||||
".cpp": "cxx",
|
||||
|
||||
@@ -984,9 +984,13 @@ def test_prefetch_downloads_each_archive_with_resume(tmp_path: Path) -> None:
|
||||
kwargs = calls[("https://example.com/cmake.tar.gz", dist / "cmake-3.30.2.tar.gz")]
|
||||
assert kwargs["sha256"] == "ab" * 32
|
||||
assert kwargs["size"] == 123
|
||||
# every archive reports into the one combined progress bar
|
||||
# every archive reports into the one combined progress bar via the
|
||||
# cancellation-checked wrapper; verify it delegates to the tracker
|
||||
progress_cls.assert_called_once_with("Downloading ESP-IDF tools", 123 + 45)
|
||||
assert all(kw["progress"] is tracker for kw in calls.values())
|
||||
before = tracker.call_count
|
||||
for kw in calls.values():
|
||||
kw["progress"](7)
|
||||
assert tracker.call_count == before + len(calls)
|
||||
|
||||
|
||||
def test_prefetch_downloads_archives_concurrently(tmp_path: Path) -> None:
|
||||
|
||||
@@ -1158,6 +1158,40 @@ class TestDownloadWithResume:
|
||||
assert seen == [8]
|
||||
|
||||
|
||||
def test_run_batch_downloads_ctrl_c_aborts_in_flight_jobs() -> None:
|
||||
"""Ctrl-C cancels in-flight downloads at their next tick instead of
|
||||
letting non-daemon workers download to completion."""
|
||||
import threading
|
||||
import time
|
||||
|
||||
from esphome.framework_helpers import BatchDownloadProgress, run_batch_downloads
|
||||
|
||||
started = threading.Event()
|
||||
ticks: list[int] = []
|
||||
|
||||
def interrupter(tracker) -> None:
|
||||
started.wait(5)
|
||||
raise KeyboardInterrupt
|
||||
|
||||
def slow_download(tracker) -> None:
|
||||
started.set()
|
||||
for i in range(500):
|
||||
tracker(i)
|
||||
ticks.append(i)
|
||||
time.sleep(0.01)
|
||||
|
||||
t0 = time.monotonic()
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
run_batch_downloads(
|
||||
BatchDownloadProgress("Downloading", 0),
|
||||
[("boom", interrupter), ("slow", slow_download)],
|
||||
max_workers=2,
|
||||
)
|
||||
# Uncancelled, slow_download alone takes ~5s
|
||||
assert time.monotonic() - t0 < 3
|
||||
assert len(ticks) < 500
|
||||
|
||||
|
||||
class TestBatchDownloadProgress:
|
||||
def test_sums_trackers_into_one_bar(self) -> None:
|
||||
with patch("esphome.framework_helpers.ProgressBar") as bar_cls:
|
||||
|
||||
@@ -276,6 +276,8 @@ def test_wave_requirement_growth_defers_the_superseded_download(tmp_path, monkey
|
||||
(self.path / "library.json").write_text(json.dumps(manifests[self.name]))
|
||||
|
||||
monkeypatch.setattr(ConvertedLibrary, "download", fake_download)
|
||||
# Hermetic: unknown sizes take the sequential path instead of real HEADs
|
||||
monkeypatch.setattr(lib, "_content_lengths", lambda urls: [None] * len(urls))
|
||||
_patch_registry_resolve(monkeypatch)
|
||||
top = convert_libraries(
|
||||
[Library("esphome/A", "1.0.0", None), Library("esphome/B", None, None)],
|
||||
@@ -710,9 +712,7 @@ def test_content_lengths_head_requests(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
return SimpleNamespace(ok=True, headers={"content-length": "123, 123"})
|
||||
return SimpleNamespace(ok=True, headers={"content-length": "123"})
|
||||
|
||||
monkeypatch.setattr(
|
||||
lib.requests if hasattr(lib, "requests") else requests, "head", fake_head
|
||||
)
|
||||
monkeypatch.setattr(requests, "head", fake_head)
|
||||
# None marks an unknown size (probe failure or non-2xx), distinct
|
||||
# from a genuine zero
|
||||
assert lib._content_lengths(
|
||||
|
||||
Reference in New Issue
Block a user