blob: 07d167d9368c1470aa2e671921e0f50e642ff985 [file] [log] [blame]
Nick Coghlan260bd3e2009-11-16 06:49:25 +00001# Common utility functions used by various script execution tests
2# e.g. test_cmd_line, test_cmd_line_script and test_runpy
3
Antoine Pitrou25f85d42015-04-13 19:41:47 +02004import collections
Brett Cannonc8287ef2012-04-27 13:52:03 -04005import importlib
Nick Coghlan260bd3e2009-11-16 06:49:25 +00006import sys
7import os
8import os.path
9import tempfile
10import subprocess
11import py_compile
12import contextlib
13import shutil
14import zipfile
15
Brett Cannon9529fbf2013-06-15 17:11:25 -040016from importlib.util import source_from_cache
Nick Coghlan55175962013-07-28 22:11:50 +100017from test.support import make_legacy_pyc, strip_python_stderr, temp_dir
Barry Warsaw28a691b2010-04-17 00:19:56 +000018
Gregory P. Smithb9a3dd92015-02-04 00:59:40 -080019
20# Cached result of the expensive test performed in the function below.
21__cached_interp_requires_environment = None
22
Gregory P. Smith8f2fae12015-02-04 01:04:31 -080023def interpreter_requires_environment():
Gregory P. Smithb9a3dd92015-02-04 00:59:40 -080024 """
25 Returns True if our sys.executable interpreter requires environment
26 variables in order to be able to run at all.
27
28 This is designed to be used with @unittest.skipIf() to annotate tests
29 that need to use an assert_python*() function to launch an isolated
30 mode (-I) or no environment mode (-E) sub-interpreter process.
31
32 A normal build & test does not run into this situation but it can happen
33 when trying to run the standard library test suite from an interpreter that
34 doesn't have an obvious home with Python's current home finding logic.
35
36 Setting PYTHONHOME is one way to get most of the testsuite to run in that
Berker Peksag4882cac2015-04-14 09:30:01 +030037 situation. PYTHONPATH or PYTHONUSERSITE are other common environment
Gregory P. Smithb9a3dd92015-02-04 00:59:40 -080038 variables that might impact whether or not the interpreter can start.
39 """
40 global __cached_interp_requires_environment
41 if __cached_interp_requires_environment is None:
42 # Try running an interpreter with -E to see if it works or not.
43 try:
44 subprocess.check_call([sys.executable, '-E',
45 '-c', 'import sys; sys.exit(0)'])
46 except subprocess.CalledProcessError:
47 __cached_interp_requires_environment = True
48 else:
49 __cached_interp_requires_environment = False
50
51 return __cached_interp_requires_environment
52
53
Antoine Pitrou25f85d42015-04-13 19:41:47 +020054_PythonRunResult = collections.namedtuple("_PythonRunResult",
55 ("rc", "out", "err"))
56
57
Nick Coghlan260bd3e2009-11-16 06:49:25 +000058# Executing the interpreter in a subprocess
Antoine Pitrou25f85d42015-04-13 19:41:47 +020059def run_python_until_end(*args, **env_vars):
Gregory P. Smith7c60eb82015-02-04 17:16:30 -080060 env_required = interpreter_requires_environment()
Victor Stinnere8785ff2013-10-12 14:44:01 +020061 if '__isolated' in env_vars:
62 isolated = env_vars.pop('__isolated')
63 else:
Gregory P. Smithc3493aa2015-02-04 17:10:19 -080064 isolated = not env_vars and not env_required
Victor Stinner383a8202013-06-25 21:24:36 +020065 cmd_line = [sys.executable, '-X', 'faulthandler']
Victor Stinnere8785ff2013-10-12 14:44:01 +020066 if isolated:
67 # isolated mode: ignore Python environment variables, ignore user
68 # site-packages, and don't add the current directory to sys.path
69 cmd_line.append('-I')
Gregory P. Smithc3493aa2015-02-04 17:10:19 -080070 elif not env_vars and not env_required:
Victor Stinnere8785ff2013-10-12 14:44:01 +020071 # ignore Python environment variables
Antoine Pitrou9bc35682010-11-09 21:33:55 +000072 cmd_line.append('-E')
Antoine Pitrouadffced2010-11-09 22:04:44 +000073 # Need to preserve the original environment, for in-place testing of
74 # shared library builds.
75 env = os.environ.copy()
Georg Brandl2daf6ae2012-02-20 19:54:16 +010076 # But a special flag that can be set to override -- in this case, the
77 # caller is responsible to pass the full environment.
78 if env_vars.pop('__cleanenv', None):
79 env = {}
Antoine Pitrouadffced2010-11-09 22:04:44 +000080 env.update(env_vars)
Georg Brandl2daf6ae2012-02-20 19:54:16 +010081 cmd_line.extend(args)
Antoine Pitrouf51d8d32010-10-08 18:05:42 +000082 p = subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
Antoine Pitrou9bc35682010-11-09 21:33:55 +000083 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
84 env=env)
Antoine Pitrouf51d8d32010-10-08 18:05:42 +000085 try:
86 out, err = p.communicate()
87 finally:
88 subprocess._cleanup()
Brian Curtinc4ac8872010-11-01 14:00:33 +000089 p.stdout.close()
90 p.stderr.close()
Antoine Pitrouf51d8d32010-10-08 18:05:42 +000091 rc = p.returncode
Eli Bendersky8f2c2bc2013-08-11 16:48:44 -070092 err = strip_python_stderr(err)
Antoine Pitrou25f85d42015-04-13 19:41:47 +020093 return _PythonRunResult(rc, out, err), cmd_line
94
95def _assert_python(expected_success, *args, **env_vars):
96 res, cmd_line = run_python_until_end(*args, **env_vars)
97 if (res.rc and expected_success) or (not res.rc and not expected_success):
Victor Stinner1335ca52015-03-20 13:38:08 +010098 # Limit to 80 lines to ASCII characters
99 maxlen = 80 * 100
Antoine Pitroucb46f0e2015-04-13 19:48:19 +0200100 out, err = res.out, res.err
Victor Stinner1335ca52015-03-20 13:38:08 +0100101 if len(out) > maxlen:
102 out = b'(... truncated stdout ...)' + out[-maxlen:]
103 if len(err) > maxlen:
104 err = b'(... truncated stderr ...)' + err[-maxlen:]
105 out = out.decode('ascii', 'replace').rstrip()
106 err = err.decode('ascii', 'replace').rstrip()
107 raise AssertionError("Process return code is %d\n"
108 "command line: %r\n"
109 "\n"
110 "stdout:\n"
111 "---\n"
112 "%s\n"
113 "---\n"
114 "\n"
115 "stderr:\n"
116 "---\n"
117 "%s\n"
118 "---"
Antoine Pitroucb46f0e2015-04-13 19:48:19 +0200119 % (res.rc, cmd_line,
Victor Stinner1335ca52015-03-20 13:38:08 +0100120 out,
121 err))
Antoine Pitrou25f85d42015-04-13 19:41:47 +0200122 return res
Antoine Pitrouf51d8d32010-10-08 18:05:42 +0000123
Antoine Pitrou9bc35682010-11-09 21:33:55 +0000124def assert_python_ok(*args, **env_vars):
125 """
126 Assert that running the interpreter with `args` and optional environment
Eli Bendersky8f2c2bc2013-08-11 16:48:44 -0700127 variables `env_vars` succeeds (rc == 0) and return a (return code, stdout,
128 stderr) tuple.
Victor Stinnere8785ff2013-10-12 14:44:01 +0200129
130 If the __cleanenv keyword is set, env_vars is used a fresh environment.
131
132 Python is started in isolated mode (command line option -I),
133 except if the __isolated keyword is set to False.
Antoine Pitrou9bc35682010-11-09 21:33:55 +0000134 """
135 return _assert_python(True, *args, **env_vars)
Antoine Pitrouf51d8d32010-10-08 18:05:42 +0000136
Antoine Pitrou9bc35682010-11-09 21:33:55 +0000137def assert_python_failure(*args, **env_vars):
138 """
139 Assert that running the interpreter with `args` and optional environment
Eli Bendersky8f2c2bc2013-08-11 16:48:44 -0700140 variables `env_vars` fails (rc != 0) and return a (return code, stdout,
141 stderr) tuple.
Victor Stinnere8785ff2013-10-12 14:44:01 +0200142
143 See assert_python_ok() for more options.
Antoine Pitrou9bc35682010-11-09 21:33:55 +0000144 """
145 return _assert_python(False, *args, **env_vars)
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000146
Antoine Pitrou9845c7e2014-05-11 13:42:17 +0200147def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw):
Eli Bendersky8f2c2bc2013-08-11 16:48:44 -0700148 """Run a Python subprocess with the given arguments.
149
150 kw is extra keyword args to pass to subprocess.Popen. Returns a Popen
151 object.
152 """
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000153 cmd_line = [sys.executable, '-E']
154 cmd_line.extend(args)
Antoine Pitrou6b4b8d02014-05-11 16:59:16 +0200155 # Under Fedora (?), GNU readline can output junk on stderr when initialized,
156 # depending on the TERM setting. Setting TERM=vt100 is supposed to disable
157 # that. References:
158 # - http://reinout.vanrees.org/weblog/2009/08/14/readline-invisible-character-hack.html
159 # - http://stackoverflow.com/questions/15760712/python-readline-module-prints-escape-character-during-import
160 # - http://lists.gnu.org/archive/html/bug-readline/2007-08/msg00004.html
Antoine Pitrou5e6b5f22014-05-11 19:13:43 +0200161 env = kw.setdefault('env', dict(os.environ))
162 env['TERM'] = 'vt100'
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000163 return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
Antoine Pitrou9845c7e2014-05-11 13:42:17 +0200164 stdout=stdout, stderr=stderr,
Victor Stinner024e37a2011-03-31 01:31:06 +0200165 **kw)
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000166
167def kill_python(p):
Eli Bendersky8f2c2bc2013-08-11 16:48:44 -0700168 """Run the given Popen process until completion and return stdout."""
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000169 p.stdin.close()
170 data = p.stdout.read()
171 p.stdout.close()
172 # try to cleanup the child so we don't appear to leak when running
Antoine Pitrou4e7dc5f2009-12-08 19:27:24 +0000173 # with regrtest -R.
174 p.wait()
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000175 subprocess._cleanup()
176 return data
177
Nick Coghlan720c7e22013-12-15 20:33:02 +1000178def make_script(script_dir, script_basename, source, omit_suffix=False):
179 script_filename = script_basename
180 if not omit_suffix:
181 script_filename += os.extsep + 'py'
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000182 script_name = os.path.join(script_dir, script_filename)
Florent Xicluna8de42e22010-02-27 16:12:22 +0000183 # The script should be encoded to UTF-8, the default string encoding
184 script_file = open(script_name, 'w', encoding='utf-8')
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000185 script_file.write(source)
186 script_file.close()
Brett Cannonc8287ef2012-04-27 13:52:03 -0400187 importlib.invalidate_caches()
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000188 return script_name
189
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000190def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None):
191 zip_filename = zip_basename+os.extsep+'zip'
192 zip_name = os.path.join(zip_dir, zip_filename)
193 zip_file = zipfile.ZipFile(zip_name, 'w')
194 if name_in_zip is None:
Barry Warsaw28a691b2010-04-17 00:19:56 +0000195 parts = script_name.split(os.sep)
196 if len(parts) >= 2 and parts[-2] == '__pycache__':
197 legacy_pyc = make_legacy_pyc(source_from_cache(script_name))
198 name_in_zip = os.path.basename(legacy_pyc)
199 script_name = legacy_pyc
200 else:
201 name_in_zip = os.path.basename(script_name)
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000202 zip_file.write(script_name, name_in_zip)
203 zip_file.close()
Florent Xicluna02ea12b22010-07-28 16:39:41 +0000204 #if test.support.verbose:
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000205 # zip_file = zipfile.ZipFile(zip_name, 'r')
206 # print 'Contents of %r:' % zip_name
207 # zip_file.printdir()
208 # zip_file.close()
209 return zip_name, os.path.join(zip_name, name_in_zip)
210
Nick Coghland26c18a2010-08-17 13:06:11 +0000211def make_pkg(pkg_dir, init_source=''):
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000212 os.mkdir(pkg_dir)
Nick Coghland26c18a2010-08-17 13:06:11 +0000213 make_script(pkg_dir, '__init__', init_source)
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000214
215def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
216 source, depth=1, compiled=False):
217 unlink = []
218 init_name = make_script(zip_dir, '__init__', '')
219 unlink.append(init_name)
220 init_basename = os.path.basename(init_name)
221 script_name = make_script(zip_dir, script_basename, source)
222 unlink.append(script_name)
223 if compiled:
Terry Jan Reedy5d828952014-06-20 17:49:10 -0400224 init_name = py_compile.compile(init_name, doraise=True)
225 script_name = py_compile.compile(script_name, doraise=True)
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000226 unlink.extend((init_name, script_name))
227 pkg_names = [os.sep.join([pkg_name]*i) for i in range(1, depth+1)]
228 script_name_in_zip = os.path.join(pkg_names[-1], os.path.basename(script_name))
229 zip_filename = zip_basename+os.extsep+'zip'
230 zip_name = os.path.join(zip_dir, zip_filename)
231 zip_file = zipfile.ZipFile(zip_name, 'w')
232 for name in pkg_names:
233 init_name_in_zip = os.path.join(name, init_basename)
234 zip_file.write(init_name, init_name_in_zip)
235 zip_file.write(script_name, script_name_in_zip)
236 zip_file.close()
237 for name in unlink:
238 os.unlink(name)
Florent Xicluna02ea12b22010-07-28 16:39:41 +0000239 #if test.support.verbose:
Nick Coghlan260bd3e2009-11-16 06:49:25 +0000240 # zip_file = zipfile.ZipFile(zip_name, 'r')
241 # print 'Contents of %r:' % zip_name
242 # zip_file.printdir()
243 # zip_file.close()
244 return zip_name, os.path.join(zip_name, script_name_in_zip)