Skip to content

Commit f0cf9e6

Browse files
authored
Update cmd from 3.13.5 (RustPython#5920)
* Update cmd from 3.13.5 * Add `test.support.pty_helepr` from 3.13.5
1 parent 2f9459c commit f0cf9e6

File tree

3 files changed

+139
-16
lines changed

3 files changed

+139
-16
lines changed

Lib/cmd.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
functions respectively.
4343
"""
4444

45-
import string, sys
45+
import inspect, string, sys
4646

4747
__all__ = ["Cmd"]
4848

@@ -108,7 +108,15 @@ def cmdloop(self, intro=None):
108108
import readline
109109
self.old_completer = readline.get_completer()
110110
readline.set_completer(self.complete)
111-
readline.parse_and_bind(self.completekey+": complete")
111+
if readline.backend == "editline":
112+
if self.completekey == 'tab':
113+
# libedit uses "^I" instead of "tab"
114+
command_string = "bind ^I rl_complete"
115+
else:
116+
command_string = f"bind {self.completekey} rl_complete"
117+
else:
118+
command_string = f"{self.completekey}: complete"
119+
readline.parse_and_bind(command_string)
112120
except ImportError:
113121
pass
114122
try:
@@ -210,9 +218,8 @@ def onecmd(self, line):
210218
if cmd == '':
211219
return self.default(line)
212220
else:
213-
try:
214-
func = getattr(self, 'do_' + cmd)
215-
except AttributeError:
221+
func = getattr(self, 'do_' + cmd, None)
222+
if func is None:
216223
return self.default(line)
217224
return func(arg)
218225

@@ -298,6 +305,7 @@ def do_help(self, arg):
298305
except AttributeError:
299306
try:
300307
doc=getattr(self, 'do_' + arg).__doc__
308+
doc = inspect.cleandoc(doc)
301309
if doc:
302310
self.stdout.write("%s\n"%str(doc))
303311
return

Lib/test/support/pty_helper.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
Helper to run a script in a pseudo-terminal.
3+
"""
4+
import os
5+
import selectors
6+
import subprocess
7+
import sys
8+
from contextlib import ExitStack
9+
from errno import EIO
10+
11+
from test.support.import_helper import import_module
12+
13+
def run_pty(script, input=b"dummy input\r", env=None):
14+
pty = import_module('pty')
15+
output = bytearray()
16+
[master, slave] = pty.openpty()
17+
args = (sys.executable, '-c', script)
18+
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
19+
os.close(slave)
20+
with ExitStack() as cleanup:
21+
cleanup.enter_context(proc)
22+
def terminate(proc):
23+
try:
24+
proc.terminate()
25+
except ProcessLookupError:
26+
# Workaround for Open/Net BSD bug (Issue 16762)
27+
pass
28+
cleanup.callback(terminate, proc)
29+
cleanup.callback(os.close, master)
30+
# Avoid using DefaultSelector and PollSelector. Kqueue() does not
31+
# work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
32+
# BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
33+
# either (Issue 20472). Hopefully the file descriptor is low enough
34+
# to use with select().
35+
sel = cleanup.enter_context(selectors.SelectSelector())
36+
sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
37+
os.set_blocking(master, False)
38+
while True:
39+
for [_, events] in sel.select():
40+
if events & selectors.EVENT_READ:
41+
try:
42+
chunk = os.read(master, 0x10000)
43+
except OSError as err:
44+
# Linux raises EIO when slave is closed (Issue 5380)
45+
if err.errno != EIO:
46+
raise
47+
chunk = b""
48+
if not chunk:
49+
return output
50+
output.extend(chunk)
51+
if events & selectors.EVENT_WRITE:
52+
try:
53+
input = input[os.write(master, input):]
54+
except OSError as err:
55+
# Apparently EIO means the slave was closed
56+
if err.errno != EIO:
57+
raise
58+
input = b"" # Stop writing
59+
if not input:
60+
sel.modify(master, selectors.EVENT_READ)
61+
62+
63+
######################################################################
64+
## Fake stdin (for testing interactive debugging)
65+
######################################################################
66+
67+
class FakeInput:
68+
"""
69+
A fake input stream for pdb's interactive debugger. Whenever a
70+
line is read, print it (to simulate the user typing it), and then
71+
return it. The set of lines to return is specified in the
72+
constructor; they should not have trailing newlines.
73+
"""
74+
def __init__(self, lines):
75+
self.lines = lines
76+
77+
def readline(self):
78+
line = self.lines.pop(0)
79+
print(line)
80+
return line + '\n'

Lib/test/test_cmd.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
import doctest
1010
import unittest
1111
import io
12+
import textwrap
1213
from test import support
14+
from test.support.import_helper import import_module
15+
from test.support.pty_helper import run_pty
1316

1417
class samplecmdclass(cmd.Cmd):
1518
"""
@@ -244,23 +247,55 @@ def test_input_reset_at_EOF(self):
244247
"(Cmd) *** Unknown syntax: EOF\n"))
245248

246249

250+
class CmdPrintExceptionClass(cmd.Cmd):
251+
"""
252+
GH-80731
253+
cmd.Cmd should print the correct exception in default()
254+
>>> mycmd = CmdPrintExceptionClass()
255+
>>> try:
256+
... raise ValueError("test")
257+
... except ValueError:
258+
... mycmd.onecmd("not important")
259+
(<class 'ValueError'>, ValueError('test'))
260+
"""
261+
262+
def default(self, line):
263+
print(sys.exc_info()[:2])
264+
265+
266+
@support.requires_subprocess()
267+
class CmdTestReadline(unittest.TestCase):
268+
def setUpClass():
269+
# Ensure that the readline module is loaded
270+
# If this fails, the test is skipped because SkipTest will be raised
271+
readline = import_module('readline')
272+
273+
def test_basic_completion(self):
274+
script = textwrap.dedent("""
275+
import cmd
276+
class simplecmd(cmd.Cmd):
277+
def do_tab_completion_test(self, args):
278+
print('tab completion success')
279+
return True
280+
281+
simplecmd().cmdloop()
282+
""")
283+
284+
# 't' and complete 'ab_completion_test' to 'tab_completion_test'
285+
input = b"t\t\n"
286+
287+
output = run_pty(script, input)
288+
289+
self.assertIn(b'ab_completion_test', output)
290+
self.assertIn(b'tab completion success', output)
291+
247292
def load_tests(loader, tests, pattern):
248293
tests.addTest(doctest.DocTestSuite())
249294
return tests
250295

251-
def test_coverage(coverdir):
252-
trace = support.import_module('trace')
253-
tracer=trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
254-
trace=0, count=1)
255-
tracer.run('import importlib; importlib.reload(cmd); test_main()')
256-
r=tracer.results()
257-
print("Writing coverage results...")
258-
r.write_results(show_missing=True, summary=True, coverdir=coverdir)
259296

260297
if __name__ == "__main__":
261-
if "-c" in sys.argv:
262-
test_coverage('/tmp/cmd.cover')
263-
elif "-i" in sys.argv:
298+
if "-i" in sys.argv:
264299
samplecmdclass().cmdloop()
265300
else:
266301
unittest.main()

0 commit comments

Comments
 (0)