PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/Lib/test/support/script_helper.py

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