Private
Public Access
0
0
Files
manual_slop/tests/test_default_layout_install.py
T
ed 3d87f8e7ed fix(gui): wire _install_default_layout_if_empty_result into App._post_init
App._post_init now resolves src = paths.get_layouts_dir()/default.ini
and dst = Path.cwd()/manualslop_layout.ini, then calls the drain-plane
helper before the warmup-complete registration block. Errors drain to
self._startup_timeline_errors per the data-oriented convention, so a
missing bundled layout (e.g. partial wheel install) does not crash the
GUI: panels just stay invisible until the user drops a real INI in.

Test fix: test_default_layout_install._GUI_SCRIPT was a relative path,
but the subprocess Popen runs with cwd = temp_workspace where sloppy.py
does not exist. Switched to an absolute path via _PROJECT_ROOT, the
same pattern conftest.py:648 uses for the live_gui fixture.
2026-06-29 16:35:20 -04:00

157 lines
4.9 KiB
Python

from __future__ import annotations
import os
import shutil
import signal
import subprocess
import time
from pathlib import Path
from typing import TextIO
import pytest
_PROJECT_ROOT: Path = Path(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
_SCRIPTS_DIR: Path = _PROJECT_ROOT / "tests" / "artifacts" / "tier2_state" / "default_layout_install_20260629"
_GUI_SCRIPT: str = str(_PROJECT_ROOT / "sloppy.py")
_INI_FILENAME: str = "manualslop_layout.ini"
def _spawn_sloppy_for(workspace: Path, log_suffix: str) -> subprocess.Popen:
_SCRIPTS_DIR.mkdir(parents=True, exist_ok=True)
log_path: Path = _SCRIPTS_DIR / f"test_{log_suffix}.log"
log_file: TextIO = open(log_path, "w", encoding="utf-8", errors="replace")
env: dict[str, str] = os.environ.copy()
env["PYTHONPATH"] = str(_PROJECT_ROOT.absolute())
args: list[str] = ["uv", "run", "python", "-u", _GUI_SCRIPT]
creation: int = subprocess.CREATE_NEW_PROCESS_GROUP if os.name == "nt" else 0
proc: subprocess.Popen = subprocess.Popen(
args,
stdout=log_file,
stderr=log_file,
text=True,
cwd=str(workspace.absolute()),
env=env,
creationflags=creation,
)
log_file.close()
return proc
def _terminate(process: subprocess.Popen) -> None:
if process.poll() is not None:
return
pid: int | None = process.pid
if pid is None:
return
try:
if os.name == "nt":
subprocess.run(
["taskkill", "/F", "/T", "/PID", str(pid)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
)
else:
os.killpg(os.getpgid(pid), signal.SIGKILL)
except Exception:
pass
try:
process.wait(timeout=2)
except Exception:
pass
def _read_ini(workspace: Path) -> str:
ini_path: Path = workspace / _INI_FILENAME
if not ini_path.exists():
return ""
try:
return ini_path.read_text(encoding="utf-8", errors="replace")
except OSError:
return ""
def _has_dockid_after_window_header(text: str) -> bool:
lines: list[str] = text.splitlines()
for idx, line in enumerate(lines):
if line.startswith("[Window][") and line.rstrip().endswith("]"):
tail: str = "\n".join(lines[idx + 1:])
if "DockId=" in tail:
return True
return False
def _workspace_for(tmp_path: Path, test_name: str) -> Path:
return tmp_path / f"_default_layout_install_{os.getpid()}_{test_name}"
def _start_subprocess_in(workspace: Path, log_suffix: str) -> subprocess.Popen:
proc: subprocess.Popen = _spawn_sloppy_for(workspace, log_suffix)
time.sleep(5.0)
return proc
def _assert_installed_default(workspace: Path) -> None:
ini_path: Path = workspace / _INI_FILENAME
assert ini_path.exists(), f"expected {ini_path} to exist after launch"
text: str = _read_ini(workspace)
assert "[Window][Project Settings]" in text, (
f"installed INI missing [Window][Project Settings]; got first 400 chars: {text[:400]!r}"
)
assert _has_dockid_after_window_header(text), (
f"installed INI has no DockId= following a [Window][...] header; got first 400 chars: {text[:400]!r}"
)
def test_default_layout_installed_when_ini_missing(tmp_path: Path) -> None:
workspace: Path = _workspace_for(tmp_path, "ini_missing")
workspace.mkdir(parents=True, exist_ok=True)
ini_path: Path = workspace / _INI_FILENAME
if ini_path.exists():
ini_path.unlink()
proc: subprocess.Popen = _start_subprocess_in(workspace, "ini_missing")
try:
_assert_installed_default(workspace)
finally:
_terminate(proc)
shutil.rmtree(workspace, ignore_errors=True)
def test_default_layout_installed_when_ini_empty(tmp_path: Path) -> None:
workspace: Path = _workspace_for(tmp_path, "ini_empty")
workspace.mkdir(parents=True, exist_ok=True)
ini_path: Path = workspace / _INI_FILENAME
ini_path.write_bytes(b"\n\n\n\n\n")
proc: subprocess.Popen = _start_subprocess_in(workspace, "ini_empty")
try:
_assert_installed_default(workspace)
finally:
_terminate(proc)
shutil.rmtree(workspace, ignore_errors=True)
def test_default_layout_NOT_installed_when_layout_present(tmp_path: Path) -> None:
workspace: Path = _workspace_for(tmp_path, "ini_custom")
workspace.mkdir(parents=True, exist_ok=True)
ini_path: Path = workspace / _INI_FILENAME
custom_lines: list[str] = ["[Window][CustomPanelPersistent]", "Pos=10,10", "DockId=0xDEAD", ""]
for i in range(32):
custom_lines.append(f"; padding line {i}")
custom_lines.append("[Window][Filler]")
custom_lines.append(f"Pos={i},{i}")
custom_lines.append("Size=100,100")
content: str = "\n".join(custom_lines) + "\n"
ini_path.write_text(content, encoding="utf-8")
assert ini_path.stat().st_size >= 1000, "custom INI must be at least 1000 bytes for this test"
proc: subprocess.Popen = _start_subprocess_in(workspace, "ini_custom")
try:
text: str = _read_ini(workspace)
assert "[Window][CustomPanelPersistent]" in text, (
f"custom INI should be preserved (no overwrite), but content changed: {text[:400]!r}"
)
assert "DockId=0xDEAD" in text, "custom DockId entry should survive install"
finally:
_terminate(proc)
shutil.rmtree(workspace, ignore_errors=True)