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

Type all remaining tests #2820

Merged
merged 45 commits into from
Oct 30, 2023
Merged

Type all remaining tests #2820

merged 45 commits into from
Oct 30, 2023

Conversation

jakkdl
Copy link
Member

@jakkdl jakkdl commented Oct 18, 2023

Decided to do a speedrun through all the remaining test files, with the help of https://github.com/JelleZijlstra/autotyping

If you see anything incorrect, you're very welcome to edit it straight away - I didn't put a lot of effort on average into the type hints, so there's probably a decent amount of them that are bad in some way or another.

@codecov
Copy link

codecov bot commented Oct 18, 2023

Codecov Report

Merging #2820 (43c7d6d) into master (7773cfb) will decrease coverage by 0.01%.
The diff coverage is 99.10%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2820      +/-   ##
==========================================
- Coverage   99.18%   99.18%   -0.01%     
==========================================
  Files         115      115              
  Lines       17466    17585     +119     
  Branches     3140     3140              
==========================================
+ Hits        17324    17442     +118     
- Misses         97       99       +2     
+ Partials       45       44       -1     
Files Coverage Δ
trio/_core/__init__.py 100.00% <100.00%> (ø)
trio/_core/_local.py 100.00% <100.00%> (ø)
trio/_core/_multierror.py 100.00% <100.00%> (ø)
trio/_core/_tests/test_guest_mode.py 100.00% <100.00%> (+0.30%) ⬆️
trio/_core/_tests/test_instrumentation.py 100.00% <100.00%> (ø)
trio/_core/_tests/test_ki.py 97.83% <100.00%> (+<0.01%) ⬆️
trio/_core/_tests/test_local.py 100.00% <100.00%> (ø)
trio/_core/_tests/test_mock_clock.py 100.00% <100.00%> (ø)
trio/_core/_tests/test_multierror.py 100.00% <100.00%> (ø)
trio/_core/_tests/test_parking_lot.py 100.00% <100.00%> (ø)
... and 31 more

... and 1 file with indirect coverage changes

@@ -322,18 +345,18 @@ async def get_woken_by_host_deadline(watb_cscope):
# 'sit_in_wait_all_tasks_blocked', we want the test to
# actually end. So in after_io_wait we schedule a second host
# call to tear things down.
class InstrumentHelper:
def __init__(self):
class InstrumentHelper(trio._abc.Instrument):
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably should import using a public name.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I wanted to consistently do this, but I was inconsistent a bunch of times when everything else in a test did relative imports. Other than when it's required because a symbol isn't exported, is there any reason not to do all imports from the public name? If so is there a linter warning for it?

# make_tree creates cycles itself, so a simple
raise MultiError([get_exc(raiser1), get_exc(raiser2)])

def simple_filter(exc):
def simple_filter(exc: BaseException) -> Exception | RuntimeError:
Copy link
Contributor

Choose a reason for hiding this comment

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

Exception | RuntimeError is redundant.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, I did it intentionally to mirror the function body. I realize now the proper way to do it would maybe be (exc: T_Exception) -> T_Exception | RunTimeError, but that's kinda overkill.

raw = io.BytesIO()
buffered = io.BufferedReader(raw)
buffered = io.BufferedReader(raw) # type: ignore[arg-type] # ????????????
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I think the reason is that BufferedReader wraps a unbuffered raw IO object, but BytesIO is already buffered - BufferedIOBase and RawIOBase are sibling classes, they don't share hierarchy. Maybe swap this to use tmp_path and open an unbuffered file?

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks, that worked!

Copy link
Member Author

Choose a reason for hiding this comment

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

Or well, getting ASYNC101 for opening a file in an async function, so one might possibly want to do a more async way of opening it.

trio/_tests/test_highlevel_generic.py Outdated Show resolved Hide resolved
trio/testing/_fake_net.py Outdated Show resolved Hide resolved
trio/_ssl.py Show resolved Hide resolved
trio/_tests/test_threads.py Outdated Show resolved Hide resolved
trio/_tests/test_threads.py Show resolved Hide resolved
@CoolCat467
Copy link
Member

How do we fix the following errors test_exports raises?

jedi can't see the following symbols in trio.lowlevel:
{'trio.lowlevel.RunVarToken': {'extra': {'previous_value', 'redeemed'},
'missing': set()}}

mypy can't see the following symbols in trio.lowlevel:
{'trio.lowlevel.RunVarToken': {'extra': {'previous_value', 'redeemed'},
'missing': set()}}

Copy link
Member

@CoolCat467 CoolCat467 left a comment

Choose a reason for hiding this comment

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

Looks pretty good except a few things here and there.

trio/_core/_tests/test_guest_mode.py Show resolved Hide resolved
trio/_core/_tests/test_instrumentation.py Show resolved Hide resolved
trio/_core/_tests/test_ki.py Show resolved Hide resolved
trio/_core/_tests/test_multierror.py Outdated Show resolved Hide resolved
trio/_tests/test_highlevel_ssl_helpers.py Show resolved Hide resolved
trio/_tests/test_threads.py Outdated Show resolved Hide resolved
trio/_tests/test_threads.py Show resolved Hide resolved
trio/_tests/test_timeouts.py Show resolved Hide resolved
trio/_tests/test_tracing.py Show resolved Hide resolved
trio/testing/_fake_net.py Outdated Show resolved Hide resolved
@CoolCat467
Copy link
Member

All of the collections.abc imports I noted would have been able to be fixed automatically if #2809 would go through, with ruff's rule UP035 (https://docs.astral.sh/ruff/rules/deprecated-import/)

@jakkdl
Copy link
Member Author

jakkdl commented Oct 19, 2023

yeah I intentionally don't bother in large part because I expect it to get autofixed, but also doesn't really matter.

@jakkdl
Copy link
Member Author

jakkdl commented Oct 25, 2023

hrm, pre-commit.ci is running ruff on 0.1.1 whereas check.sh is running ruff with 0.1.0 - this feels destined to bring a conflict at some point.

@CoolCat467
Copy link
Member

hrm, pre-commit.ci is running ruff on 0.1.1 whereas check.sh is running ruff with 0.1.0 - this feels destined to bring a conflict at some point.

I quite agree, we should update that soon.

Copy link
Member

@CoolCat467 CoolCat467 left a comment

Choose a reason for hiding this comment

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

Looks great, I'm sure the collections.abc things will get fixed at a later point if/when we run pyupgrade.

@CoolCat467
Copy link
Member

In reference to commit 6170384, if you look at the pre-commit run in the PR checks area, at the very bottom of the page it displays, it says

autofix pushing was disabled in the configuration. to autofix this pr, comment "pre-commit.ci autofix" on the pull request

@jakkdl
Copy link
Member Author

jakkdl commented Oct 26, 2023

I'll merge this soonish unless anybody has something to comment on, because this touches so much it very easily get merge conflicts. (e.g. other people adding test cases that aren't typechecked without this PR)

Copy link
Contributor

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

Well, I skipped all the test files after the actual code changes because... I mean they're just tests. Comments but none of them block anything.

in_host_after_start: Callable[[], None] | None = None,
**start_guest_run_kwargs: Any,
) -> T:
todo: queue.Queue[tuple[str, Outcome[T] | Callable[..., object]]] = queue.Queue()
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably way over the top but this could have a literal of strings and use typing.assert_never. But that's like too much for a simple test (which we know will either pass or fail based on a local run whenever we make changes so not sure exhaustive checks is really necessary).

@@ -450,7 +483,11 @@ async def trio_main():
aio_task.cancel()
return "trio-main-done"

async def aio_pingpong(from_trio, to_trio):
raise AssertionError("should never be reached") # pragma: no cover
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is necessary... unless mypy forces you to use explicit returns? async def trio_main() -> str | None.

Copy link
Member Author

Choose a reason for hiding this comment

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

-> str|None would still give "missing return statement", if you have an explicit return somewhere in the function it wants explicit returns in all branches.

class TaskRecorder:
record = attr.ib(factory=list)
class TaskRecorder(_abc.Instrument):
record: list[tuple[str, Task | None]] = attr.ib(factory=list)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can keep the variable length tuples and just use tuple length to differentiate (I believe that narrows in mypy? If not, ignore this comment!)

Specifically list[tuple[str] | tuple[str, Task]]

Copy link
Contributor

Choose a reason for hiding this comment

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

It does as of last week - python/mypy#16237. So not quite yet.

Copy link
Member Author

@jakkdl jakkdl Oct 30, 2023

Choose a reason for hiding this comment

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

ye not gonna bother waiting for that, so I'll leave the changes as is.


# Changing the set of hooks implemented by an instrument after
# it's installed doesn't make them start being called right away
instrument.before_task_step = record.append
instrument.before_task_step = record.append # type: ignore[assignment]
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure why this is type erroring?

Copy link
Member Author

Choose a reason for hiding this comment

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

trio/_core/_tests/test_instrumentation.py:222: error: Cannot assign to a method  [method-assign]
trio/_core/_tests/test_instrumentation.py:222: error: Incompatible types in assignment (expression has type "Callable[[Task], None]", variable has type "Callable[[Arg(Task, 'task')], None]")  [assignment]

If I edit _abc.Instrument.before_task_step to make task positional-only then I only get the first error. Seems like ignore[assignment] covers both, but made it extra explicit by ignoring both, and added comment that explains the errors.

@_core.enable_ki_protection
@async_generator
async def agen_protected1():
@async_generator # type: ignore[misc] # untyped generator
Copy link
Contributor

Choose a reason for hiding this comment

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

Just checking understanding, this is cause async_generator isn't typed?

Copy link
Member Author

@jakkdl jakkdl Oct 30, 2023

Choose a reason for hiding this comment

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

yeah, adding a comment.

@@ -488,21 +489,22 @@ async def main():


# Regression test for #461
def test_ki_with_broken_threads():
# don't know if _active not being visible is a problem
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably more something to bring up with typeshed? But it's not documented AFAICT so probably intentional.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, it's only _active and not __active, so I'm pretty sure a lot of code actually inspects it or modifies it in a subclass, but yeah makes sense that typeshed sticks to the documented parts.

trio/_core/_tests/test_multierror.py Show resolved Hide resolved
def check_sequence_matches(
seq: Sequence[object], template: Iterable[object | set[object]]
) -> None:
def check_sequence_matches(seq: Sequence[T], template: Iterable[T | set[T]]) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Hm. I'm not sure I quite follow why the T here -- but I don't remember the variance of Iterable so maybe this makes sense... Though I guess maybe for set.... aaa whatever, might as well ask.

Copy link
Member Author

Choose a reason for hiding this comment

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

I.. don't quite follow why the signature would be confusing? Though the comment above reversing them might be the cause of confusion.
You pass it a sequence of some thing, e.g. [1, 2, 3, 4, 5] or [1, 2, 3, 5, 4], and use this function to see whether that matches a template - which can include either objects of the same type, or if you expect the order to sometimes be jumbled you can specify them inside a set*: [1, 2, 3, {4, 5}]. So the template should be an Iterable which contains objects of the same type your sequence consists of, or sets of that object. Variance is not related in any way.

The Sequence vs Iterable is because the function does slicing on seq, but only iterates over template.

* I did not expect this to be how the function actually worked, and wrote an incorrect description at first where I assumed the sets were about unions. Not a great name imho.

Improved the comments accompanying the function.

Copy link
Contributor

Choose a reason for hiding this comment

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

Well the T w/ Sequence (covariant) means that something like this would pass typechecking: check_sequence_matches([5.0, 4.0], [5, 4]) but I was in fact being blind to the fact that object has that problem too.

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, huh. Yeah I don't think we really care about that level of precision, as it's only really used in a few tests and not a public function. I need to get better at variance, for sure.

Comment on lines 42 to 43
Args:
host (bytes or str): The host to connect to. We require the server
host (str): The host to connect to. We require the server
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this passing bytes error at runtime?

assert (sys.platform != "win32" and sys.platform != "darwin") or not TYPE_CHECKING
if TYPE_CHECKING:

def sync_wait_reapable(pid: int) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Mind leaving a comment about why this function specifically is up here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's to provide a definition if imported on other platforms.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's for trio/_tests/test_subprocess.py:test_waitid_eintr():

@slow
def test_waitid_eintr() -> None:
    # This only matters on PyPy (where we're coding EINTR handling
    # ourselves) but the test works on all waitid platforms.
    from .._subprocess_platform import wait_child_exiting

    if TYPE_CHECKING and sys.platform == "win32":
        return

    if not wait_child_exiting.__module__.endswith("waitid"):
        pytest.skip("waitid only")
    from .._subprocess_platform.waitid import sync_wait_reapable

    ...

where despite having a
if TYPE_CHECKING and sys.platform == "win32": return it will still complain about the import on line 522. I guess this one could be reported upstream?

Though I realize now it's probably better to silence the error in the test than to make people think they can import sync_wait_reapable globally on all platforms only to get an ImportError at runtime.

trio/_tests/test_exports.py Outdated Show resolved Hide resolved
trio/_tests/test_highlevel_serve_listeners.py Outdated Show resolved Hide resolved
assert (sys.platform != "win32" and sys.platform != "darwin") or not TYPE_CHECKING
if TYPE_CHECKING:

def sync_wait_reapable(pid: int) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's to provide a definition if imported on other platforms.

@CoolCat467
Copy link
Member

pre-commit.ci autofix

@CoolCat467 CoolCat467 added the typing Adding static types to trio's interface label Oct 29, 2023
@jakkdl jakkdl merged commit 12e2da8 into python-trio:master Oct 30, 2023
27 of 29 checks passed
@jakkdl jakkdl deleted the type_tests branch October 30, 2023 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
typing Adding static types to trio's interface
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants