zapretgui/tests/test_gui_thread_priority.py
2026-10-17 17:41:47 +03:00

92 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Окно получает замок Python у фоновых потоков без ожидания, пока оно на экране.
Замер на win10, сборка страницы в GUI-потоке рядом с одним занятым фоновым
потоком: интервал переключения 1 мс — 2568 мс, интервал 0,5 мс — 92 мс.
Windows не ждёт меньше миллисекунды: интервал в 1 мс длится ~2 мс, а интервал
меньше 1 мс CPython округляет до нуля.
"""
from __future__ import annotations
import inspect
import os
import sys
import unittest
from pathlib import Path
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from PyQt6.QtWidgets import QApplication # noqa: E402
from ui import gui_thread_priority # noqa: E402
from ui.gui_thread_priority import ( # noqa: E402
BACKGROUND_GIL_SWITCH_INTERVAL_SEC,
GUI_GIL_SWITCH_INTERVAL_SEC,
set_gui_gil_priority,
)
_APP = QApplication.instance() or QApplication([])
class GuiGilSwitchIntervalTests(unittest.TestCase):
def setUp(self) -> None:
self._original_interval = sys.getswitchinterval()
self.addCleanup(sys.setswitchinterval, self._original_interval)
def test_gui_interval_is_below_one_millisecond(self) -> None:
# Ровно 1 мс и больше возвращают ожидание в ~2 мс на каждое
# обращение окна к своему коду.
self.assertLess(GUI_GIL_SWITCH_INTERVAL_SEC, 0.001)
self.assertGreater(GUI_GIL_SWITCH_INTERVAL_SEC, 0)
def test_startup_lowers_the_interval(self) -> None:
from main.qt_runtime import apply_gui_gil_switch_interval
sys.setswitchinterval(0.005)
apply_gui_gil_switch_interval()
self.assertEqual(sys.getswitchinterval(), GUI_GIL_SWITCH_INTERVAL_SEC)
def test_hidden_window_returns_python_default(self) -> None:
# В трее рывков никто не видит, а ждущий замка поток при коротком
# интервале не спит, а крутится.
set_gui_gil_priority(True)
set_gui_gil_priority(False)
self.assertEqual(sys.getswitchinterval(), BACKGROUND_GIL_SWITCH_INTERVAL_SEC)
self.assertEqual(BACKGROUND_GIL_SWITCH_INTERVAL_SEC, 0.005)
class GilPriorityWiringTests(unittest.TestCase):
def test_startup_turns_it_on_before_the_window_exists(self) -> None:
from main import qt_runtime
# На запуск приходится больше всего фоновой работы.
self.assertIn("apply_gui_gil_switch_interval()", inspect.getsource(qt_runtime.ensure_qt_runtime))
def test_window_presence_owns_it_after_startup(self) -> None:
from main import entry
source = inspect.getsource(entry._finish_event_loop_bootstrap)
self.assertIn("install_window_presence(window)", source)
def test_program_does_not_change_the_system_clock_step(self) -> None:
# Точный системный таймер Windows (timeBeginPeriod) был нужен только
# при интервале в 1 мс. Анимациям он не нужен: Qt ведёт свои точные
# таймеры сам (замер на win10: такт 33 мс — кадры через 33,0 мс и с
# ним, и без него), а энергию он расходует.
source_root = Path(gui_thread_priority.__file__).resolve().parents[1]
users = sorted(
str(path.relative_to(source_root))
for path in source_root.rglob("*.py")
if "timeBeginPeriod" in path.read_text(encoding="utf-8").replace("`timeBeginPeriod(1)`", "")
)
self.assertEqual(users, [])
if __name__ == "__main__":
unittest.main()