Skip to content

Commit 56cdff2

Browse files
committed
[GR-52191] Fix cpyext test errors on windows
PullRequest: graalpython/3233
2 parents cc361f1 + 0010a15 commit 56cdff2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+212
-335
lines changed

graalpython/com.oracle.graal.python.cext/src/bytesobject.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2839,8 +2839,8 @@ _Py_COMP_DIAG_POP
28392839
}
28402840
#endif // GraalPy change
28412841

2842-
// GraalPy change: different signature, uses C array instead of bytes
2843-
PyObject *
2842+
// GraalPy change: export for downcall, uses C array instead of bytes
2843+
PyAPI_FUNC(PyObject *)
28442844
bytes_subtype_new(PyTypeObject *type, int8_t* contents, Py_ssize_t n) {
28452845
// GraalPy change: different implementation
28462846
PyObject* bytes = type->tp_alloc(type, n);

graalpython/com.oracle.graal.python.cext/src/exceptions.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -52,7 +52,8 @@ void initialize_exceptions() {
5252
#undef EXCEPTION
5353
}
5454

55-
PyObject* exception_subtype_new(PyTypeObject *type, PyObject *args) {
55+
PyAPI_FUNC(PyObject *)
56+
exception_subtype_new(PyTypeObject *type, PyObject *args) {
5657
PyBaseExceptionObject *self;
5758

5859
self = (PyBaseExceptionObject *)type->tp_alloc(type, 0);

graalpython/com.oracle.graal.python.cext/src/memoryobject.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3320,11 +3320,15 @@ PyTypeObject PyMemoryView_Type = {
33203320

33213321
// GraalPy additions
33223322
/* called from memoryview implementation to do pointer arithmetics currently not possible from Java */
3323-
int8_t* truffle_add_suboffset(int8_t *ptr, Py_ssize_t offset, Py_ssize_t suboffset) {
3323+
PyAPI_FUNC(int8_t *)
3324+
truffle_add_suboffset(int8_t *ptr, Py_ssize_t offset, Py_ssize_t suboffset)
3325+
{
33243326
return *(int8_t**)(ptr + offset) + suboffset;
33253327
}
33263328

3327-
PyObject* PyTruffle_MemoryViewFromObject(PyObject *v, int flags) {
3329+
PyAPI_FUNC(PyObject *)
3330+
PyTruffle_MemoryViewFromObject(PyObject *v, int flags)
3331+
{
33283332
if (PyObject_CheckBuffer(v)) {
33293333
Py_buffer* buffer = malloc(sizeof(Py_buffer));
33303334
if (PyObject_GetBuffer(v, buffer, flags) < 0) {
@@ -3364,7 +3368,9 @@ PyObject* PyTruffle_MemoryViewFromObject(PyObject *v, int flags) {
33643368
}
33653369

33663370
/* Release buffer struct allocated in PyTruffle_MemoryViewFromObject */
3367-
void PyTruffle_ReleaseBuffer(Py_buffer* buffer) {
3371+
PyAPI_FUNC(void)
3372+
PyTruffle_ReleaseBuffer(Py_buffer* buffer)
3373+
{
33683374
if (buffer->obj != NULL) {
33693375
PyBufferProcs *pb;
33703376
pb = Py_TYPE(buffer->obj)->tp_as_buffer;

graalpython/com.oracle.graal.python.cext/src/modsupport.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ static int getbuffer(PyObject *arg, Py_buffer *view, const char **errmsg) {
6868
return 0;
6969
}
7070

71-
int get_buffer_r(PyObject *arg, Py_buffer *view) {
71+
PyAPI_FUNC(int) get_buffer_r(PyObject *arg, Py_buffer *view) {
7272
if (PyObject_GetBuffer(arg, view, PyBUF_SIMPLE) != 0) {
7373
return -1;
7474
}
@@ -79,7 +79,7 @@ int get_buffer_r(PyObject *arg, Py_buffer *view) {
7979
return 0;
8080
}
8181

82-
int get_buffer_rw(PyObject *arg, Py_buffer *view) {
82+
PyAPI_FUNC(int) get_buffer_rw(PyObject *arg, Py_buffer *view) {
8383
if (PyObject_GetBuffer(arg, view, PyBUF_WRITABLE) != 0) {
8484
PyErr_Clear();
8585
return -1;
@@ -91,7 +91,7 @@ int get_buffer_rw(PyObject *arg, Py_buffer *view) {
9191
return 0;
9292
}
9393

94-
Py_ssize_t convertbuffer(PyObject *arg, const void **p) {
94+
PyAPI_FUNC(Py_ssize_t) convertbuffer(PyObject *arg, const void **p) {
9595
PyBufferProcs *pb = Py_TYPE(arg)->tp_as_buffer;
9696
Py_ssize_t count;
9797
Py_buffer view;

graalpython/com.oracle.graal.python.cext/src/tupleobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ tuple_vectorcall(PyObject *type, PyObject * const*args,
758758

759759
PyObject* PyTruffle_Tuple_Alloc(PyTypeObject* cls, Py_ssize_t nitems);
760760

761-
PyObject * // GraalPy change: not static
761+
PyAPI_FUNC(PyObject *) // GraalPy change: export for downcall
762762
tuple_subtype_new(PyTypeObject *type, PyObject *iterable)
763763
{
764764
// GraalPy change: different implementation

graalpython/com.oracle.graal.python.cext/src/unicodeobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14461,7 +14461,7 @@ unicode_new_impl(PyTypeObject *type, PyObject *x, const char *encoding,
1446114461
}
1446214462
#endif // GraalPy change
1446314463

14464-
PyObject * // GraalPy change: not static
14464+
PyAPI_FUNC(PyObject *) // GraalPy change: export for downcall
1446514465
unicode_subtype_new(PyTypeObject *type, PyObject *unicode)
1446614466
{
1446714467
// GraalPy change: temporarily define struct access macros

graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py

Lines changed: 83 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39-
import os
4039
from importlib import invalidate_caches
41-
from io import StringIO
42-
from pathlib import Path
43-
from string import Formatter
4440

4541
import gc
42+
import os
43+
import shutil
4644
import sys
45+
import unittest
46+
from copy import deepcopy
47+
from io import StringIO
48+
from pathlib import Path
49+
from string import Formatter
4750

4851
DIR = Path(__file__).parent.absolute()
4952

@@ -81,23 +84,14 @@ def unhandled_error_compare_with_message(x, y):
8184
else:
8285
return x == y
8386

84-
class CPyExtTestCase():
8587

86-
def setUpClass(self):
87-
for typ in type(self).mro():
88-
for k, v in typ.__dict__.items():
89-
if k.startswith("test_"):
90-
modname = k.replace("test_", "")
91-
if k.startswith("test_graalpython_"):
92-
if not GRAALPYTHON:
93-
continue
94-
else:
95-
modname = k.replace("test_graalpython_", "")
96-
self.compile_module(modname)
88+
class CPyExtTestCase(unittest.TestCase):
89+
pass
9790

9891

9992
compiled_registry = set()
10093

94+
10195
def ccompile(self, name, check_duplicate_name=True):
10296
from distutils.core import setup, Extension
10397
from distutils.sysconfig import get_config_var
@@ -115,19 +109,21 @@ def ccompile(self, name, check_duplicate_name=True):
115109
m.update(block)
116110
cur_checksum = m.hexdigest()
117111

112+
build_dir = DIR / 'build' / name
113+
118114
# see if there is already a checksum file
119-
checksum_file = DIR / f'{name}{EXT_SUFFIX}.sha256'
115+
checksum_file = build_dir / f'{name}{EXT_SUFFIX}.sha256'
120116
available_checksum = ""
121117
if checksum_file.exists():
122118
# read checksum file
123119
with open(checksum_file, "r") as f:
124120
available_checksum = f.readline()
125121

126122
# note, the suffix is already a string like '.so'
127-
lib_file = DIR / f'{name}{EXT_SUFFIX}'
123+
lib_file = build_dir / f'{name}{EXT_SUFFIX}'
128124

129125
if check_duplicate_name and available_checksum != cur_checksum and name in compiled_registry:
130-
print(f"\n\nWARNING: module with name '{name}' was already compiled, but with different source code. "
126+
raise RuntimeError(f"\n\nModule with name '{name}' was already compiled, but with different source code. "
131127
"Have you accidentally used the same name for two different CPyExtType, CPyExtHeapType, "
132128
"or similar helper calls? Modules with same name can sometimes confuse the import machinery "
133129
"and cause all sorts of trouble.\n")
@@ -138,17 +134,28 @@ def ccompile(self, name, check_duplicate_name=True):
138134
# Note: It could be that the C source file's checksum didn't change but someone
139135
# manually deleted the shared library file.
140136
if available_checksum != cur_checksum or not lib_file.exists():
141-
module = Extension(name, sources=[str(source_file)])
142-
verbosity = '--verbose' if sys.flags.verbose else '--quiet'
143-
args = [verbosity, 'build', 'install_lib', '-f', f'--install-dir={DIR}', 'clean']
144-
setup(
145-
script_name='setup',
146-
script_args=args,
147-
name=name,
148-
version='1.0',
149-
description='',
150-
ext_modules=[module]
151-
)
137+
os.makedirs(build_dir, exist_ok=True)
138+
# MSVC linker doesn't like absolute paths in some parameters, so just run from the build dir
139+
old_cwd = os.getcwd()
140+
os.chdir(build_dir)
141+
try:
142+
shutil.copy(source_file, '.')
143+
module = Extension(name, sources=[source_file.name])
144+
args = [
145+
'--verbose' if sys.flags.verbose else '--quiet',
146+
'build',
147+
'install_lib', '-f', '--install-dir=.',
148+
]
149+
setup(
150+
script_name='setup',
151+
script_args=args,
152+
name=name,
153+
version='1.0',
154+
description='',
155+
ext_modules=[module]
156+
)
157+
finally:
158+
os.chdir(old_cwd)
152159

153160
# write new checksum
154161
with open(checksum_file, "w") as f:
@@ -164,6 +171,8 @@ def ccompile(self, name, check_duplicate_name=True):
164171
if GRAALPYTHON:
165172
file_not_empty(lib_file)
166173

174+
return str(build_dir)
175+
167176

168177
def file_not_empty(path):
169178
for i in range(3):
@@ -340,13 +349,13 @@ def file_not_empty(path):
340349
"""
341350

342351

343-
class CPyExtFunction():
352+
class CPyExtFunction:
344353

345354
def __init__(self, pfunc, parameters, template=c_template, cmpfunc=None, stderr_validator=None, **kwargs):
346355
self.template = template
347356
self.pfunc = pfunc
348357
self.parameters = parameters
349-
kwargs["name"] = kwargs["name"] if "name" in kwargs else None
358+
kwargs.setdefault("name", None)
350359
self.name = kwargs["name"]
351360
if "code" in kwargs:
352361
kwargs["customcode"] = kwargs["code"]
@@ -401,12 +410,28 @@ def _insert(self, d, name, default_value):
401410
def __repr__(self):
402411
return "<CPyExtFunction %s>" % self.name
403412

404-
def test(self):
405-
sys.path.insert(0, str(DIR))
413+
def __set_name__(self, owner, name):
414+
if self.name:
415+
raise RuntimeError(f"{type(self)} already assigned to a test suite. Use copy() method to duplicate a test")
416+
self.name = name.removeprefix('test_')
417+
self.__name__ = name
418+
self.__qualname__ = f'{owner.__qualname__}.{name}'
419+
420+
def copy(self):
421+
inst = deepcopy(self)
422+
inst.name = None
423+
return inst
424+
425+
@property
426+
def __code__(self):
427+
return type(self).__call__.__code__
428+
429+
def __call__(self):
406430
try:
407-
cmodule = __import__(self.name)
408-
finally:
409-
sys.path.pop(0)
431+
self.create_module(self.name)
432+
cmodule = compile_module_from_file(self.name)
433+
except Exception as e:
434+
raise RuntimeError(f"{self.__qualname__}: Failed to create module") from e
410435
ctest = getattr(cmodule, "test_%s" % self.name)
411436
cargs = self.parameters()
412437
pargs = self.parameters()
@@ -416,7 +441,7 @@ def test(self):
416441
sys.stderr = StringIO()
417442
try:
418443
cresult = ctest(cargs[i])
419-
except BaseException as e:
444+
except Exception as e:
420445
cresult = e
421446
else:
422447
if self.stderr_validator:
@@ -430,20 +455,13 @@ def test(self):
430455
except BaseException as e:
431456
presult = e
432457

433-
if not self.cmpfunc:
434-
assert cresult == presult, ("%r == %r in %s(%s)" % (cresult, presult, self.name, pargs[i]))
458+
if self.cmpfunc:
459+
success = self.cmpfunc(cresult, presult)
435460
else:
436-
assert self.cmpfunc(cresult, presult), ("%r == %r in %s(%s)" % (cresult, presult, self.name, pargs[i]))
461+
success = cresult == presult
462+
assert success, f"{self.__qualname__}: C result not equal to python reference implementation result.\n\tArguments: {pargs[i]!r}\n\tExpected result: {presult!r}\n\tActual C result: {cresult!r}"
437463
gc.collect()
438464

439-
def __get__(self, instance, typ=None):
440-
if typ is None:
441-
return self
442-
else:
443-
CPyExtFunction.test.__name__ = self.name
444-
CPyExtFunction.test.__qualname__ = f'{CPyExtFunction.__name__}.test_{self.name}'
445-
return self.test
446-
447465

448466
class CPyExtFunctionOutVars(CPyExtFunction):
449467
'''
@@ -509,27 +527,28 @@ def get_value(self, key, args, kwds):
509527
return Formatter.get_value(key, args, kwds)
510528

511529

512-
def _compile_module(c_source, name):
530+
def compile_module_from_string(c_source: str, name: str):
513531
source_file = DIR / f'{name}.c'
514532
with open(source_file, "wb", buffering=0) as f:
515533
f.write(bytes(c_source, 'utf-8'))
516-
# ensure file was really written
517-
try:
518-
stat_result = os.stat(source_file)
519-
if stat_result[6] == 0:
520-
raise SystemError("empty source file %s" % (source_file,))
521-
except FileNotFoundError:
522-
raise SystemError("source file %s not available" % (source_file,))
523-
ccompile(None, name)
524-
sys.path.insert(0, str(DIR))
534+
return compile_module_from_file(name)
535+
536+
537+
def compile_module_from_file(module_name: str):
538+
install_dir = ccompile(None, module_name)
539+
sys.path.insert(0, install_dir)
525540
try:
526-
cmodule = __import__(name)
541+
cmodule = __import__(module_name)
527542
finally:
528543
sys.path.pop(0)
529544
return cmodule
530545

531546

532547
def CPyExtType(name, code='', **kwargs):
548+
kwargs['set_base_code'] = ''
549+
# We set tp_base later, because MSVC doesn't see the usual &PySomething_Type expressions as constants
550+
if tp_base := kwargs.get('tp_base'):
551+
kwargs['set_base_code'] = f'{name}Type.tp_base = {tp_base};'
533552
mod_template = """
534553
static PyModuleDef {name}module = {{
535554
PyModuleDef_HEAD_INIT,
@@ -546,6 +565,7 @@ def CPyExtType(name, code='', **kwargs):
546565
if (m == NULL)
547566
return NULL;
548567
568+
{set_base_code}
549569
{ready_code}
550570
if (PyType_Ready(&{name}Type) < 0)
551571
return NULL;
@@ -564,7 +584,7 @@ def CPyExtType(name, code='', **kwargs):
564584
kwargs.setdefault("post_ready_code", "")
565585
c_source = type_decl + UnseenFormatter().format(mod_template, **kwargs)
566586

567-
cmodule = _compile_module(c_source, name)
587+
cmodule = compile_module_from_string(c_source, name)
568588
return getattr(cmodule, name)
569589

570590

@@ -690,7 +710,7 @@ def CPyExtTypeDecl(name, code='', **kwargs):
690710
{name}_methods, /* tp_methods */
691711
{name}_members, /* tp_members */
692712
{name}_getset, /* tp_getset */
693-
{tp_base}, /* tp_base */
713+
0, /* set later */ /* tp_base */
694714
{tp_dict}, /* tp_dict */
695715
{tp_descr_get}, /* tp_descr_get */
696716
{tp_descr_set}, /* tp_descr_set */
@@ -781,7 +801,7 @@ def CPyExtHeapType(name, bases=(object), code='', slots=None, **kwargs):
781801
kwargs.setdefault("ready_code", "")
782802
kwargs.setdefault("post_ready_code", "")
783803
code = UnseenFormatter().format(template, **kwargs)
784-
mod = _compile_module(code, name)
804+
mod = compile_module_from_string(code, name)
785805
return mod.create(bases)
786806

787807

0 commit comments

Comments
 (0)