Skip to content

Commit

Permalink
Fix test_SIGHUP_tty under pytest
Browse files Browse the repository at this point in the history
Since pytest patches stdin unless `-s` is used, it caused
`os.isatty(sys.stdin.fileno())` to report that the process is
daemonized. Pytest does not have a mechanism to temporarily unpatch
stdin so this change explicitly creates a Pseudo-TTY (PTY) and binds
it to subprocesses created by the test helper called `Process`.

Refs:
* https://stackoverflow.com/a/43012138/595220
* https://stackoverflow.com/q/41274719/595220
  • Loading branch information
webknjaz committed Mar 13, 2021
1 parent 438d329 commit a9a3024
Showing 1 changed file with 22 additions and 1 deletion.
23 changes: 22 additions & 1 deletion magicbus/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from magicbus.compat import HTTPServer, HTTPConnection, HTTPHandler
import os
import pty
from subprocess import Popen
import threading
import time
Expand All @@ -17,22 +18,42 @@ class Process(object):
def __init__(self, args):
self.args = args
self.process = None
self._pty_stdin = None

def start(self):
# Exceptions in the child will be re-raised in the parent,
# so if you're expecting one, trap this call and check for it.
cwd = os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))
env = os.environ.copy()
env['PYTHONPATH'] = cwd
self.process = Popen(self.args, env=env)
# NOTE: Openning a pseudo-terminal to interact with subprocess
# NOTE: is necessary because `pytest` captures stdin unless `-s`
# NOTE: is used and it's impossible to disable this with
# NOTE: `capsys` because it only unpatches stdout+stderr but not
# NOTE: stdin.
master_fd, self._pty_stdin = pty.openpty()
self.process = Popen(
self.args, env=env,
# Ref: https://stackoverflow.com/a/43012138/595220
preexec_fn=os.setsid, stdin=self._pty_stdin,
stdout=master_fd, stderr=master_fd, # both needed for TTY
)
os.close(master_fd) # Only needed to spawn the process
self.process.poll() # W/o this the subprocess doesn't see a TTY

def stop(self):
if self.process is not None:
self.process.kill()
if self._pty_stdin is not None:
os.close(self._pty_stdin)

def join(self):
return self.process.wait()

def __del__(self):
if self._pty_stdin is not None:
os.close(self._pty_stdin)


class WebServer(HTTPServer):

Expand Down

0 comments on commit a9a3024

Please sign in to comment.