diff --git a/Furious/Qt/QtWidgets.py b/Furious/Qt/QtWidgets.py index 456b26fa..67bc7f29 100644 --- a/Furious/Qt/QtWidgets.py +++ b/Furious/Qt/QtWidgets.py @@ -1163,6 +1163,7 @@ class AppQMessageBox(AppQTransientDialog): | QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.NoDropShadowWindowHint ) + self.setWindowFlags(windowFlags) self.setAttribute( @@ -1176,6 +1177,7 @@ class AppQMessageBox(AppQTransientDialog): QPalette.ColorRole.Window, QColor(QtCore.Qt.GlobalColor.transparent), ) + self.setPalette(windowPalette) self.setModal(True) diff --git a/Furious/Service/SubscriptionSync.py b/Furious/Service/SubscriptionSync.py index 8e6d7d30..b47bfe48 100644 --- a/Furious/Service/SubscriptionSync.py +++ b/Furious/Service/SubscriptionSync.py @@ -241,6 +241,7 @@ class SubscriptionSynchronizer: if profile.itemSubscription == groupId and profile.itemSubscriptionManaged ] insertionIndex = min(managedIndexes) if managedIndexes else len(profiles) + existingByKey = { legacyKeys.get( id(profiles[index]), diff --git a/Furious/Widget/EndpointInfoWidget.py b/Furious/Widget/EndpointInfoWidget.py index 9667b0ed..c7cdb38c 100644 --- a/Furious/Widget/EndpointInfoWidget.py +++ b/Furious/Widget/EndpointInfoWidget.py @@ -727,6 +727,7 @@ class EndpointInfoWidget(Mixins.ThemeAware, Mixins.QTranslatable, QFrame): super().__init__(parent) self.service = service + self.setObjectName('MetricsSection') self.setMinimumHeight(360) @@ -899,6 +900,7 @@ class EndpointInfoWidget(Mixins.ThemeAware, Mixins.QTranslatable, QFrame): mapMessage = '' self.mapWidget.setLocation(location, mapMessage, loading=mapLoading) + self._setLoadingAnimationsEnabled(self.isVisible()) def _setLoadingAnimationsEnabled(self, enabled): diff --git a/tests/fixtures/shutdown_probe.py b/tests/fixtures/shutdown_probe.py index c0f94d00..10ff89ac 100644 --- a/tests/fixtures/shutdown_probe.py +++ b/tests/fixtures/shutdown_probe.py @@ -209,6 +209,7 @@ def runShutdownProbe(mode, *, timeout=0.5, exitcode=0, idleMilliseconds=0): """Return actual child reaping and exit classification, with a test failsafe.""" with tempfile.TemporaryDirectory(prefix='furious-shutdown-') as directory: path = str(Path(directory) / 'events.txt') + # The child may be killed before publishing any events. Preserve an # empty log so the result still exposes its exit/deadline diagnostics. Path(path).touch() @@ -241,6 +242,7 @@ def runShutdownProbe(mode, *, timeout=0.5, exitcode=0, idleMilliseconds=0): try: result = process.waitForExit() + return { 'mode': mode, 'exitcode': result, diff --git a/tests/test_application_process.py b/tests/test_application_process.py index 8f4fc2ef..4055d0dd 100644 --- a/tests/test_application_process.py +++ b/tests/test_application_process.py @@ -247,6 +247,7 @@ class AppMainProcessTest(unittest.TestCase): ApplicationRunner.ExitCode.UnknownException.value, result, ) + self.assertEqual( diagnostic.count('Traceback (most recent call last):'), 1, result ) diff --git a/tests/test_controllers.py b/tests/test_controllers.py index ab73b052..14f83c2d 100644 --- a/tests/test_controllers.py +++ b/tests/test_controllers.py @@ -937,6 +937,7 @@ class RoutingControllerTest(unittest.TestCase): mock.patch('sys.excepthook') as exceptionHook, ): AppSettings.set('Routing', 'Custom:active') + controller = RoutingController() with mock.patch.object(app, 'routingController', controller): @@ -954,6 +955,7 @@ class RoutingControllerTest(unittest.TestCase): ) prompt = view.findChild(MBoxNewChangesNextTime) + self.assertIsNotNone(prompt) prompt.button(AppQMessageBox.StandardButton.Yes).click() @@ -970,6 +972,7 @@ class RoutingControllerTest(unittest.TestCase): view.indexWidget(view.sourceModel.index(0, 2)).setCurrentIndex( 0 ) + tray.rebuildMenu() self.assertEqual(controller.routing, fallback) @@ -1013,6 +1016,7 @@ class RoutingControllerTest(unittest.TestCase): view.indexWidget(view.sourceModel.index(0, 2)).setCurrentIndex( 0 ) + tray.rebuildMenu() self.assertEqual(controller.routing, fallback) @@ -1021,13 +1025,17 @@ class RoutingControllerTest(unittest.TestCase): exceptionHook.assert_not_called() finally: connection.startDisconnection() + view.deleteLater() controller.deleteLater() + processQtEvents() connection.deleteLater() logManager.deleteLater() + registry.shutdown() + processQtEvents() def testOnlyExplicitInvalidationPersistsFallbackWithoutReconnecting(self): diff --git a/tests/test_endpoint_info.py b/tests/test_endpoint_info.py index 4bd66327..e6d7e0a4 100644 --- a/tests/test_endpoint_info.py +++ b/tests/test_endpoint_info.py @@ -841,6 +841,7 @@ class EndpointInfoServiceTest(unittest.TestCase): self.assertIsInstance(widget.mapWidget.webView, QWebEngineView) self.assertEqual(widget.mapWidget.webProfile.httpUserAgent(), 'Mozilla/5.0') self.assertNotIn('Furious', widget.mapWidget.webProfile.httpUserAgent()) + self.assertTrue(widget.mapWidget._lastWebState['markerVisible']) self.assertEqual( widget.mapWidget._lastWebState['fontFamily'], @@ -942,6 +943,7 @@ class EndpointInfoServiceTest(unittest.TestCase): self.assertIn("'source-layer': 'place'", mapScript) self.assertIn("'source-layer': 'poi'", mapScript) self.assertIn("'text-field': nameField", mapScript) + self.assertIn('Number.isFinite(longitude)', mapScript) self.assertNotIn('lightStyleUrl', widget.mapWidget._lastWebState) self.assertNotIn('darkStyleUrl', widget.mapWidget._lastWebState) diff --git a/tests/test_hysteria2_compatibility.py b/tests/test_hysteria2_compatibility.py index 735633cb..62a1ed1e 100644 --- a/tests/test_hysteria2_compatibility.py +++ b/tests/test_hysteria2_compatibility.py @@ -286,6 +286,7 @@ class Hysteria2CompatibilityTest(unittest.TestCase): for theme in (AppStyleSheet.Light, AppStyleSheet.Dark): with self.subTest(theme=theme): app.setStyleSheet(AppStyleSheet.forTheme(theme)) + editor = Hysteria2Editor() editor.show() app.processEvents() @@ -334,6 +335,7 @@ class Hysteria2CompatibilityTest(unittest.TestCase): editor.groupBoxSequence(), firstWidgets ) ) + self.assertEqual(len(set(firstPositions)), 1) row = advanced.optionsRow._widget diff --git a/tests/test_log_manager_generation.py b/tests/test_log_manager_generation.py index bba4a3b5..796d7673 100644 --- a/tests/test_log_manager_generation.py +++ b/tests/test_log_manager_generation.py @@ -204,11 +204,13 @@ def _assertManagerInvariants(testCase, manager, model=None): for categoryId, categoryEntries in generation.entriesByCategory.items(): category = manager._categories[categoryId] + testCase.assertIs( manager._generationForCategoryLocked(category), generation ) indexed = tuple(categoryEntries.entries.values()) + testCase.assertTrue(indexed) testCase.assertEqual( tuple(entry.sequence for entry in indexed), @@ -901,35 +903,44 @@ class GenerationLogManagerContractTest(unittest.TestCase): for message, categoryId in messages: manager.append(message, categoryId) + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager) coreIndex = page.filterComboBox.findData(CORE_LOG_CATEGORY) page.filterComboBox.setCurrentIndex(coreIndex) + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager, CORE_LOG_CATEGORY) manager.clear(CORE_LOG_CATEGORY) + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager, CORE_LOG_CATEGORY) manager.append('runtime\ncore', CORE_LOG_CATEGORY) manager.append('runtime\rcomponent', TUN_LOG_CATEGORY) + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager, CORE_LOG_CATEGORY) manager.clear(runtimeOnly=True) + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager, CORE_LOG_CATEGORY) allIndex = page.filterComboBox.findData('all') page.filterComboBox.setCurrentIndex(allIndex) + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager) manager.append('before\nfull clear') + self.assertTrue(waitFor(lambda: not page._entriesDirty)) + manager.clear() + self.assertTrue(waitFor(lambda: not page._entriesDirty)) self.assertPageMatchesManager(page, manager) diff --git a/tests/test_profile_test_jobs.py b/tests/test_profile_test_jobs.py index d1effdfd..10c4ea55 100644 --- a/tests/test_profile_test_jobs.py +++ b/tests/test_profile_test_jobs.py @@ -1423,6 +1423,7 @@ assert not errors, errors profile.metadata.speed = f'{profile.itemRemark} speed' manager = self._manager((retained, removed, other, manual)) + latencyScheduler = manager._latencyScheduler pool = _ControlledThreadPool() latencyScheduler.threadPool = pool @@ -1434,6 +1435,7 @@ assert not errors, errors concurrent=False, ) manager.testDownloadSpeed((retained,), concurrent=True) + processQtEvents() activeLatency = pool.started[0] @@ -1487,6 +1489,7 @@ assert not errors, errors self.assertEqual(len(pool.started), 2) otherLatency = pool.started[1] + otherLatency.finish('9ms') processQtEvents() diff --git a/tests/test_sing_tun_linux_host.py b/tests/test_sing_tun_linux_host.py index 9b7d8d09..6549ebf1 100644 --- a/tests/test_sing_tun_linux_host.py +++ b/tests/test_sing_tun_linux_host.py @@ -103,6 +103,7 @@ class SingTUNLinuxHostTest(unittest.TestCase): Path(arguments[2]).parent.joinpath('ready').write_text( '77', encoding='utf-8' ) + return process link = {'ifname': 'utun101', 'ifindex': 77} @@ -117,8 +118,10 @@ class SingTUNLinuxHostTest(unittest.TestCase): self.module, '_command', return_value=json.dumps([link]) ): self.host.prepare() + with self.assertRaisesRegex(RuntimeError, 'already active'): self.host.execute([]) + self.host.applyDNS('1.1.1.1') self.assertEqual(self.host.index, 77) @@ -135,6 +138,7 @@ class SingTUNLinuxHostTest(unittest.TestCase): self.assertIn('/sys/class/net/utun101/ifindex', scripts[0]) self.assertIn('resolvectl dns utun101 1.1.1.1', scripts[0]) self.assertNotIn('socks5', ''.join(scripts)) + self.assertIs(self.host._process, process) self.assertIsNotNone(self.host._directory) @@ -157,6 +161,7 @@ class SingTUNLinuxHostTest(unittest.TestCase): checked = subprocess.run( [bash, '-n', '-c', script], capture_output=True, timeout=5 ) + self.assertEqual( checked.returncode, 0, checked.stderr.decode(errors='replace') ) diff --git a/tests/test_stylesheet_states.py b/tests/test_stylesheet_states.py index efad2c43..a7244a8b 100644 --- a/tests/test_stylesheet_states.py +++ b/tests/test_stylesheet_states.py @@ -429,6 +429,7 @@ class StyleSheetStateRenderingTest(unittest.TestCase): buttons = edit.findChildren(QToolButton) self.assertEqual(len(buttons), 1) button = buttons[0] + self.assertTrue(edit.rect().contains(button.geometry())) self.assertLessEqual( abs( @@ -439,6 +440,7 @@ class StyleSheetStateRenderingTest(unittest.TestCase): ) self.assertFalse(button.icon().isNull()) + pixmap = button.icon().pixmap(16, 16).toImage() colors = [ pixmap.pixelColor(x, y) @@ -464,6 +466,7 @@ class StyleSheetStateRenderingTest(unittest.TestCase): edit.setText('read only') edit.setReadOnly(True) + app.setStyleSheet(AppStyleSheet.forTheme(AppStyleSheet.Light)) processQtEvents()