Skip to content

Commit 177ae7b

Browse files
committed
Improve tests for sync implementation slightly.
1 parent 8282d22 commit 177ae7b

File tree

3 files changed

+56
-25
lines changed

3 files changed

+56
-25
lines changed

src/websockets/sync/connection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ def send(self, message: Data | Iterable[Data]) -> None:
373373

374374
except RuntimeError:
375375
# We didn't start sending a fragmented message.
376+
# The connection is still usable.
376377
raise
377378

378379
except Exception:
@@ -756,7 +757,7 @@ def set_recv_exc(self, exc: BaseException | None) -> None:
756757
757758
"""
758759
assert self.protocol_mutex.locked()
759-
if self.recv_exc is None:
760+
if self.recv_exc is None: # pragma: no branch
760761
self.recv_exc = exc
761762

762763
def close_socket(self) -> None:

tests/sync/connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class InterceptingConnection(Connection):
88
"""
99
Connection subclass that can intercept outgoing packets.
1010
11-
By interfacing with this connection, you can simulate network conditions
11+
By interfacing with this connection, we simulate network conditions
1212
affecting what the component being tested receives during a test.
1313
1414
"""

tests/sync/test_connection.py

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -246,21 +246,24 @@ def test_recv_streaming_connection_closed_ok(self):
246246
"""recv_streaming raises ConnectionClosedOK after a normal closure."""
247247
self.remote_connection.close()
248248
with self.assertRaises(ConnectionClosedOK):
249-
list(self.connection.recv_streaming())
249+
for _ in self.connection.recv_streaming():
250+
self.fail("did not raise")
250251

251252
def test_recv_streaming_connection_closed_error(self):
252253
"""recv_streaming raises ConnectionClosedError after an error."""
253254
self.remote_connection.close(code=CloseCode.INTERNAL_ERROR)
254255
with self.assertRaises(ConnectionClosedError):
255-
list(self.connection.recv_streaming())
256+
for _ in self.connection.recv_streaming():
257+
self.fail("did not raise")
256258

257259
def test_recv_streaming_during_recv(self):
258260
"""recv_streaming raises RuntimeError when called concurrently with recv."""
259261
recv_thread = threading.Thread(target=self.connection.recv)
260262
recv_thread.start()
261263

262264
with self.assertRaises(RuntimeError) as raised:
263-
list(self.connection.recv_streaming())
265+
for _ in self.connection.recv_streaming():
266+
self.fail("did not raise")
264267
self.assertEqual(
265268
str(raised.exception),
266269
"cannot call recv_streaming while another thread "
@@ -278,7 +281,8 @@ def test_recv_streaming_during_recv_streaming(self):
278281
recv_streaming_thread.start()
279282

280283
with self.assertRaises(RuntimeError) as raised:
281-
list(self.connection.recv_streaming())
284+
for _ in self.connection.recv_streaming():
285+
self.fail("did not raise")
282286
self.assertEqual(
283287
str(raised.exception),
284288
r"cannot call recv_streaming while another thread "
@@ -374,7 +378,7 @@ def test_send_empty_iterable(self):
374378
"""send does nothing when called with an empty iterable."""
375379
self.connection.send([])
376380
self.connection.close()
377-
self.assertEqual(list(iter(self.remote_connection)), [])
381+
self.assertEqual(list(self.remote_connection), [])
378382

379383
def test_send_mixed_iterable(self):
380384
"""send raises TypeError when called with an iterable of inconsistent types."""
@@ -437,7 +441,7 @@ def test_close_waits_for_connection_closed(self):
437441

438442
def test_close_timeout_waiting_for_close_frame(self):
439443
"""close times out if no close frame is received."""
440-
with self.drop_eof_rcvd(), self.drop_frames_rcvd():
444+
with self.drop_frames_rcvd(), self.drop_eof_rcvd():
441445
self.connection.close()
442446

443447
with self.assertRaises(ConnectionClosedError) as raised:
@@ -464,6 +468,10 @@ def test_close_timeout_waiting_for_connection_closed(self):
464468
self.assertIsInstance(exc.__cause__, (socket.timeout, TimeoutError))
465469

466470
def test_close_waits_for_recv(self):
471+
# The sync implementation doesn't have a buffer for incoming messsages.
472+
# It requires reading incoming frames until the close frame is reached.
473+
# This behavior — close() blocks until recv() is called — is less than
474+
# ideal and inconsistent with the asyncio implementation.
467475
self.remote_connection.send("😀")
468476

469477
close_thread = threading.Thread(target=self.connection.close)
@@ -547,6 +555,25 @@ def closer():
547555

548556
close_thread.join()
549557

558+
def test_close_during_recv(self):
559+
"""close aborts recv when called concurrently with recv."""
560+
561+
def closer():
562+
time.sleep(MS)
563+
self.connection.close()
564+
565+
close_thread = threading.Thread(target=closer)
566+
close_thread.start()
567+
568+
with self.assertRaises(ConnectionClosedOK) as raised:
569+
self.connection.recv()
570+
571+
exc = raised.exception
572+
self.assertEqual(str(exc), "sent 1000 (OK); then received 1000 (OK)")
573+
self.assertIsNone(exc.__cause__)
574+
575+
close_thread.join()
576+
550577
def test_close_during_send(self):
551578
"""close fails the connection when called concurrently with send."""
552579
close_gate = threading.Event()
@@ -599,42 +626,45 @@ def test_ping_explicit_binary(self):
599626
self.connection.ping(b"ping")
600627
self.assertFrameSent(Frame(Opcode.PING, b"ping"))
601628

602-
def test_ping_duplicate_payload(self):
603-
"""ping rejects the same payload until receiving the pong."""
604-
with self.remote_connection.protocol_mutex: # block response to ping
605-
pong_waiter = self.connection.ping("idem")
606-
with self.assertRaises(RuntimeError) as raised:
607-
self.connection.ping("idem")
608-
self.assertEqual(
609-
str(raised.exception),
610-
"already waiting for a pong with the same data",
611-
)
612-
self.assertTrue(pong_waiter.wait(MS))
613-
self.connection.ping("idem") # doesn't raise an exception
614-
615629
def test_acknowledge_ping(self):
616630
"""ping is acknowledged by a pong with the same payload."""
617-
with self.drop_frames_rcvd():
631+
with self.drop_frames_rcvd(): # drop automatic response to ping
618632
pong_waiter = self.connection.ping("this")
619-
self.assertFalse(pong_waiter.wait(MS))
620633
self.remote_connection.pong("this")
621634
self.assertTrue(pong_waiter.wait(MS))
622635

623636
def test_acknowledge_ping_non_matching_pong(self):
624637
"""ping isn't acknowledged by a pong with a different payload."""
625-
with self.drop_frames_rcvd():
638+
with self.drop_frames_rcvd(): # drop automatic response to ping
626639
pong_waiter = self.connection.ping("this")
627640
self.remote_connection.pong("that")
628641
self.assertFalse(pong_waiter.wait(MS))
629642

630643
def test_acknowledge_previous_ping(self):
631644
"""ping is acknowledged by a pong with the same payload as a later ping."""
632-
with self.drop_frames_rcvd():
645+
with self.drop_frames_rcvd(): # drop automatic response to ping
633646
pong_waiter = self.connection.ping("this")
634647
self.connection.ping("that")
635648
self.remote_connection.pong("that")
636649
self.assertTrue(pong_waiter.wait(MS))
637650

651+
def test_ping_duplicate_payload(self):
652+
"""ping rejects the same payload until receiving the pong."""
653+
with self.drop_frames_rcvd(): # drop response to ping
654+
pong_waiter = self.connection.ping("idem")
655+
656+
with self.assertRaises(RuntimeError) as raised:
657+
self.connection.ping("idem")
658+
self.assertEqual(
659+
str(raised.exception),
660+
"already waiting for a pong with the same data",
661+
)
662+
663+
self.remote_connection.pong("idem")
664+
self.assertTrue(pong_waiter.wait(MS))
665+
666+
self.connection.ping("idem") # doesn't raise an exception
667+
638668
# Test pong.
639669

640670
def test_pong(self):

0 commit comments

Comments
 (0)