100 lines
3.0 KiB
Python
100 lines
3.0 KiB
Python
"""
|
|
Helper for testing.
|
|
"""
|
|
|
|
import sys
|
|
import warnings
|
|
import os.path
|
|
import re
|
|
import subprocess
|
|
import threading
|
|
|
|
import pytest
|
|
import _pytest
|
|
|
|
|
|
raises = pytest.raises
|
|
warns = pytest.warns
|
|
SkipTest = _pytest.runner.Skipped
|
|
skipif = pytest.mark.skipif
|
|
fixture = pytest.fixture
|
|
parametrize = pytest.mark.parametrize
|
|
timeout = pytest.mark.timeout
|
|
xfail = pytest.mark.xfail
|
|
param = pytest.param
|
|
|
|
|
|
def warnings_to_stdout():
|
|
""" Redirect all warnings to stdout.
|
|
"""
|
|
showwarning_orig = warnings.showwarning
|
|
|
|
def showwarning(msg, cat, fname, lno, file=None, line=0):
|
|
showwarning_orig(msg, cat, os.path.basename(fname), line, sys.stdout)
|
|
|
|
warnings.showwarning = showwarning
|
|
# warnings.simplefilter('always')
|
|
|
|
|
|
def check_subprocess_call(cmd, timeout=5, stdout_regex=None,
|
|
stderr_regex=None):
|
|
"""Runs a command in a subprocess with timeout in seconds.
|
|
|
|
A SIGTERM is sent after `timeout` and if it does not terminate, a
|
|
SIGKILL is sent after `2 * timeout`.
|
|
|
|
Also checks returncode is zero, stdout if stdout_regex is set, and
|
|
stderr if stderr_regex is set.
|
|
"""
|
|
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
|
|
def terminate_process(): # pragma: no cover
|
|
"""
|
|
Attempt to terminate a leftover process spawned during test execution:
|
|
ideally this should not be needed but can help avoid clogging the CI
|
|
workers in case of deadlocks.
|
|
"""
|
|
warnings.warn(f"Timeout running {cmd}")
|
|
proc.terminate()
|
|
|
|
def kill_process(): # pragma: no cover
|
|
"""
|
|
Kill a leftover process spawned during test execution: ideally this
|
|
should not be needed but can help avoid clogging the CI workers in
|
|
case of deadlocks.
|
|
"""
|
|
warnings.warn(f"Timeout running {cmd}")
|
|
proc.kill()
|
|
|
|
try:
|
|
if timeout is not None:
|
|
terminate_timer = threading.Timer(timeout, terminate_process)
|
|
terminate_timer.start()
|
|
kill_timer = threading.Timer(2 * timeout, kill_process)
|
|
kill_timer.start()
|
|
stdout, stderr = proc.communicate()
|
|
stdout, stderr = stdout.decode(), stderr.decode()
|
|
if proc.returncode != 0:
|
|
message = (
|
|
'Non-zero return code: {}.\nStdout:\n{}\n'
|
|
'Stderr:\n{}').format(
|
|
proc.returncode, stdout, stderr)
|
|
raise ValueError(message)
|
|
|
|
if (stdout_regex is not None and
|
|
not re.search(stdout_regex, stdout)):
|
|
raise ValueError(
|
|
"Unexpected stdout: {!r} does not match:\n{!r}".format(
|
|
stdout_regex, stdout))
|
|
if (stderr_regex is not None and
|
|
not re.search(stderr_regex, stderr)):
|
|
raise ValueError(
|
|
"Unexpected stderr: {!r} does not match:\n{!r}".format(
|
|
stderr_regex, stderr))
|
|
|
|
finally:
|
|
if timeout is not None:
|
|
terminate_timer.cancel()
|
|
kill_timer.cancel()
|