use pytest stdout/err capture (#22584)

* On windows, write to StringIO without redirection in test cases to avoid conflicting with logger
This commit is contained in:
loulawrence 2021-04-01 15:34:56 -04:00 committed by Peter Scheibel
parent a0164793cb
commit f587a9ce68
3 changed files with 58 additions and 43 deletions

View File

@ -12,11 +12,11 @@
import textwrap import textwrap
import traceback import traceback
from datetime import datetime from datetime import datetime
from sys import platform as _platform
import six import six
from six import StringIO from six import StringIO
from six.moves import input from six.moves import input
from sys import platform as _platform
if _platform != "win32": if _platform != "win32":
import fcntl import fcntl

View File

@ -8,20 +8,19 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import atexit import atexit
import ctypes
import errno import errno
import io
import multiprocessing import multiprocessing
import os import os
import re import re
import io
import select import select
import signal import signal
import sys import sys
import ctypes
import traceback
import tempfile
import threading import threading
from threading import Thread import traceback
from contextlib import contextmanager from contextlib import contextmanager
from threading import Thread
from types import ModuleType # novm from types import ModuleType # novm
from typing import Optional # novm from typing import Optional # novm
@ -757,6 +756,9 @@ def __init__(self, logfile, echo=False, debug=0, env=None):
self.stdout = StreamWrapper('stdout') self.stdout = StreamWrapper('stdout')
self.stderr = StreamWrapper('stderr') self.stderr = StreamWrapper('stderr')
self._active = False self._active = False
self._ioflag = False
self.old_stdout = sys.stdout
self.old_stderr = sys.stderr
def __enter__(self): def __enter__(self):
if self._active: if self._active:
@ -768,51 +770,59 @@ def __enter__(self):
# Open both write and reading on logfile # Open both write and reading on logfile
if type(self.logfile) == StringIO: if type(self.logfile) == StringIO:
self._ioflag = True
# cannot have two streams on tempfile, so we must make our own # cannot have two streams on tempfile, so we must make our own
self.writer = open('temp.txt', mode='wb+') sys.stdout = self.logfile
self.reader = open('temp.txt', mode='rb+') sys.stderr = self.logfile
else: else:
self.writer = open(self.logfile, mode='wb+') self.writer = open(self.logfile, mode='wb+')
self.reader = open(self.logfile, mode='rb+') self.reader = open(self.logfile, mode='rb+')
# Dup stdout so we can still write to it after redirection
self.echo_writer = open(os.dup(sys.stdout.fileno()), "w")
# Redirect stdout and stderr to write to logfile
self.stderr.redirect_stream(self.writer.fileno())
self.stdout.redirect_stream(self.writer.fileno())
self._kill = threading.Event()
def background_reader(reader, echo_writer, _kill): # Dup stdout so we can still write to it after redirection
# for each line printed to logfile, read it self.echo_writer = open(os.dup(sys.stdout.fileno()), "w")
# if echo: write line to user # Redirect stdout and stderr to write to logfile
while True: self.stderr.redirect_stream(self.writer.fileno())
is_killed = _kill.wait(.1) self.stdout.redirect_stream(self.writer.fileno())
self.stderr.flush() self._kill = threading.Event()
self.stdout.flush()
line = reader.readline() def background_reader(reader, echo_writer, _kill):
while line: # for each line printed to logfile, read it
if self.echo: # if echo: write line to user
self.echo_writer.write('{0}'.format(line.decode())) while True:
self.echo_writer.flush() is_killed = _kill.wait(.1)
self.stderr.flush()
self.stdout.flush()
line = reader.readline() line = reader.readline()
while line:
if self.echo:
self.echo_writer.write('{0}'.format(line.decode()))
self.echo_writer.flush()
line = reader.readline()
if is_killed: if is_killed:
break break
self._active = True self._active = True
with replace_environment(self.env): with replace_environment(self.env):
self._thread = Thread(target=background_reader, args=(self.reader, self.echo_writer, self._kill)) self._thread = Thread(target=background_reader,
self._thread.start() args=(self.reader, self.echo_writer, self._kill))
self._thread.start()
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
self.echo_writer.flush() if self._ioflag:
self.stdout.flush() sys.stdout = self.old_stdout
self.stderr.flush() sys.stderr = self.old_stderr
self._kill.set() self._ioflag = False
self._thread.join() else:
self.stdout.close() self.writer.close()
self.stderr.close() self.reader.close()
if os.path.exists("temp.txt"): self.echo_writer.flush()
os.remove("temp.txt") self.stdout.flush()
self.stderr.flush()
self._kill.set()
self._thread.join()
self.stdout.close()
self.stderr.close()
self._active = False self._active = False
@contextmanager @contextmanager
@ -835,7 +845,6 @@ def force_echo(self):
sys.stdout.flush() sys.stdout.flush()
def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo, def _writer_daemon(stdin_multiprocess_fd, read_multiprocess_fd, write_fd, echo,
log_file_wrapper, control_pipe, filter_fn): log_file_wrapper, control_pipe, filter_fn):
"""Daemon used by ``log_output`` to write to a log file and to ``stdout``. """Daemon used by ``log_output`` to write to a log file and to ``stdout``.

View File

@ -19,7 +19,6 @@
import re import re
import signal import signal
import sys import sys
import termios
import time import time
import traceback import traceback
@ -27,6 +26,13 @@
from spack.util.executable import which from spack.util.executable import which
termios = None
try:
import termios as term_mod
termios = term_mod
except ImportError:
pass
class ProcessController(object): class ProcessController(object):
"""Wrapper around some fundamental process control operations. """Wrapper around some fundamental process control operations.