11"""Unit tests for the LSP connection module."""
22
33import asyncio
4+ import contextlib
45import socket
56import subprocess
7+ from collections .abc import Iterator
68from unittest .mock import AsyncMock , MagicMock , patch
79
810import pytest
1618)
1719
1820
21+ @contextlib .contextmanager
22+ def _sync_test_event_loop () -> Iterator [asyncio .AbstractEventLoop ]:
23+ """Bind a fresh loop so ``asyncio.Future()`` / ``create_future`` is safe in sync tests."""
24+ loop = asyncio .new_event_loop ()
25+ asyncio .set_event_loop (loop )
26+ try :
27+ yield loop
28+ finally :
29+ loop .close ()
30+ asyncio .set_event_loop (None )
31+
32+
1933class TestJsonRpcMessage :
2034 """Test JsonRpcMessage dataclass."""
2135
@@ -555,27 +569,28 @@ def test_handle_response_message(self, tmp_path):
555569
556570 conn = SocketLSPConnection ([str (binary_path )], "/test" )
557571
558- # Create a pending request
559- future = asyncio .Future ()
560- conn .state .pending_requests [42 ] = future
572+ with _sync_test_event_loop () as loop :
573+ # Create a pending request
574+ future = loop .create_future ()
575+ conn .state .pending_requests [42 ] = future
561576
562- # Handle response message
563- message = JsonRpcMessage (id = 42 , result = {"success" : True })
577+ # Handle response message
578+ message = JsonRpcMessage (id = 42 , result = {"success" : True })
564579
565- with patch .object (future , "get_loop" ) as mock_get_loop :
566- mock_loop = MagicMock ()
567- mock_get_loop .return_value = mock_loop
580+ with patch .object (future , "get_loop" ) as mock_get_loop :
581+ mock_loop = MagicMock ()
582+ mock_get_loop .return_value = mock_loop
568583
569- conn ._handle_incoming_message (message )
584+ conn ._handle_incoming_message (message )
570585
571- # Verify future was resolved
572- mock_loop .call_soon_threadsafe .assert_called_once ()
573- args = mock_loop .call_soon_threadsafe .call_args [0 ]
574- assert args [0 ] == future .set_result
575- assert args [1 ] == {"success" : True }
586+ # Verify future was resolved
587+ mock_loop .call_soon_threadsafe .assert_called_once ()
588+ args = mock_loop .call_soon_threadsafe .call_args [0 ]
589+ assert args [0 ] == future .set_result
590+ assert args [1 ] == {"success" : True }
576591
577- # Verify request was removed from pending
578- assert 42 not in conn .state .pending_requests
592+ # Verify request was removed from pending
593+ assert 42 not in conn .state .pending_requests
579594
580595 def test_handle_error_response (self , tmp_path ):
581596 """Test handling error response."""
@@ -584,25 +599,26 @@ def test_handle_error_response(self, tmp_path):
584599
585600 conn = SocketLSPConnection ([str (binary_path )], "/test" )
586601
587- # Create a pending request
588- future = asyncio .Future ()
589- conn .state .pending_requests [42 ] = future
602+ with _sync_test_event_loop () as loop :
603+ # Create a pending request
604+ future = loop .create_future ()
605+ conn .state .pending_requests [42 ] = future
590606
591- # Handle error response
592- message = JsonRpcMessage (
593- id = 42 , error = {"code" : - 32601 , "message" : "Method not found" }
594- )
607+ # Handle error response
608+ message = JsonRpcMessage (
609+ id = 42 , error = {"code" : - 32601 , "message" : "Method not found" }
610+ )
595611
596- with patch .object (future , "get_loop" ) as mock_get_loop :
597- mock_loop = MagicMock ()
598- mock_get_loop .return_value = mock_loop
612+ with patch .object (future , "get_loop" ) as mock_get_loop :
613+ mock_loop = MagicMock ()
614+ mock_get_loop .return_value = mock_loop
599615
600- conn ._handle_incoming_message (message )
616+ conn ._handle_incoming_message (message )
601617
602- # Verify future was rejected
603- mock_loop .call_soon_threadsafe .assert_called_once ()
604- args = mock_loop .call_soon_threadsafe .call_args [0 ]
605- assert args [0 ] == future .set_exception
618+ # Verify future was rejected
619+ mock_loop .call_soon_threadsafe .assert_called_once ()
620+ args = mock_loop .call_soon_threadsafe .call_args [0 ]
621+ assert args [0 ] == future .set_exception
606622
607623 def test_handle_unknown_response (self , tmp_path ):
608624 """Test handling response for unknown request ID."""
@@ -631,40 +647,41 @@ def test_handle_notification(self, tmp_path):
631647
632648 conn = SocketLSPConnection ([str (binary_path )], "/test" )
633649
634- # Create futures waiting for compile complete event
635- future1 = asyncio .Future ()
636- future2 = asyncio .Future ()
637- conn .state .pending_notifications [LspEventName .compileComplete ] = [
638- future1 ,
639- future2 ,
640- ]
641-
642- # Handle compile complete notification
643- message = JsonRpcMessage (
644- method = "dbt/lspCompileComplete" , params = {"success" : True }
645- )
650+ with _sync_test_event_loop () as loop :
651+ # Create futures waiting for compile complete event
652+ future1 = loop .create_future ()
653+ future2 = loop .create_future ()
654+ conn .state .pending_notifications [LspEventName .compileComplete ] = [
655+ future1 ,
656+ future2 ,
657+ ]
658+
659+ # Handle compile complete notification
660+ message = JsonRpcMessage (
661+ method = "dbt/lspCompileComplete" , params = {"success" : True }
662+ )
646663
647- with (
648- patch .object (future1 , "get_loop" ) as mock_get_loop1 ,
649- patch .object (future2 , "get_loop" ) as mock_get_loop2 ,
650- ):
651- mock_loop1 = MagicMock ()
652- mock_loop2 = MagicMock ()
653- mock_get_loop1 .return_value = mock_loop1
654- mock_get_loop2 .return_value = mock_loop2
664+ with (
665+ patch .object (future1 , "get_loop" ) as mock_get_loop1 ,
666+ patch .object (future2 , "get_loop" ) as mock_get_loop2 ,
667+ ):
668+ mock_loop1 = MagicMock ()
669+ mock_loop2 = MagicMock ()
670+ mock_get_loop1 .return_value = mock_loop1
671+ mock_get_loop2 .return_value = mock_loop2
655672
656- conn ._handle_incoming_message (message )
673+ conn ._handle_incoming_message (message )
657674
658- # Verify futures were resolved
659- mock_loop1 .call_soon_threadsafe .assert_called_once_with (
660- future1 .set_result , {"success" : True }
661- )
662- mock_loop2 .call_soon_threadsafe .assert_called_once_with (
663- future2 .set_result , {"success" : True }
664- )
675+ # Verify futures were resolved
676+ mock_loop1 .call_soon_threadsafe .assert_called_once_with (
677+ future1 .set_result , {"success" : True }
678+ )
679+ mock_loop2 .call_soon_threadsafe .assert_called_once_with (
680+ future2 .set_result , {"success" : True }
681+ )
665682
666- # Verify compile state was set
667- assert conn .state .compiled is True
683+ # Verify compile state was set
684+ assert conn .state .compiled is True
668685
669686 def test_handle_unknown_notification (self , tmp_path ):
670687 """Test handling unknown notification."""
0 commit comments