-
-
Notifications
You must be signed in to change notification settings - Fork 357
Improve REPL KI #3030
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
Open
richardsheridan
wants to merge
13
commits into
python-trio:main
Choose a base branch
from
richardsheridan:repl_ki
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Improve REPL KI #3030
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
39d0ca2
improve repl KI
richardsheridan 46a3f02
Merge branch 'main' into repl_ki
CoolCat467 a5dcdbf
apply suggestion from review, ensure calls in handler are safe
richardsheridan 039bad1
grab token and install handler in one go
richardsheridan 240f9ff
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 611a89d
Merge branch 'main' into repl_ki
A5rocks 301f361
Don't add extra newlines on KI
A5rocks 95f0d33
Add some tests
A5rocks b71a1b8
First pass at CI failures
A5rocks 919cc8b
The CI runners have `dev.tty.legacy_tiocsti` set to `0`
A5rocks 6c8d1e4
Hacky fixes for Windows
A5rocks 04abe8d
Try to avoid flakiness
A5rocks 7f135cc
Address PR review and first pass at codecov
A5rocks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Make ctrl+c work in more situations in the Trio REPL (``python -m trio``). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
import signal | ||
import subprocess | ||
import sys | ||
from functools import partial | ||
from typing import Protocol | ||
|
||
import pytest | ||
|
@@ -239,3 +242,179 @@ def test_main_entrypoint() -> None: | |
""" | ||
repl = subprocess.run([sys.executable, "-m", "trio"], input=b"exit()") | ||
assert repl.returncode == 0 | ||
|
||
|
||
# TODO: skip this based on sysctls? Or Linux version? | ||
@pytest.mark.skipif(True, reason="the ioctl we use is disabled in CI") | ||
def test_ki_newline_injection() -> None: # TODO: test this line | ||
# TODO: we want to remove this functionality, eg by using vendored | ||
# pyrepls. | ||
assert sys.platform != "win32" | ||
|
||
import pty | ||
|
||
# NOTE: this cannot be subprocess.Popen because pty.fork | ||
# does some magic to set the controlling terminal. | ||
# (which I don't know how to replicate... so I copied this | ||
# structure from pty.spawn...) | ||
pid, pty_fd = pty.fork() # type: ignore[attr-defined,unused-ignore] | ||
if pid == 0: | ||
os.execlp(sys.executable, *[sys.executable, "-u", "-m", "trio"]) | ||
|
||
# setup: | ||
buffer = b"" | ||
while not buffer.endswith(b"import trio\r\n>>> "): | ||
buffer += os.read(pty_fd, 4096) | ||
|
||
# sanity check: | ||
print(buffer.decode()) | ||
buffer = b"" | ||
os.write(pty_fd, b'print("hello!")\n') | ||
while not buffer.endswith(b">>> "): | ||
buffer += os.read(pty_fd, 4096) | ||
|
||
assert buffer.count(b"hello!") == 2 | ||
|
||
# press ctrl+c | ||
print(buffer.decode()) | ||
buffer = b"" | ||
os.kill(pid, signal.SIGINT) | ||
while not buffer.endswith(b">>> "): | ||
buffer += os.read(pty_fd, 4096) | ||
|
||
assert b"KeyboardInterrupt" in buffer | ||
|
||
# press ctrl+c later | ||
print(buffer.decode()) | ||
buffer = b"" | ||
os.write(pty_fd, b'print("hello!")') | ||
os.kill(pid, signal.SIGINT) | ||
while not buffer.endswith(b">>> "): | ||
buffer += os.read(pty_fd, 4096) | ||
|
||
assert b"KeyboardInterrupt" in buffer | ||
print(buffer.decode()) | ||
os.close(pty_fd) | ||
os.waitpid(pid, 0)[1] | ||
|
||
|
||
async def test_ki_in_repl() -> None: | ||
async with trio.open_nursery() as nursery: | ||
proc = await nursery.start( | ||
partial( | ||
trio.run_process, | ||
[sys.executable, "-u", "-m", "trio"], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.STDOUT, | ||
stdin=subprocess.PIPE, | ||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0, # type: ignore[attr-defined,unused-ignore] | ||
) | ||
) | ||
|
||
async with proc.stdout: | ||
# setup | ||
buffer = b"" | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
# TODO: consider making run_process stdout have some universal newlines thing | ||
if buffer.replace(b"\r\n", b"\n").endswith(b"import trio\n>>> "): | ||
break | ||
|
||
# ensure things work | ||
print(buffer.decode()) | ||
buffer = b"" | ||
await proc.stdin.send_all(b'print("hello!")\n') | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
if buffer.endswith(b">>> "): | ||
break | ||
|
||
assert b"hello!" in buffer | ||
print(buffer.decode()) | ||
|
||
# this seems to be necessary on Windows for reasons | ||
# (the parents of process groups ignore ctrl+c by default...) | ||
if sys.platform == "win32": | ||
buffer = b"" | ||
await proc.stdin.send_all( | ||
b"import ctypes; ctypes.windll.kernel32.SetConsoleCtrlHandler(None, False)\n" | ||
) | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
if buffer.endswith(b">>> "): | ||
break | ||
|
||
print(buffer.decode()) | ||
|
||
# try to decrease flakiness... | ||
buffer = b"" | ||
await proc.stdin.send_all( | ||
b"import coverage; trio.lowlevel.enable_ki_protection(coverage.pytracer.PyTracer._trace)\n" | ||
) | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
if buffer.endswith(b">>> "): | ||
break | ||
|
||
print(buffer.decode()) | ||
|
||
# ensure that ctrl+c on a prompt works | ||
# NOTE: for some reason, signal.SIGINT doesn't work for this test. | ||
# Using CTRL_C_EVENT is also why we need subprocess.CREATE_NEW_PROCESS_GROUP | ||
signal_sent = signal.CTRL_C_EVENT if sys.platform == "win32" else signal.SIGINT # type: ignore[attr-defined,unused-ignore] | ||
os.kill(proc.pid, signal_sent) | ||
if sys.platform == "win32": | ||
# we rely on EOFError which... doesn't happen with pipes. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I never dug in to why EOFError pops out, maybe that knowledge could help us test here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't dug into this yet unfortunately. |
||
# I'm not sure how to fix it... | ||
await proc.stdin.send_all(b"\n") | ||
else: | ||
# we test injection separately | ||
await proc.stdin.send_all(b"\n") | ||
|
||
buffer = b"" | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
if buffer.endswith(b">>> "): | ||
break | ||
|
||
assert b"KeyboardInterrupt" in buffer | ||
|
||
# ensure ctrl+c while a command runs works | ||
print(buffer.decode()) | ||
await proc.stdin.send_all(b'print("READY"); await trio.sleep_forever()\n') | ||
killed = False | ||
buffer = b"" | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
if buffer.replace(b"\r\n", b"\n").endswith(b"READY\n") and not killed: | ||
os.kill(proc.pid, signal_sent) | ||
killed = True | ||
if buffer.endswith(b">>> "): | ||
break | ||
|
||
assert b"trio" in buffer | ||
assert b"KeyboardInterrupt" in buffer | ||
|
||
# make sure it works for sync commands too | ||
# (though this would be hard to break) | ||
print(buffer.decode()) | ||
await proc.stdin.send_all( | ||
b'import time; print("READY"); time.sleep(99999)\n' | ||
) | ||
killed = False | ||
buffer = b"" | ||
async for part in proc.stdout: # pragma: no branch | ||
buffer += part | ||
if buffer.replace(b"\r\n", b"\n").endswith(b"READY\n") and not killed: | ||
os.kill(proc.pid, signal_sent) | ||
killed = True | ||
if buffer.endswith(b">>> "): | ||
break | ||
|
||
assert b"Traceback" in buffer | ||
assert b"KeyboardInterrupt" in buffer | ||
|
||
print(buffer.decode()) | ||
|
||
# kill the process | ||
nursery.cancel_scope.cancel() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of simply suppressing OSError, maybe catch it and prompt the user to press enter to continue by printing text? We could also get the OSError code at that time which could be useful for understanding the situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hopefully my change worked, I'm not on a Linux machine so I cannot check yet.