Skip to content

gh-92906: Enable test_cext and test_cppext on Windows #117000

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2251,16 +2251,25 @@ def _findwheel(pkgname):
# and returns the path to the venv directory and the path to the python executable
@contextlib.contextmanager
def setup_venv_with_pip_setuptools_wheel(venv_dir):
import shlex
import subprocess
from .os_helper import temp_cwd

def run_command(cmd):
if verbose:
print()
print('Run:', ' '.join(map(shlex.quote, cmd)))
subprocess.run(cmd, check=True)
else:
subprocess.run(cmd,
stdout=subprocess.PIPE,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can block on large output. Should not it be DEVNUL?

Copy link
Member Author

@vstinner vstinner Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it block, .run() consumes the ouput, no? I want to see output if the build fails (check=True).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, then sorry.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's output to see the output when something goes wrong :-)

stderr=subprocess.STDOUT,
check=True)

with temp_cwd() as temp_dir:
# Create virtual environment to get setuptools
cmd = [sys.executable, '-X', 'dev', '-m', 'venv', venv_dir]
if verbose:
print()
print('Run:', ' '.join(cmd))
subprocess.run(cmd, check=True)
run_command(cmd)

venv = os.path.join(temp_dir, venv_dir)

Expand All @@ -2275,10 +2284,7 @@ def setup_venv_with_pip_setuptools_wheel(venv_dir):
'-m', 'pip', 'install',
_findwheel('setuptools'),
_findwheel('wheel')]
if verbose:
print()
print('Run:', ' '.join(cmd))
subprocess.run(cmd, check=True)
run_command(cmd)

yield python

Expand Down
29 changes: 19 additions & 10 deletions Lib/test/test_cext/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# gh-116869: Build a basic C test extension to check that the Python C API
# does not emit C compiler warnings.
#
# Python C API must build with -Werror=declaration-after-statement.
# The Python C API must be compatible with building
# with the -Werror=declaration-after-statement compiler flag.

import os.path
import shlex
import shutil
import subprocess
import unittest
Expand All @@ -14,9 +16,10 @@
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')


# With MSVC, the linker fails with: cannot open file 'python311.lib'
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
# With MSVC on a debug build, the linker fails with: cannot open file
# 'python311.lib', it should look 'python311_d.lib'.
@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG,
'test fails on Windows debug build')
# Building and running an extension in clang sanitizing mode is not
# straightforward
@support.skip_if_sanitizer('test does not work with analyzing builds',
Expand All @@ -26,17 +29,22 @@
@support.requires_subprocess()
@support.requires_resource('cpu')
class TestExt(unittest.TestCase):
def test_build_c99(self):
self.check_build('_test_c99_ext', std='c99')
# Default build with no options
def test_build(self):
self.check_build('_test_cext')

def test_build_c11(self):
self.check_build('_test_c11_ext', std='c11')
self.check_build('_test_c11_cext', std='c11')

@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c99")
def test_build_c99(self):
self.check_build('_test_c99_cext', std='c99')

def test_build_limited(self):
self.check_build('_test_limited_ext', limited=True)
self.check_build('_test_limited_cext', limited=True)

def test_build_limited_c11(self):
self.check_build('_test_limited_c11_ext', limited=True, std='c11')
self.check_build('_test_limited_c11_cext', limited=True, std='c11')

def check_build(self, extension_name, std=None, limited=False):
venv_dir = 'env'
Expand All @@ -58,7 +66,7 @@ def run_cmd(operation, cmd):
env['CPYTHON_TEST_LIMITED'] = '1'
env['CPYTHON_TEST_EXT_NAME'] = extension_name
if support.verbose:
print('Run:', ' '.join(cmd))
print('Run:', ' '.join(map(shlex.quote, cmd)))
subprocess.run(cmd, check=True, env=env)
else:
proc = subprocess.run(cmd,
Expand All @@ -67,6 +75,7 @@ def run_cmd(operation, cmd):
stderr=subprocess.STDOUT,
text=True)
if proc.returncode:
print('Run:', ' '.join(map(shlex.quote, cmd)))
print(proc.stdout, end='')
self.fail(
f"{operation} failed with exit code {proc.returncode}")
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_cext/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ static PyMethodDef _testcext_methods[] = {
static int
_testcext_exec(PyObject *module)
{
#ifdef __STDC_VERSION__
if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) {
return -1;
}
#endif
return 0;
}

Expand Down
35 changes: 30 additions & 5 deletions Lib/test/test_cext/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# gh-91321: Build a basic C test extension to check that the Python C API is
# compatible with C and does not emit C compiler warnings.
import os
import platform
import shlex
import sys
import sysconfig
Expand All @@ -17,8 +18,8 @@
# extension using the Python C API does not emit C compiler warnings.
'-Werror',

# gh-116869: The Python C API must build with
# -Werror=declaration-after-statement.
# gh-116869: The Python C API must be compatible with building
# with the -Werror=declaration-after-statement compiler flag.
'-Werror=declaration-after-statement',
]
else:
Expand All @@ -34,22 +35,44 @@ def main():
cflags = list(CFLAGS)
cflags.append(f'-DMODULE_NAME={module_name}')

# Add -std=STD or /std:STD (MSVC) compiler flag
if std:
cflags.append(f'-std={std}')
if support.MS_WINDOWS:
cflags.append(f'/std:{std}')
std_prefix = '/std'
else:
cflags.append(f'-std={std}')
std_prefix = '-std'

# Remove existing -std options to only test ours
cmd = (sysconfig.get_config_var('CC') or '')
if cmd is not None:
cmd = shlex.split(cmd)
cmd = [arg for arg in cmd if not arg.startswith('-std=')]
cmd = [arg for arg in cmd if not arg.startswith(std_prefix)]
cmd = shlex.join(cmd)
# CC env var overrides sysconfig CC variable in setuptools
os.environ['CC'] = cmd

# Define Py_LIMITED_API macro
if limited:
version = sys.hexversion
cflags.append(f'-DPy_LIMITED_API={version:#x}')

# On Windows, add PCbuild\amd64\ to include and library directories
include_dirs = []
library_dirs = []
if support.MS_WINDOWS:
srcdir = sysconfig.get_config_var('srcdir')
machine = platform.uname().machine
pcbuild = os.path.join(srcdir, 'PCbuild', machine)
if os.path.exists(pcbuild):
# pyconfig.h is generated in PCbuild\amd64\
include_dirs.append(pcbuild)
# python313.lib is generated in PCbuild\amd64\
library_dirs.append(pcbuild)
print(f"Add PCbuild directory: {pcbuild}")

# Display information to help debugging
for env_name in ('CC', 'CFLAGS'):
if env_name in os.environ:
print(f"{env_name} env var: {os.environ[env_name]!r}")
Expand All @@ -60,7 +83,9 @@ def main():
ext = Extension(
module_name,
sources=[SOURCE],
extra_compile_args=cflags)
extra_compile_args=cflags,
include_dirs=include_dirs,
library_dirs=library_dirs)
setup(name=f'internal_{module_name}',
version='0.0',
ext_modules=[ext])
Expand Down
35 changes: 23 additions & 12 deletions Lib/test/test_cppext/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# gh-91321: Build a basic C++ test extension to check that the Python C API is
# compatible with C++ and does not emit C++ compiler warnings.
import os.path
import shlex
import shutil
import unittest
import subprocess
import unittest
from test import support


SOURCE = os.path.join(os.path.dirname(__file__), 'extension.cpp')
SETUP = os.path.join(os.path.dirname(__file__), 'setup.py')


# With MSVC, the linker fails with: cannot open file 'python311.lib'
# https://github.com/python/cpython/pull/32175#issuecomment-1111175897
@unittest.skipIf(support.MS_WINDOWS, 'test fails on Windows')
# With MSVC on a debug build, the linker fails with: cannot open file
# 'python311.lib', it should look 'python311_d.lib'.
@unittest.skipIf(support.MS_WINDOWS and support.Py_DEBUG,
'test fails on Windows debug build')
# Building and running an extension in clang sanitizing mode is not
# straightforward
@support.skip_if_sanitizer('test does not work with analyzing builds',
Expand All @@ -23,29 +25,37 @@
@support.requires_subprocess()
@support.requires_resource('cpu')
class TestCPPExt(unittest.TestCase):
def test_build_cpp11(self):
self.check_build(False, '_testcpp11ext')
def test_build(self):
self.check_build('_testcppext')

def test_build_cpp03(self):
self.check_build(True, '_testcpp03ext')
self.check_build('_testcpp03ext', std='c++03')

@unittest.skipIf(support.MS_WINDOWS, "MSVC doesn't support /std:c++11")
def test_build_cpp11(self):
self.check_build('_testcpp11ext', std='c++11')

def test_build_cpp14(self):
self.check_build('_testcpp14ext', std='c++14')

def check_build(self, std_cpp03, extension_name):
def check_build(self, extension_name, std=None):
venv_dir = 'env'
with support.setup_venv_with_pip_setuptools_wheel(venv_dir) as python_exe:
self._check_build(std_cpp03, extension_name, python_exe)
self._check_build(extension_name, python_exe, std=std)

def _check_build(self, std_cpp03, extension_name, python_exe):
def _check_build(self, extension_name, python_exe, std):
pkg_dir = 'pkg'
os.mkdir(pkg_dir)
shutil.copy(SETUP, os.path.join(pkg_dir, os.path.basename(SETUP)))
shutil.copy(SOURCE, os.path.join(pkg_dir, os.path.basename(SOURCE)))

def run_cmd(operation, cmd):
env = os.environ.copy()
env['CPYTHON_TEST_CPP_STD'] = 'c++03' if std_cpp03 else 'c++11'
if std:
env['CPYTHON_TEST_CPP_STD'] = std
env['CPYTHON_TEST_EXT_NAME'] = extension_name
if support.verbose:
print('Run:', ' '.join(cmd))
print('Run:', ' '.join(map(shlex.quote, cmd)))
subprocess.run(cmd, check=True, env=env)
else:
proc = subprocess.run(cmd,
Expand All @@ -54,6 +64,7 @@ def run_cmd(operation, cmd):
stderr=subprocess.STDOUT,
text=True)
if proc.returncode:
print('Run:', ' '.join(map(shlex.quote, cmd)))
print(proc.stdout, end='')
self.fail(
f"{operation} failed with exit code {proc.returncode}")
Expand Down
12 changes: 5 additions & 7 deletions Lib/test/test_cppext/extension.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@

#include "Python.h"

#if __cplusplus >= 201103
# define NAME _testcpp11ext
#else
# define NAME _testcpp03ext
#ifndef MODULE_NAME
# error "MODULE_NAME macro must be defined"
#endif

#define _STR(NAME) #NAME
Expand Down Expand Up @@ -160,7 +158,7 @@ PyType_Slot VirtualPyObject_Slots[] = {
};

PyType_Spec VirtualPyObject_Spec = {
/* .name */ STR(NAME) ".VirtualPyObject",
/* .name */ STR(MODULE_NAME) ".VirtualPyObject",
/* .basicsize */ sizeof(VirtualPyObject),
/* .itemsize */ 0,
/* .flags */ Py_TPFLAGS_DEFAULT,
Expand Down Expand Up @@ -240,7 +238,7 @@ PyDoc_STRVAR(_testcppext_doc, "C++ test extension.");

static struct PyModuleDef _testcppext_module = {
PyModuleDef_HEAD_INIT, // m_base
STR(NAME), // m_name
STR(MODULE_NAME), // m_name
_testcppext_doc, // m_doc
0, // m_size
_testcppext_methods, // m_methods
Expand All @@ -254,7 +252,7 @@ static struct PyModuleDef _testcppext_module = {
#define FUNC_NAME(NAME) _FUNC_NAME(NAME)

PyMODINIT_FUNC
FUNC_NAME(NAME)(void)
FUNC_NAME(MODULE_NAME)(void)
{
return PyModuleDef_Init(&_testcppext_module);
}
Loading