From 7a4d050dba97734d3145d31dea5fddafcda915ee Mon Sep 17 00:00:00 2001 From: Jatin K Malik Date: Fri, 28 Aug 2026 23:29:38 -0700 Subject: [PATCH] fix(tests): stop leaking Factory.new onto MagicMock (#748) The IBus test stubs assigned Bus and Factory to the MagicMock class, then set Factory.new. That attribute lands on unittest.mock.MagicMock itself, so every mock in the pytest session shares the same .new. Bus and Factory are instances now, matching Text. A regression test fails if importing the module puts new on the class. --- tests/test_ibus_engine_core.py | 35 +++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/test_ibus_engine_core.py b/tests/test_ibus_engine_core.py index 8ef3489..c913c05 100644 --- a/tests/test_ibus_engine_core.py +++ b/tests/test_ibus_engine_core.py @@ -49,10 +49,13 @@ class _FakeIBusEngine: """Parent implementation invoked through ``super().do_destroy``.""" -# Set up IBus mock - Engine is a plain class so the engine can subclass it +# Set up IBus mock - Engine is a plain class so the engine can subclass it. +# Bus and Factory must be instances. Assigning the MagicMock class and then +# setting Factory.new puts `new` on unittest.mock.MagicMock itself, so every +# MagicMock() in the pytest session shares it (#747). mock_ibus.Engine = _FakeIBusEngine -mock_ibus.Bus = MagicMock -mock_ibus.Factory = MagicMock +mock_ibus.Bus = MagicMock() +mock_ibus.Factory = MagicMock() mock_ibus.Factory.new = MagicMock(return_value=MagicMock()) mock_ibus.Text = MagicMock() mock_ibus.Text.new_from_string = MagicMock(return_value=MagicMock()) @@ -71,6 +74,32 @@ sys.modules["gi.repository"].GLib = mock_glib sys.modules["gi.repository"].GObject = mock_gobject +class TestIBusMockIsolation(unittest.TestCase): + """Module-level IBus stubs must not leak onto unittest.mock.MagicMock.""" + + def test_factory_new_is_not_on_the_magicmock_class(self): + """Factory.new must live on the Factory mock, not on MagicMock itself (#747).""" + self.assertNotIn("new", vars(MagicMock)) + a, b = MagicMock(), MagicMock() + self.assertIsNot(a.Notification.new, b.Whatever.new) + + def test_fresh_interpreter_does_not_put_new_on_magicmock(self): + """Same check in a subprocess so session order cannot hide a leak (#747).""" + probe = ( + "from unittest.mock import MagicMock; import runpy; " + "runpy.run_path(%r, run_name='_747'); " + "print('new' in vars(MagicMock))" + ) % os.path.abspath(__file__) + result = subprocess.run( + [sys.executable, "-c", probe], + capture_output=True, + text=True, + timeout=20, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.strip(), "False") + + class TestIBusEngineModuleFunctions(unittest.TestCase): """Test module-level functions in ibus_engine.py."""