mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-04 12:58:08 +03:00
Fix native runtime lifecycle ordering
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
@@ -484,17 +484,16 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
|
||||
|
||||
if AppSettings.get('SystemProxyMode') == AppBuiltinProxyMode.Auto.value:
|
||||
SystemProxy.off()
|
||||
SystemProxy.daemonOn_()
|
||||
|
||||
self._cleanupStack.register(
|
||||
'automatic system proxy', self._cleanupSystemProxy
|
||||
)
|
||||
# sysproxy's change-notification daemon is a Windows-only resource.
|
||||
# ConnectionController owns the configured OS proxy and turns it
|
||||
# off during disconnection/shutdown on every platform.
|
||||
if PLATFORM == 'Windows':
|
||||
SystemProxy.daemonOn_()
|
||||
|
||||
@staticmethod
|
||||
def _cleanupSystemProxy():
|
||||
"""Release the exact automatic system-proxy integration."""
|
||||
SystemProxy.off()
|
||||
SystemProxy.daemonOff()
|
||||
self._cleanupStack.register(
|
||||
'Windows system proxy daemon', SystemProxy.daemonOff
|
||||
)
|
||||
|
||||
def _initializeUI(self):
|
||||
"""Create and bootstrap the application-owned main window and tray."""
|
||||
|
||||
@@ -511,14 +511,12 @@ class ProcessOutputRedirector:
|
||||
|
||||
time.sleep(MsgQueue.MSG_PRODUCE_THRESHOLD / 1000)
|
||||
|
||||
# TODO: try to resolve messages not shown in edge case
|
||||
time.sleep(2)
|
||||
|
||||
msgThread = threading.Thread(target=produceMsg, daemon=True)
|
||||
msgThread.start()
|
||||
|
||||
# TODO: try to resolve messages not shown in edge case
|
||||
time.sleep(2)
|
||||
|
||||
# The reader starts at the beginning of the temporary file, so output
|
||||
# written before its first scheduling slice is still consumed. Do not
|
||||
# delay the actual core entry point: the parent startup contract already
|
||||
# provides its bounded readiness interval.
|
||||
with tmpFileStream:
|
||||
entrypoint()
|
||||
|
||||
@@ -30,11 +30,15 @@ from PySide6 import QtCore
|
||||
from types import SimpleNamespace
|
||||
from unittest import TestCase, mock
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
import subprocess
|
||||
|
||||
CoreProcessWorkerModule = importlib.import_module('Furious.Core.CoreProcessWorker')
|
||||
|
||||
|
||||
class _Runtime(CoreRuntime):
|
||||
"""Record exact runtime ownership without starting a host process."""
|
||||
@@ -223,6 +227,140 @@ class ApplicationLifecycleTransactionTest(TestCase):
|
||||
runListener.assert_called_once_with()
|
||||
stopListener.assert_called_once_with()
|
||||
|
||||
def testSystemIntegrationSkipsWindowsProxyDaemonOnMacOS(self):
|
||||
"""Do not register a nonexistent native proxy-daemon resource on macOS."""
|
||||
cleanupStack = _ApplicationCleanupStack()
|
||||
application = SimpleNamespace(
|
||||
_cleanupStack=cleanupStack,
|
||||
_sessionShutdownRequested=SimpleNamespace(emit=mock.Mock()),
|
||||
setQuitOnLastWindowClosed=mock.Mock(),
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.Win32Session.set',
|
||||
return_value=False,
|
||||
),
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.AppSettings.get',
|
||||
return_value='Auto',
|
||||
),
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.PLATFORM',
|
||||
'Darwin',
|
||||
),
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.SystemProxy.off'
|
||||
) as proxyOff,
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.SystemProxy.daemonOn_'
|
||||
) as daemonOn,
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.SystemProxy.daemonOff'
|
||||
) as daemonOff,
|
||||
):
|
||||
DesktopApplication._initializeSystemIntegration(application)
|
||||
cleanupStack.close()
|
||||
|
||||
proxyOff.assert_called_once_with()
|
||||
daemonOn.assert_not_called()
|
||||
daemonOff.assert_not_called()
|
||||
|
||||
def testSystemIntegrationOwnsOnlyWindowsProxyDaemonCleanup(self):
|
||||
"""Keep OS proxy state under the connection controller's ownership."""
|
||||
cleanupStack = _ApplicationCleanupStack()
|
||||
application = SimpleNamespace(
|
||||
_cleanupStack=cleanupStack,
|
||||
_sessionShutdownRequested=SimpleNamespace(emit=mock.Mock()),
|
||||
setQuitOnLastWindowClosed=mock.Mock(),
|
||||
)
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.Win32Session.set',
|
||||
return_value=False,
|
||||
),
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.AppSettings.get',
|
||||
return_value='Auto',
|
||||
),
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.PLATFORM',
|
||||
'Windows',
|
||||
),
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.SystemProxy.off'
|
||||
) as proxyOff,
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.SystemProxy.daemonOn_'
|
||||
) as daemonOn,
|
||||
mock.patch(
|
||||
'Furious.Application.DesktopApplication.SystemProxy.daemonOff'
|
||||
) as daemonOff,
|
||||
):
|
||||
DesktopApplication._initializeSystemIntegration(application)
|
||||
cleanupStack.close()
|
||||
|
||||
proxyOff.assert_called_once_with()
|
||||
daemonOn.assert_called_once_with()
|
||||
daemonOff.assert_called_once_with()
|
||||
|
||||
def testProcessOutputRedirectorDoesNotDelayCoreEntrypoint(self):
|
||||
"""Begin the core immediately while its file reader drains early output."""
|
||||
|
||||
class _Stream:
|
||||
def __init__(self, descriptor):
|
||||
self.descriptor = descriptor
|
||||
|
||||
def fileno(self):
|
||||
return self.descriptor
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
class _TemporaryDir:
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
|
||||
def isValid(self):
|
||||
return True
|
||||
|
||||
def filePath(self, name):
|
||||
return os.path.join(self.directory, name)
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
thread = SimpleNamespace(start=mock.Mock())
|
||||
entrypoint = mock.Mock()
|
||||
fakeSys = SimpleNamespace(stdout=_Stream(1), stderr=_Stream(2))
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
CoreProcessWorkerModule.SystemRuntime,
|
||||
'isPythonw',
|
||||
return_value=False,
|
||||
),
|
||||
mock.patch.object(
|
||||
CoreProcessWorkerModule.ProcessOutputRedirector,
|
||||
'TemporaryDir',
|
||||
_TemporaryDir(directory),
|
||||
),
|
||||
mock.patch.object(CoreProcessWorkerModule, 'sys', fakeSys),
|
||||
mock.patch.object(CoreProcessWorkerModule.os, 'dup2'),
|
||||
mock.patch.object(
|
||||
CoreProcessWorkerModule.threading,
|
||||
'Thread',
|
||||
return_value=thread,
|
||||
),
|
||||
mock.patch.object(CoreProcessWorkerModule.time, 'sleep') as sleep,
|
||||
):
|
||||
CoreProcessWorkerModule.ProcessOutputRedirector.launch(
|
||||
mock.Mock(), entrypoint, True
|
||||
)
|
||||
|
||||
sleep.assert_not_called()
|
||||
thread.start.assert_called_once_with()
|
||||
entrypoint.assert_called_once_with()
|
||||
|
||||
def testFailuresAtMeaningfulStagesRollBackOnlyEarlierStages(self):
|
||||
expected = {
|
||||
'storage': ['cleanup storage', 'cleanup plugins'],
|
||||
|
||||
Reference in New Issue
Block a user