Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get and enforce 100% coverage #3159

Merged
merged 42 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2aa5771
test waitid on linux even with pidfd support
graingert Dec 19, 2024
8501b0c
refactor duplicate code in gen_exports
graingert Dec 19, 2024
be08460
more coverage in test_dtls
graingert Dec 19, 2024
2722418
cover monitors
graingert Dec 19, 2024
8858398
test cancel wait without pidfd
graingert Dec 19, 2024
e5fa8cd
test OSError in waitid
graingert Dec 19, 2024
340373d
Update src/trio/_core/_tests/test_io.py
graingert Dec 19, 2024
d436b06
Update src/trio/_core/_tests/test_io.py
graingert Dec 20, 2024
0ac717d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2024
1398349
Merge branch 'main' into 100-coverage
graingert Dec 20, 2024
34d6399
filterwarning
graingert Dec 20, 2024
818094a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2024
3dac42d
try two assertions
graingert Dec 20, 2024
dda9a27
ok what, this is very strange, but works.
graingert Dec 20, 2024
bd81624
no cover some unreachable pytest.fail code
graingert Dec 20, 2024
640f581
remove some unused code from test_subprocess.py
graingert Dec 20, 2024
50d848d
always use SSL_OP_NO_TLSv1_3 - now that it's available
graingert Dec 20, 2024
28ee44a
cowardly hide from the coverage check using a ternery
graingert Dec 20, 2024
ac30b6b
Revert "more coverage in test_dtls"
graingert Dec 20, 2024
f312a70
an empty string joined with '' is a noop, so this branch is redundant
graingert Dec 20, 2024
badc910
test errors in CancelScope ctor
graingert Dec 20, 2024
a57247a
cover code in the contextvars counter
graingert Dec 20, 2024
5344e71
test more of kqueue
graingert Dec 20, 2024
8f05dbf
actually enter the cmgr
graingert Dec 20, 2024
1ec824c
no cover unreachable cmgr inside
graingert Dec 20, 2024
a0f33ee
remove await - it seems to timeout
graingert Dec 20, 2024
cc4b217
add newsfragment
graingert Dec 20, 2024
24917b7
bump the lower end of the coverage range
graingert Dec 20, 2024
68de044
add todo to nocov
graingert Dec 20, 2024
95b1f75
ignore some coverage with TODOs
graingert Dec 20, 2024
becdf2a
assert there's no node in cached_type_info
graingert Dec 20, 2024
3f34731
require 100% coverage
graingert Dec 20, 2024
bd097dd
Update .codecov.yml
graingert Dec 20, 2024
dcfe01a
Merge branch 'main' into 100-coverage
graingert Dec 20, 2024
431f2ed
more TODO comments
graingert Dec 20, 2024
117fe04
add one last TODO
graingert Dec 20, 2024
84de11a
fix patches comment in .codecov.yml
graingert Dec 20, 2024
2484749
swap branch for line in dtls
graingert Dec 20, 2024
d055b38
Update newsfragments/3159.misc.rst
graingert Dec 20, 2024
afa13f5
Update src/trio/_tests/test_exports.py
graingert Dec 21, 2024
e78f317
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 21, 2024
d962e05
Update src/trio/_tools/gen_exports.py
graingert Dec 21, 2024
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
10 changes: 7 additions & 3 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ comment:

coverage:
# required range
range: 99.6..100
precision: 5
round: down
range: 100..100
status:
# require patches to be 100%
patch:
project:
default:
target: 100%
patch:
default:
target: 100% # require patches to be 100%
1 change: 1 addition & 0 deletions newsfragments/3159.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Get more coverage
graingert marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 9 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -308,18 +308,16 @@ precision = 1
skip_covered = true
skip_empty = true
show_missing = true
exclude_lines = [
"pragma: no cover",
"abc.abstractmethod",
"if TYPE_CHECKING.*:",
"if _t.TYPE_CHECKING:",
"if t.TYPE_CHECKING:",
"@overload",
'class .*\bProtocol\b.*\):',
"raise NotImplementedError",
]
exclude_also = [
'^\s*@pytest\.mark\.xfail',
"abc.abstractmethod",
"if TYPE_CHECKING.*:",
"if _t.TYPE_CHECKING:",
"if t.TYPE_CHECKING:",
"@overload",
'class .*\bProtocol\b.*\):',
"raise NotImplementedError",
'TODO: test this line'
Comment on lines +313 to +320
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider integrating the covdefaults plugin, as it injects most of these automatically. The only thing you'd need to override additionally is the fail_under setting, which it sets to 100%, but it's not compatible with runtime-dependent tests that cannot reach 100% under one specific runtime. So it's good to set it to something sufficiently low while requiring 100% in Codecov.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah nice, I'll do that in a followup

]
partial_branches = [
"pragma: no branch",
Expand All @@ -329,4 +327,5 @@ partial_branches = [
"if .* or not TYPE_CHECKING:",
"if .* or not _t.TYPE_CHECKING:",
"if .* or not t.TYPE_CHECKING:",
'TODO: test this branch',
]
8 changes: 4 additions & 4 deletions src/trio/_core/_io_kqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def get_events(self, timeout: float) -> EventResult:
events += batch
if len(batch) < max_events:
break
else:
else: # TODO: test this line
timeout = 0
# and loop back to the start
return events
Expand All @@ -93,12 +93,12 @@ def process_events(self, events: EventResult) -> None:
self._force_wakeup.drain()
continue
receiver = self._registered[key]
if event.flags & select.KQ_EV_ONESHOT:
if event.flags & select.KQ_EV_ONESHOT: # TODO: test this branch
del self._registered[key]
if isinstance(receiver, _core.Task):
_core.reschedule(receiver, outcome.Value(event))
else:
receiver.put_nowait(event)
receiver.put_nowait(event) # TODO: test this line

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

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

Expand Down
4 changes: 2 additions & 2 deletions src/trio/_core/_io_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -856,9 +856,9 @@ async def wait_overlapped(
<https://github.com/python-trio/trio/issues/52>`__.
"""
handle = _handle(handle_)
if isinstance(lpOverlapped, int):
if isinstance(lpOverlapped, int): # TODO: test this line
lpOverlapped = ffi.cast("LPOVERLAPPED", lpOverlapped)
if lpOverlapped in self._overlapped_waiters:
if lpOverlapped in self._overlapped_waiters: # TODO: test this line
raise _core.BusyResourceError(
"another task is already waiting on that lpOverlapped",
)
Expand Down
3 changes: 2 additions & 1 deletion src/trio/_core/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ def _public(fn: RetT) -> RetT:
_r = random.Random()


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

global _ALLOW_DETERMINISTIC_SCHEDULING
Expand Down
41 changes: 41 additions & 0 deletions src/trio/_core/_tests/test_io.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from __future__ import annotations

import random
import select
import socket as stdlib_socket
import sys
from collections.abc import Awaitable, Callable
from contextlib import suppress
from typing import TYPE_CHECKING, TypeVar
Expand Down Expand Up @@ -343,6 +345,7 @@ def check(*, expected_readers: int, expected_writers: int) -> None:
assert iostats.tasks_waiting_write == expected_writers
else:
assert iostats.backend == "kqueue"
assert iostats.monitors == 0
assert iostats.tasks_waiting == expected_readers + expected_writers

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


@pytest.mark.filterwarnings("ignore:.*UnboundedQueue:trio.TrioDeprecationWarning")
async def test_io_manager_kqueue_monitors_statistics() -> None:
graingert marked this conversation as resolved.
Show resolved Hide resolved
def check(
*,
expected_monitors: int,
expected_readers: int,
expected_writers: int,
) -> None:
statistics = _core.current_statistics()
print(statistics)
iostats = statistics.io_statistics
assert iostats.backend == "kqueue"
assert iostats.monitors == expected_monitors
assert iostats.tasks_waiting == expected_readers + expected_writers

a1, b1 = stdlib_socket.socketpair()
for sock in [a1, b1]:
sock.setblocking(False)

with a1, b1:
# let the call_soon_task settle down
await wait_all_tasks_blocked()

if sys.platform != "win32" and sys.platform != "linux":
# 1 for call_soon_task
check(expected_monitors=0, expected_readers=1, expected_writers=0)

with _core.monitor_kevent(a1.fileno(), select.KQ_FILTER_READ):
with (
pytest.raises(_core.BusyResourceError),
_core.monitor_kevent(a1.fileno(), select.KQ_FILTER_READ),
):
pass # pragma: no cover
check(expected_monitors=1, expected_readers=1, expected_writers=0)

check(expected_monitors=0, expected_readers=1, expected_writers=0)


async def test_can_survive_unnotified_close() -> None:
# An "unnotified" close is when the user closes an fd/socket/handle
# directly, without calling notify_closing first. This should never happen
Expand Down
21 changes: 20 additions & 1 deletion src/trio/_core/_tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from contextlib import ExitStack, contextmanager, suppress
from math import inf, nan
from typing import TYPE_CHECKING, NoReturn, TypeVar
from unittest import mock

import outcome
import pytest
Expand All @@ -26,7 +27,7 @@
assert_checkpoints,
wait_all_tasks_blocked,
)
from .._run import DEADLINE_HEAP_MIN_PRUNE_THRESHOLD
from .._run import DEADLINE_HEAP_MIN_PRUNE_THRESHOLD, _count_context_run_tb_frames
from .tutil import (
check_sequence_matches,
create_asyncio_future_in_new_loop,
Expand Down Expand Up @@ -371,6 +372,15 @@ async def test_cancel_scope_validation() -> None:
match="^Cannot specify both a deadline and a relative deadline$",
):
_core.CancelScope(deadline=7, relative_deadline=3)

with pytest.raises(ValueError, match="^deadline must not be NaN$"):
_core.CancelScope(deadline=nan)
with pytest.raises(ValueError, match="^relative deadline must not be NaN$"):
_core.CancelScope(relative_deadline=nan)

with pytest.raises(ValueError, match="^timeout must be non-negative$"):
_core.CancelScope(relative_deadline=-3)

scope = _core.CancelScope()

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

assert isinstance(exc, MyException)
assert gc.get_referrers(exc) == no_other_refs()


def test_context_run_tb_frames() -> None:
class Context:
def run(self, fn: Callable[[], object]) -> object:
return fn()

with mock.patch("trio._core._run.copy_context", return_value=Context()):
assert _count_context_run_tb_frames() == 1
15 changes: 7 additions & 8 deletions src/trio/_dtls.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def worst_case_mtu(sock: SocketType) -> int:
if sock.family == trio.socket.AF_INET:
return 576 - packet_header_overhead(sock)
else:
return 1280 - packet_header_overhead(sock)
return 1280 - packet_header_overhead(sock) # TODO: test this line


def best_guess_mtu(sock: SocketType) -> int:
Expand Down Expand Up @@ -222,7 +222,7 @@ def decode_handshake_fragment_untrusted(payload: bytes) -> HandshakeFragment:
frag_offset_bytes,
frag_len_bytes,
) = HANDSHAKE_MESSAGE_HEADER.unpack_from(payload)
except struct.error as exc:
except struct.error as exc: # TODO: test this line
raise BadPacket("bad handshake message header") from exc
# 'struct' doesn't have built-in support for 24-bit integers, so we
# have to do it by hand. These can't fail.
Expand Down Expand Up @@ -425,14 +425,14 @@ def encode_volley(
for message in messages:
if isinstance(message, OpaqueHandshakeMessage):
encoded = encode_record(message.record)
if mtu - len(packet) - len(encoded) <= 0:
if mtu - len(packet) - len(encoded) <= 0: # TODO: test this line
packets.append(packet)
packet = bytearray()
packet += encoded
assert len(packet) <= mtu
elif isinstance(message, PseudoHandshakeMessage):
space = mtu - len(packet) - RECORD_HEADER.size - len(message.payload)
if space <= 0:
if space <= 0: # TODO: test this line
packets.append(packet)
packet = bytearray()
packet += RECORD_HEADER.pack(
Expand Down Expand Up @@ -1039,7 +1039,7 @@ def read_volley() -> list[_AnyHandshakeMessage]:
if (
isinstance(maybe_volley[0], PseudoHandshakeMessage)
and maybe_volley[0].content_type == ContentType.alert
):
): # TODO: test this line
# we're sending an alert (e.g. due to a corrupted
# packet). We want to send it once, but don't save it to
# retransmit -- keep the last volley as the current
Expand Down Expand Up @@ -1326,9 +1326,8 @@ async def handler(dtls_channel):
raise trio.BusyResourceError("another task is already listening")
try:
self.socket.getsockname()
except OSError:
# TODO: Write test that triggers this
raise RuntimeError( # pragma: no cover
except OSError: # TODO: test this line
raise RuntimeError(
"DTLS socket must be bound before it can serve",
) from None
self._ensure_receive_loop()
Expand Down
4 changes: 3 additions & 1 deletion src/trio/_tests/test_dtls.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ async def echo_handler(dtls_channel: DTLSChannel) -> None:
print("server starting do_handshake")
await dtls_channel.do_handshake()
print("server finished do_handshake")
async for packet in dtls_channel:
# no branch for leaving this for loop because we only leave
# a channel by cancellation.
async for packet in dtls_channel: # pragma: no branch
print(f"echoing {packet!r} -> {dtls_channel.peer_address!r}")
await dtls_channel.send(packet)
except trio.BrokenResourceError: # pragma: no cover
Expand Down
7 changes: 5 additions & 2 deletions src/trio/_tests/test_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,11 @@ def lookup_symbol(symbol: str) -> dict[str, str]:
elif tool == "mypy":
# load the cached type information
cached_type_info = cache_json["names"][class_name]
if "node" not in cached_type_info:
cached_type_info = lookup_symbol(cached_type_info["cross_ref"])
# 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!
assert "node" not in cached_type_info
graingert marked this conversation as resolved.
Show resolved Hide resolved
cached_type_info = lookup_symbol(cached_type_info["cross_ref"])

assert "node" in cached_type_info
node = cached_type_info["node"]
Expand Down
4 changes: 1 addition & 3 deletions src/trio/_tests/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,7 @@ async def test_sniff_sockopts() -> None:
from socket import AF_INET, AF_INET6, SOCK_DGRAM, SOCK_STREAM

# generate the combinations of families/types we're testing:
families = [AF_INET]
if can_create_ipv6:
families.append(AF_INET6)
families = (AF_INET, AF_INET6) if can_create_ipv6 else (AF_INET,)
sockets = [
stdlib_socket.socket(family, type_)
for family in families
Expand Down
20 changes: 2 additions & 18 deletions src/trio/_tests/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,27 +210,11 @@ def __init__(
# we still have to support versions before that, and that means we
# need to test renegotiation support, which means we need to force this
# to use a lower version where this test server can trigger
# renegotiations. Of course TLS 1.3 support isn't released yet, but
# I'm told that this will work once it is. (And once it is we can
# remove the pragma: no cover too.) Alternatively, we could switch to
# using TLSv1_2_METHOD.
#
# Discussion: https://github.com/pyca/pyopenssl/issues/624

# This is the right way, but we can't use it until this PR is in a
# released:
# https://github.com/pyca/pyopenssl/pull/861
#
# if hasattr(SSL, "OP_NO_TLSv1_3"):
# ctx.set_options(SSL.OP_NO_TLSv1_3)
#
# Fortunately pyopenssl uses cryptography under the hood, so we can be
# confident that they're using the same version of openssl
# renegotiations.
from cryptography.hazmat.bindings.openssl.binding import Binding

b = Binding()
if hasattr(b.lib, "SSL_OP_NO_TLSv1_3"):
ctx.set_options(b.lib.SSL_OP_NO_TLSv1_3)
ctx.set_options(b.lib.SSL_OP_NO_TLSv1_3)

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