test: make gateway async tests xdist-safe (#1281)

* feat: improve context compaction handoff summaries

Adapt PR #916 onto current main by replacing the old context summary marker
with a clearer handoff wrapper, updating the summarization prompt for
resume-oriented summaries, and preserving the current call_llm-based
compression path.

* fix: clearer error when docker backend is unavailable

* fix: preserve docker discovery in backend preflight

Follow up on salvaged PR #940 by reusing find_docker() during the new
availability check so non-PATH Docker Desktop installs still work. Add
a regression test covering the resolved executable path.

* test: make gateway async tests xdist-safe

Replace sync test usage of asyncio.get_event_loop().run_until_complete()
with asyncio.run() so tests do not depend on an ambient current event loop.
Also create the email disconnect poll task inside a running loop. This fixes
xdist/CI failures where workers have no current loop in MainThread.

---------

Co-authored-by: aydnOktay <xaydinoktay@gmail.com>
This commit is contained in:
Teknium 2026-03-14 03:12:15 -07:00 committed by GitHub
parent 29312a23d9
commit b91cac7b4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -407,7 +407,7 @@ class TestDispatchMessage(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
adapter._message_handler.assert_not_called() adapter._message_handler.assert_not_called()
def test_subject_included_in_text(self): def test_subject_included_in_text(self):
@ -441,7 +441,7 @@ class TestDispatchMessage(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
self.assertEqual(len(captured_events), 1) self.assertEqual(len(captured_events), 1)
self.assertIn("[Subject: Help with Python]", captured_events[0].text) self.assertIn("[Subject: Help with Python]", captured_events[0].text)
self.assertIn("How do I use lists?", captured_events[0].text) self.assertIn("How do I use lists?", captured_events[0].text)
@ -469,7 +469,7 @@ class TestDispatchMessage(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
self.assertEqual(len(captured_events), 1) self.assertEqual(len(captured_events), 1)
self.assertNotIn("[Subject:", captured_events[0].text) self.assertNotIn("[Subject:", captured_events[0].text)
self.assertEqual(captured_events[0].text, "Thanks for the help!") self.assertEqual(captured_events[0].text, "Thanks for the help!")
@ -497,7 +497,7 @@ class TestDispatchMessage(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
self.assertEqual(len(captured_events), 1) self.assertEqual(len(captured_events), 1)
self.assertIn("(empty email)", captured_events[0].text) self.assertIn("(empty email)", captured_events[0].text)
@ -525,7 +525,7 @@ class TestDispatchMessage(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
self.assertEqual(len(captured_events), 1) self.assertEqual(len(captured_events), 1)
self.assertEqual(captured_events[0].message_type, MessageType.PHOTO) self.assertEqual(captured_events[0].message_type, MessageType.PHOTO)
self.assertEqual(captured_events[0].media_urls, ["/tmp/img.jpg"]) self.assertEqual(captured_events[0].media_urls, ["/tmp/img.jpg"])
@ -553,7 +553,7 @@ class TestDispatchMessage(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
event = captured_events[0] event = captured_events[0]
self.assertEqual(event.source.chat_id, "john@example.com") self.assertEqual(event.source.chat_id, "john@example.com")
self.assertEqual(event.source.user_id, "john@example.com") self.assertEqual(event.source.user_id, "john@example.com")
@ -598,7 +598,7 @@ class TestThreadContext(unittest.TestCase):
"date": "", "date": "",
} }
asyncio.get_event_loop().run_until_complete(adapter._dispatch_message(msg_data)) asyncio.run(adapter._dispatch_message(msg_data))
ctx = adapter._thread_context.get("user@test.com") ctx = adapter._thread_context.get("user@test.com")
self.assertIsNotNone(ctx) self.assertIsNotNone(ctx)
self.assertEqual(ctx["subject"], "Project question") self.assertEqual(ctx["subject"], "Project question")
@ -680,7 +680,7 @@ class TestSendMethods(unittest.TestCase):
mock_server = MagicMock() mock_server = MagicMock()
mock_smtp.return_value = mock_server mock_smtp.return_value = mock_server
result = asyncio.get_event_loop().run_until_complete( result = asyncio.run(
adapter.send("user@test.com", "Hello from Hermes!") adapter.send("user@test.com", "Hello from Hermes!")
) )
@ -698,7 +698,7 @@ class TestSendMethods(unittest.TestCase):
with patch("smtplib.SMTP") as mock_smtp: with patch("smtplib.SMTP") as mock_smtp:
mock_smtp.side_effect = Exception("Connection refused") mock_smtp.side_effect = Exception("Connection refused")
result = asyncio.get_event_loop().run_until_complete( result = asyncio.run(
adapter.send("user@test.com", "Hello") adapter.send("user@test.com", "Hello")
) )
@ -713,7 +713,7 @@ class TestSendMethods(unittest.TestCase):
adapter.send = AsyncMock(return_value=SendResult(success=True)) adapter.send = AsyncMock(return_value=SendResult(success=True))
asyncio.get_event_loop().run_until_complete( asyncio.run(
adapter.send_image("user@test.com", "https://img.com/photo.jpg", "My photo") adapter.send_image("user@test.com", "https://img.com/photo.jpg", "My photo")
) )
@ -737,7 +737,7 @@ class TestSendMethods(unittest.TestCase):
mock_server = MagicMock() mock_server = MagicMock()
mock_smtp.return_value = mock_server mock_smtp.return_value = mock_server
result = asyncio.get_event_loop().run_until_complete( result = asyncio.run(
adapter.send_document("user@test.com", tmp_path, "Here is the file") adapter.send_document("user@test.com", tmp_path, "Here is the file")
) )
@ -759,7 +759,7 @@ class TestSendMethods(unittest.TestCase):
import asyncio import asyncio
adapter = self._make_adapter() adapter = self._make_adapter()
# Should not raise # Should not raise
asyncio.get_event_loop().run_until_complete(adapter.send_typing("user@test.com")) asyncio.run(adapter.send_typing("user@test.com"))
def test_get_chat_info(self): def test_get_chat_info(self):
"""get_chat_info should return email address as chat info.""" """get_chat_info should return email address as chat info."""
@ -767,7 +767,7 @@ class TestSendMethods(unittest.TestCase):
adapter = self._make_adapter() adapter = self._make_adapter()
adapter._thread_context["user@test.com"] = {"subject": "Test", "message_id": "<m@t>"} adapter._thread_context["user@test.com"] = {"subject": "Test", "message_id": "<m@t>"}
info = asyncio.get_event_loop().run_until_complete( info = asyncio.run(
adapter.get_chat_info("user@test.com") adapter.get_chat_info("user@test.com")
) )
@ -804,7 +804,7 @@ class TestConnectDisconnect(unittest.TestCase):
mock_server = MagicMock() mock_server = MagicMock()
mock_smtp.return_value = mock_server mock_smtp.return_value = mock_server
result = asyncio.get_event_loop().run_until_complete(adapter.connect()) result = asyncio.run(adapter.connect())
self.assertTrue(result) self.assertTrue(result)
self.assertTrue(adapter._running) self.assertTrue(adapter._running)
@ -821,7 +821,7 @@ class TestConnectDisconnect(unittest.TestCase):
adapter = self._make_adapter() adapter = self._make_adapter()
with patch("imaplib.IMAP4_SSL", side_effect=Exception("IMAP down")): with patch("imaplib.IMAP4_SSL", side_effect=Exception("IMAP down")):
result = asyncio.get_event_loop().run_until_complete(adapter.connect()) result = asyncio.run(adapter.connect())
self.assertFalse(result) self.assertFalse(result)
self.assertFalse(adapter._running) self.assertFalse(adapter._running)
@ -835,7 +835,7 @@ class TestConnectDisconnect(unittest.TestCase):
with patch("imaplib.IMAP4_SSL", return_value=mock_imap), \ with patch("imaplib.IMAP4_SSL", return_value=mock_imap), \
patch("smtplib.SMTP", side_effect=Exception("SMTP down")): patch("smtplib.SMTP", side_effect=Exception("SMTP down")):
result = asyncio.get_event_loop().run_until_complete(adapter.connect()) result = asyncio.run(adapter.connect())
self.assertFalse(result) self.assertFalse(result)
def test_disconnect_cancels_poll(self): def test_disconnect_cancels_poll(self):
@ -843,9 +843,12 @@ class TestConnectDisconnect(unittest.TestCase):
import asyncio import asyncio
adapter = self._make_adapter() adapter = self._make_adapter()
adapter._running = True adapter._running = True
adapter._poll_task = asyncio.ensure_future(asyncio.sleep(100))
asyncio.get_event_loop().run_until_complete(adapter.disconnect()) async def _exercise_disconnect():
adapter._poll_task = asyncio.create_task(asyncio.sleep(100))
await adapter.disconnect()
asyncio.run(_exercise_disconnect())
self.assertFalse(adapter._running) self.assertFalse(adapter._running)
self.assertIsNone(adapter._poll_task) self.assertIsNone(adapter._poll_task)
@ -967,7 +970,7 @@ class TestPollLoop(unittest.TestCase):
mock_imap.fetch.return_value = ("OK", [(b"1", raw_email.as_bytes())]) mock_imap.fetch.return_value = ("OK", [(b"1", raw_email.as_bytes())])
with patch("imaplib.IMAP4_SSL", return_value=mock_imap): with patch("imaplib.IMAP4_SSL", return_value=mock_imap):
asyncio.get_event_loop().run_until_complete(adapter._check_inbox()) asyncio.run(adapter._check_inbox())
self.assertEqual(len(dispatched), 1) self.assertEqual(len(dispatched), 1)
self.assertEqual(dispatched[0]["subject"], "Inbox Test") self.assertEqual(dispatched[0]["subject"], "Inbox Test")
@ -991,7 +994,7 @@ class TestSendEmailStandalone(unittest.TestCase):
mock_server = MagicMock() mock_server = MagicMock()
mock_smtp.return_value = mock_server mock_smtp.return_value = mock_server
result = asyncio.get_event_loop().run_until_complete( result = asyncio.run(
_send_email({"address": "hermes@test.com", "smtp_host": "smtp.test.com"}, "user@test.com", "Hello") _send_email({"address": "hermes@test.com", "smtp_host": "smtp.test.com"}, "user@test.com", "Hello")
) )
@ -1009,7 +1012,7 @@ class TestSendEmailStandalone(unittest.TestCase):
from tools.send_message_tool import _send_email from tools.send_message_tool import _send_email
with patch("smtplib.SMTP", side_effect=Exception("SMTP error")): with patch("smtplib.SMTP", side_effect=Exception("SMTP error")):
result = asyncio.get_event_loop().run_until_complete( result = asyncio.run(
_send_email({"address": "hermes@test.com", "smtp_host": "smtp.test.com"}, "user@test.com", "Hello") _send_email({"address": "hermes@test.com", "smtp_host": "smtp.test.com"}, "user@test.com", "Hello")
) )
@ -1022,7 +1025,7 @@ class TestSendEmailStandalone(unittest.TestCase):
import asyncio import asyncio
from tools.send_message_tool import _send_email from tools.send_message_tool import _send_email
result = asyncio.get_event_loop().run_until_complete( result = asyncio.run(
_send_email({}, "user@test.com", "Hello") _send_email({}, "user@test.com", "Hello")
) )