Skip to content

Commit 39f0bb5

Browse files
authored
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.
1 parent 122fc13 commit 39f0bb5

File tree

3 files changed

+207
-186
lines changed

3 files changed

+207
-186
lines changed

Lib/test/test_capi.py

Lines changed: 1 addition & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# Run the _testcapi module tests (tests for the Python/C API): by defn,
22
# these are all functions _testcapi exports whose name begins with 'test_'.
33

4-
from collections import namedtuple, OrderedDict
4+
from collections import OrderedDict
55
import os
66
import pickle
7-
import platform
87
import random
98
import re
109
import subprocess
@@ -420,190 +419,6 @@ def test(self):
420419
self.assertEqual(_testcapi.argparsing("Hello", "World"), 1)
421420

422421

423-
class EmbeddingTests(unittest.TestCase):
424-
def setUp(self):
425-
here = os.path.abspath(__file__)
426-
basepath = os.path.dirname(os.path.dirname(os.path.dirname(here)))
427-
exename = "_testembed"
428-
if sys.platform.startswith("win"):
429-
ext = ("_d" if "_d" in sys.executable else "") + ".exe"
430-
exename += ext
431-
exepath = os.path.dirname(sys.executable)
432-
else:
433-
exepath = os.path.join(basepath, "Programs")
434-
self.test_exe = exe = os.path.join(exepath, exename)
435-
if not os.path.exists(exe):
436-
self.skipTest("%r doesn't exist" % exe)
437-
# This is needed otherwise we get a fatal error:
438-
# "Py_Initialize: Unable to get the locale encoding
439-
# LookupError: no codec search functions registered: can't find encoding"
440-
self.oldcwd = os.getcwd()
441-
os.chdir(basepath)
442-
443-
def tearDown(self):
444-
os.chdir(self.oldcwd)
445-
446-
def run_embedded_interpreter(self, *args, env=None):
447-
"""Runs a test in the embedded interpreter"""
448-
cmd = [self.test_exe]
449-
cmd.extend(args)
450-
if env is not None and sys.platform == 'win32':
451-
# Windows requires at least the SYSTEMROOT environment variable to
452-
# start Python.
453-
env = env.copy()
454-
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
455-
456-
p = subprocess.Popen(cmd,
457-
stdout=subprocess.PIPE,
458-
stderr=subprocess.PIPE,
459-
universal_newlines=True,
460-
env=env)
461-
(out, err) = p.communicate()
462-
self.assertEqual(p.returncode, 0,
463-
"bad returncode %d, stderr is %r" %
464-
(p.returncode, err))
465-
return out, err
466-
467-
def run_repeated_init_and_subinterpreters(self):
468-
out, err = self.run_embedded_interpreter("repeated_init_and_subinterpreters")
469-
self.assertEqual(err, "")
470-
471-
# The output from _testembed looks like this:
472-
# --- Pass 0 ---
473-
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
474-
# interp 1 <0x1d4f690>, thread state <0x1d35350>: id(modules) = 139650431165784
475-
# interp 2 <0x1d5a690>, thread state <0x1d99ed0>: id(modules) = 139650413140368
476-
# interp 3 <0x1d4f690>, thread state <0x1dc3340>: id(modules) = 139650412862200
477-
# interp 0 <0x1cf9330>, thread state <0x1cf9700>: id(modules) = 139650431942728
478-
# --- Pass 1 ---
479-
# ...
480-
481-
interp_pat = (r"^interp (\d+) <(0x[\dA-F]+)>, "
482-
r"thread state <(0x[\dA-F]+)>: "
483-
r"id\(modules\) = ([\d]+)$")
484-
Interp = namedtuple("Interp", "id interp tstate modules")
485-
486-
numloops = 0
487-
current_run = []
488-
for line in out.splitlines():
489-
if line == "--- Pass {} ---".format(numloops):
490-
self.assertEqual(len(current_run), 0)
491-
if support.verbose:
492-
print(line)
493-
numloops += 1
494-
continue
495-
496-
self.assertLess(len(current_run), 5)
497-
match = re.match(interp_pat, line)
498-
if match is None:
499-
self.assertRegex(line, interp_pat)
500-
501-
# Parse the line from the loop. The first line is the main
502-
# interpreter and the 3 afterward are subinterpreters.
503-
interp = Interp(*match.groups())
504-
if support.verbose:
505-
print(interp)
506-
self.assertTrue(interp.interp)
507-
self.assertTrue(interp.tstate)
508-
self.assertTrue(interp.modules)
509-
current_run.append(interp)
510-
511-
# The last line in the loop should be the same as the first.
512-
if len(current_run) == 5:
513-
main = current_run[0]
514-
self.assertEqual(interp, main)
515-
yield current_run
516-
current_run = []
517-
518-
def test_subinterps_main(self):
519-
for run in self.run_repeated_init_and_subinterpreters():
520-
main = run[0]
521-
522-
self.assertEqual(main.id, '0')
523-
524-
def test_subinterps_different_ids(self):
525-
for run in self.run_repeated_init_and_subinterpreters():
526-
main, *subs, _ = run
527-
528-
mainid = int(main.id)
529-
for i, sub in enumerate(subs):
530-
self.assertEqual(sub.id, str(mainid + i + 1))
531-
532-
def test_subinterps_distinct_state(self):
533-
for run in self.run_repeated_init_and_subinterpreters():
534-
main, *subs, _ = run
535-
536-
if '0x0' in main:
537-
# XXX Fix on Windows (and other platforms): something
538-
# is going on with the pointers in Programs/_testembed.c.
539-
# interp.interp is 0x0 and interp.modules is the same
540-
# between interpreters.
541-
raise unittest.SkipTest('platform prints pointers as 0x0')
542-
543-
for sub in subs:
544-
# A new subinterpreter may have the same
545-
# PyInterpreterState pointer as a previous one if
546-
# the earlier one has already been destroyed. So
547-
# we compare with the main interpreter. The same
548-
# applies to tstate.
549-
self.assertNotEqual(sub.interp, main.interp)
550-
self.assertNotEqual(sub.tstate, main.tstate)
551-
self.assertNotEqual(sub.modules, main.modules)
552-
553-
def test_forced_io_encoding(self):
554-
# Checks forced configuration of embedded interpreter IO streams
555-
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
556-
out, err = self.run_embedded_interpreter("forced_io_encoding", env=env)
557-
if support.verbose > 1:
558-
print()
559-
print(out)
560-
print(err)
561-
expected_stream_encoding = "utf-8"
562-
expected_errors = "surrogateescape"
563-
expected_output = '\n'.join([
564-
"--- Use defaults ---",
565-
"Expected encoding: default",
566-
"Expected errors: default",
567-
"stdin: {in_encoding}:{errors}",
568-
"stdout: {out_encoding}:{errors}",
569-
"stderr: {out_encoding}:backslashreplace",
570-
"--- Set errors only ---",
571-
"Expected encoding: default",
572-
"Expected errors: ignore",
573-
"stdin: {in_encoding}:ignore",
574-
"stdout: {out_encoding}:ignore",
575-
"stderr: {out_encoding}:backslashreplace",
576-
"--- Set encoding only ---",
577-
"Expected encoding: latin-1",
578-
"Expected errors: default",
579-
"stdin: latin-1:{errors}",
580-
"stdout: latin-1:{errors}",
581-
"stderr: latin-1:backslashreplace",
582-
"--- Set encoding and errors ---",
583-
"Expected encoding: latin-1",
584-
"Expected errors: replace",
585-
"stdin: latin-1:replace",
586-
"stdout: latin-1:replace",
587-
"stderr: latin-1:backslashreplace"])
588-
expected_output = expected_output.format(
589-
in_encoding=expected_stream_encoding,
590-
out_encoding=expected_stream_encoding,
591-
errors=expected_errors)
592-
# This is useful if we ever trip over odd platform behaviour
593-
self.maxDiff = None
594-
self.assertEqual(out.strip(), expected_output)
595-
596-
def test_pre_initialization_api(self):
597-
"""
598-
Checks the few parts of the C-API that work before the runtine
599-
is initialized (via Py_Initialize()).
600-
"""
601-
env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
602-
out, err = self.run_embedded_interpreter("pre_initialization_api", env=env)
603-
self.assertEqual(out, '')
604-
self.assertEqual(err, '')
605-
606-
607422
class SkipitemTest(unittest.TestCase):
608423

609424
def test_skipitem(self):

0 commit comments

Comments
 (0)