testing.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. """
  2. Helper for testing.
  3. """
  4. import os.path
  5. import re
  6. import subprocess
  7. import sys
  8. import threading
  9. import warnings
  10. import _pytest
  11. import pytest
  12. raises = pytest.raises
  13. warns = pytest.warns
  14. SkipTest = _pytest.runner.Skipped
  15. skipif = pytest.mark.skipif
  16. fixture = pytest.fixture
  17. parametrize = pytest.mark.parametrize
  18. timeout = pytest.mark.timeout
  19. xfail = pytest.mark.xfail
  20. param = pytest.param
  21. def warnings_to_stdout():
  22. """Redirect all warnings to stdout."""
  23. showwarning_orig = warnings.showwarning
  24. def showwarning(msg, cat, fname, lno, file=None, line=0):
  25. showwarning_orig(msg, cat, os.path.basename(fname), line, sys.stdout)
  26. warnings.showwarning = showwarning
  27. # warnings.simplefilter('always')
  28. def check_subprocess_call(cmd, timeout=5, stdout_regex=None, stderr_regex=None):
  29. """Runs a command in a subprocess with timeout in seconds.
  30. A SIGTERM is sent after `timeout` and if it does not terminate, a
  31. SIGKILL is sent after `2 * timeout`.
  32. Also checks returncode is zero, stdout if stdout_regex is set, and
  33. stderr if stderr_regex is set.
  34. """
  35. proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  36. def terminate_process(): # pragma: no cover
  37. """
  38. Attempt to terminate a leftover process spawned during test execution:
  39. ideally this should not be needed but can help avoid clogging the CI
  40. workers in case of deadlocks.
  41. """
  42. warnings.warn(f"Timeout running {cmd}")
  43. proc.terminate()
  44. def kill_process(): # pragma: no cover
  45. """
  46. Kill a leftover process spawned during test execution: ideally this
  47. should not be needed but can help avoid clogging the CI workers in
  48. case of deadlocks.
  49. """
  50. warnings.warn(f"Timeout running {cmd}")
  51. proc.kill()
  52. try:
  53. if timeout is not None:
  54. terminate_timer = threading.Timer(timeout, terminate_process)
  55. terminate_timer.start()
  56. kill_timer = threading.Timer(2 * timeout, kill_process)
  57. kill_timer.start()
  58. stdout, stderr = proc.communicate()
  59. stdout, stderr = stdout.decode(), stderr.decode()
  60. if proc.returncode != 0:
  61. message = ("Non-zero return code: {}.\nStdout:\n{}\nStderr:\n{}").format(
  62. proc.returncode, stdout, stderr
  63. )
  64. raise ValueError(message)
  65. if stdout_regex is not None and not re.search(stdout_regex, stdout):
  66. raise ValueError(
  67. "Unexpected stdout: {!r} does not match:\n{!r}".format(
  68. stdout_regex, stdout
  69. )
  70. )
  71. if stderr_regex is not None and not re.search(stderr_regex, stderr):
  72. raise ValueError(
  73. "Unexpected stderr: {!r} does not match:\n{!r}".format(
  74. stderr_regex, stderr
  75. )
  76. )
  77. finally:
  78. if timeout is not None:
  79. terminate_timer.cancel()
  80. kill_timer.cancel()