Skip to content

Commit 45fed26

Browse files
committed
fix leaking coroutines in test_stay_connected_menu_interruptions
It looks like the mock menu had to have the side effect of an actual async function for it to return a proper coroutine. When its side effect was hardcoded values, the AsyncMock function was unable to be cancelled properly, causing coroutines to leak.
1 parent ec917ab commit 45fed26

File tree

1 file changed

+45
-21
lines changed

1 file changed

+45
-21
lines changed

tests/test_cli.py

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -663,27 +663,44 @@ async def passthrough_awaitable(awaitable):
663663
@pytest.mark.asyncio
664664
async def test_stay_connected_menu_interruptions(self):
665665
"""Test the stay_connected_menu being interrupted by a power button press or hub disconnect."""
666-
disconnect_call_count = 0
667-
power_call_count = 0
666+
mock_menu_call_count = 0
668667

669668
# simulates the hub disconnecting on the first call,
670669
async def mock_race_disconnect(awaitable):
671-
task = asyncio.ensure_future(awaitable)
672-
nonlocal disconnect_call_count
673-
disconnect_call_count += 1
674-
if disconnect_call_count == 1:
670+
task = asyncio.create_task(awaitable)
671+
try:
672+
if mock_menu_call_count == 1:
673+
await asyncio.sleep(0.01)
674+
task.cancel()
675+
raise HubDisconnectError("hub disconnected")
676+
return await task
677+
except BaseException:
678+
await asyncio.sleep(0.01)
675679
task.cancel()
676-
raise HubDisconnectError("hub disconnected")
677-
return await awaitable
680+
raise
678681

682+
# simulate the power button being pressed on the second call
679683
async def mock_race_power_button_press(awaitable):
680-
task = asyncio.ensure_future(awaitable)
681-
nonlocal power_call_count
682-
power_call_count += 1
683-
if power_call_count == 2:
684+
task = asyncio.create_task(awaitable)
685+
try:
686+
if mock_menu_call_count == 2:
687+
await asyncio.sleep(0.01)
688+
task.cancel()
689+
raise HubPowerButtonPressedError("power button pressed")
690+
return await task
691+
except BaseException:
692+
await asyncio.sleep(0.01)
684693
task.cancel()
685-
raise HubPowerButtonPressedError("power button pressed")
686-
return await awaitable
694+
raise
695+
696+
# should be called but cancelled twice, returning "Recompile and Run" the third time
697+
async def mock_menu_function():
698+
nonlocal mock_menu_call_count
699+
mock_menu_call_count += 1
700+
if mock_menu_call_count <= 3:
701+
return "Recompile and Run"
702+
else:
703+
return "Exit"
687704

688705
# Create a mock hub
689706
mock_hub = AsyncMock()
@@ -700,12 +717,11 @@ async def mock_race_power_button_press(awaitable):
700717
mock_hub._wait_for_user_program_stop = AsyncMock()
701718
# create a mock questionary menu
702719
mock_menu = AsyncMock()
703-
mock_menu.ask_async.side_effect = [
704-
"Recompile and Run",
705-
"Exit",
706-
]
707-
mock_confirm = AsyncMock()
708-
mock_confirm.ask_async.return_value = True
720+
mock_menu.ask_async.side_effect = mock_menu_function
721+
722+
# create a mock confirmation menu to reconnect to the hub
723+
mock_confirm_menu = AsyncMock()
724+
mock_confirm_menu.ask_async.return_value = True
709725

710726
# Set up mocks using ExitStack
711727
with contextlib.ExitStack() as stack:
@@ -742,17 +758,25 @@ async def mock_race_power_button_press(awaitable):
742758
mock_selector = stack.enter_context(
743759
patch("questionary.select", return_value=mock_menu)
744760
)
745-
stack.enter_context(patch("questionary.confirm", return_value=mock_confirm))
761+
mock_confirm = stack.enter_context(
762+
patch("questionary.confirm", return_value=mock_confirm_menu)
763+
)
746764

747765
# Run the command
748766
run_cmd = Run()
749767
await run_cmd.stay_connected_menu(mock_hub, args)
750768

751769
assert mock_selector.call_count == 4
770+
# a confirmation menu should be triggered and the hub should be re-instantiated upon a HubDisconnectError
771+
mock_confirm.assert_called_once()
752772
mock_hub_class.assert_called_once()
753773
mock_hub.connect.assert_called_once()
774+
775+
# these functions should be triggered upon a HubPowerButtonPressedError
754776
mock_hub._wait_for_power_button_release.assert_called_once()
755777
mock_hub._wait_for_user_program_stop.assert_called_once()
778+
779+
# this should only be called once because the menu was canceled the first two times it was called
756780
mock_hub.run.assert_called_once_with(temp_path, wait=True)
757781

758782

0 commit comments

Comments
 (0)