Skip to content

Commit a670d60

Browse files
authored
Merge pull request #3159 from graingert/100-coverage
get and enforce 100% coverage
2 parents 8a3307f + d962e05 commit a670d60

File tree

16 files changed

+179
-80
lines changed

16 files changed

+179
-80
lines changed

.codecov.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ comment:
2121

2222
coverage:
2323
# required range
24-
range: 99.6..100
24+
precision: 5
25+
round: down
26+
range: 100..100
2527
status:
26-
# require patches to be 100%
27-
patch:
28+
project:
2829
default:
2930
target: 100%
31+
patch:
32+
default:
33+
target: 100% # require patches to be 100%

newsfragments/3159.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Get and enforce 100% coverage

pyproject.toml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,16 @@ precision = 1
308308
skip_covered = true
309309
skip_empty = true
310310
show_missing = true
311-
exclude_lines = [
312-
"pragma: no cover",
313-
"abc.abstractmethod",
314-
"if TYPE_CHECKING.*:",
315-
"if _t.TYPE_CHECKING:",
316-
"if t.TYPE_CHECKING:",
317-
"@overload",
318-
'class .*\bProtocol\b.*\):',
319-
"raise NotImplementedError",
320-
]
321311
exclude_also = [
322312
'^\s*@pytest\.mark\.xfail',
313+
"abc.abstractmethod",
314+
"if TYPE_CHECKING.*:",
315+
"if _t.TYPE_CHECKING:",
316+
"if t.TYPE_CHECKING:",
317+
"@overload",
318+
'class .*\bProtocol\b.*\):',
319+
"raise NotImplementedError",
320+
'TODO: test this line'
323321
]
324322
partial_branches = [
325323
"pragma: no branch",
@@ -329,4 +327,5 @@ partial_branches = [
329327
"if .* or not TYPE_CHECKING:",
330328
"if .* or not _t.TYPE_CHECKING:",
331329
"if .* or not t.TYPE_CHECKING:",
330+
'TODO: test this branch',
332331
]

src/trio/_core/_io_kqueue.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def get_events(self, timeout: float) -> EventResult:
8181
events += batch
8282
if len(batch) < max_events:
8383
break
84-
else:
84+
else: # TODO: test this line
8585
timeout = 0
8686
# and loop back to the start
8787
return events
@@ -93,12 +93,12 @@ def process_events(self, events: EventResult) -> None:
9393
self._force_wakeup.drain()
9494
continue
9595
receiver = self._registered[key]
96-
if event.flags & select.KQ_EV_ONESHOT:
96+
if event.flags & select.KQ_EV_ONESHOT: # TODO: test this branch
9797
del self._registered[key]
9898
if isinstance(receiver, _core.Task):
9999
_core.reschedule(receiver, outcome.Value(event))
100100
else:
101-
receiver.put_nowait(event)
101+
receiver.put_nowait(event) # TODO: test this line
102102

103103
# kevent registration is complicated -- e.g. aio submission can
104104
# implicitly perform a EV_ADD, and EVFILT_PROC with NOTE_TRACK will
@@ -162,7 +162,7 @@ async def wait_kevent(
162162

163163
def abort(raise_cancel: RaiseCancelT) -> Abort:
164164
r = abort_func(raise_cancel)
165-
if r is _core.Abort.SUCCEEDED:
165+
if r is _core.Abort.SUCCEEDED: # TODO: test this branch
166166
del self._registered[key]
167167
return r
168168

src/trio/_core/_io_windows.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,9 +856,9 @@ async def wait_overlapped(
856856
<https://github.com/python-trio/trio/issues/52>`__.
857857
"""
858858
handle = _handle(handle_)
859-
if isinstance(lpOverlapped, int):
859+
if isinstance(lpOverlapped, int): # TODO: test this line
860860
lpOverlapped = ffi.cast("LPOVERLAPPED", lpOverlapped)
861-
if lpOverlapped in self._overlapped_waiters:
861+
if lpOverlapped in self._overlapped_waiters: # TODO: test this line
862862
raise _core.BusyResourceError(
863863
"another task is already waiting on that lpOverlapped",
864864
)

src/trio/_core/_run.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ def _public(fn: RetT) -> RetT:
115115
_r = random.Random()
116116

117117

118-
def _hypothesis_plugin_setup() -> None:
118+
# no cover because we don't check the hypothesis plugin works with hypothesis
119+
def _hypothesis_plugin_setup() -> None: # pragma: no cover
119120
from hypothesis import register_random
120121

121122
global _ALLOW_DETERMINISTIC_SCHEDULING

src/trio/_core/_tests/test_io.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import annotations
22

33
import random
4+
import select
45
import socket as stdlib_socket
6+
import sys
57
from collections.abc import Awaitable, Callable
68
from contextlib import suppress
79
from typing import TYPE_CHECKING, TypeVar
@@ -343,6 +345,7 @@ def check(*, expected_readers: int, expected_writers: int) -> None:
343345
assert iostats.tasks_waiting_write == expected_writers
344346
else:
345347
assert iostats.backend == "kqueue"
348+
assert iostats.monitors == 0
346349
assert iostats.tasks_waiting == expected_readers + expected_writers
347350

348351
a1, b1 = stdlib_socket.socketpair()
@@ -381,6 +384,44 @@ def check(*, expected_readers: int, expected_writers: int) -> None:
381384
check(expected_readers=1, expected_writers=0)
382385

383386

387+
@pytest.mark.filterwarnings("ignore:.*UnboundedQueue:trio.TrioDeprecationWarning")
388+
async def test_io_manager_kqueue_monitors_statistics() -> None:
389+
def check(
390+
*,
391+
expected_monitors: int,
392+
expected_readers: int,
393+
expected_writers: int,
394+
) -> None:
395+
statistics = _core.current_statistics()
396+
print(statistics)
397+
iostats = statistics.io_statistics
398+
assert iostats.backend == "kqueue"
399+
assert iostats.monitors == expected_monitors
400+
assert iostats.tasks_waiting == expected_readers + expected_writers
401+
402+
a1, b1 = stdlib_socket.socketpair()
403+
for sock in [a1, b1]:
404+
sock.setblocking(False)
405+
406+
with a1, b1:
407+
# let the call_soon_task settle down
408+
await wait_all_tasks_blocked()
409+
410+
if sys.platform != "win32" and sys.platform != "linux":
411+
# 1 for call_soon_task
412+
check(expected_monitors=0, expected_readers=1, expected_writers=0)
413+
414+
with _core.monitor_kevent(a1.fileno(), select.KQ_FILTER_READ):
415+
with (
416+
pytest.raises(_core.BusyResourceError),
417+
_core.monitor_kevent(a1.fileno(), select.KQ_FILTER_READ),
418+
):
419+
pass # pragma: no cover
420+
check(expected_monitors=1, expected_readers=1, expected_writers=0)
421+
422+
check(expected_monitors=0, expected_readers=1, expected_writers=0)
423+
424+
384425
async def test_can_survive_unnotified_close() -> None:
385426
# An "unnotified" close is when the user closes an fd/socket/handle
386427
# directly, without calling notify_closing first. This should never happen

src/trio/_core/_tests/test_run.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from contextlib import ExitStack, contextmanager, suppress
1212
from math import inf, nan
1313
from typing import TYPE_CHECKING, NoReturn, TypeVar
14+
from unittest import mock
1415

1516
import outcome
1617
import pytest
@@ -26,7 +27,7 @@
2627
assert_checkpoints,
2728
wait_all_tasks_blocked,
2829
)
29-
from .._run import DEADLINE_HEAP_MIN_PRUNE_THRESHOLD
30+
from .._run import DEADLINE_HEAP_MIN_PRUNE_THRESHOLD, _count_context_run_tb_frames
3031
from .tutil import (
3132
check_sequence_matches,
3233
create_asyncio_future_in_new_loop,
@@ -371,6 +372,15 @@ async def test_cancel_scope_validation() -> None:
371372
match="^Cannot specify both a deadline and a relative deadline$",
372373
):
373374
_core.CancelScope(deadline=7, relative_deadline=3)
375+
376+
with pytest.raises(ValueError, match="^deadline must not be NaN$"):
377+
_core.CancelScope(deadline=nan)
378+
with pytest.raises(ValueError, match="^relative deadline must not be NaN$"):
379+
_core.CancelScope(relative_deadline=nan)
380+
381+
with pytest.raises(ValueError, match="^timeout must be non-negative$"):
382+
_core.CancelScope(relative_deadline=-3)
383+
374384
scope = _core.CancelScope()
375385

376386
with pytest.raises(ValueError, match="^deadline must not be NaN$"):
@@ -2836,3 +2846,12 @@ async def handle_error() -> None:
28362846

28372847
assert isinstance(exc, MyException)
28382848
assert gc.get_referrers(exc) == no_other_refs()
2849+
2850+
2851+
def test_context_run_tb_frames() -> None:
2852+
class Context:
2853+
def run(self, fn: Callable[[], object]) -> object:
2854+
return fn()
2855+
2856+
with mock.patch("trio._core._run.copy_context", return_value=Context()):
2857+
assert _count_context_run_tb_frames() == 1

src/trio/_dtls.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def worst_case_mtu(sock: SocketType) -> int:
5858
if sock.family == trio.socket.AF_INET:
5959
return 576 - packet_header_overhead(sock)
6060
else:
61-
return 1280 - packet_header_overhead(sock)
61+
return 1280 - packet_header_overhead(sock) # TODO: test this line
6262

6363

6464
def best_guess_mtu(sock: SocketType) -> int:
@@ -222,7 +222,7 @@ def decode_handshake_fragment_untrusted(payload: bytes) -> HandshakeFragment:
222222
frag_offset_bytes,
223223
frag_len_bytes,
224224
) = HANDSHAKE_MESSAGE_HEADER.unpack_from(payload)
225-
except struct.error as exc:
225+
except struct.error as exc: # TODO: test this line
226226
raise BadPacket("bad handshake message header") from exc
227227
# 'struct' doesn't have built-in support for 24-bit integers, so we
228228
# have to do it by hand. These can't fail.
@@ -425,14 +425,14 @@ def encode_volley(
425425
for message in messages:
426426
if isinstance(message, OpaqueHandshakeMessage):
427427
encoded = encode_record(message.record)
428-
if mtu - len(packet) - len(encoded) <= 0:
428+
if mtu - len(packet) - len(encoded) <= 0: # TODO: test this line
429429
packets.append(packet)
430430
packet = bytearray()
431431
packet += encoded
432432
assert len(packet) <= mtu
433433
elif isinstance(message, PseudoHandshakeMessage):
434434
space = mtu - len(packet) - RECORD_HEADER.size - len(message.payload)
435-
if space <= 0:
435+
if space <= 0: # TODO: test this line
436436
packets.append(packet)
437437
packet = bytearray()
438438
packet += RECORD_HEADER.pack(
@@ -1039,7 +1039,7 @@ def read_volley() -> list[_AnyHandshakeMessage]:
10391039
if (
10401040
isinstance(maybe_volley[0], PseudoHandshakeMessage)
10411041
and maybe_volley[0].content_type == ContentType.alert
1042-
):
1042+
): # TODO: test this line
10431043
# we're sending an alert (e.g. due to a corrupted
10441044
# packet). We want to send it once, but don't save it to
10451045
# retransmit -- keep the last volley as the current
@@ -1326,9 +1326,8 @@ async def handler(dtls_channel):
13261326
raise trio.BusyResourceError("another task is already listening")
13271327
try:
13281328
self.socket.getsockname()
1329-
except OSError:
1330-
# TODO: Write test that triggers this
1331-
raise RuntimeError( # pragma: no cover
1329+
except OSError: # TODO: test this line
1330+
raise RuntimeError(
13321331
"DTLS socket must be bound before it can serve",
13331332
) from None
13341333
self._ensure_receive_loop()

src/trio/_tests/test_dtls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ async def echo_handler(dtls_channel: DTLSChannel) -> None:
7575
print("server starting do_handshake")
7676
await dtls_channel.do_handshake()
7777
print("server finished do_handshake")
78-
async for packet in dtls_channel:
78+
# no branch for leaving this for loop because we only leave
79+
# a channel by cancellation.
80+
async for packet in dtls_channel: # pragma: no branch
7981
print(f"echoing {packet!r} -> {dtls_channel.peer_address!r}")
8082
await dtls_channel.send(packet)
8183
except trio.BrokenResourceError: # pragma: no cover

src/trio/_tests/test_exports.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,8 +384,10 @@ def lookup_symbol(symbol: str) -> dict[str, str]:
384384
elif tool == "mypy":
385385
# load the cached type information
386386
cached_type_info = cache_json["names"][class_name]
387-
if "node" not in cached_type_info:
388-
cached_type_info = lookup_symbol(cached_type_info["cross_ref"])
387+
assert (
388+
"node" not in cached_type_info
389+
), "previously this was an 'if' but it seems it's no longer possible for this cache to contain 'node', if this assert raises for you please let us know!"
390+
cached_type_info = lookup_symbol(cached_type_info["cross_ref"])
389391

390392
assert "node" in cached_type_info
391393
node = cached_type_info["node"]

src/trio/_tests/test_socket.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,9 +376,7 @@ async def test_sniff_sockopts() -> None:
376376
from socket import AF_INET, AF_INET6, SOCK_DGRAM, SOCK_STREAM
377377

378378
# generate the combinations of families/types we're testing:
379-
families = [AF_INET]
380-
if can_create_ipv6:
381-
families.append(AF_INET6)
379+
families = (AF_INET, AF_INET6) if can_create_ipv6 else (AF_INET,)
382380
sockets = [
383381
stdlib_socket.socket(family, type_)
384382
for family in families

src/trio/_tests/test_ssl.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -210,27 +210,11 @@ def __init__(
210210
# we still have to support versions before that, and that means we
211211
# need to test renegotiation support, which means we need to force this
212212
# to use a lower version where this test server can trigger
213-
# renegotiations. Of course TLS 1.3 support isn't released yet, but
214-
# I'm told that this will work once it is. (And once it is we can
215-
# remove the pragma: no cover too.) Alternatively, we could switch to
216-
# using TLSv1_2_METHOD.
217-
#
218-
# Discussion: https://github.com/pyca/pyopenssl/issues/624
219-
220-
# This is the right way, but we can't use it until this PR is in a
221-
# released:
222-
# https://github.com/pyca/pyopenssl/pull/861
223-
#
224-
# if hasattr(SSL, "OP_NO_TLSv1_3"):
225-
# ctx.set_options(SSL.OP_NO_TLSv1_3)
226-
#
227-
# Fortunately pyopenssl uses cryptography under the hood, so we can be
228-
# confident that they're using the same version of openssl
213+
# renegotiations.
229214
from cryptography.hazmat.bindings.openssl.binding import Binding
230215

231216
b = Binding()
232-
if hasattr(b.lib, "SSL_OP_NO_TLSv1_3"):
233-
ctx.set_options(b.lib.SSL_OP_NO_TLSv1_3)
217+
ctx.set_options(b.lib.SSL_OP_NO_TLSv1_3)
234218

235219
# Unfortunately there's currently no way to say "use 1.3 or worse", we
236220
# can only disable specific versions. And if the two sides start

0 commit comments

Comments
 (0)