bpo-32136: Separate embedding tests from C API tests (GH-4567)
Some parts of the C API are only relevant to larger
applications embedding CPython as a runtime engine.
The helpers to test those APIs are already separated
out into Programs/_testembed.c, this update moves
the associated test cases out into their own dedicated
test file.
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 2fe0fec..7a10cda 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -1,10 +1,9 @@
# Run the _testcapi module tests (tests for the Python/C API): by defn,
# these are all functions _testcapi exports whose name begins with 'test_'.
-from collections import namedtuple, OrderedDict
+from collections import OrderedDict
import os
import pickle
-import platform
import random
import re
import subprocess
@@ -420,190 +419,6 @@
self.assertEqual(_testcapi.argparsing("Hello", "World"), 1)
-class EmbeddingTests(unittest.TestCase):
- def setUp(self):
- here = os.path.abspath(__file__)
- basepath = os.path.dirname(os.path.dirname(os.path.dirname(here)))
- exename = "_testembed"
- if sys.platform.startswith("win"):
- ext = ("_d" if "_d" in sys.executable else "") + ".exe"
- exename += ext
- exepath = os.path.dirname(sys.executable)
- else:
- exepath = os.path.join(basepath, "Programs")
- self.test_exe = exe = os.path.join(exepath, exename)
- if not os.path.exists(exe):
- self.skipTest("%r doesn't exist" % exe)
- # This is needed otherwise we get a fatal error:
- # "Py_Initialize: Unable to get the locale encoding
- # LookupError: no codec search functions registered: can't find encoding"
- self.oldcwd = os.getcwd()
- os.chdir(basepath)
-
- def tearDown(self):
- os.chdir(self.oldcwd)
-
- def run_embedded_interpreter(self, *args, env=None):
- """Runs a test in the embedded interpreter"""
- cmd = [self.test_exe]
- cmd.extend(args)
- if env is not None and sys.platform == 'win32':
- # Windows requires at least the SYSTEMROOT environment variable to
- # start Python.
- env = env.copy()
- env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
-
- p = subprocess.Popen(cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True,
- env=env)
- (out, err) = p.communicate()
- self.assertEqual(p.returncode, 0,
- "bad returncode %d, stderr is %r" %
- (p.returncode, err))
- return out, err
-
- def run_repeated_init_and_subinterpreters(self):
- out, err = self.run_embedded_interpreter("repeated_init_and_subinterpreters")
- self.assertEqual(err, "")
-
- # The output from _testembed looks like this:
- # --- Pass 0 ---
- # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
- # interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
- # interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
- # interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
- # interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
- # --- Pass 1 ---
- # ...
-
- interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
- r"thread state <(0x[\dA-F]+)>: "
- r"id\(modules\) = ([\d]+)$")
- Interp = namedtuple("Interp", "id interp tstate modules")
-
- numloops = 0
- current_run = []
- for line in out.splitlines():
- if line == "--- Pass {} ---".format(numloops):
- self.assertEqual(len(current_run), 0)
- if support.verbose:
- print(line)
- numloops += 1
- continue
-
- self.assertLess(len(current_run), 5)
- match = re.match(interp_pat, line)
- if match is None:
- self.assertRegex(line, interp_pat)
-
- # Parse the line from the loop. The first line is the main
- # interpreter and the 3 afterward are subinterpreters.
- interp = Interp(*match.groups())
- if support.verbose:
- print(interp)
- self.assertTrue(interp.interp)
- self.assertTrue(interp.tstate)
- self.assertTrue(interp.modules)
- current_run.append(interp)
-
- # The last line in the loop should be the same as the first.
- if len(current_run) == 5:
- main = current_run[0]
- self.assertEqual(interp, main)
- yield current_run
- current_run = []
-
- def test_subinterps_main(self):
- for run in self.run_repeated_init_and_subinterpreters():
- main = run[0]
-
- self.assertEqual(main.id, '0')
-
- def test_subinterps_different_ids(self):
- for run in self.run_repeated_init_and_subinterpreters():
- main, *subs, _ = run
-
- mainid = int(main.id)
- for i, sub in enumerate(subs):
- self.assertEqual(sub.id, str(mainid + i + 1))
-
- def test_subinterps_distinct_state(self):
- for run in self.run_repeated_init_and_subinterpreters():
- main, *subs, _ = run
-
- if '0x0' in main:
- # XXX Fix on Windows (and other platforms): something
- # is going on with the pointers in Programs/_testembed.c.
- # interp.interp is 0x0 and interp.modules is the same
- # between interpreters.
- raise unittest.SkipTest('platform prints pointers as 0x0')
-
- for sub in subs:
- # A new subinterpreter may have the same
- # PyInterpreterState pointer as a previous one if
- # the earlier one has already been destroyed. So
- # we compare with the main interpreter. The same
- # applies to tstate.
- self.assertNotEqual(sub.interp, main.interp)
- self.assertNotEqual(sub.tstate, main.tstate)
- self.assertNotEqual(sub.modules, main.modules)
-
- def test_forced_io_encoding(self):
- # Checks forced configuration of embedded interpreter IO streams
- env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
- out, err = self.run_embedded_interpreter("forced_io_encoding", env=env)
- if support.verbose > 1:
- print()
- print(out)
- print(err)
- expected_stream_encoding = "utf-8"
- expected_errors = "surrogateescape"
- expected_output = '\n'.join([
- "--- Use defaults ---",
- "Expected encoding: default",
- "Expected errors: default",
- "stdin: {in_encoding}:{errors}",
- "stdout: {out_encoding}:{errors}",
- "stderr: {out_encoding}:backslashreplace",
- "--- Set errors only ---",
- "Expected encoding: default",
- "Expected errors: ignore",
- "stdin: {in_encoding}:ignore",
- "stdout: {out_encoding}:ignore",
- "stderr: {out_encoding}:backslashreplace",
- "--- Set encoding only ---",
- "Expected encoding: latin-1",
- "Expected errors: default",
- "stdin: latin-1:{errors}",
- "stdout: latin-1:{errors}",
- "stderr: latin-1:backslashreplace",
- "--- Set encoding and errors ---",
- "Expected encoding: latin-1",
- "Expected errors: replace",
- "stdin: latin-1:replace",
- "stdout: latin-1:replace",
- "stderr: latin-1:backslashreplace"])
- expected_output = expected_output.format(
- in_encoding=expected_stream_encoding,
- out_encoding=expected_stream_encoding,
- errors=expected_errors)
- # This is useful if we ever trip over odd platform behaviour
- self.maxDiff = None
- self.assertEqual(out.strip(), expected_output)
-
- def test_pre_initialization_api(self):
- """
- Checks the few parts of the C-API that work before the runtine
- is initialized (via Py_Initialize()).
- """
- env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
- out, err = self.run_embedded_interpreter("pre_initialization_api", env=env)
- self.assertEqual(out, '')
- self.assertEqual(err, '')
-
-
class SkipitemTest(unittest.TestCase):
def test_skipitem(self):