mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-09-28 18:08:11 +03:00
Update protocol editor tests and guidance
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
+50
-22
@@ -1,10 +1,12 @@
|
||||
# Furious test suite
|
||||
|
||||
The suite uses Python's built-in `unittest` runner. Qt tests select the
|
||||
`offscreen` platform before importing PySide6, construct one deliberately small
|
||||
test `QApplication`, and route `QSettings` to a unique temporary directory.
|
||||
They do not initialize Furious's singleton IPC server, production repositories,
|
||||
system proxy, TUN, routing, update network clients, or real proxy cores.
|
||||
The suite uses Python's built-in `unittest` runner. In the main test process,
|
||||
Qt tests use one deliberately small `QApplication` and route `QSettings` to a
|
||||
unique temporary directory. Focused lifecycle regressions may start a hermetic
|
||||
child Python process, and external-runtime tests use that child interpreter as
|
||||
a harmless stand-in for a core. Tests do not initialize Furious's singleton IPC
|
||||
server, production repositories, system proxy, TUN, routing, update network
|
||||
clients, or real proxy cores.
|
||||
|
||||
## Coverage map
|
||||
|
||||
@@ -12,10 +14,11 @@ system proxy, TUN, routing, update network clients, or real proxy cores.
|
||||
| --- | --- |
|
||||
| Configuration, profiles, migration, repositories | `test_models_and_services.py` |
|
||||
| Low-level application, runtime, editor, and storage contracts | `test_interface.py` |
|
||||
| Application composition, startup rollback, and connection ownership | `test_architecture_refactors.py` |
|
||||
| Plugin registration, capability dispatch, factories, rollback | `test_plugin_architecture.py` |
|
||||
| Controller state and error transitions with injected runtimes | `test_controllers.py` |
|
||||
| SOCKS/SIP002 Shadowsocks codecs and generated round trips | `test_socks_uri.py`, `test_shadowsocks_uri.py` |
|
||||
| Subscription-group reconciliation and ownership isolation | `test_subscription_sync.py` |
|
||||
| SOCKS and SIP002 Shadowsocks codecs and generated round trips | `test_socks_uri.py`, `test_shadowsocks_uri.py` |
|
||||
| Subscription workflow, timers, stale requests, and reconciliation | `test_subscription_manager.py`, `test_subscription_sync.py` |
|
||||
| External process launch, output, shutdown, threads, TUN metadata | `test_external_core.py` |
|
||||
| Xray/Hysteria2 native-TUN ownership and proxy-only stripping | `test_native_tun_semantics.py` |
|
||||
| Rolling metrics, stable buckets, lazy rendering, and hover | `test_metrics_behavior.py` |
|
||||
@@ -38,7 +41,14 @@ than by starting the production application runtime.
|
||||
|
||||
## Commands
|
||||
|
||||
From the repository root, select the offscreen Qt platform for your shell.
|
||||
Run commands from the repository root with the project and its runtime
|
||||
dependencies installed in the active environment. If the root contains a
|
||||
`.venv*` or `venv*` environment, activate it first; the examples intentionally
|
||||
use its `python` command rather than a machine-specific interpreter path.
|
||||
|
||||
Select the offscreen Qt platform **before Python starts**. Some test modules
|
||||
import Qt-backed Furious modules before `tests.support` can apply its defensive
|
||||
default, so setting it only after test discovery begins is too late.
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
@@ -58,15 +68,14 @@ Linux, macOS, and other Unix-compatible shells:
|
||||
export QT_QPA_PLATFORM=offscreen
|
||||
```
|
||||
|
||||
Then run the desired test tier. These commands use the active Python
|
||||
environment, so activate the project's virtual environment first when needed.
|
||||
Then run the desired test tier.
|
||||
|
||||
```text
|
||||
# Everything
|
||||
# Complete suite, including repeated lifetime/process stress tests
|
||||
python -m unittest discover -s tests -v
|
||||
|
||||
# Fast logic, persistence, plugin, controller, codec, and UI behavior
|
||||
python -m unittest tests.test_interface tests.test_models_and_services tests.test_plugin_architecture tests.test_controllers tests.test_subscription_sync tests.test_socks_uri tests.test_shadowsocks_uri tests.test_native_tun_semantics tests.test_metrics_behavior tests.test_endpoint_info tests.test_service_runtime tests.test_frozenlib tests.test_isolation_and_navigation tests.test_ui_behavior -v
|
||||
# Regular logic, persistence, plugin, controller, codec, and UI regressions
|
||||
python -m unittest tests.test_interface tests.test_models_and_services tests.test_architecture_refactors tests.test_plugin_architecture tests.test_controllers tests.test_subscription_manager tests.test_subscription_sync tests.test_socks_uri tests.test_shadowsocks_uri tests.test_native_tun_semantics tests.test_metrics_behavior tests.test_endpoint_info tests.test_service_runtime tests.test_frozenlib tests.test_isolation_and_navigation tests.test_ui_behavior -v
|
||||
|
||||
# Direct Qt/process integration and destruction/lifetime checks
|
||||
python -m unittest tests.test_external_core tests.test_qt_lifetime -v
|
||||
@@ -74,21 +83,38 @@ python -m unittest tests.test_external_core tests.test_qt_lifetime -v
|
||||
# Explicit slow stress tier
|
||||
python -m unittest tests.test_qt_stress tests.test_process_stress -v
|
||||
|
||||
# Order-independence spot check (reverse the module order, then run all tests)
|
||||
python -m unittest tests.test_ui_behavior tests.test_isolation_and_navigation tests.test_metrics_behavior tests.test_subscription_sync tests.test_controllers tests.test_plugin_architecture tests.test_models_and_services -v
|
||||
# Shared-state order-independence spot check
|
||||
python -m unittest tests.test_ui_behavior tests.test_isolation_and_navigation tests.test_frozenlib tests.test_service_runtime tests.test_endpoint_info tests.test_metrics_behavior tests.test_native_tun_semantics tests.test_shadowsocks_uri tests.test_socks_uri tests.test_subscription_sync tests.test_subscription_manager tests.test_controllers tests.test_plugin_architecture tests.test_architecture_refactors tests.test_models_and_services tests.test_interface -v
|
||||
python -m unittest discover -s tests -v
|
||||
```
|
||||
|
||||
No external network access or installed Xray/Hysteria executable is required.
|
||||
To run one module, class, or method while developing, pass its dotted test name
|
||||
to the same runner, for example:
|
||||
|
||||
```text
|
||||
python -m unittest tests.test_endpoint_info -v
|
||||
python -m unittest tests.test_endpoint_info.EndpointInfoServiceTest -v
|
||||
```
|
||||
|
||||
No external network access or separately installed Xray/Hysteria executable is
|
||||
required. Process-boundary tests create only temporary scripts and launch the
|
||||
active Python interpreter. Two regressions cover Windows-specific behavior and
|
||||
are skipped on other platforms; native handle/RSS trend checks use the safe
|
||||
platform counters available on the current host.
|
||||
|
||||
Several negative-path tests intentionally emit warning or error log messages,
|
||||
and the stress tier prints resource summaries. Treat the runner's final status
|
||||
and exit code as authoritative; expected diagnostic output still ends in
|
||||
`OK`.
|
||||
|
||||
## Packaged-build smoke procedure
|
||||
|
||||
Packaged/Nuitka builds cannot be safely driven by these in-process `unittest`
|
||||
fixtures. For an optional release smoke check, use an otherwise disposable test
|
||||
OS account or VM, redirect all Furious application-data/settings locations to a
|
||||
temporary directory, and keep system proxy and TUN disabled. Open and close each
|
||||
transient editor family 50 times, verify one reusable `TextEditorWindow` does
|
||||
not duplicate actions, and compare live-object diagnostics from an instrumented
|
||||
Packaged/Nuitka behavior is outside the source-level `unittest` fixtures. For an
|
||||
optional release smoke check, use an otherwise disposable test OS account or
|
||||
VM, redirect all Furious application-data/settings locations to a temporary
|
||||
directory, and keep system proxy and TUN disabled. Open and close each transient
|
||||
editor family 50 times, verify one reusable `TextEditorWindow` does not
|
||||
duplicate actions, and compare live-object diagnostics from an instrumented
|
||||
build before/after the loop. Do not run this procedure against a production
|
||||
profile or rely on process-name cleanup; close only the exact packaged process
|
||||
started for the smoke test.
|
||||
@@ -96,6 +122,8 @@ started for the smoke test.
|
||||
## Isolation rules
|
||||
|
||||
- Tests clean up only exact subprocess handles/PIDs and threads they create.
|
||||
- Child-process lifecycle tests disable single-instance discovery and host
|
||||
integration before running a real Qt event loop.
|
||||
- Tests never search for, signal, or terminate another Furious/core process.
|
||||
- Persistence tests use temporary INI-backed `QSettings` namespaces.
|
||||
- Controller tests inject fake runtime managers and patch host-mutation APIs.
|
||||
|
||||
@@ -72,6 +72,7 @@ class FixtureProtocolHandler(ProtocolHandler):
|
||||
'FIXTURE',
|
||||
'Fixture',
|
||||
'Add Fixture...',
|
||||
'Add Fixture',
|
||||
subscriptionImportable=True,
|
||||
)
|
||||
schemes = ('fixture',)
|
||||
@@ -244,6 +245,14 @@ class PluginRegistryTest(unittest.TestCase):
|
||||
self.registry.capability(CapabilityKind.Protocol, 'FIXTURE'),
|
||||
self.plugin.capabilities[0],
|
||||
)
|
||||
self.assertEqual(
|
||||
self.plugin.capabilities[0].descriptor.addActionText,
|
||||
'Add Fixture...',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.plugin.capabilities[0].descriptor.editorWindowTitle,
|
||||
'Add Fixture',
|
||||
)
|
||||
self.assertEqual(
|
||||
self.registry.pluginsWithCapability(CapabilityKind.CoreRuntimeFactory),
|
||||
(self.plugin,),
|
||||
@@ -352,7 +361,12 @@ class PluginRollbackTest(unittest.TestCase):
|
||||
class Handler(FixtureProtocolHandler):
|
||||
"""Use a distinct protocol ID with the same scheme."""
|
||||
|
||||
descriptor = ProtocolDescriptor('OTHER', 'Other', 'Add Other')
|
||||
descriptor = ProtocolDescriptor(
|
||||
'OTHER',
|
||||
'Other',
|
||||
'Add Other...',
|
||||
'Add Other',
|
||||
)
|
||||
|
||||
capabilities = (Handler(),)
|
||||
|
||||
|
||||
@@ -24,8 +24,15 @@ from Furious.Backends.ExternalCore.Configuration import (
|
||||
ConfigExternalCore,
|
||||
)
|
||||
from Furious.Backends.ExternalCore.Editor import ExternalCoreEditor
|
||||
from Furious.Backends.Hysteria1.Editor import Hysteria1Editor
|
||||
from Furious.Backends.Hysteria2.Editor import Hysteria2Editor
|
||||
from Furious.Backends.Xray.AssetListWidget import XrayAssetListWidget
|
||||
from Furious.Backends.Xray.RoutingWindow import RoutingRulesDialog
|
||||
from Furious.Backends.Xray.ShadowsocksEditor import ShadowsocksEditor
|
||||
from Furious.Backends.Xray.SocksEditor import SocksEditor
|
||||
from Furious.Backends.Xray.TrojanEditor import TrojanEditor
|
||||
from Furious.Backends.Xray.VlessEditor import VlessEditor
|
||||
from Furious.Backends.Xray.VmessEditor import VmessEditor
|
||||
from Furious.Actions.Connection import ConnectAction
|
||||
from Furious.Controllers.ConnectionController import (
|
||||
ConnectionController,
|
||||
@@ -35,9 +42,9 @@ from Furious.Controllers.SettingsController import (
|
||||
LOG_AUTO_CLEAR_SETTING,
|
||||
LOG_AUTO_SCROLL_DOWN_SETTING,
|
||||
)
|
||||
from Furious.Frozenlib import AppSettings
|
||||
from Furious.Frozenlib import AppSettings, Mixins
|
||||
from Furious.Models import ProfileMetadata, ServerProfile
|
||||
from Furious.Qt import AppHue, AppQMessageBox, AppQSwitch
|
||||
from Furious.Qt import AppHue, AppQMessageBox, AppQSwitch, gettext as _
|
||||
from Furious.Service import (
|
||||
APPLICATION_LOG_CATEGORY,
|
||||
CORE_LOG_CATEGORY,
|
||||
@@ -144,6 +151,38 @@ class EditorMappingTest(unittest.TestCase):
|
||||
|
||||
editor.close()
|
||||
|
||||
def testEveryProtocolEditorRetranslatesItsDedicatedWindowTitle(self):
|
||||
"""Retain title source text when switching from Chinese to English."""
|
||||
editorTypes = (
|
||||
(ExternalCoreEditor, 'Add External Core'),
|
||||
(Hysteria1Editor, 'Add Hysteria1 Server'),
|
||||
(Hysteria2Editor, 'Add Hysteria2 Server'),
|
||||
(ShadowsocksEditor, 'Add Shadowsocks Server'),
|
||||
(SocksEditor, 'Add SOCKS Server'),
|
||||
(TrojanEditor, 'Add Trojan Server'),
|
||||
(VlessEditor, 'Add VLESS Server'),
|
||||
(VmessEditor, 'Add VMess Server'),
|
||||
)
|
||||
|
||||
with isolatedSettings():
|
||||
AppSettings.set('Language', 'ZH')
|
||||
editors = []
|
||||
|
||||
for editorType, sourceTitle in editorTypes:
|
||||
editor = editorType(windowTitle=_(sourceTitle))
|
||||
editors.append((editor, sourceTitle))
|
||||
|
||||
self.assertEqual(editor.windowTitle(), _(sourceTitle))
|
||||
|
||||
AppSettings.set('Language', 'EN')
|
||||
|
||||
Mixins.QTranslatable.retranslateAll()
|
||||
|
||||
for editor, sourceTitle in editors:
|
||||
self.assertEqual(editor.windowTitle(), sourceTitle)
|
||||
|
||||
editor.close()
|
||||
|
||||
def testSubscriptionEditorNormalizesPresentationValues(self):
|
||||
"""Return one complete subscription record from its visual controls."""
|
||||
with isolatedSettings():
|
||||
|
||||
Reference in New Issue
Block a user