diff --git a/bridge/cutter/rea_bridge.py b/bridge/cutter/rea_bridge.py index c0b77e17e8..9dfb4d8751 100644 --- a/bridge/cutter/rea_bridge.py +++ b/bridge/cutter/rea_bridge.py @@ -28,9 +28,9 @@ if os.name == "nt": import cutter try: - from PySide6.QtCore import QObject, Signal + from PySide6.QtCore import QCoreApplication, QObject, Signal except ImportError: - from PySide2.QtCore import QObject, Signal + from PySide2.QtCore import QCoreApplication, QObject, Signal MAX_FRAME_BYTES = 16 * 1024 * 1024 @@ -326,7 +326,8 @@ class _RequestDispatcher(QObject): self._receipt_lock = threading.Lock() self.current_file = self._read_current_file() try: - self.cutter_version = str(cutter.core().getVersionInformation()) + value = QCoreApplication.applicationVersion() + self.cutter_version = value if isinstance(value, str) and value else None except Exception: self.cutter_version = None diff --git a/tests/fixtures/cutter_bridge_protocol_test.py b/tests/fixtures/cutter_bridge_protocol_test.py index 0f617e6b6d..b1abdc275a 100644 --- a/tests/fixtures/cutter_bridge_protocol_test.py +++ b/tests/fixtures/cutter_bridge_protocol_test.py @@ -24,6 +24,7 @@ def _install_host_stubs(): qtcore = types.ModuleType("PySide6.QtCore") qtcore.QObject = type("QObject", (), {}) qtcore.Signal = lambda *_args: types.SimpleNamespace(connect=lambda _handler: None) + qtcore.QCoreApplication = types.SimpleNamespace(applicationVersion=lambda: "Cutter test host") pyside = types.ModuleType("PySide6") pyside.QtCore = qtcore sys.modules["PySide6"] = pyside @@ -229,7 +230,6 @@ class DocumentIdentityTest(unittest.TestCase): configuration = {"file.path": first} core = types.SimpleNamespace( getConfig=configuration.__getitem__, - getVersionInformation=lambda: "backend fixture", ) commands = [] with patch.object(bridge.cutter, "core", lambda: core, create=True), patch.object( @@ -280,6 +280,46 @@ class DocumentIdentityTest(unittest.TestCase): self.assertIsNone(bridge._RequestDispatcher._read_current_file()) +class HostVersionTest(unittest.TestCase): + def test_host_version_reaches_status_and_command_without_backend_attribution(self): + backend_reads = [] + core = types.SimpleNamespace( + getConfig=lambda _key: None, + getVersionInformation=lambda: backend_reads.append(True) or "Rizin 0.9.1", + ) + application = sys.modules["PySide6.QtCore"].QCoreApplication + with patch.object(bridge.cutter, "core", lambda: core, create=True), patch.object( + application, "applicationVersion", return_value="2.5.0-HEAD-fixture" + ), patch.object(bridge.cutter, "cmd", lambda _command: "observed", create=True): + dispatcher = bridge._RequestDispatcher() + self.assertEqual(dispatcher.cutter_version, "2.5.0-HEAD-fixture") + for values in [ + {"kind": "status"}, + {"kind": "command", "request_id": str(uuid.uuid4()), "expected_generation": 0, "command": "iI"}, + ]: + request = {**values, "_event": threading.Event(), "_cancelled": threading.Event()} + reply = {} + dispatcher._handle(request, reply) + self.assertTrue(request["_event"].is_set()) + self.assertEqual(reply["cutter_version"], "2.5.0-HEAD-fixture") + self.assertEqual(backend_reads, []) + + def test_unavailable_host_version_stays_unknown_without_backend_fallback(self): + core = types.SimpleNamespace( + getConfig=lambda _key: None, + getVersionInformation=lambda: "Rizin 0.9.1", + ) + application = sys.modules["PySide6.QtCore"].QCoreApplication + with patch.object(bridge.cutter, "core", lambda: core, create=True): + for value in ["", None, object()]: + with self.subTest(value=type(value).__name__), patch.object( + application, "applicationVersion", return_value=value + ): + self.assertIsNone(bridge._RequestDispatcher().cutter_version) + with patch.object(application, "applicationVersion", side_effect=RuntimeError("host unavailable")): + self.assertIsNone(bridge._RequestDispatcher().cutter_version) + + class ProtocolProducerTest(unittest.TestCase): def test_rejects_legacy_peers_before_ui_dispatch(self): with socket.create_connection(("127.0.0.1", self.server.port), timeout=2) as connection: