Skip to content
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
7 changes: 7 additions & 0 deletions graalpython/com.oracle.graal.python.cext/src/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,17 @@
PY_EXCEPTIONS
#undef EXCEPTION

#ifdef MS_WINDOWS
PyObject* PyExc_WindowsError = NULL;
#endif

Py_LOCAL_SYMBOL void initialize_exceptions() {
#define EXCEPTION(NAME) PyExc_##NAME = (PyObject*) GraalPyPrivate_Type(#NAME);
PY_EXCEPTIONS
#undef EXCEPTION
#ifdef MS_WINDOWS
PyExc_WindowsError = PyExc_OSError;
#endif
}

GraalPy_CAPI_HELPER_SYMBOL PyObject *
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,20 @@ def raise_error():


class TestPyErr(CPyExtTestCase):
if sys.platform == 'win32':
test_PyExc_WindowsError = CPyExtFunction(
lambda args: OSError,
lambda: ((),),
code="""PyObject* get_PyExc_WindowsError(void) {
return Py_NewRef(PyExc_WindowsError);
}
""",
resultspec='O',
argspec='',
arguments=[],
callfunction='get_PyExc_WindowsError',
)


test_PyErr_SetString = CPyExtFunctionVoid(
_reference_setstring,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,72 @@ def compare_unicode_bytes_with_kind(x, y):

class TestPyMemoryView(CPyExtTestCase):

def test_native_subclass_without_getbuffer(self):
for name, buffer_procs in (
('NativeWithoutBuffer', 'NULL'),
('NativeWithEmptyBuffer', '&empty_buffer'),
):
with self.subTest(buffer_procs=buffer_procs):
NativeType = CPyExtType(
name,
'''
static PyBufferProcs empty_buffer = {NULL, NULL};
''',
cmembers='int value;',
tp_flags='Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE',
tp_as_buffer=buffer_procs,
)

class Iterable(NativeType):
def __iter__(self):
return iter((97, 98, 99))

for obj in (NativeType(), Iterable()):
assert (bytearray() == obj) is False
assert (bytearray() != obj) is True
with self.assertRaises(TypeError):
memoryview(obj)

data = bytearray(b'x')
data.extend(Iterable())
assert data == b'xabc'

test_heap_type_buffer_procs = CPyExtFunction(
lambda args: b'abc',
lambda: ((type('HeapBuffer', (), {}),),),
code='''
static int heap_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
static char data[] = "abc";
return PyBuffer_FillInfo(view, obj, data, 3, 0, flags);
}
static PyObject *heap_type_buffer_procs(PyObject *type) {
PyTypeObject *tp = (PyTypeObject *)type;
if (tp->tp_as_buffer == NULL ||
tp->tp_as_buffer != &((PyHeapTypeObject *)tp)->as_buffer) {
PyErr_SetString(PyExc_AssertionError, "heap type must own its buffer procs");
return NULL;
}
getbufferproc previous = tp->tp_as_buffer->bf_getbuffer;
tp->tp_as_buffer->bf_getbuffer = heap_getbuffer;
PyObject *obj = PyObject_CallNoArgs(type);
PyObject *result = NULL;
if (obj != NULL) {
Py_buffer view;
if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) == 0) {
result = PyBytes_FromStringAndSize(view.buf, view.len);
PyBuffer_Release(&view);
}
Py_DECREF(obj);
}
tp->tp_as_buffer->bf_getbuffer = previous;
return result;
}
''',
argspec='O',
arguments=['PyObject* type'],
callfunction='heap_type_buffer_procs',
)

test_memoryview_read = CPyExtFunction(
lambda args: args[1],
lambda: (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1820,3 +1820,77 @@ def test_cmp(op_lambda, rop_lambda, op_name, rop_name):
assert create_mock(ne=1) == create_mock(eq=1)
assert create_mock(ne=1, eq=0) != create_mock(ne=0, eq=1)
assert not create_mock(ne=1, eq=0) == create_mock(ne=0, eq=1)


def test_heap_type_slot_groups():
checker = CPyExtType("HeapSlotGroupChecker", """
static PyObject *check_groups(PyObject *self, PyObject *type) {
PyTypeObject *tp = (PyTypeObject *)type;
PyHeapTypeObject *heap = (PyHeapTypeObject *)type;
if (!PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE) ||
tp->tp_as_async != &heap->as_async ||
tp->tp_as_number != &heap->as_number ||
tp->tp_as_sequence != &heap->as_sequence ||
tp->tp_as_mapping != &heap->as_mapping ||
tp->tp_as_buffer != &heap->as_buffer) {
PyErr_SetString(PyExc_AssertionError, "heap type must own all slot groups");
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *call_slots(PyObject *self, PyObject *obj) {
PyTypeObject *tp = Py_TYPE(obj);
PyObject *sum = NULL, *item = NULL, *awaitable = NULL, *result = NULL;
if (tp->tp_as_number->nb_add == NULL ||
tp->tp_as_sequence->sq_length == NULL ||
tp->tp_as_mapping->mp_subscript == NULL ||
tp->tp_as_async->am_await == NULL) {
PyErr_SetString(PyExc_AssertionError, "populated slots must be preserved");
return NULL;
}
sum = tp->tp_as_number->nb_add(obj, obj);
if (sum == NULL) goto done;
Py_ssize_t length = tp->tp_as_sequence->sq_length(obj);
if (length < 0) goto done;
item = tp->tp_as_mapping->mp_subscript(obj, Py_None);
if (item == NULL) goto done;
awaitable = tp->tp_as_async->am_await(obj);
if (awaitable == NULL) goto done;
result = Py_BuildValue("(OnOO)", sum, length, item, awaitable);
done:
Py_XDECREF(sum);
Py_XDECREF(item);
Py_XDECREF(awaitable);
return result;
}
""", tp_methods='''
{"check", (PyCFunction)check_groups, METH_O, NULL},
{"call_slots", (PyCFunction)call_slots, METH_O, NULL}
''')()

class Empty:
pass

class Populated:
def __add__(self, other):
return 42

def __len__(self):
return 3

def __getitem__(self, key):
return 17

def __await__(self):
return iter(())

class Inherited(Populated):
pass

for cls in (Empty, Populated, Inherited, *(type('HeapSubclass', (base,), {})
for base in (int, list, dict, bytearray))):
checker.check(cls)
for cls in (Populated, Inherited):
total, length, item, awaitable = checker.call_slots(cls())
assert (total, length, item) == (42, 3, 17)
assert list(awaitable) == []
84 changes: 84 additions & 0 deletions graalpython/com.oracle.graal.python.test/src/tests/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,90 @@ def test_import_star_all():
assert 'unlistedProperty' not in g


def test_import_star_non_module():
class Imported:
__all__ = ('public', '_private')
public = 42
_private = 43

name = '_test_import_star_non_module'
sys.modules[name] = Imported()
try:
namespace = {}
exec('from _test_import_star_non_module import *', namespace)
assert namespace['public'] == 42
assert namespace['_private'] == 43
del Imported.__all__
sys.modules[name].public = 44
sys.modules[name]._private = 45
namespace = {}
exec('from _test_import_star_non_module import *', namespace)
assert namespace['public'] == 44
assert '_private' not in namespace
finally:
del sys.modules[name]


def test_import_star_materializes_mapping_keys():
for fail in (False, True):
events = []

class Namespace:
def keys(self):
events.append('keys started')
yield 'public'
if fail:
raise RuntimeError('keys failed')
events.append('keys ended')

class Imported:
@property
def __dict__(self):
return Namespace()

def __getattr__(self, name):
if name == 'public':
events.append('attribute read')
return tuple(events)
raise AttributeError(name)

name = '_test_import_star_mapping_keys'
sys.modules[name] = Imported()
try:
namespace = {}
if fail:
try:
exec('from _test_import_star_mapping_keys import *', namespace)
except RuntimeError as exc:
assert str(exc) == 'keys failed'
else:
assert False, 'key iteration errors must propagate'
assert events == ['keys started']
assert 'public' not in namespace
else:
exec('from _test_import_star_mapping_keys import *', namespace)
assert namespace['public'] == ('keys started', 'keys ended', 'attribute read')
finally:
del sys.modules[name]


def test_import_star_missing_export():
class Imported:
__all__ = ('missing',)

name = '_test_import_star_missing_export'
sys.modules[name] = Imported()
try:
try:
exec('from _test_import_star_missing_export import *', {})
except AttributeError:
pass
else:
assert False, 'missing exports must raise AttributeError'
finally:
del sys.modules[name]


def test_import_as_local():
from sys import path
from sys import path as mypath
Expand Down
83 changes: 83 additions & 0 deletions graalpython/com.oracle.graal.python.test/src/tests/test_nt.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,71 @@
@unittest.skipUnless(sys.platform == 'win32', 'requires Windows')
class NtTests(unittest.TestCase):

def test_windows_path_helpers(self):
import nt
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'MiXeD_\u03be_\U0001f40d'
path.touch()
expected = '\\\\?\\' + str(path)
for value in (str(path), path, os.fsencode(path)):
with self.subTest(value=value):
result = nt._getfinalpathname(value)
self.assertEqual(result, os.fsencode(expected) if isinstance(value, bytes) else expected)
self.assertEqual(nt._findfirstfile(value), path.name)
self.assertEqual(nt._findfirstfile(str(path).lower()), path.name)
for helper in (nt._getfinalpathname, nt._findfirstfile):
with self.assertRaises(TypeError):
helper(path=path)
missing = str(Path(directory) / 'missing')
with self.assertRaises(FileNotFoundError) as caught:
helper(missing)
self.assertEqual(caught.exception.winerror, 2)
self.assertEqual(caught.exception.filename, missing)
with self.assertRaises(ValueError):
helper('bad\0path')

def test_findfirstfile_dot_components(self):
import nt
with tempfile.TemporaryDirectory() as directory:
parent = Path(directory)
child = parent / 'MiXeD_directory'
child.mkdir()
for path, expected in (
(str(child) + '\\.', child.name),
(str(child) + '\\..', parent.name),
(str(child).lower() + '\\.', child.name),
(str(child) + '\\..\\' + child.name + '\\.', child.name),
):
with self.subTest(path=path):
self.assertEqual(nt._findfirstfile(path), expected)

def test_windows_path_helpers_invalid_path(self):
import nt
with tempfile.TemporaryDirectory() as directory:
invalid = str(Path(directory) / 'bad|name')
for helper in (nt._getfinalpathname, nt._findfirstfile):
with self.subTest(helper=helper):
with self.assertRaises(OSError) as caught:
helper(invalid)
self.assertEqual(caught.exception.errno, errno.EINVAL)
self.assertEqual(caught.exception.winerror, 123)
self.assertEqual(caught.exception.filename, invalid)
self.assertEqual(os.path.realpath(invalid), invalid)
with self.assertRaises(OSError):
os.path.realpath(invalid, strict=True)

@unittest.skipIf(JAVA_BACKEND, 'requires the Windows native POSIX backend')
def test_getfinalpathname_long_path(self):
import nt
with tempfile.TemporaryDirectory() as directory:
path = Path(directory)
for _ in range(6):
path /= 'segment_' + 'x' * 45
path.mkdir()
self.assertGreater(len(str(path)), 260)
self.assertEqual(nt._getfinalpathname(path), '\\\\?\\' + str(path))
self.assertEqual(nt._findfirstfile(path), path.name)

@unittest.skipIf(JAVA_BACKEND, 'requires the Windows native POSIX backend')
def test_app_execution_alias_stat(self):
local_app_data = os.environ.get('LOCALAPPDATA')
Expand Down Expand Up @@ -215,6 +280,24 @@ def test_fchmod(self):

@unittest.skipUnless(sys.platform == 'win32', 'requires Windows')
class NtSymlinkTests(unittest.TestCase):
@unittest.skipIf(JAVA_BACKEND, 'requires the Windows native POSIX backend')
def test_realpath_symlink_missing_leaf(self):
import nt
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / 'target'
target.mkdir()
link = root / 'link'
self.create_link('target', link, target_is_directory=True)
self.assertEqual(nt._getfinalpathname(link), '\\\\?\\' + str(target))
self.assertEqual(nt._findfirstfile(link), 'link')
self.assertEqual(os.path.realpath(link / 'missing', strict=os.path.ALLOW_MISSING), str(target / 'missing'))
self.assertEqual(os.path.realpath(os.fsencode(link / 'missing'), strict=os.path.ALLOW_MISSING),
os.fsencode(target / 'missing'))
with self.assertRaises(FileNotFoundError):
os.path.realpath(link / 'missing', strict=True)
os.rmdir(link)

def create_link(self, source, destination, **kwargs):
try:
os.symlink(source, destination, **kwargs)
Expand Down
Loading
Loading