Skip to content

Commit 4e28377

Browse files
authored
gh-92584: test_cppext uses setuptools (#92639)
Rewrite test_cppext to run in a virtual environment and to build the C++ extension with setuptools rather than distutils.
1 parent 8cf2906 commit 4e28377

File tree

2 files changed

+77
-67
lines changed

2 files changed

+77
-67
lines changed

Lib/test/setup_testcppext.py

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# gh-91321: Build a basic C++ test extension to check that the Python C API is
2+
# compatible with C++ and does not emit C++ compiler warnings.
3+
import sys
4+
from test import support
5+
6+
from setuptools import setup, Extension
7+
8+
9+
MS_WINDOWS = (sys.platform == 'win32')
10+
11+
12+
SOURCE = support.findfile('_testcppext.cpp')
13+
if not MS_WINDOWS:
14+
# C++ compiler flags for GCC and clang
15+
CPPFLAGS = [
16+
# Python currently targets C++11
17+
'-std=c++11',
18+
# gh-91321: The purpose of _testcppext extension is to check that building
19+
# a C++ extension using the Python C API does not emit C++ compiler
20+
# warnings
21+
'-Werror',
22+
# Warn on old-style cast (C cast) like: (PyObject*)op
23+
'-Wold-style-cast',
24+
# Warn when using NULL rather than _Py_NULL in static inline functions
25+
'-Wzero-as-null-pointer-constant',
26+
]
27+
else:
28+
# Don't pass any compiler flag to MSVC
29+
CPPFLAGS = []
30+
31+
32+
def main():
33+
cpp_ext = Extension(
34+
'_testcppext',
35+
sources=[SOURCE],
36+
language='c++',
37+
extra_compile_args=CPPFLAGS)
38+
setup(name="_testcppext", ext_modules=[cpp_ext])
39+
40+
41+
if __name__ == "__main__":
42+
main()

Lib/test/test_cppext.py

+35-67
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,59 @@
11
# gh-91321: Build a basic C++ test extension to check that the Python C API is
22
# compatible with C++ and does not emit C++ compiler warnings.
3-
import contextlib
4-
import os
3+
import os.path
54
import sys
65
import unittest
7-
import warnings
6+
import subprocess
87
from test import support
98
from test.support import os_helper
109

11-
with warnings.catch_warnings():
12-
warnings.simplefilter('ignore', DeprecationWarning)
13-
from distutils.core import setup, Extension
14-
import distutils.sysconfig
15-
1610

1711
MS_WINDOWS = (sys.platform == 'win32')
1812

1913

20-
SOURCE = support.findfile('_testcppext.cpp')
21-
if not MS_WINDOWS:
22-
# C++ compiler flags for GCC and clang
23-
CPPFLAGS = [
24-
# Python currently targets C++11
25-
'-std=c++11',
26-
# gh-91321: The purpose of _testcppext extension is to check that building
27-
# a C++ extension using the Python C API does not emit C++ compiler
28-
# warnings
29-
'-Werror',
30-
# Warn on old-style cast (C cast) like: (PyObject*)op
31-
'-Wold-style-cast',
32-
# Warn when using NULL rather than _Py_NULL in static inline functions
33-
'-Wzero-as-null-pointer-constant',
34-
]
35-
else:
36-
# Don't pass any compiler flag to MSVC
37-
CPPFLAGS = []
14+
SETUP_TESTCPPEXT = support.findfile('setup_testcppext.py')
3815

3916

4017
@support.requires_subprocess()
4118
class TestCPPExt(unittest.TestCase):
42-
def build(self):
43-
cpp_ext = Extension(
44-
'_testcppext',
45-
sources=[SOURCE],
46-
language='c++',
47-
extra_compile_args=CPPFLAGS)
48-
capture_stdout = (not support.verbose)
49-
50-
try:
51-
try:
52-
if capture_stdout:
53-
stdout = support.captured_stdout()
54-
else:
55-
print()
56-
stdout = contextlib.nullcontext()
57-
with (stdout,
58-
support.swap_attr(sys, 'argv', ['setup.py', 'build_ext', '--verbose'])):
59-
setup(name="_testcppext", ext_modules=[cpp_ext])
60-
return
61-
except:
62-
if capture_stdout:
63-
# Show output on error
64-
print()
65-
print(stdout.getvalue())
66-
raise
67-
except SystemExit:
68-
self.fail("Build failed")
69-
7019
# With MSVC, the linker fails with: cannot open file 'python311.lib'
7120
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
7221
@unittest.skipIf(MS_WINDOWS, 'test fails on Windows')
7322
def test_build(self):
74-
# save/restore os.environ
75-
def restore_env(old_env):
76-
os.environ.clear()
77-
os.environ.update(old_env)
78-
self.addCleanup(restore_env, dict(os.environ))
79-
80-
def restore_sysconfig_vars(old_config_vars):
81-
distutils.sysconfig._config_vars.clear()
82-
distutils.sysconfig._config_vars.update(old_config_vars)
83-
self.addCleanup(restore_sysconfig_vars,
84-
dict(distutils.sysconfig._config_vars))
85-
8623
# Build in a temporary directory
8724
with os_helper.temp_cwd():
88-
self.build()
25+
self._test_build()
26+
27+
def _test_build(self):
28+
venv_dir = 'env'
29+
30+
# Create virtual environment to get setuptools
31+
cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
32+
if support.verbose:
33+
print()
34+
print('Run:', ' '.join(cmd))
35+
subprocess.run(cmd, check=True)
36+
37+
# Get the Python executable of the venv
38+
python_exe = 'python'
39+
if sys.executable.endswith('.exe'):
40+
python_exe += '.exe'
41+
if MS_WINDOWS:
42+
python = os.path.join(venv_dir, 'Scripts', python_exe)
43+
else:
44+
python = os.path.join(venv_dir, 'bin', python_exe)
45+
46+
# Build the C++ extension
47+
cmd = [python, '-X', 'dev', SETUP_TESTCPPEXT, 'build_ext', '--verbose']
48+
if support.verbose:
49+
print('Run:', ' '.join(cmd))
50+
proc = subprocess.run(cmd,
51+
stdout=subprocess.PIPE,
52+
stderr=subprocess.STDOUT,
53+
text=True)
54+
if proc.returncode:
55+
print(proc.stdout, end='')
56+
self.fail(f"Build failed with exit code {proc.returncode}")
8957

9058

9159
if __name__ == "__main__":

0 commit comments

Comments
 (0)