zapretgui/tests/test_startup_autostart_order.py
loop-uh f402387851
All checks were successful
Zapret source guards / validate (push) Successful in 9s
Windows-сборка / Dev Dev 21.1.7.67 опубликован
Запуск: окно появляется один раз и сразу готовым, первые секунды без рывков
Окно показывается после сборки интерфейса, а не до неё: раньше Windows
выводила на экран пустое белое окно, и оно стояло так всё время сборки.
Между показом и первой отрисовкой окно скрыто от экрана средством Windows
(ui/window_first_frame.py), поэтому белой вспышки нет. Боковое меню целиком
и поле поиска строятся до показа, заголовки групп встают на место сразу —
меню больше не достраивается и не съезжает на глазах.

Фоновые задачи запуска идут по одной на общей дорожке StartupLane
(main/post_startup_threading.py), а не в своём потоке у каждой подсистемы:
в первые полторы секунды одновременно работали пять-семь потоков и по
очереди отбирали общий замок Python друг у друга и у окна. Свой поток
остался только у очередей, которые ждут сеть. То, что сразу видно на главной
странице (список профилей), считается первым; подсчёт частоты стратегий
вынесен в отдельный прогрев в конец очереди. Скрытые страницы собираются
заранее, только когда дорожка пуста и обход уже запущен.

Интервал переключения замка Python для видимого окна уменьшен с 1 до 0,5 мс:
Windows не ждёт меньше миллисекунды, поэтому 1 мс длился ~2 мс на каждое
обращение окна к своему коду, а интервал меньше 1 мс CPython округляет до
нуля.

Замеры на win10 (до → после): рывки в первые полторы секунды после
появления окна ~1150 мс → 96 мс; список профилей 960 → 140 мс; сборка
списков адресов 1097 → 18 мс; запуск winws2 1695 → 912 мс; сборка страницы
в GUI-потоке рядом с одним занятым фоновым потоком 2568 → 113 мс.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 01:41:53 +03:00

107 lines
3.9 KiB
Python

from __future__ import annotations
import sys
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock, patch
PROJECT_SRC = Path(__file__).resolve().parents[1] / "src"
if str(PROJECT_SRC) not in sys.path:
sys.path.insert(0, str(PROJECT_SRC))
class StartupAutostartOrderTests(unittest.TestCase):
def test_theme_and_background_steps_do_not_block_startup_autostart(self) -> None:
from main import startup_coordinator
from main.startup_coordinator import StartupCoordinator
class Runtime:
def __init__(self) -> None:
self.calls: list[str] = []
def init_launch_runtime_api(self) -> None:
self.calls.append("runtime_api")
def init_launch_runtime(self) -> None:
self.calls.append("runtime")
def init_process_monitor(self) -> None:
self.calls.append("process_monitor")
def init_core_startup(self) -> None:
self.calls.append("core_startup")
def snapshot(self):
return SimpleNamespace(launch_method="zapret2_mode")
def start_autostart(self, launch_method: str | None = None) -> None:
self.calls.append(f"autostart:{launch_method}")
runtime = Runtime()
migrate_gui_autostart = Mock(return_value=False)
coordinator = StartupCoordinator(
runtime_feature=runtime,
tray_feature=SimpleNamespace(init=Mock(), is_initialized=Mock(return_value=False)),
window_shell=SimpleNamespace(
start_in_tray=False,
set_status=Mock(),
mark_startup_core_ready=Mock(),
mark_startup_post_init_done=Mock(),
init_theme_manager=Mock(side_effect=lambda: runtime.calls.append("theme")),
),
log_startup_metric=Mock(),
migrate_gui_autostart=migrate_gui_autostart,
)
scheduled: list[tuple[int, object]] = []
background_targets: list[tuple[str, object]] = []
with (
patch.object(startup_coordinator, "run_queued", side_effect=lambda callback: scheduled.append((0, callback))),
patch.object(
startup_coordinator.QTimer,
"singleShot",
side_effect=lambda delay_ms, callback: scheduled.append((int(delay_ms), callback)),
),
patch.object(
startup_coordinator,
"start_daemon_thread",
side_effect=lambda name, target: background_targets.append((str(name), target)),
),
patch.object(
startup_coordinator,
"enqueue_subsystem_task",
side_effect=lambda _queue, name, target: background_targets.append((str(name), target)),
),
):
coordinator.run_async_init()
while scheduled:
_delay_ms, callback = scheduled.pop(0)
callback()
self.assertEqual(
runtime.calls,
["runtime_api", "runtime", "autostart:zapret2_mode", "theme", "process_monitor"],
)
targets_by_name = dict(background_targets)
self.assertEqual(
set(targets_by_name),
{"GuiAutostartMigration", "StartupStep-core_startup"},
)
targets_by_name["GuiAutostartMigration"]()
migrate_gui_autostart.assert_called_once_with()
targets_by_name["StartupStep-core_startup"]()
while scheduled:
_delay_ms, callback = scheduled.pop(0)
callback()
self.assertEqual(
runtime.calls,
["runtime_api", "runtime", "autostart:zapret2_mode", "theme", "process_monitor", "core_startup"],
)
if __name__ == "__main__":
unittest.main()