Files
AstronautCheetah--frame-tweaks/tests/test_window_start.py
T
AstronautCheetah e83962408e fix(installer): update loops, uninstall checks, status crashes, relative paths, reports
From the bug hunt (each reproduced, each with a test that fails without the fix):
- A version record outliving its tweak made every update reinstall everything, for good
  (stale_tweaks counted it; cmd_reinstall skipped the tweak, so the record stayed). Only
  installed tweaks count, and an update forgets the records of tweaks that aren't installed.
- uninstall refused to remove a dependency only when the dependent's status was 0; one showing
  a warning or "restart" (2) is installed too (is_installed).
- ↻ of an installed tweak this Frame doesn't need (needed.sh) removed it: the reinstall forgot
  its record, then install skipped it. Tweaks being reinstalled are installed again.
- A status.sh that stopped at a failing command (set -e, exit 1 with no "missing" line) read as
  "not installed": updates skipped it, forget-settings deleted its settings, the window unticked
  it. For a tweak with a version record, or with an exit code other than 0-2, it's "status check
  failed" (2), in install.sh and the window. Firefox's no-Default= case uses status_missing.
- update returned 0 when a tweak didn't install again: now 3 (Frame Tweaks updated, a tweak
  failed), which get.sh tells from an update that couldn't happen and still opens the window.
- update without .git ignored holds (no FRAME_TWEAKS_UPDATING); rc → release ran no migrations
  (sort -V put 0.7.0 before 0.7.0-rc.2: tag_newer now).
- Run as frame-tweaks/install.sh, or by get.sh with a relative FRAME_TWEAKS_DIR, common.sh's cd
  failed and set -e stopped everything; get.sh makes the folder absolute.
- With every pack off, `mods on core` (the way back), update, help, version and the report were
  refused.
- Shared clipboard's status exited 1 under set -e for builds without a VERSION file, so it read
  as not installed and was never updated.
- ~/.bashrc without a final newline got the PATH/Homebrew line glued to its last line.
- The window's sft-python copy was never refreshed (cp -u over a read-only copy); PyQt6 checks
  now import QtWidgets; the menu entry is written whole (English if translating it fails).
- uninstall-self missed commits made on a detached HEAD; a fresh get.sh install ignored the
  saved channel; the report replaced "steamos" inside SteamOS names (a personal user name is
  still replaced everywhere).
2026-10-16 07:55:36 -04:00

108 lines
5.2 KiB
Python

"""./install.sh gui never ends with nothing on screen: setting up PyQt6 happens where it can be
seen (a Konsole window of its own, 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: yes to the setup question, cancel for the checklist)
stub(self.stubs, tool, f'echo "{tool} $*" >> {self.calls}\n'
'[ "$1" = --title ] && [ "$3" = --yesno ] && exit 0\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): the setup is offered,
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("It needs PyQt6, the toolkit it's drawn with", 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.assertIn("It needs PyQt6, the toolkit it's drawn with", calls)
self.assertIn(f"konsole --separate -e {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()