Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 13 additions & 1 deletion src/swift/Swift.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,19 @@ def run(self, duration: float | None = None, dt: float = 0.05, timeout: float |

try:
while duration is None or (self.sim_time - start_time) < duration:
self.step(dt)
try:
self.step(dt)
except TimeoutError:
# A disconnect noticed *during* step() (SwiftRoute.py's
# expect_message()/producer racing wait_closed()) raises
# here directly, rather than only ever surfacing via
# the _check_disconnected() poll below -- without this,
# it would propagate straight out of run() as a raw
# traceback instead of the same graceful message/return
# every other disconnect path already gets.
print("\nSwift browser tab closed.")
self.close()
return
time.sleep(dt)
disconnected_since, expired = self._check_disconnected(disconnected_since, timeout)
if expired:
Expand Down
23 changes: 23 additions & 0 deletions tests/test_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,29 @@ def sleep_raises(s):
assert closed == [True]


def test_run_exits_quietly_on_disconnect_during_step(capsys):
# A disconnect noticed *during* step() (SwiftRoute.py's
# expect_message()/producer racing wait_closed()) raises TimeoutError
# directly out of step(), rather than only ever surfacing via
# _check_disconnected()'s poll between steps -- run() must catch this
# the same way it already handles that poll-detected case (print,
# close(), return), not let it propagate as a raw traceback.
env = make_env()
env.headless = True

def step_raises(dt=0.05, render=True):
raise TimeoutError("Swift browser tab stopped responding")

env.step = step_raises
closed = []
env.close = lambda *a, **kw: closed.append(True)

env.run() # must return normally, not raise

assert closed == [True]
assert "Swift browser tab closed." in capsys.readouterr().out


def test_hold_exits_quietly_on_keyboard_interrupt(monkeypatch):
env = make_env()
env.headless = True
Expand Down
Loading