Files
AstronautCheetah af63405f81 feat(gui): set up the installer window without asking
./install.sh gui set up PyQt6 only after a kdialog question whose other answer was the simple
checklist, which is only a stand-in for when the window can't be had. It's set up at once now:
in the terminal under the splash (the one-liner), or from the app menu in a Konsole window of
its own, also under the splash. The checklist still stands in if the setup fails.
2026-10-16 19:12:29 -04:00

108 lines
5.1 KiB
Python

"""./install.sh gui never ends with nothing on screen: setting up PyQt6 happens without a
question, where it can be seen (a Konsole window of its own, under the splash, started from the
app menu), and a window that ends with an
error before it's shown leaves the simple checklist instead, saying so; both go to
logs/window.log. PyQt6 without its widgets is set up again, and the window's own copy of Python
follows the venv's. In a throwaway home: a stand-in Python, kdialog and konsole record what
they're asked."""
import os
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(Path(__file__).resolve().parent))
from helpers import sandbox_env, stub # noqa: E402
class WindowStart(unittest.TestCase):
def setUp(self):
self.home = Path(tempfile.mkdtemp())
self.addCleanup(shutil.rmtree, self.home, True)
self.stubs = self.home / "stubs"
self.calls = self.home / "calls"
for tool in ("kdialog", "konsole", "notify-send"):
# (kdialog: cancel for the checklist)
stub(self.stubs, tool, f'echo "{tool} $*" >> {self.calls}\n'
'case "$*" in *--checklist*) exit 1 ;; esac; exit 0')
self.venv = self.home / ".local/share/arrowpad/venv/bin"
self.venv.mkdir(parents=True)
def python(self, has_pyqt, window):
"""The shared venv's Python: PyQt6 there or not; the window's own run (a script)."""
p = self.venv / "python"
p.write_text("#!/bin/bash\n" + f'[ "$1" = -c ] && exit {0 if has_pyqt else 1}\n' + window + "\n")
p.chmod(0o755)
def gui(self):
env = sandbox_env(self.home, self.stubs, DISPLAY=":9", FRAME_TWEAKS_PACKS="")
return subprocess.run([str(ROOT / "install.sh"), "gui"], env=env, stdin=subprocess.DEVNULL,
capture_output=True, text=True, timeout=60)
def log(self):
return (self.home / ".local/state/frame-tweaks/logs/window.log").read_text()
def test_a_window_that_doesnt_open_leaves_the_checklist(self):
self.python(True, 'echo "Traceback: no display" >&2; exit 1')
r = self.gui()
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
calls = self.calls.read_text()
self.assertIn("kdialog --sorry The full window didn't open", calls)
self.assertIn("--checklist", calls) # (the simple checklist instead)
self.assertIn("Traceback: no display", self.log())
self.assertIn("the window ended with 1", self.log())
def test_a_window_closed_as_usual_leaves_nothing_more(self):
self.python(True, "exit 0")
r = self.gui()
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
self.assertNotIn("--sorry", self.calls.read_text() if self.calls.exists() else "")
self.assertNotIn("--checklist", self.calls.read_text() if self.calls.exists() else "")
def test_pyqt6_without_its_widgets_is_set_up_again(self):
"""import PyQt6 works but PyQt6.QtWidgets doesn't (a setup cut short): it's set up again,
rather than a window that can't open."""
p = self.venv / "python"
p.write_text('#!/bin/bash\nif [ "$1" = -c ]; then [[ $2 == *QtWidgets* ]] && exit 1; exit 0; fi\n'
f'echo "the window ran" >> {self.calls}\n')
p.chmod(0o755)
r = self.gui()
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
calls = self.calls.read_text()
self.assertIn("_setup-window", calls)
self.assertNotIn("the window ran", calls)
self.assertIn("--checklist", calls)
def test_the_windows_python_is_the_venvs(self):
"""bin/sft-python, the copy of the venv's Python the window runs on (its own name for
SteamVR), is replaced when it differs: also when it's read-only, as a copy of Homebrew's
Python is, or dated later than the venv's."""
self.python(True, f'echo "new $0" >> {self.calls}')
old = self.venv / "sft-python"
old.write_text(f'#!/bin/bash\n[ "$1" = -c ] && exit 0\necho "old $0" >> {self.calls}\n')
old.chmod(0o555)
os.utime(old, (time.time() + 3600,) * 2)
r = self.gui()
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
self.assertEqual(old.read_text(), (self.venv / "python").read_text())
self.assertEqual(self.calls.read_text(), f"new {old}\n")
def test_from_the_app_menu_the_setup_has_a_window_of_its_own(self):
self.python(False, "exit 0") # (the stand-in konsole sets up nothing)
r = self.gui()
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
calls = self.calls.read_text()
self.assertNotIn("--yesno", calls) # (no question first)
self.assertIn(f"konsole --separate -e python3 {ROOT}/lib/splash.py -- {ROOT}/install.sh _setup-window", calls)
self.assertIn("kdialog --sorry Couldn't install PyQt6", calls)
self.assertIn("--checklist", calls)
self.assertIn("setting up PyQt6 for the full window", self.log())
if __name__ == "__main__":
unittest.main()