ยปCore Development>Code coverage>Lib/test/support/script_helper.py

Python code coverage for Lib/test/support/script_helper.py

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