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.
3import os.path
4import sys
5import unittest
6import subprocess
7import sysconfig
8from test import support
9from test.support import os_helper
10
11
12MS_WINDOWS = (sys.platform == 'win32')
13
14
15SETUP_TESTCPPEXT = support.findfile('setup_testcppext.py')
16
17
18@support.requires_subprocess()
19class TestCPPExt(unittest.TestCase):
20    def test_build_cpp11(self):
21        self.check_build(False, '_testcpp11ext')
22
23    def test_build_cpp03(self):
24        self.check_build(True, '_testcpp03ext')
25
26    # With MSVC, the linker fails with: cannot open file 'python311.lib'
27    # https://github.com/python/cpython/pull/32175#issuecomment-1111175897
28    @unittest.skipIf(MS_WINDOWS, 'test fails on Windows')
29    # Building and running an extension in clang sanitizing mode is not
30    # straightforward
31    @unittest.skipIf(
32        '-fsanitize' in (sysconfig.get_config_var('PY_CFLAGS') or ''),
33        'test does not work with analyzing builds')
34    # the test uses venv+pip: skip if it's not available
35    @support.requires_venv_with_pip()
36    def check_build(self, std_cpp03, extension_name):
37        # Build in a temporary directory
38        with os_helper.temp_cwd():
39            self._check_build(std_cpp03, extension_name)
40
41    def _check_build(self, std_cpp03, extension_name):
42        venv_dir = 'env'
43        verbose = support.verbose
44
45        # Create virtual environment to get setuptools
46        cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
47        if verbose:
48            print()
49            print('Run:', ' '.join(cmd))
50        subprocess.run(cmd, check=True)
51
52        # Get the Python executable of the venv
53        python_exe = 'python'
54        if sys.executable.endswith('.exe'):
55            python_exe += '.exe'
56        if MS_WINDOWS:
57            python = os.path.join(venv_dir, 'Scripts', python_exe)
58        else:
59            python = os.path.join(venv_dir, 'bin', python_exe)
60
61        def run_cmd(operation, cmd):
62            if verbose:
63                print('Run:', ' '.join(cmd))
64                subprocess.run(cmd, check=True)
65            else:
66                proc = subprocess.run(cmd,
67                                      stdout=subprocess.PIPE,
68                                      stderr=subprocess.STDOUT,
69                                      text=True)
70                if proc.returncode:
71                    print(proc.stdout, end='')
72                    self.fail(
73                        f"{operation} failed with exit code {proc.returncode}")
74
75        # Build the C++ extension
76        cmd = [python, '-X', 'dev',
77               SETUP_TESTCPPEXT, 'build_ext', '--verbose']
78        if std_cpp03:
79            cmd.append('-std=c++03')
80        run_cmd('Build', cmd)
81
82        # Install the C++ extension
83        cmd = [python, '-X', 'dev',
84               SETUP_TESTCPPEXT, 'install']
85        run_cmd('Install', cmd)
86
87        # Do a reference run. Until we test that running python
88        # doesn't leak references (gh-94755), run it so one can manually check
89        # -X showrefcount results against this baseline.
90        cmd = [python,
91               '-X', 'dev',
92               '-X', 'showrefcount',
93               '-c', 'pass']
94        run_cmd('Reference run', cmd)
95
96        # Import the C++ extension
97        cmd = [python,
98               '-X', 'dev',
99               '-X', 'showrefcount',
100               '-c', f"import {extension_name}"]
101        run_cmd('Import', cmd)
102
103
104if __name__ == "__main__":
105    unittest.main()
106