Skip to content

Commit b333f28

Browse files
committed
Run tests with tty (fixes #869)
1 parent ee3b0e9 commit b333f28

File tree

1 file changed

+61
-20
lines changed

1 file changed

+61
-20
lines changed

bpython/test/test_args.py

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,67 @@
1+
import errno
2+
import os
3+
import pty
14
import re
5+
import select
26
import subprocess
37
import sys
48
import tempfile
59
import unittest
610

711
from textwrap import dedent
812
from bpython import args
13+
from bpython.config import getpreferredencoding
914
from bpython.test import FixLanguageTestCase as TestCase
1015

1116

17+
def run_with_tty(command):
18+
# based on https://stackoverflow.com/questions/52954248/capture-output-as-a-tty-in-python
19+
master_stdout, slave_stdout = pty.openpty()
20+
master_stderr, slave_stderr = pty.openpty()
21+
master_stdin, slave_stdin = pty.openpty()
22+
23+
p = subprocess.Popen(
24+
command,
25+
stdout=slave_stdout,
26+
stderr=slave_stderr,
27+
stdin=slave_stdin,
28+
close_fds=True,
29+
)
30+
for fd in (slave_stdout, slave_stderr, slave_stdin):
31+
os.close(fd)
32+
33+
readable = [master_stdout, master_stderr]
34+
result = {master_stdout: b"", master_stderr: b""}
35+
try:
36+
while readable:
37+
ready, _, _ = select.select(readable, [], [], 1)
38+
for fd in ready:
39+
try:
40+
data = os.read(fd, 512)
41+
except OSError as e:
42+
if e.errno != errno.EIO:
43+
raise
44+
# EIO means EOF on some systems
45+
readable.remove(fd)
46+
else:
47+
if not data: # EOF
48+
readable.remove(fd)
49+
result[fd] += data
50+
finally:
51+
for fd in (master_stdout, master_stderr, master_stdin):
52+
os.close(fd)
53+
if p.poll() is None:
54+
p.kill()
55+
p.wait()
56+
if p.returncode:
57+
raise RuntimeError(f"Subprocess exited with {p.returncode}")
58+
59+
return (
60+
result[master_stdout].decode(getpreferredencoding()),
61+
result[master_stderr].decode(getpreferredencoding()),
62+
)
63+
64+
1265
class TestExecArgs(unittest.TestCase):
1366
# These tests are currently not very useful. Under pytest neither stdout nor stdin are ttys,
1467
# hence bpython.args.parse will just exectute code by falling back to Python.
@@ -24,13 +77,9 @@ def test_exec_dunder_file(self):
2477
)
2578
)
2679
f.flush()
27-
p = subprocess.Popen(
28-
[sys.executable] + ["-m", "bpython.curtsies", f.name],
29-
stderr=subprocess.PIPE,
30-
universal_newlines=True,
80+
_, stderr = run_with_tty(
81+
[sys.executable] + ["-m", "bpython.curtsies", f.name]
3182
)
32-
(_, stderr) = p.communicate()
33-
3483
self.assertEqual(stderr.strip(), f.name)
3584

3685
def test_exec_nonascii_file(self):
@@ -44,33 +93,25 @@ def test_exec_nonascii_file(self):
4493
)
4594
)
4695
f.flush()
47-
try:
48-
subprocess.check_call(
49-
[sys.executable, "-m", "bpython.curtsies", f.name]
50-
)
51-
except subprocess.CalledProcessError:
52-
self.fail("Error running module with nonascii characters")
96+
_, stderr = run_with_tty(
97+
[sys.executable, "-m", "bpython.curtsies", f.name],
98+
)
99+
self.assertEqual(len(stderr), 0)
53100

54101
def test_exec_nonascii_file_linenums(self):
55102
with tempfile.NamedTemporaryFile(mode="w") as f:
56103
f.write(
57104
dedent(
58105
"""\
59-
#!/usr/bin/env python
60-
# coding: utf-8
61106
1/0
62107
"""
63108
)
64109
)
65110
f.flush()
66-
p = subprocess.Popen(
111+
_, stderr = run_with_tty(
67112
[sys.executable, "-m", "bpython.curtsies", f.name],
68-
stderr=subprocess.PIPE,
69-
universal_newlines=True,
70113
)
71-
(_, stderr) = p.communicate()
72-
73-
self.assertIn("line 3", clean_colors(stderr))
114+
self.assertIn("line 1", clean_colors(stderr))
74115

75116

76117
def clean_colors(s):

0 commit comments

Comments
 (0)