PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/test/support/script_helper.py

https://gitlab.com/takluyver/zipfile36
Python | 248 lines | 215 code | 8 blank | 25 comment | 9 complexity | 240643f2fff3664ed301ae31df77125d MD5 | raw file
  1. # Common utility functions used by various script execution tests
  2. # e.g. test_cmd_line, test_cmd_line_script and test_runpy
  3. import collections
  4. import importlib
  5. import sys
  6. import os
  7. import os.path
  8. import tempfile
  9. import subprocess
  10. import py_compile
  11. import contextlib
  12. import shutil
  13. import zipfile
  14. from importlib.util import source_from_cache
  15. from test.support import make_legacy_pyc, strip_python_stderr
  16. # Cached result of the expensive test performed in the function below.
  17. __cached_interp_requires_environment = None
  18. def interpreter_requires_environment():
  19. """
  20. Returns True if our sys.executable interpreter requires environment
  21. variables in order to be able to run at all.
  22. This is designed to be used with @unittest.skipIf() to annotate tests
  23. that need to use an assert_python*() function to launch an isolated
  24. mode (-I) or no environment mode (-E) sub-interpreter process.
  25. A normal build & test does not run into this situation but it can happen
  26. when trying to run the standard library test suite from an interpreter that
  27. doesn't have an obvious home with Python's current home finding logic.
  28. Setting PYTHONHOME is one way to get most of the testsuite to run in that
  29. situation. PYTHONPATH or PYTHONUSERSITE are other common environment
  30. variables that might impact whether or not the interpreter can start.
  31. """
  32. global __cached_interp_requires_environment
  33. if __cached_interp_requires_environment is None:
  34. # Try running an interpreter with -E to see if it works or not.
  35. try:
  36. subprocess.check_call([sys.executable, '-E',
  37. '-c', 'import sys; sys.exit(0)'])
  38. except subprocess.CalledProcessError:
  39. __cached_interp_requires_environment = True
  40. else:
  41. __cached_interp_requires_environment = False
  42. return __cached_interp_requires_environment
  43. _PythonRunResult = collections.namedtuple("_PythonRunResult",
  44. ("rc", "out", "err"))
  45. # Executing the interpreter in a subprocess
  46. def run_python_until_end(*args, **env_vars):
  47. env_required = interpreter_requires_environment()
  48. if '__isolated' in env_vars:
  49. isolated = env_vars.pop('__isolated')
  50. else:
  51. isolated = not env_vars and not env_required
  52. cmd_line = [sys.executable, '-X', 'faulthandler']
  53. if isolated:
  54. # isolated mode: ignore Python environment variables, ignore user
  55. # site-packages, and don't add the current directory to sys.path
  56. cmd_line.append('-I')
  57. elif not env_vars and not env_required:
  58. # ignore Python environment variables
  59. cmd_line.append('-E')
  60. # Need to preserve the original environment, for in-place testing of
  61. # shared library builds.
  62. env = os.environ.copy()
  63. # set TERM='' unless the TERM environment variable is passed explicitly
  64. # see issues #11390 and #18300
  65. if 'TERM' not in env_vars:
  66. env['TERM'] = ''
  67. # But a special flag that can be set to override -- in this case, the
  68. # caller is responsible to pass the full environment.
  69. if env_vars.pop('__cleanenv', None):
  70. env = {}
  71. env.update(env_vars)
  72. cmd_line.extend(args)
  73. proc = subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
  74. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  75. env=env)
  76. with proc:
  77. try:
  78. out, err = proc.communicate()
  79. finally:
  80. proc.kill()
  81. subprocess._cleanup()
  82. rc = proc.returncode
  83. err = strip_python_stderr(err)
  84. return _PythonRunResult(rc, out, err), cmd_line
  85. def _assert_python(expected_success, *args, **env_vars):
  86. res, cmd_line = run_python_until_end(*args, **env_vars)
  87. if (res.rc and expected_success) or (not res.rc and not expected_success):
  88. # Limit to 80 lines to ASCII characters
  89. maxlen = 80 * 100
  90. out, err = res.out, res.err
  91. if len(out) > maxlen:
  92. out = b'(... truncated stdout ...)' + out[-maxlen:]
  93. if len(err) > maxlen:
  94. err = b'(... truncated stderr ...)' + err[-maxlen:]
  95. out = out.decode('ascii', 'replace').rstrip()
  96. err = err.decode('ascii', 'replace').rstrip()
  97. raise AssertionError("Process return code is %d\n"
  98. "command line: %r\n"
  99. "\n"
  100. "stdout:\n"
  101. "---\n"
  102. "%s\n"
  103. "---\n"
  104. "\n"
  105. "stderr:\n"
  106. "---\n"
  107. "%s\n"
  108. "---"
  109. % (res.rc, cmd_line,
  110. out,
  111. err))
  112. return res
  113. def assert_python_ok(*args, **env_vars):
  114. """
  115. Assert that running the interpreter with `args` and optional environment
  116. variables `env_vars` succeeds (rc == 0) and return a (return code, stdout,
  117. stderr) tuple.
  118. If the __cleanenv keyword is set, env_vars is used as a fresh environment.
  119. Python is started in isolated mode (command line option -I),
  120. except if the __isolated keyword is set to False.
  121. """
  122. return _assert_python(True, *args, **env_vars)
  123. def assert_python_failure(*args, **env_vars):
  124. """
  125. Assert that running the interpreter with `args` and optional environment
  126. variables `env_vars` fails (rc != 0) and return a (return code, stdout,
  127. stderr) tuple.
  128. See assert_python_ok() for more options.
  129. """
  130. return _assert_python(False, *args, **env_vars)
  131. def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw):
  132. """Run a Python subprocess with the given arguments.
  133. kw is extra keyword args to pass to subprocess.Popen. Returns a Popen
  134. object.
  135. """
  136. cmd_line = [sys.executable, '-E']
  137. cmd_line.extend(args)
  138. # Under Fedora (?), GNU readline can output junk on stderr when initialized,
  139. # depending on the TERM setting. Setting TERM=vt100 is supposed to disable
  140. # that. References:
  141. # - http://reinout.vanrees.org/weblog/2009/08/14/readline-invisible-character-hack.html
  142. # - http://stackoverflow.com/questions/15760712/python-readline-module-prints-escape-character-during-import
  143. # - http://lists.gnu.org/archive/html/bug-readline/2007-08/msg00004.html
  144. env = kw.setdefault('env', dict(os.environ))
  145. env['TERM'] = 'vt100'
  146. return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
  147. stdout=stdout, stderr=stderr,
  148. **kw)
  149. def kill_python(p):
  150. """Run the given Popen process until completion and return stdout."""
  151. p.stdin.close()
  152. data = p.stdout.read()
  153. p.stdout.close()
  154. # try to cleanup the child so we don't appear to leak when running
  155. # with regrtest -R.
  156. p.wait()
  157. subprocess._cleanup()
  158. return data
  159. def make_script(script_dir, script_basename, source, omit_suffix=False):
  160. script_filename = script_basename
  161. if not omit_suffix:
  162. script_filename += os.extsep + 'py'
  163. script_name = os.path.join(script_dir, script_filename)
  164. # The script should be encoded to UTF-8, the default string encoding
  165. script_file = open(script_name, 'w', encoding='utf-8')
  166. script_file.write(source)
  167. script_file.close()
  168. importlib.invalidate_caches()
  169. return script_name
  170. def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None):
  171. zip_filename = zip_basename+os.extsep+'zip'
  172. zip_name = os.path.join(zip_dir, zip_filename)
  173. zip_file = zipfile.ZipFile(zip_name, 'w')
  174. if name_in_zip is None:
  175. parts = script_name.split(os.sep)
  176. if len(parts) >= 2 and parts[-2] == '__pycache__':
  177. legacy_pyc = make_legacy_pyc(source_from_cache(script_name))
  178. name_in_zip = os.path.basename(legacy_pyc)
  179. script_name = legacy_pyc
  180. else:
  181. name_in_zip = os.path.basename(script_name)
  182. zip_file.write(script_name, name_in_zip)
  183. zip_file.close()
  184. #if test.support.verbose:
  185. # zip_file = zipfile.ZipFile(zip_name, 'r')
  186. # print 'Contents of %r:' % zip_name
  187. # zip_file.printdir()
  188. # zip_file.close()
  189. return zip_name, os.path.join(zip_name, name_in_zip)
  190. def make_pkg(pkg_dir, init_source=''):
  191. os.mkdir(pkg_dir)
  192. make_script(pkg_dir, '__init__', init_source)
  193. def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
  194. source, depth=1, compiled=False):
  195. unlink = []
  196. init_name = make_script(zip_dir, '__init__', '')
  197. unlink.append(init_name)
  198. init_basename = os.path.basename(init_name)
  199. script_name = make_script(zip_dir, script_basename, source)
  200. unlink.append(script_name)
  201. if compiled:
  202. init_name = py_compile.compile(init_name, doraise=True)
  203. script_name = py_compile.compile(script_name, doraise=True)
  204. unlink.extend((init_name, script_name))
  205. pkg_names = [os.sep.join([pkg_name]*i) for i in range(1, depth+1)]
  206. script_name_in_zip = os.path.join(pkg_names[-1], os.path.basename(script_name))
  207. zip_filename = zip_basename+os.extsep+'zip'
  208. zip_name = os.path.join(zip_dir, zip_filename)
  209. zip_file = zipfile.ZipFile(zip_name, 'w')
  210. for name in pkg_names:
  211. init_name_in_zip = os.path.join(name, init_basename)
  212. zip_file.write(init_name, init_name_in_zip)
  213. zip_file.write(script_name, script_name_in_zip)
  214. zip_file.close()
  215. for name in unlink:
  216. os.unlink(name)
  217. #if test.support.verbose:
  218. # zip_file = zipfile.ZipFile(zip_name, 'r')
  219. # print 'Contents of %r:' % zip_name
  220. # zip_file.printdir()
  221. # zip_file.close()
  222. return zip_name, os.path.join(zip_name, script_name_in_zip)