1+ import errno
2+ import os
3+ import pty
14import re
5+ import select
26import subprocess
37import sys
48import tempfile
59import unittest
610
711from textwrap import dedent
812from bpython import args
13+ from bpython .config import getpreferredencoding
914from 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+
1265class 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
76117def clean_colors (s ):
0 commit comments