1

I'm trying to unittest a module that gets individual keypresses from stdin. The key-getting code works perfectly, but writing exactly a character (byte?) to a subprocess' stdin is giving me some issues.

I'm using essentially what was recommended here with modifications according to the docs and other SO answers:

for ch in range(0, 128):
    p = sp.Popen(
        [py, "-u", TEST_HELP, "getch"],
        stdin=sp.PIPE,
        stdout=sp.PIPE,
        stderr=sp.PIPE,
        bufsize=1
    )
    out, err = p.communicate(input=bytes(chr(ch), "ascii"))
    print(out, ",", err)

What I want is for p to recieve exactly one ASCII character of stdin and then exit. The fact that ch is sometimes NUL, EOF and other control chars is not a problem; it's exactly what I want.

The problem is that this seems to hang doing nothing until I press CTRL - C, and then it exits with a keyboard interrupt. The last line of the stack trace is in selectors.py: fd_event_list = self._poll.poll(timeout), which tells me it was waiting for a timeout(?), but I didn't supply the timeout=int kwarg.

The command I'm using resolves to python3 -u helptest.py getch, which looks like this and which works properly when I run it myself from the command line.

Here's the relevant part of helptest:

def getch():
    write_and_flush(ord(_ic._Getch()))

(write_and_flush just runs stdout.write; stdout.flush)

and _ic._Getch() is:

def _Getch():
    if sys.stdin.isatty():
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
            return ch
    else:
        return sys.stdin.read(1)

What am I doing wrong in the subprocess call that breaks this?


Changing the call to:

p = sp.Popen(
    [py, TEST_HELP, "getch"],
    stdin=sp.PIPE,
    stdout=sp.PIPE,
    stderr=sp.PIPE,
)
out, err = p.communicate(input=bytes(chr(ch) + "\n", "ascii"))

by omitting bufsize, removing the --unbuffered option, and adding an "EOL" (and variations therein) doesn't/don't change anything.

Community
  • 1
  • 1
cat
  • 3,888
  • 5
  • 32
  • 61

3 Answers3

-1

The last call to print should have Flush=True.

cat
  • 3,888
  • 5
  • 32
  • 61
-1

p.communicate() code is correct if you want to send a single byte (ignoring the data corruption that Windows may introduce for the redirected data). The issue is your helptest.py.

Ctrl+C behavior indicates that helptest.py tries to read from the console directly instead of using stdin. It seems that you use _Getch() from here that uses msvcrt.getch() on Windows i.e., it may read from the console instead of stdin.

Also, sys.stdin.read(1) may read more than one byte -- sys.stdin is in text mode by default. To read a byte from stdin, you could use b = os.read(0, 1) or reopen sys.stdin in binary mode e.g., call sys.stdin.detach() on Python 3.

If your intent is to read a key then use you could use readchar package. It is better to fix possible (subtle) issues in one place.

Unrelated: bufferring doesn't matter if you use .communicate() i.e., you can drop -u, bufsize. Unless the child process is broken input=s should behave similar to input=s + newline (i.e., EOF is implicit EOL).

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • @cat: no. You most likely have changed something else too. I don't see where you put `Flush=True` and how it may explain the observed behavior. – jfs Feb 22 '16 at 16:06
  • I replaced the call to `print` with a call to `write_and_flush` which fixed it. `write_and_flush == print(Flush=True)` – cat Feb 22 '16 at 16:14
  • @cat : 1- your code in the question already uses `write_and_flush()` 2- `flush` should be lowercase 3- it doesn't explain Ctrl+C behavior. The only `print()` that I see is at the end in the parent and it won't hang unless something else is broken. – jfs Feb 22 '16 at 16:28
-2

Your code suggests that you expect bufsize=1 to set the buffer size to exactly 1, but instead it turns on line-buffered mode. Communication blocks until an EOL was written.

Thomas Lotze
  • 5,153
  • 1
  • 16
  • 16
  • That hasn't solved it, and no combination of `bufsize` with the `-u`nbuffered option changes anything – cat Feb 21 '16 at 23:06