3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
123 lines
4.6 KiB
Python
123 lines
4.6 KiB
Python
# SPDX-License-Identifier: MIT
|
|
"""Tests for detached ``winpodx gui`` launch (#549)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import importlib.util
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from winpodx.gui import spawn
|
|
|
|
# The dispatch path imports winpodx.gui.main_window (PySide6); the spawn helper
|
|
# itself does not. CI's test job installs .[dev] without [gui], so guard only
|
|
# the dispatch tests — should_detach/spawn coverage still runs everywhere.
|
|
_HAS_PYSIDE6 = importlib.util.find_spec("PySide6") is not None
|
|
|
|
|
|
class TestShouldDetachGui:
|
|
def test_foreground_never_detaches(self):
|
|
assert spawn.should_detach_gui(foreground=True) is False
|
|
|
|
def test_interactive_tty_detaches(self):
|
|
with patch("sys.stdout") as so, patch("sys.stdin") as si:
|
|
so.isatty.return_value = True
|
|
si.isatty.return_value = False
|
|
assert spawn.should_detach_gui(foreground=False) is True
|
|
|
|
def test_non_tty_stays_inline(self):
|
|
# .desktop autostart / launcher subprocess: no terminal to free.
|
|
with patch("sys.stdout") as so, patch("sys.stdin") as si:
|
|
so.isatty.return_value = False
|
|
si.isatty.return_value = False
|
|
assert spawn.should_detach_gui(foreground=False) is False
|
|
|
|
def test_isatty_raising_is_safe(self):
|
|
with patch("sys.stdout") as so:
|
|
so.isatty.side_effect = ValueError("detached")
|
|
assert spawn.should_detach_gui(foreground=False) is False
|
|
|
|
|
|
class TestSpawnGuiDetached:
|
|
def test_spawns_foreground_child_in_new_session(self):
|
|
with (
|
|
patch("winpodx.gui.spawn.shutil.which", return_value="/usr/bin/winpodx"),
|
|
patch("winpodx.gui.spawn.subprocess.Popen") as popen,
|
|
):
|
|
assert spawn.spawn_gui_detached() is True
|
|
args, kw = popen.call_args
|
|
assert args[0] == ["/usr/bin/winpodx", "gui", "--foreground"]
|
|
assert kw["start_new_session"] is True
|
|
|
|
def test_source_checkout_fallback(self):
|
|
with (
|
|
patch("winpodx.gui.spawn.shutil.which", return_value=None),
|
|
patch("winpodx.gui.spawn.subprocess.Popen") as popen,
|
|
):
|
|
assert spawn.spawn_gui_detached() is True
|
|
cmd = popen.call_args[0][0]
|
|
assert cmd[1:] == ["-m", "winpodx", "gui", "--foreground"]
|
|
|
|
def test_popen_failure_returns_false(self):
|
|
with (
|
|
patch("winpodx.gui.spawn.shutil.which", return_value="/usr/bin/winpodx"),
|
|
patch("winpodx.gui.spawn.subprocess.Popen", side_effect=OSError("boom")),
|
|
):
|
|
assert spawn.spawn_gui_detached() is False
|
|
|
|
|
|
@pytest.mark.skipif(not _HAS_PYSIDE6, reason="GUI dispatch path requires PySide6")
|
|
class TestGuiDispatch:
|
|
def _dispatch(self, foreground):
|
|
from winpodx.cli.main import _dispatch
|
|
|
|
_dispatch(argparse.Namespace(command="gui", foreground=foreground))
|
|
|
|
def test_interactive_detaches_without_running_loop(self, capsys):
|
|
with (
|
|
patch("winpodx.gui.main_window.run_gui") as run_gui,
|
|
patch("winpodx.gui.spawn.should_detach_gui", return_value=True),
|
|
patch("winpodx.gui.spawn.spawn_gui_detached", return_value=True) as sp,
|
|
):
|
|
self._dispatch(foreground=False)
|
|
sp.assert_called_once()
|
|
run_gui.assert_not_called()
|
|
assert "launched" in capsys.readouterr().out.lower()
|
|
|
|
def test_foreground_runs_loop_inline(self):
|
|
with (
|
|
patch("winpodx.gui.main_window.run_gui") as run_gui,
|
|
patch("winpodx.gui.spawn.should_detach_gui", return_value=False),
|
|
patch("winpodx.gui.spawn.spawn_gui_detached") as sp,
|
|
):
|
|
self._dispatch(foreground=True)
|
|
run_gui.assert_called_once()
|
|
sp.assert_not_called()
|
|
|
|
def test_spawn_failure_falls_back_to_inline(self):
|
|
# Detach wanted but the spawn failed -> still show a GUI in-process.
|
|
with (
|
|
patch("winpodx.gui.main_window.run_gui") as run_gui,
|
|
patch("winpodx.gui.spawn.should_detach_gui", return_value=True),
|
|
patch("winpodx.gui.spawn.spawn_gui_detached", return_value=False),
|
|
):
|
|
self._dispatch(foreground=False)
|
|
run_gui.assert_called_once()
|
|
|
|
|
|
def test_gui_subparser_accepts_foreground():
|
|
from winpodx.cli.main import cli
|
|
|
|
# --foreground must parse; we stub the heavy dispatch so nothing launches.
|
|
with (
|
|
patch("winpodx.cli.main._dispatch") as disp,
|
|
patch("winpodx.cli.first_run.maybe_run_first_run_prompt"),
|
|
):
|
|
try:
|
|
cli(["gui", "--foreground"])
|
|
except SystemExit:
|
|
pytest.fail("`gui --foreground` should parse cleanly")
|
|
assert disp.call_args[0][0].foreground is True
|