mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-22 23:08:08 +03:00
Fix platform-dependent regression test fixtures
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
@@ -252,6 +252,22 @@ cover an aliased root and rejection of paths outside that root. The QR responsiv
|
||||
queues unrelated work after generation starts and requires delivery before completion;
|
||||
it does not assume a platform-specific order between zero-delay Qt timers.
|
||||
|
||||
On the CI Qt 6.8.3 runtime, the shared offscreen application uses
|
||||
`fixtures/offscreen.json` to provide a 1920×1080 virtual desktop. Geometry fixtures
|
||||
account for translated controls' Qt minimum sizes instead of assuming one host's
|
||||
font metrics. Menu tests explicitly reactivate the owning test window after popup
|
||||
dismissal: macOS offscreen does not supply the desktop's reactivation event. They
|
||||
still assert the retained child focus and focus-scoped shortcuts. Selection-color
|
||||
checks sample an empty cell in logical coordinates, avoiding text glyphs and
|
||||
high-DPI image-coordinate assumptions. Log catch-up checks wait for the separate
|
||||
queued scroll update as well as document rendering.
|
||||
|
||||
The singleton race uses a short unique socket name that fits under macOS's long
|
||||
temporary paths and retains child stderr in failure diagnostics. The Windows
|
||||
executable-path test copies a Python interpreter and its DLLs into a disposable
|
||||
path with spaces, so it also works when Python and the checkout are on different
|
||||
volumes; it waits for child output before asserting shutdown.
|
||||
|
||||
The Home/Log workflow regressions exercise debounced typing, explicit submit/clear, navigation catch-up, focus-scoped
|
||||
Find/edit shortcuts, and live log filtering. These are real Qt tests in `test_qt_interactions.py` and
|
||||
`test_ui_behavior.py`; no production log source or profile network is started. The two optional pause regressions
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"screens": [
|
||||
{
|
||||
"name": "Furious test screen",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"logicalDpi": 96,
|
||||
"logicalBaseDpi": 96,
|
||||
"dpr": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
+17
-1
@@ -63,7 +63,23 @@ class TestApplication(QApplication):
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize one side-effect-free application for this test process."""
|
||||
super().__init__([])
|
||||
arguments = []
|
||||
|
||||
if os.environ.get('QT_QPA_PLATFORM') == 'offscreen':
|
||||
# The plugin's default 800x800 desktop cannot contain the translated
|
||||
# MainWindow minimum on all hosts. Give geometry tests a real-sized
|
||||
# virtual desktop without altering any physical display.
|
||||
configuration = Path(__file__).with_name('fixtures') / 'offscreen.json'
|
||||
# Platform arguments are colon-separated, including on Windows.
|
||||
configuration = os.path.relpath(configuration)
|
||||
|
||||
arguments = [
|
||||
'furious-tests',
|
||||
'-platform',
|
||||
f'offscreen:configfile={configuration}',
|
||||
]
|
||||
|
||||
super().__init__(arguments)
|
||||
|
||||
self.setApplicationName('Furious Tests')
|
||||
self.setOrganizationName('Furious Tests')
|
||||
|
||||
@@ -675,7 +675,9 @@ class ApplicationLifecycleTransactionTest(TestCase):
|
||||
|
||||
def testConcurrentProcessesElectExactlyOnePrimary(self):
|
||||
"""Exercise the real Qt local-server race with isolated processes."""
|
||||
serverName = f'furious-singleton-test-{uuid.uuid4()}'
|
||||
# Qt prefixes relative socket names with the temporary directory. macOS
|
||||
# paths are long enough that a descriptive prefix exceeds sun_path.
|
||||
serverName = uuid.uuid4().hex
|
||||
script = textwrap.dedent(r"""
|
||||
import os
|
||||
import sys
|
||||
@@ -799,7 +801,8 @@ class ApplicationLifecycleTransactionTest(TestCase):
|
||||
|
||||
for process in processes:
|
||||
stdout, stderr = process.communicate(timeout=15)
|
||||
outputs.append(stdout)
|
||||
outputs.append(stdout + stderr)
|
||||
|
||||
self.assertEqual(
|
||||
process.returncode,
|
||||
0,
|
||||
|
||||
@@ -48,6 +48,7 @@ import sys
|
||||
import json
|
||||
import time
|
||||
import tempfile
|
||||
import shutil
|
||||
import threading
|
||||
import subprocess
|
||||
import unittest
|
||||
@@ -593,27 +594,42 @@ class ExternalCoreProcessTest(unittest.TestCase):
|
||||
|
||||
lease.release()
|
||||
|
||||
@unittest.skipUnless(os.name == 'nt', 'Windows executable hard-link coverage')
|
||||
@unittest.skipUnless(os.name == 'nt', 'Windows executable path coverage')
|
||||
def testExecutablePathContainingSpaces(self):
|
||||
"""Launch an executable hard link whose local path contains spaces."""
|
||||
"""Launch a copied interpreter whose local path contains spaces."""
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix='furious executable path ', dir=Path.cwd()
|
||||
) as directory:
|
||||
executable = Path(directory) / 'python executable.exe'
|
||||
# The interpreter and checkout can be on different Windows volumes.
|
||||
# Copy the base interpreter (not a venv redirector) and its DLLs;
|
||||
# PYTHONHOME supplies its existing standard library without installing
|
||||
# an environment or writing into the interpreter's directory.
|
||||
shutil.copy2(sys._base_executable, executable)
|
||||
|
||||
os.link(sys.executable, executable)
|
||||
for library in Path(sys.base_prefix).glob('python*.dll'):
|
||||
shutil.copy2(library, directory)
|
||||
|
||||
config = self.configuration(
|
||||
['-c', 'import time; time.sleep(60)'],
|
||||
['-c', 'import time; print("ready", flush=True); time.sleep(60)'],
|
||||
directory,
|
||||
environment={'PYTHONHOME': sys.base_prefix},
|
||||
)
|
||||
config['executable'] = str(executable)
|
||||
messages = []
|
||||
runtime = ExternalCoreProcess(config, msgCallback=messages.append)
|
||||
|
||||
runtime = ExternalCoreProcess(config)
|
||||
|
||||
runtime.start()
|
||||
|
||||
runtime.stop()
|
||||
try:
|
||||
runtime.start()
|
||||
self.assertTrue(
|
||||
self.waitFor(
|
||||
lambda: any('ready' in message for message in messages)
|
||||
),
|
||||
messages,
|
||||
)
|
||||
self.assertTrue(runtime.isRunning())
|
||||
finally:
|
||||
runtime.stop()
|
||||
|
||||
self.assertFalse(runtime.isRunning())
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ from Furious.Backends.Hysteria2.Protocols import Hysteria2ProtocolHandler
|
||||
from Furious.Backends.Hysteria2.TunSettingsDialog import (
|
||||
GuiHysteria2TUNSettingsGroupBoxInterface,
|
||||
)
|
||||
from Furious.Frozenlib import AppSettings, Mixins
|
||||
from Furious.Frozenlib import AppSettings, Mixins, PLATFORM
|
||||
from Furious.Models.Profile import ServerProfile
|
||||
from Furious.Plugins.Runtime import serializeRuntimeConfiguration
|
||||
from Furious.Qt import AppStyleSheet
|
||||
@@ -226,7 +226,7 @@ class Hysteria2CompatibilityTest(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(
|
||||
proxyBandwidth.bandwidthFields._widget.layout().contentsMargins().top(),
|
||||
20,
|
||||
2 if PLATFORM == 'Darwin' else 20,
|
||||
)
|
||||
|
||||
editor.show()
|
||||
@@ -346,7 +346,9 @@ class Hysteria2CompatibilityTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(rowLayout.contentsMargins().left(), 0)
|
||||
self.assertEqual(rowLayout.contentsMargins().right(), 0)
|
||||
self.assertEqual(rowLayout.spacing(), 8)
|
||||
self.assertEqual(
|
||||
rowLayout.spacing(), 10 if PLATFORM == 'Darwin' else 8
|
||||
)
|
||||
self.assertIsNotNone(
|
||||
rowLayout.itemAt(rowLayout.count() - 1).spacerItem()
|
||||
)
|
||||
|
||||
@@ -243,6 +243,8 @@ class AppQMainWindowLifecycleTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
with isolatedSettings():
|
||||
AppSettings.set('Language', 'EN')
|
||||
|
||||
for windowType, expectedSize in cases:
|
||||
with self.subTest(windowType=windowType.__name__):
|
||||
window = windowType()
|
||||
@@ -250,7 +252,13 @@ class AppQMainWindowLifecycleTest(unittest.TestCase):
|
||||
with patch('Furious.Qt.QtWidgets.moveToCenter') as moveToCenter:
|
||||
window.show()
|
||||
|
||||
self.assertEqual(window.size(), expectedSize)
|
||||
self.assertEqual(
|
||||
window.size(),
|
||||
# Use the enforced layout constraint: a fixed-size
|
||||
# window may deliberately override its size hint.
|
||||
expectedSize.expandedTo(window.minimumSize()),
|
||||
)
|
||||
|
||||
moveToCenter.assert_called_once_with(window)
|
||||
|
||||
window.move(61, 73)
|
||||
@@ -343,12 +351,18 @@ class MainWindowNavigationSessionTest(unittest.TestCase):
|
||||
|
||||
def testNewWindowResetsSessionStateButRestoresGeometry(self):
|
||||
"""Reset page and expansion while retaining persistent geometry."""
|
||||
expectedSize = QtCore.QSize(760, 600)
|
||||
|
||||
with isolatedSettings() as settings:
|
||||
AppSettings.set('Language', 'EN')
|
||||
|
||||
with _realMainWindow() as firstWindow:
|
||||
firstWindow.show()
|
||||
expectedSize = (
|
||||
QtCore.QSize(760, 600)
|
||||
.expandedTo(firstWindow.minimumSizeHint())
|
||||
.expandedTo(firstWindow.minimumSize())
|
||||
)
|
||||
firstWindow.resize(expectedSize)
|
||||
self.assertEqual(firstWindow.size(), expectedSize)
|
||||
firstWindow.navigationView.setExpanded(True, animated=False)
|
||||
firstWindow.showPage('settings')
|
||||
firstWindow.cleanup()
|
||||
@@ -723,7 +737,14 @@ class MainWindowGeometryTest(unittest.TestCase):
|
||||
def testRoutingWindowKeepsValidSmallGeometryAndIgnoresInvalidState(self):
|
||||
"""Preserve intentional compact geometry independently from layout state."""
|
||||
with isolatedSettings():
|
||||
expected = QtCore.QRect(35, 45, 420, 260)
|
||||
AppSettings.set('Language', 'EN')
|
||||
window = XrayRoutingWindow()
|
||||
compactSize = (
|
||||
QtCore.QSize(420, 260)
|
||||
.expandedTo(window.minimumSizeHint())
|
||||
.expandedTo(window.minimumSize())
|
||||
)
|
||||
expected = QtCore.QRect(QtCore.QPoint(35, 45), compactSize)
|
||||
AppSettings.set(
|
||||
'UserRoutingWindowGeometry',
|
||||
self._saveGeometry(expected),
|
||||
@@ -733,8 +754,6 @@ class MainWindowGeometryTest(unittest.TestCase):
|
||||
QtCore.QByteArray(b'broken'),
|
||||
)
|
||||
|
||||
window = XrayRoutingWindow()
|
||||
|
||||
with patch('Furious.Qt.QtWidgets.moveToCenter') as moveToCenter:
|
||||
window.show()
|
||||
|
||||
|
||||
@@ -1316,6 +1316,16 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
p.metadata.profileId for p in (profiles[0], profiles[2])
|
||||
}
|
||||
|
||||
def selectionColor():
|
||||
# Grab in logical widget coordinates; raw image pixels
|
||||
# otherwise need conversion for the device pixel ratio.
|
||||
return (
|
||||
table.viewport()
|
||||
.grab(QtCore.QRect(sample, QtCore.QSize(1, 1)))
|
||||
.toImage()
|
||||
.pixelColor(0, 0)
|
||||
)
|
||||
|
||||
for theme in (AppStyleSheet.Light, AppStyleSheet.Dark):
|
||||
with self.subTest(theme=theme), mock.patch.object(
|
||||
application(), 'theme', return_value=theme
|
||||
@@ -1338,11 +1348,12 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
QTest.keyRelease(table, QtCore.Qt.Key_Control)
|
||||
processQtEvents()
|
||||
|
||||
rect = table.visualRect(table.proxyModel.index(0, 0))
|
||||
sample = QtCore.QPoint(rect.right() - 12, rect.center().y())
|
||||
before = (
|
||||
table.viewport().grab().toImage().pixelColor(sample)
|
||||
)
|
||||
# The address is empty in this fixture. Sample its
|
||||
# background rather than a font-dependent remark glyph.
|
||||
index = table.proxyModel.index(0, 2)
|
||||
self.assertEqual(index.data(), '')
|
||||
sample = table.visualRect(index).center()
|
||||
before = selectionColor()
|
||||
|
||||
self.assertEqual(
|
||||
before,
|
||||
@@ -1357,7 +1368,7 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
|
||||
self.assertFalse(home.testMenu.isVisible())
|
||||
self.assertEqual(
|
||||
table.viewport().grab().toImage().pixelColor(sample),
|
||||
selectionColor(),
|
||||
before,
|
||||
)
|
||||
|
||||
@@ -1373,7 +1384,7 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
expectedIds,
|
||||
)
|
||||
self.assertEqual(
|
||||
table.viewport().grab().toImage().pixelColor(sample),
|
||||
selectionColor(),
|
||||
before,
|
||||
)
|
||||
|
||||
@@ -1393,9 +1404,13 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
expectedIds,
|
||||
)
|
||||
|
||||
self.assertTrue(home.testButton.hasFocus())
|
||||
# macOS offscreen activates popups but does not
|
||||
# reactivate their owner on dismissal. Supply only
|
||||
# window activation; Qt must retain the focused child.
|
||||
home.activateWindow()
|
||||
self.assertTrue(waitFor(home.testButton.hasFocus))
|
||||
self.assertEqual(
|
||||
table.viewport().grab().toImage().pixelColor(sample),
|
||||
selectionColor(),
|
||||
before,
|
||||
)
|
||||
|
||||
@@ -1416,7 +1431,7 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
|
||||
self.assertFalse(table.property('keepSelectionHighlighted'))
|
||||
self.assertEqual(
|
||||
table.viewport().grab().toImage().pixelColor(sample),
|
||||
selectionColor(),
|
||||
QtGui.QColor(
|
||||
AppStyleSheet.paletteForTheme(theme)['raised']
|
||||
),
|
||||
@@ -1426,14 +1441,12 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
home.testButton.setFocus(QtCore.Qt.TabFocusReason)
|
||||
processQtEvents()
|
||||
|
||||
before = table.viewport().grab().toImage().pixelColor(sample)
|
||||
before = selectionColor()
|
||||
|
||||
QTest.keyPress(home.testButton, QtCore.Qt.Key_Space)
|
||||
processQtEvents()
|
||||
|
||||
self.assertEqual(
|
||||
table.viewport().grab().toImage().pixelColor(sample), before
|
||||
)
|
||||
self.assertEqual(selectionColor(), before)
|
||||
|
||||
QTest.keyRelease(home.testButton, QtCore.Qt.Key_Space)
|
||||
processQtEvents()
|
||||
@@ -1443,7 +1456,8 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
QTest.keyClick(home.testMenu, QtCore.Qt.Key_Escape)
|
||||
processQtEvents()
|
||||
|
||||
self.assertTrue(home.testButton.hasFocus())
|
||||
home.activateWindow()
|
||||
self.assertTrue(waitFor(home.testButton.hasFocus))
|
||||
|
||||
home.searchLineEdit.setFocus()
|
||||
|
||||
@@ -1510,8 +1524,9 @@ class SharedSettingsQtWorkflowTest(unittest.TestCase):
|
||||
self.assertEqual(imported, [True])
|
||||
|
||||
table = home.userServersQTableWidget
|
||||
home.activateWindow()
|
||||
table.setFocus()
|
||||
processQtEvents()
|
||||
self.assertTrue(waitFor(table.hasFocus))
|
||||
QTest.keyClick(table, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||||
processQtEvents()
|
||||
|
||||
|
||||
@@ -1999,7 +1999,8 @@ class UnifiedLogPageTest(unittest.TestCase):
|
||||
scrollbar = page.textBrowser.verticalScrollBar()
|
||||
|
||||
self.assertGreater(scrollbar.maximum(), 0)
|
||||
self.assertEqual(scrollbar.value(), scrollbar.maximum())
|
||||
# Rendering completion schedules a separate zero-delay scroll update.
|
||||
self.assertTrue(waitFor(lambda: scrollbar.value() == scrollbar.maximum()))
|
||||
self.assertTrue(page._followTail)
|
||||
|
||||
page.hide()
|
||||
@@ -3130,7 +3131,8 @@ class DialogBehaviorTest(unittest.TestCase):
|
||||
[rule['ruleTag'] for rule in view.rules()], ['0', '1', '2', '3']
|
||||
)
|
||||
self.assertEqual(view.selectedIndex, [2])
|
||||
self.assertTrue(view.hasFocus())
|
||||
dialog.activateWindow()
|
||||
self.assertTrue(waitFor(view.hasFocus))
|
||||
|
||||
def testHomeAndRoutingMoveMenusRetranslateWithoutChangingActions(self):
|
||||
with isolatedSettings():
|
||||
|
||||
Reference in New Issue
Block a user