From f32e3e75f4d2c6ea2cdfa4ddcbb6599190a9977a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 3 Sep 2026 22:05:29 +0200 Subject: [PATCH] Address review: guard utime against peer reap, decorator anchored use detector, invariant holds without a stamp, print for executor thread messages --- script/helpers.py | 2 +- tests/integration/conftest.py | 32 ++++++++++++++++++-------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/script/helpers.py b/script/helpers.py index 6b8267635b..c5102e85ea 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -1105,7 +1105,7 @@ def get_components_per_integration_fixture() -> dict[str, set[str]]: _TEST_FUNC_RE = re.compile(r"async def (test_\w+)") _SHARED_YAML_RE = re.compile(r"mark\.shared_yaml\([\"'](\w+)[\"']\)") -_SHARED_YAML_USE_RE = re.compile(r"\bmark\.shared_yaml\b") +_SHARED_YAML_USE_RE = re.compile(r"^\s*@[\w.]*mark\.shared_yaml\b", re.MULTILINE) @cache diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 8df2222991..55a716d23e 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -4,7 +4,7 @@ from __future__ import annotations import asyncio from collections.abc import AsyncGenerator, Callable, Generator -from contextlib import AbstractAsyncContextManager, asynccontextmanager +from contextlib import AbstractAsyncContextManager, asynccontextmanager, suppress import fcntl from functools import cache import hashlib @@ -21,7 +21,6 @@ import sys import tempfile import time from typing import TextIO -import warnings from aioesphomeapi import APIClient, APIConnectionError, LogParser, ReconnectLogic import pytest @@ -336,16 +335,16 @@ def _read_stamp(stamp: Path, shared_dir: Path) -> Path | None: except FileNotFoundError: return None except OSError as err: - warnings.warn(f"Cannot read {stamp}: {err}", stacklevel=2) + print(f"Cannot read {stamp}: {err}") return None if not text: - warnings.warn(f"Ignoring empty stamp {stamp}", stacklevel=2) + print(f"Ignoring empty stamp {stamp}") return None built = Path(text) # Never trust a stamp pointing outside its own build dir as an unlink target if shared_dir.resolve() in built.resolve().parents: return built - warnings.warn(f"Ignoring stamp {stamp} pointing outside {shared_dir}", stacklevel=2) + print(f"Ignoring stamp {stamp} pointing outside {shared_dir}") return None @@ -362,9 +361,8 @@ def _unused_since(stale: Path, cutoff: float) -> bool: except NotADirectoryError: return True # a stray file where a dir should be; reclaimable except OSError as err: - # Treat as reclaimable; the prune attempt will surface the error - warnings.warn(f"Cannot age-probe {stale}: {err}", stacklevel=2) - return True + print(f"Cannot age-probe {stale}: {err}") + return False # unknown never authorizes deletion newest = mtime if newest is None else max(newest, mtime) return newest is not None and newest < cutoff @@ -389,11 +387,11 @@ def _prune_stale_builds(name: str, keep: Path) -> None: except FileNotFoundError: continue # pruned by another worker meanwhile except NotADirectoryError: - warnings.warn(f"Removing stray file {stale}", stacklevel=2) + print(f"Removing stray file {stale}") stale.unlink(missing_ok=True) continue except OSError as err: - warnings.warn(f"Cannot prune {stale}: {err}", stacklevel=2) + print(f"Cannot prune {stale}: {err}") continue with lock_file: try: @@ -415,7 +413,7 @@ def _prune_stale_builds(name: str, keep: Path) -> None: try: rmtree(stale) except OSError as err: - warnings.warn(f"Failed to prune {stale}: {err}", stacklevel=2) + print(f"Failed to prune {stale}: {err}") async def _run_esphome_compile( @@ -499,8 +497,10 @@ async def compile_esphome( shared_dir = _shared_build_dir(name) shared_dir.mkdir(parents=True, exist_ok=True) # Freshen the dir before locking so a concurrent age sweep, which - # re-probes under the lock, never reaps a dir a worker just claimed - os.utime(shared_dir) + # re-probes under the lock, never reaps a dir a worker just claimed; + # if a peer reaped it already, the guarded lock open recreates it + with suppress(FileNotFoundError): + os.utime(shared_dir) if shared_dir not in _pruned_dirs: _pruned_dirs.add(shared_dir) await loop.run_in_executor(None, _prune_stale_builds, name, shared_dir) @@ -550,9 +550,13 @@ async def compile_esphome( if (built := _shared_elf_paths.get(shared_dir)) is None: built = await loop.run_in_executor(None, _read_stamp, stamp, shared_dir) # Delete the ELF before compiling: whatever exists afterwards is - # this compile's output, so no staleness check is ever needed + # this compile's output, so no staleness check is ever needed. + # With no usable stamp, sweep any leftover at the known layout if built is not None: built.unlink(missing_ok=True) + else: + for leftover in shared_dir.glob(".esphome/build/*/.pioenvs/*/program"): + leftover.unlink() await loop.run_in_executor( None, write_file_if_changed, shared_config, content )