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.
 
     
     
    