Skip to content

Commit a46366b

Browse files
committed
Add tests for repl
1 parent f207af3 commit a46366b

File tree

1 file changed

+150
-0
lines changed
  • graalpython/com.oracle.graal.python.test/src/tests

1 file changed

+150
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
import os
41+
import re
42+
import select
43+
import subprocess
44+
import sys
45+
import termios
46+
from textwrap import dedent
47+
48+
49+
def validate_repl(stdin):
50+
env = os.environ.copy()
51+
env['TERM'] = 'ansi'
52+
env['PYTHONIOENCODING'] = 'utf-8'
53+
pty_parent, pty_child = os.openpty()
54+
termios.tcsetwinsize(pty_parent, (60, 80))
55+
proc = subprocess.Popen([sys.executable, '-I'], env=env, stdin=pty_child, stdout=pty_child, stderr=pty_child)
56+
out = ''
57+
input_and_output = []
58+
in_matches = list(re.finditer(r'^(>>>|\.\.\.) (.*)', stdin, flags=re.MULTILINE))
59+
for i, match in enumerate(in_matches):
60+
input_and_output.append((
61+
match.group(1),
62+
match.group(2),
63+
stdin[match.end():in_matches[i + 1].start() - 1 if i + 1 < len(in_matches) else -1],
64+
))
65+
first_prompt = True
66+
index = 0
67+
whole_out = ''
68+
while True:
69+
rlist, _, _ = select.select([pty_parent], [], [], 30)
70+
assert pty_parent in rlist, f"Timed out waiting for REPL output. Output: {whole_out}{out}"
71+
out += os.read(pty_parent, 1024).decode('utf-8')
72+
out = out.replace('\r\n', '\n')
73+
out = re.sub(r'\x1b\[(?:\?2004[hl]|\d+[A-G])', '', out)
74+
if out.endswith(('\n>>> ', '\n... ')):
75+
if not first_prompt:
76+
prompt = out[:3]
77+
expected_prompt, current_in, expected_out = input_and_output[index]
78+
assert prompt == expected_prompt
79+
expected = f'{expected_prompt} {current_in}{expected_out}'
80+
actual = out[:-5]
81+
assert actual == expected, f'Actual:\n{actual!r}\nExpected:\n{expected!r}'
82+
index += 1
83+
first_prompt = False
84+
whole_out += out[:-4]
85+
out = out[-4:]
86+
if index >= len(input_and_output):
87+
os.close(pty_child)
88+
os.close(pty_parent)
89+
proc.wait(timeout=5)
90+
return
91+
_, next_in, _ = input_and_output[index]
92+
os.write(pty_parent, next_in.encode('utf-8') + b'\r')
93+
94+
95+
def test_basic_repl():
96+
validate_repl(dedent("""\
97+
>>> 1023 + 1
98+
1024
99+
>>> None
100+
>>> "hello"
101+
'hello'
102+
"""))
103+
104+
105+
def test_continuation():
106+
validate_repl(dedent(r'''\
107+
>>> def foo():
108+
... a = 1
109+
... return a
110+
...
111+
>>> class Foo:
112+
... def meth(self):
113+
... return 1
114+
...
115+
>>> from functools import wraps
116+
>>> @wraps
117+
... def foo(fn):
118+
... return fn
119+
...
120+
>>> from contextlib import contextmanager
121+
>>> @contextmanager
122+
... class Foo:
123+
... pass
124+
...
125+
>>> """
126+
... asdf
127+
... """
128+
'\nasdf\n'
129+
'''))
130+
131+
132+
def test_exceptions():
133+
validate_repl(dedent("""\
134+
>>> 1 / 0
135+
Traceback (most recent call last):
136+
File "<stdin>", line 1, in <module>
137+
ZeroDivisionError: division by zero
138+
>>> import sys
139+
>>> sys.last_value
140+
ZeroDivisionError('division by zero')
141+
>>> class BrokenRepr:
142+
... def __repr__(self):
143+
... asdf
144+
...
145+
>>> BrokenRepr()
146+
Traceback (most recent call last):
147+
File "<stdin>", line 1, in <module>
148+
File "<stdin>", line 3, in __repr__
149+
NameError: name 'asdf' is not defined
150+
"""))

0 commit comments

Comments
 (0)