common utility functions used by various script execution tests e g testcmdline testcmdlinescript and testrunpy cached result of the expensive test performed in the function below returns true if our sys executable interpreter requires environment variables in order to be able to run at all this is designed to be used with unittest skipif to annotate tests that need to use an assertpython function to launch an isolated mode i or no environment mode e subinterpreter process a normal build test does not run into this situation but it can happen when trying to run the standard library test suite from an interpreter that doesn t have an obvious home with python s current home finding logic setting pythonhome is one way to get most of the testsuite to run in that situation pythonpath or pythonusersite are other common environment variables that might impact whether or not the interpreter can start if pythonhome is set assume that we need it cannot run subprocess assume we don t need it try running an interpreter with e to see if it works or not helper for reporting python subprocess run results def failself cmdline limit to 80 lines to ascii characters executing the interpreter in a subprocess isolated mode ignore python environment variables ignore user sitepackages and don t add the current directory to sys path ignore python environment variables but a special flag that can be set to override in this case the caller is responsible to pass the full environment windows requires at least the systemroot environment variable to start python other interesting environment variables not copied currently comspec home path temp tmpdir tmp need to preserve the original environment for inplace testing of shared library builds set term unless the term environment variable is passed explicitly see issues 11390 and 18300 assert that running the interpreter with args and optional environment variables envvars succeeds rc 0 and return a return code stdout stderr tuple if the cleanenv keyword is set envvars is used as a fresh environment python is started in isolated mode command line option i except if the isolated keyword is set to false assert that running the interpreter with args and optional environment variables envvars fails rc 0 and return a return code stdout stderr tuple see assertpythonok for more options run a python subprocess with the given arguments kw is extra keyword args to pass to subprocess popen returns a popen object under fedora gnu readline can output junk on stderr when initialized depending on the term setting setting termvt100 is supposed to disable that references http reinout vanrees orgweblog20090814readlineinvisiblecharacterhack html http stackoverflow comquestions15760712pythonreadlinemoduleprintsescapecharacterduringimport http lists gnu orgarchivehtmlbugreadline200708msg00004 html run the given popen process until completion and return stdout p stdin close data p stdout read p stdout close try to cleanup the child so we don t appear to leak when running with regrtest r p wait subprocess cleanup return data def makescriptscriptdir scriptbasename source omitsuffixfalse scriptfilename scriptbasename if not omitsuffix scriptfilename os extsep py scriptname os path joinscriptdir scriptfilename the script should be encoded to utf8 the default string encoding with openscriptname w encoding utf8 as scriptfile scriptfile writesource importlib invalidatecaches return scriptname def makezipscriptzipdir zipbasename scriptname nameinzipnone import zipfile zipfilename zipbasenameos extsep zip zipname os path joinzipdir zipfilename with zipfile zipfilezipname w as zipfile if nameinzip is none parts scriptname splitos sep if lenparts 2 and parts2 pycache legacypyc makelegacypycsourcefromcachescriptname nameinzip os path basenamelegacypyc scriptname legacypyc else nameinzip os path basenamescriptname zipfile writescriptname nameinzip if test support verbose with zipfile zipfilezipname r as zipfile print contents of r zipname zipfile printdir return zipname os path joinzipname nameinzip def makepkgpkgdir initsource os mkdirpkgdir makescriptpkgdir init initsource def makezippkgzipdir zipbasename pkgname scriptbasename source depth1 compiledfalse import zipfile unlink initname makescriptzipdir init unlink appendinitname initbasename os path basenameinitname scriptname makescriptzipdir scriptbasename source unlink appendscriptname if compiled initname pycompile compileinitname doraisetrue scriptname pycompile compilescriptname doraisetrue unlink extendinitname scriptname pkgnames os sep joinpkgnamei for i in range1 depth1 scriptnameinzip os path joinpkgnames1 os path basenamescriptname zipfilename zipbasenameos extsep zip zipname os path joinzipdir zipfilename with zipfile zipfilezipname w as zipfile for name in pkgnames initnameinzip os path joinname initbasename zipfile writeinitname initnameinzip zipfile writescriptname scriptnameinzip for name in unlink os unlinkname if test support verbose with zipfile zipfilezipname r as zipfile print contents of r zipname zipfile printdir return zipname os path joinzipname scriptnameinzip support requiressubprocess def runtestscriptscript use u to try to get the full output if the test hangs or crash if support verbose def titletext return f text name fscript os path basenamescript print printtitlename flushtrue in verbose mode the child process inherit stdout and stdout to see output in realtime and reduce the risk of losing output args sys executable e x faulthandler u script v proc subprocess runargs printtitlefname completed exit code proc returncode flushtrue if proc returncode raise assertionerrorfname failed else assertpythonoku script v common utility functions used by various script execution tests e g test_cmd_line test_cmd_line_script and test_runpy cached result of the expensive test performed in the function below returns true if our sys executable interpreter requires environment variables in order to be able to run at all this is designed to be used with unittest skipif to annotate tests that need to use an assert_python function to launch an isolated mode i or no environment mode e sub interpreter process a normal build test does not run into this situation but it can happen when trying to run the standard library test suite from an interpreter that doesn t have an obvious home with python s current home finding logic setting pythonhome is one way to get most of the testsuite to run in that situation pythonpath or pythonusersite are other common environment variables that might impact whether or not the interpreter can start if pythonhome is set assume that we need it cannot run subprocess assume we don t need it try running an interpreter with e to see if it works or not helper for reporting python subprocess run results provide helpful details about failed subcommand runs limit to 80 lines to ascii characters executing the interpreter in a subprocess isolated mode ignore python environment variables ignore user site packages and don t add the current directory to sys path ignore python environment variables but a special flag that can be set to override in this case the caller is responsible to pass the full environment windows requires at least the systemroot environment variable to start python other interesting environment variables not copied currently comspec home path temp tmpdir tmp need to preserve the original environment for in place testing of shared library builds set term unless the term environment variable is passed explicitly see issues 11390 and 18300 assert that running the interpreter with args and optional environment variables env_vars succeeds rc 0 and return a return code stdout stderr tuple if the __cleanenv keyword is set env_vars is used as a fresh environment python is started in isolated mode command line option i except if the __isolated keyword is set to false assert that running the interpreter with args and optional environment variables env_vars fails rc 0 and return a return code stdout stderr tuple see assert_python_ok for more options run a python subprocess with the given arguments kw is extra keyword args to pass to subprocess popen returns a popen object under fedora gnu readline can output junk on stderr when initialized depending on the term setting setting term vt100 is supposed to disable that references http reinout vanrees org weblog 2009 08 14 readline invisible character hack html http stackoverflow com questions 15760712 python readline module prints escape character during import http lists gnu org archive html bug readline 2007 08 msg00004 html run the given popen process until completion and return stdout try to cleanup the child so we don t appear to leak when running with regrtest r the script should be encoded to utf 8 the default string encoding if test support verbose with zipfile zipfile zip_name r as zip_file print contents of r zip_name zip_file printdir if test support verbose with zipfile zipfile zip_name r as zip_file print contents of r zip_name zip_file printdir use u to try to get the full output if the test hangs or crash in verbose mode the child process inherit stdout and stdout to see output in realtime and reduce the risk of losing output
import collections
import importlib
import sys
import os
import os.path
import subprocess
import py_compile

from importlib.util import source_from_cache
from test import support
from test.support.import_helper import make_legacy_pyc



__cached_interp_requires_environment = None


def interpreter_requires_environment():
    
    global __cached_interp_requires_environment
    if __cached_interp_requires_environment is None:
        
        if 'PYTHONHOME' in os.environ:
            __cached_interp_requires_environment = True
            return True
        
        if not support.has_subprocess_support:
            __cached_interp_requires_environment = False
            return False

        
        try:
            subprocess.check_call([sys.executable, '-E',
                                   '-c', 'import sys; sys.exit(0)'])
        except subprocess.CalledProcessError:
            __cached_interp_requires_environment = True
        else:
            __cached_interp_requires_environment = False

    return __cached_interp_requires_environment


class _PythonRunResult(collections.namedtuple("_PythonRunResult",
                                          ("rc", "out", "err"))):
    
    def fail(self, cmd_line):
        
        
        maxlen = 80 * 100
        out, err = self.out, self.err
        if len(out) > maxlen:
            out = b'(... truncated stdout ...)' + out[-maxlen:]
        if len(err) > maxlen:
            err = b'(... truncated stderr ...)' + err[-maxlen:]
        out = out.decode('ascii', 'replace').rstrip()
        err = err.decode('ascii', 'replace').rstrip()
        raise AssertionError("Process return code is %d\n"
                             "command line: %r\n"
                             "\n"
                             "stdout:\n"
                             "---\n"
                             "%s\n"
                             "---\n"
                             "\n"
                             "stderr:\n"
                             "---\n"
                             "%s\n"
                             "---"
                             % (self.rc, cmd_line,
                                out,
                                err))



@support.requires_subprocess()
def run_python_until_end(*args, **env_vars):
    env_required = interpreter_requires_environment()
    cwd = env_vars.pop('__cwd', None)
    if '__isolated' in env_vars:
        isolated = env_vars.pop('__isolated')
    else:
        isolated = not env_vars and not env_required
    cmd_line = [sys.executable, '-X', 'faulthandler']
    if isolated:
        
        
        cmd_line.append('-I')
    elif not env_vars and not env_required:
        
        cmd_line.append('-E')

    
    
    if env_vars.pop('__cleanenv', None):
        env = {}
        if sys.platform == 'win32':
            
            
            env['SYSTEMROOT'] = os.environ['SYSTEMROOT']

        
        
    else:
        
        
        env = os.environ.copy()

    
    
    if 'TERM' not in env_vars:
        env['TERM'] = ''

    env.update(env_vars)
    cmd_line.extend(args)
    proc = subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                         env=env, cwd=cwd)
    with proc:
        try:
            out, err = proc.communicate()
        finally:
            proc.kill()
            subprocess._cleanup()
    rc = proc.returncode
    return _PythonRunResult(rc, out, err), cmd_line


@support.requires_subprocess()
def _assert_python(expected_success, /, *args, **env_vars):
    res, cmd_line = run_python_until_end(*args, **env_vars)
    if (res.rc and expected_success) or (not res.rc and not expected_success):
        res.fail(cmd_line)
    return res


def assert_python_ok(*args, **env_vars):
    
    return _assert_python(True, *args, **env_vars)


def assert_python_failure(*args, **env_vars):
    
    return _assert_python(False, *args, **env_vars)


@support.requires_subprocess()
def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw):
    
    cmd_line = [sys.executable]
    if not interpreter_requires_environment():
        cmd_line.append('-E')
    cmd_line.extend(args)
    
    
    
    
    
    
    env = kw.setdefault('env', dict(os.environ))
    env['TERM'] = 'vt100'
    return subprocess.Popen(cmd_line, stdin=subprocess.PIPE,
                            stdout=stdout, stderr=stderr,
                            **kw)


def kill_python(p):
    
    p.stdin.close()
    data = p.stdout.read()
    p.stdout.close()
    
    
    p.wait()
    subprocess._cleanup()
    return data


def make_script(script_dir, script_basename, source, omit_suffix=False):
    script_filename = script_basename
    if not omit_suffix:
        script_filename += os.extsep + 'py'
    script_name = os.path.join(script_dir, script_filename)
    
    with open(script_name, 'w', encoding='utf-8') as script_file:
        script_file.write(source)
    importlib.invalidate_caches()
    return script_name


def make_zip_script(zip_dir, zip_basename, script_name, name_in_zip=None):
    import zipfile
    zip_filename = zip_basename+os.extsep+'zip'
    zip_name = os.path.join(zip_dir, zip_filename)
    with zipfile.ZipFile(zip_name, 'w') as zip_file:
        if name_in_zip is None:
            parts = script_name.split(os.sep)
            if len(parts) >= 2 and parts[-2] == '__pycache__':
                legacy_pyc = make_legacy_pyc(source_from_cache(script_name))
                name_in_zip = os.path.basename(legacy_pyc)
                script_name = legacy_pyc
            else:
                name_in_zip = os.path.basename(script_name)
        zip_file.write(script_name, name_in_zip)
    
    
    
    
    return zip_name, os.path.join(zip_name, name_in_zip)


def make_pkg(pkg_dir, init_source=''):
    os.mkdir(pkg_dir)
    make_script(pkg_dir, '__init__', init_source)


def make_zip_pkg(zip_dir, zip_basename, pkg_name, script_basename,
                 source, depth=1, compiled=False):
    import zipfile
    unlink = []
    init_name = make_script(zip_dir, '__init__', '')
    unlink.append(init_name)
    init_basename = os.path.basename(init_name)
    script_name = make_script(zip_dir, script_basename, source)
    unlink.append(script_name)
    if compiled:
        init_name = py_compile.compile(init_name, doraise=True)
        script_name = py_compile.compile(script_name, doraise=True)
        unlink.extend((init_name, script_name))
    pkg_names = [os.sep.join([pkg_name]*i) for i in range(1, depth+1)]
    script_name_in_zip = os.path.join(pkg_names[-1], os.path.basename(script_name))
    zip_filename = zip_basename+os.extsep+'zip'
    zip_name = os.path.join(zip_dir, zip_filename)
    with zipfile.ZipFile(zip_name, 'w') as zip_file:
        for name in pkg_names:
            init_name_in_zip = os.path.join(name, init_basename)
            zip_file.write(init_name, init_name_in_zip)
        zip_file.write(script_name, script_name_in_zip)
    for name in unlink:
        os.unlink(name)
    
    
    
    
    return zip_name, os.path.join(zip_name, script_name_in_zip)


@support.requires_subprocess()
def run_test_script(script):
    
    if support.verbose:
        def title(text):
            return f"===== {text} ======"

        name = f"script {os.path.basename(script)}"
        print()
        print(title(name), flush=True)
        
        
        args = [sys.executable, "-E", "-X", "faulthandler", "-u", script, "-v"]
        proc = subprocess.run(args)
        print(title(f"{name} completed: exit code {proc.returncode}"),
              flush=True)
        if proc.returncode:
            raise AssertionError(f"{name} failed")
    else:
        assert_python_ok("-u", script, "-v")