Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pymongo/asynchronous/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ async def _hello(
hello.logical_session_timeout_minutes is not None and hello.is_readable
)
self.logical_session_timeout_minutes: Optional[int] = hello.logical_session_timeout_minutes
self.hello_ok = hello.hello_ok
# PYTHON-5904

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this comment a little more informative rather than just the ticket name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem.

self.hello_ok = self.hello_ok or hello.hello_ok
self.is_repl = hello.server_type in (
SERVER_TYPE.RSPrimary,
SERVER_TYPE.RSSecondary,
Expand Down
3 changes: 2 additions & 1 deletion pymongo/synchronous/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ def _hello(
hello.logical_session_timeout_minutes is not None and hello.is_readable
)
self.logical_session_timeout_minutes: Optional[int] = hello.logical_session_timeout_minutes
self.hello_ok = hello.hello_ok
# PYTHON-5904
self.hello_ok = self.hello_ok or hello.hello_ok
self.is_repl = hello.server_type in (
SERVER_TYPE.RSPrimary,
SERVER_TYPE.RSSecondary,
Expand Down
67 changes: 67 additions & 0 deletions test/asynchronous/test_hello_latched.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import unittest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs the standard license header, see another test file for an example.

from unittest.mock import AsyncMock
from types import SimpleNamespace
from pymongo.asynchronous.pool import AsyncConnection

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line _IS_SYNC = False needs to be here after the imports.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I will make these changes.
Thanks Noah.

class TestHelloLatched(unittest.IsolatedAsyncioTestCase):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class TestHelloLatched(unittest.IsolatedAsyncioTestCase):
class TestHelloLatched(AsyncUnitTest):


def setUp(self):
self._sent = []

def create_connection(self) -> AsyncConnection:
"""Returns a minimal connection object for _hello"""
conn = object.__new__(AsyncConnection)
conn.hello_ok = False
conn.performed_handshake = True
conn.opts = SimpleNamespace(
server_api = None,
load_balanced = False,
_credentials = None
)

return conn

async def mock_conn_command(self, db, cmd, **kwargs):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't used elsewhere, let's inline it within test_hello_is_latched.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no problem.

"""Returns mocked hello and ismaster results for conn.command"""
self._sent.append(cmd.copy())
if cmd.get("ismaster") == 1:
return {"ok":1, "helloOk": True, "ismaster": True, "maxWireVersion": 25}
return {"ok":1, "isWritablePrimary": True, "maxWireVersion": 25}


async def test_hello_is_latched(self):
"""
Regression Test for PYTHON-5904
Tests for connection hello_ok persistence when connection
Switches from ismaster to hello
"""
conn = self.create_connection()
conn.command = AsyncMock(side_effect=self.mock_conn_command)

# First hello
await conn._hello(None, None)
# Verify hello_ok is True
self.assertTrue(conn.hello_ok)
# Verify command sent is ismaster
self.assertEqual(self._sent[0].get("ismaster"), 1)
self.assertEqual(self._sent[0].get("helloOk"), True)

# Second hello
await conn._hello(None, None)
# Verify hello_ok has not changed
self.assertTrue(conn.hello_ok)
# Verify command sent is hello
self.assertEqual(self._sent[1].get("hello"), 1)
self.assertIsNone(self._sent[1].get("ismaster", None))

# Third hello
await conn._hello(None, None)
# Verify connection continues to use hello
self.assertEqual(self._sent[2].get("hello"), 1)
self.assertIsNone(self._sent[1].get("ismaster", None))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assertIsNone(self._sent[1].get("ismaster", None))
self.assertIsNone(self._sent[2].get("ismaster", None))



if __name__ == "__main__":
unittest.main()


74 changes: 74 additions & 0 deletions test/test_hello_latched.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import unittest
from unittest.mock import Mock
from types import SimpleNamespace
from pymongo.synchronous.pool import Connection


class TestHelloLatched(unittest.TestCase):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file must be generated using the synchro pre-commit hook from the asynchronous version.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it. I misunderstood that these run on commit.

"""
Test to Ensure that hello_ok remains latched
when the RTT thread switches to hello
"""

def setUp(self):
self._sent = []

def create_connection(self) -> Connection:
"""Returns a minimal connection object"""
conn = object.__new__(Connection)
conn.hello_ok = False
# Isolate this test to post-handshake hello_ok persistence
conn.performed_handshake = True
conn.opts = SimpleNamespace(
server_api = None,
load_balanced = False,
_credentials = None
)

return conn

def mock_connection_command(self, db, cmd, **kwargs):
"""Returns mocked hello and ismaster results for conn.command"""
self._sent.append(cmd.copy())
if cmd.get("ismaster") == 1:
return {"ok":1, "helloOk": True, "ismaster": True, "maxWireVersion": 25}
return {"ok":1, "isWritablePrimary": True, "maxWireVersion": 25}


def test_hello_is_latched(self):
"""
Regression Test for PYTHON-5904
Tests for connection hello_ok persistence when connection
Switches from ismaster to hello
"""

conn = self.create_connection()
conn.command = Mock(side_effect=self.mock_connection_command)

# First hello
conn._hello(None, None)
# Verify hello_ok is True
self.assertTrue(conn.hello_ok)
# Verify command sent is ismaster
self.assertEqual(self._sent[0].get("ismaster"), 1)
self.assertEqual(self._sent[0].get("helloOk"), True)

# Second hello
conn._hello(None, None)
# Verify hello_ok has not changed
self.assertTrue(conn.hello_ok)
# Verify command sent is hello
self.assertEqual(self._sent[1].get("hello"), 1)
self.assertIsNone(self._sent[1].get("ismaster", None))

# Third hello
conn._hello(None, None)
# Verify connection continues to use hello
self.assertEqual(self._sent[2].get("hello"), 1)
self.assertIsNone(self._sent[1].get("ismaster", None))


if __name__ == "__main__":
unittest.main()


Loading