aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatti Picus <matti.picus@gmail.com>2021-03-31 20:45:53 +0300
committerMatti Picus <matti.picus@gmail.com>2021-03-31 20:45:53 +0300
commitdb5be4d0cb59068a2aa6c773ff241fde46fc29f0 (patch)
tree9302b383fac824e3930f47736d8edf0df3a867a5
parentmerge default into py3.7 (diff)
parentMerge branch 'branch/hpy-update-to-eb07982b6' into 'branch/hpy' (diff)
downloadpypy-db5be4d0cb59068a2aa6c773ff241fde46fc29f0.tar.gz
pypy-db5be4d0cb59068a2aa6c773ff241fde46fc29f0.tar.bz2
pypy-db5be4d0cb59068a2aa6c773ff241fde46fc29f0.zip
merge hpy into py3.7
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/__init__.py372
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyfunc_declare.h4
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyslot.h4
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_impl.h14
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/runtime/ctx_call.h10
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/version.h4
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/autogen_hpyfunc_trampolines.h4
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/hpy.h7
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_ctx.h9
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_hpyfunc_trampolines.h4
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_trampolines.h16
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/src/runtime/ctx_call.c38
-rw-r--r--pypy/module/_hpy_universal/_vendored/hpy/devel/version.py4
-rw-r--r--pypy/module/_hpy_universal/interp_call.py21
-rw-r--r--pypy/module/_hpy_universal/interp_hpy.py5
-rw-r--r--pypy/module/_hpy_universal/interp_long.py5
-rw-r--r--pypy/module/_hpy_universal/interp_module.py13
-rw-r--r--pypy/module/_hpy_universal/interp_object.py6
-rw-r--r--pypy/module/_hpy_universal/interp_tuple.py8
-rw-r--r--pypy/module/_hpy_universal/llapi.py10
-rw-r--r--pypy/module/_hpy_universal/src/hpyerr.c6
-rw-r--r--pypy/module/_hpy_universal/src/hpyerr.h1
-rw-r--r--pypy/module/_hpy_universal/state.py1
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/conftest.py17
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/support.py118
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_00_basic.py (renamed from pypy/module/_hpy_universal/test/_vendored/test_basic.py)0
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_call.py110
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_cpy_compat.py8
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_hpyerr.py25
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_hpylong.py55
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_hpytuple.py19
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_hpytype.py2
-rw-r--r--pypy/module/_hpy_universal/test/_vendored/test_importing.py30
-rw-r--r--pypy/module/_hpy_universal/test/support.py18
-rw-r--r--pypy/module/_hpy_universal/test/test_extra.py46
35 files changed, 768 insertions, 246 deletions
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/__init__.py b/pypy/module/_hpy_universal/_vendored/hpy/devel/__init__.py
index ba2118745c..8c8800454e 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/__init__.py
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/__init__.py
@@ -1,164 +1,272 @@
import os.path
+import functools
from pathlib import Path
-from setuptools import Extension
-from distutils.errors import DistutilsError, DistutilsSetupError
+from distutils import log
+from distutils.command.build import build
+from distutils.errors import DistutilsError
+from setuptools.command import bdist_egg as bdist_egg_mod
+from setuptools.command.build_ext import build_ext
# NOTE: this file is also imported by PyPy tests, so it must be compatible
# with both Python 2.7 and Python 3.x
-_BASE_DIR = Path(__file__).parent
class HPyDevel:
- def __init__(self, base_dir=_BASE_DIR):
+ """ Extra sources for building HPy extensions with hpy.devel. """
+
+ _DEFAULT_BASE_DIR = Path(__file__).parent
+
+ def __init__(self, base_dir=_DEFAULT_BASE_DIR):
self.base_dir = Path(base_dir)
self.include_dir = self.base_dir.joinpath('include')
self.src_dir = self.base_dir.joinpath('src', 'runtime')
- # extra_sources are needed both in CPython and Universal mode
- self._extra_sources = [
- self.src_dir.joinpath('argparse.c'),
- ]
- # ctx_sources are needed only in Universal mode
- self._ctx_sources = list(self.src_dir.glob('ctx_*.c'))
+
+ def get_extra_include_dirs(self):
+ """ Extra include directories needed by extensions in both CPython and
+ Universal modes.
+ """
+ return list(map(str, [
+ self.include_dir,
+ ]))
def get_extra_sources(self):
- return list(map(str, self._extra_sources))
+ """ Extra sources needed by extensions in both CPython and Universal
+ modes.
+ """
+ return list(map(str, [
+ self.src_dir.joinpath('argparse.c'),
+ ]))
def get_ctx_sources(self):
- return list(map(str, self._ctx_sources))
-
- def fix_extension(self, ext, hpy_abi):
- """
- Modify an existing setuptools.Extension to generate an HPy module.
+ """ Extra sources needed only in the CPython ABI mode.
"""
- if hasattr(ext, 'hpy_abi'):
- return
- ext.hpy_abi = hpy_abi
- ext.include_dirs.append(str(self.include_dir))
- ext.sources += self.get_extra_sources()
- if hpy_abi == 'cpython':
- ext.sources += self.get_ctx_sources()
- if hpy_abi == 'universal':
- ext.define_macros.append(('HPY_UNIVERSAL_ABI', None))
+ return list(map(str, self.src_dir.glob('ctx_*.c')))
+
+ def fix_distribution(self, dist):
+ """ Override build_ext to support hpy modules.
- def collect_hpy_ext_names(self, dist, hpy_ext_modules):
+ Used from both setup.py and hpy/test.
"""
- This is sub-optimal but it should work in 99% of the cases, and complain
- clearly in the others.
+ dist.hpydevel = self
- In order to implement build_hpy_ext, we need to know whether an
- Extension was put inside hpy_ext_modules or ext_modules, and we need
- to know it ONLY by looking at its name (because that's all we get when
- distutils calls build_hpy_ext.get_ext_filename). So here we collect
- and return all hpy_ext_names.
+ base_build = dist.cmdclass.get("build", build)
+ base_build_ext = dist.cmdclass.get("build_ext", build_ext)
+ orig_bdist_egg_write_stub = bdist_egg_mod.write_stub
- However, there is a problem: if the module is inside a package,
- distutils' build_ext.get_ext_fullpath calls get_ext_filename with ONLY
- the last part of the dotted name (see distutils/commands/build_ext.py).
+ class build_hpy_ext(build_hpy_ext_mixin, base_build_ext, object):
+ _base_build_ext = base_build_ext
- This means that there is a risk of conflicts if we have two ext
- modules with the same name in two different packages, of which one is
- HPy and the other is legacy; e.g.::
+ def dist_has_ext_modules(self):
+ if self.ext_modules or self.hpy_ext_modules:
+ return True
+ return False
- setup(ext_modules = [Extension(name='foo.mymod', ...)],
- hpy_ext_modules = [Extension(name='bar.mymod', ...)],)
+ def build_has_ext_modules(self):
+ return self.distribution.has_ext_modules()
- In that case, we cannot know whether ``mymod`` is an HPy ext module or
- not. If we detect such a problem, we exit early, and the only solution
- is to rename one of them :(
- """
- def collect_ext_names(exts):
- if exts is None:
- return set()
- names = set()
- for ext in exts:
- names.add(ext.name) # full name, e.g. 'foo.bar.baz'
- names.add(ext.name.split('.')[-1]) # modname, e.g. 'baz'
- return names
-
- hpy_ext_names = collect_ext_names(hpy_ext_modules)
- ext_names = collect_ext_names(dist.ext_modules)
- conflicts = hpy_ext_names.intersection(ext_names)
- if conflicts:
- lines = ['\n']
- lines.append('Name conflict between ext_modules and hpy_ext_modules:')
- for name in conflicts:
- lines.append(' - %s' % name)
- lines.append('You can not have modules ending with the same name in both')
- lines.append('ext_modules and hpy_ext_modules: this is a limitation of ')
- lines.append('hpy.devel, please rename one of them.')
- raise DistutilsSetupError('\n'.join(lines))
- return hpy_ext_names
-
- def fix_distribution(self, dist, hpy_ext_modules):
- from setuptools.command.build_ext import build_ext
- from setuptools.command.install import install
-
- def is_hpy_extension(ext_name):
- return ext_name in is_hpy_extension._ext_names
- is_hpy_extension._ext_names = self.collect_hpy_ext_names(dist, hpy_ext_modules)
-
- # add the hpy_extension modules to the normal ext_modules
- if dist.ext_modules is None:
- dist.ext_modules = []
- dist.ext_modules += hpy_ext_modules
-
- hpy_devel = self
- base_build_ext = dist.cmdclass.get('build_ext', build_ext)
- base_install = dist.cmdclass.get('install', install)
-
- class build_hpy_ext(base_build_ext):
- """
- Custom distutils command which properly recognizes and handle hpy
- extensions:
-
- - modify 'include_dirs', 'sources' and 'define_macros' depending on
- the selected hpy_abi
-
- - modify the filename extension if we are targeting the universal
- ABI.
- """
-
- def build_extension(self, ext):
- if is_hpy_extension(ext.name):
- # add the required include_dirs, sources and macros
- hpy_devel.fix_extension(ext, hpy_abi=self.distribution.hpy_abi)
- return base_build_ext.build_extension(self, ext)
-
- def get_ext_filename(self, ext_name):
- # this is needed to give the .hpy.so extension to universal extensions
- if is_hpy_extension(ext_name) and self.distribution.hpy_abi == 'universal':
- ext_path = ext_name.split('.')
- ext_suffix = '.hpy.so' # XXX Windows?
- return os.path.join(*ext_path) + ext_suffix
- return base_build_ext.get_ext_filename(self, ext_name)
-
- class install_hpy(base_install):
-
- def run(self):
- if self.distribution.hpy_abi == 'universal':
- raise DistutilsError(
- 'setup.py install is not supported for HPy universal modules.\n'
- ' At the moment, the only supported method is: \n'
- ' setup.py --hpy-abi-universal build_ext --inplace')
- return base_install.run(self)
+ def bdist_egg_write_stub(resource, pyfile):
+ if resource.endswith(".hpy.so"):
+ log.info("stub file already created for %s", resource)
+ return
+ orig_bdist_egg_write_stub(resource, pyfile)
+ # replace build_ext subcommand
dist.cmdclass['build_ext'] = build_hpy_ext
- dist.cmdclass['install'] = install_hpy
-
+ dist.__class__.has_ext_modules = dist_has_ext_modules
+ base_build.has_ext_modules = build_has_ext_modules
+ # setuptools / distutils store subcommands in .subcommands which
+ # is a list of tuples of (extension_name, extension_needs_to_run_func).
+ # The two lines below replace .subcommand entry for build_ext.
+ idx = [sub[0] for sub in base_build.sub_commands].index("build_ext")
+ base_build.sub_commands[idx] = ("build_ext", build_has_ext_modules)
+ bdist_egg_mod.write_stub = bdist_egg_write_stub
def handle_hpy_ext_modules(dist, attr, hpy_ext_modules):
- """
- setuptools entry point, see setup.py
+ """ Distuils hpy_ext_module setup(...) argument and --hpy-abi option.
+
+ See hpy's setup.py where this function is registered as an entry
+ point.
"""
assert attr == 'hpy_ext_modules'
- # Add a global option --hpy-abi to setup.py
- if not hasattr(dist.__class__, 'hpy_abi'):
- dist.__class__.hpy_abi = 'cpython'
- dist.__class__.global_options += [
- ('hpy-abi=', None, 'Specify the HPy ABI mode (default: cpython)')
- ]
+ # add a global option --hpy-abi to setup.py
+ dist.__class__.hpy_abi = 'cpython'
+ dist.__class__.global_options += [
+ ('hpy-abi=', None, 'Specify the HPy ABI mode (default: cpython)')
+ ]
+ hpydevel = HPyDevel()
+ hpydevel.fix_distribution(dist)
+
+
+_HPY_UNIVERSAL_MODULE_STUB_TEMPLATE = """
+class Spec:
+ def __init__(self, name, origin):
+ self.name = name
+ self.origin = origin
+
+
+def __bootstrap__():
+ import sys, pkg_resources
+ from hpy.universal import load_from_spec
+ ext_filepath = pkg_resources.resource_filename(__name__, {ext_file!r})
+ m = load_from_spec(Spec({module_name!r}, ext_filepath))
+ m.__file__ = ext_filepath
+ m.__loader__ = __loader__
+ m.__package__ = __package__
+ m.__spec__ = __spec__
+ sys.modules[__name__] = m
+
+__bootstrap__()
+"""
+
+
+class HPyExtensionName(str):
+ """ Wrapper around str to allow HPy extension modules to be identified.
+
+ The following build_ext command methods are passed only the *name*
+ of the extension and not the full extension object. The
+ build_hpy_ext_mixin class needs to detect when HPy are extensions
+ passed to these methods and override the default behaviour.
+
+ This str sub-class allows HPy extensions to be detected, while
+ still allowing the extension name to be used as an ordinary string.
+ """
+
+ def split(self, *args, **kw):
+ result = str.split(self, *args, **kw)
+ return [self.__class__(s) for s in result]
+
+ def translate(self, *args, **kw):
+ result = str.translate(self, *args, **kw)
+ return self.__class__(result)
+
+
+def is_hpy_extension(ext_name):
+ """ Return True if the extension name is for an HPy extension. """
+ return isinstance(ext_name, HPyExtensionName)
+
+
+def remember_hpy_extension(f):
+ """ Decorator for remembering whether an extension name belongs to an
+ HPy extension.
+ """
+ @functools.wraps(f)
+ def wrapper(self, ext_name):
+ if self._only_hpy_extensions:
+ assert is_hpy_extension(ext_name), (
+ "Extension name %r is not marked as an HPyExtensionName"
+ " but only HPy extensions are present. This is almost"
+ " certainly a bug in HPy's overriding of setuptools"
+ " build_ext. Please report this error the HPy maintainers."
+ % (ext_name,)
+ )
+ result = f(self, ext_name)
+ if is_hpy_extension(ext_name):
+ result = HPyExtensionName(result)
+ return result
+ return wrapper
+
+
+class build_hpy_ext_mixin:
+ """ A mixin class for setuptools build_ext to add support for buidling
+ HPy extensions.
+ """
+
+ # Ideally we would have simply added the HPy extensions to .extensions
+ # at the end of .initialize_options() but the setuptools build_ext
+ # .finalize_options both iterate over and needless overwrite the
+ # .extensions attribute, so we hide the full extension list in
+ # ._extensions and expose it as a settable property that ignores attempts
+ # to overwrite it:
+
+ _extensions = None
+
+ @property
+ def extensions(self):
+ return self._extensions
+
+ @extensions.setter
+ def extensions(self, value):
+ pass # ignore any attempts to change the list of extensions directly
+
+ def initialize_options(self):
+ self._base_build_ext.initialize_options(self)
+ self.hpydevel = self.distribution.hpydevel
+
+ def _finalize_hpy_ext(self, ext):
+ if hasattr(ext, "hpy_abi"):
+ return
+ ext.name = HPyExtensionName(ext.name)
+ ext.hpy_abi = self.distribution.hpy_abi
+ ext.include_dirs += self.hpydevel.get_extra_include_dirs()
+ ext.sources += self.hpydevel.get_extra_sources()
+ if ext.hpy_abi == 'cpython':
+ ext.sources += self.hpydevel.get_ctx_sources()
+ ext._hpy_needs_stub = False
+ if ext.hpy_abi == 'universal':
+ ext.define_macros.append(('HPY_UNIVERSAL_ABI', None))
+ ext._hpy_needs_stub = True
+
+ def finalize_options(self):
+ self._extensions = self.distribution.ext_modules or []
+ # _only_hpy_extensions is used only as a sanity check that no
+ # hpy extensions are misidentified as legacy C API extensions in the
+ # case where only hpy extensions are present.
+ self._only_hpy_extensions = not bool(self._extensions)
+ hpy_ext_modules = self.distribution.hpy_ext_modules or []
+ for ext in hpy_ext_modules:
+ self._finalize_hpy_ext(ext)
+ self._extensions.extend(hpy_ext_modules)
+ self._base_build_ext.finalize_options(self)
+ for ext in hpy_ext_modules:
+ ext._needs_stub = ext._hpy_needs_stub
+
+ @remember_hpy_extension
+ def get_ext_fullname(self, ext_name):
+ return self._base_build_ext.get_ext_fullname(self, ext_name)
+
+ @remember_hpy_extension
+ def get_ext_fullpath(self, ext_name):
+ return self._base_build_ext.get_ext_fullpath(self, ext_name)
+
+ @remember_hpy_extension
+ def get_ext_filename(self, ext_name):
+ if not is_hpy_extension(ext_name):
+ return self._base_build_ext.get_ext_filename(self, ext_name)
+ if self.distribution.hpy_abi == 'universal':
+ ext_path = ext_name.split('.')
+ ext_suffix = '.hpy.so' # XXX Windows?
+ ext_filename = os.path.join(*ext_path) + ext_suffix
+ else:
+ ext_filename = self._base_build_ext.get_ext_filename(
+ self, ext_name)
+ return ext_filename
- hpy_devel = HPyDevel()
- hpy_devel.fix_distribution(dist, hpy_ext_modules)
+ def write_stub(self, output_dir, ext, compile=False):
+ if (not hasattr(ext, "hpy_abi") or
+ self.distribution.hpy_abi != 'universal'):
+ return self._base_build_ext.write_stub(
+ self, output_dir, ext, compile=compile)
+ pkgs = ext._full_name.split('.')
+ if compile:
+ # compile is true when .write_stub is called while copying
+ # extensions to the source folder as part of build_ext --inplace.
+ # In this situation, output_dir includes the folders that make up
+ # the packages containing the module. When compile is false,
+ # output_dir does not include those folders (and is just the
+ # build_lib folder).
+ pkgs = [pkgs[-1]]
+ stub_file = os.path.join(output_dir, *pkgs) + '.py'
+ log.info(
+ "writing hpy universal stub loader for %s to %s",
+ ext._full_name, stub_file)
+ if compile and os.path.exists(stub_file):
+ raise DistutilsError(stub_file + " already exists! Please delete.")
+ ext_file = os.path.basename(ext._file_name)
+ module_name = ext_file.split(".")[0]
+ if not self.dry_run:
+ with open(stub_file, 'w') as f:
+ f.write(_HPY_UNIVERSAL_MODULE_STUB_TEMPLATE.format(
+ ext_file=ext_file, module_name=module_name)
+ )
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyfunc_declare.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyfunc_declare.h
index 2cd90d4456..251338d57e 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyfunc_declare.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyfunc_declare.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.hpyfunc.autogen_hpyfunc_declare_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyslot.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyslot.h
index ce63a06394..01918fe0bb 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyslot.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_hpyslot.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.hpyslot.autogen_hpyslot_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_impl.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_impl.h
index 17ae3dfc50..4b9a1b940b 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_impl.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/autogen_impl.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.trampolines.autogen_impl_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
@@ -268,6 +270,11 @@ HPyAPI_STORAGE HPy _HPy_IMPL_NAME_NOPREFIX(InPlaceOr)(HPyContext ctx, HPy h1, HP
return _py2h(PyNumber_InPlaceOr(_h2py(h1), _h2py(h2)));
}
+HPyAPI_STORAGE int _HPy_IMPL_NAME(Callable_Check)(HPyContext ctx, HPy h)
+{
+ return PyCallable_Check(_h2py(h));
+}
+
HPyAPI_STORAGE void _HPy_IMPL_NAME(Err_SetString)(HPyContext ctx, HPy h_type, const char *message)
{
PyErr_SetString(_h2py(h_type), message);
@@ -448,3 +455,8 @@ HPyAPI_STORAGE HPy _HPy_IMPL_NAME(Dict_New)(HPyContext ctx)
return _py2h(PyDict_New());
}
+HPyAPI_STORAGE int _HPy_IMPL_NAME(Tuple_Check)(HPyContext ctx, HPy h)
+{
+ return PyTuple_Check(_h2py(h));
+}
+
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/runtime/ctx_call.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/runtime/ctx_call.h
new file mode 100644
index 0000000000..0f64ea9eec
--- /dev/null
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/runtime/ctx_call.h
@@ -0,0 +1,10 @@
+#ifndef HPY_COMMON_RUNTIME_CALL_H
+#define HPY_COMMON_RUNTIME_CALL_H
+
+#include <Python.h>
+#include "hpy.h"
+
+_HPy_HIDDEN HPy
+ctx_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw);
+
+#endif /* HPY_COMMON_RUNTIME_CALL_H */
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/version.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/version.h
index 3c022d32b5..70cf346452 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/version.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/common/version.h
@@ -1,4 +1,4 @@
// automatically generated by setup.py:get_scm_config()
-#define HPY_VERSION "0.1.dev875+g7c832a2"
-#define HPY_GIT_REVISION "7c832a2"
+#define HPY_VERSION "0.1.dev959+geb07982"
+#define HPY_GIT_REVISION "eb07982"
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/autogen_hpyfunc_trampolines.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/autogen_hpyfunc_trampolines.h
index e5f7f9917f..dbcc723f4b 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/autogen_hpyfunc_trampolines.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/autogen_hpyfunc_trampolines.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.hpyfunc.autogen_cpython_hpyfunc_trampoline_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/hpy.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/hpy.h
index 8f97c4be17..750684340b 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/hpy.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/cpython/hpy.h
@@ -272,6 +272,7 @@ HPy_AsPyObject(HPyContext ctx, HPy h)
#include "../common/hpydef.h"
#include "../common/hpytype.h"
#include "../common/hpymodule.h"
+#include "../common/runtime/ctx_call.h"
#include "../common/runtime/ctx_module.h"
#include "../common/runtime/ctx_type.h"
#include "../common/runtime/ctx_listbuilder.h"
@@ -315,6 +316,12 @@ _HPy_Cast(HPyContext ctx, HPy h)
return ctx_Cast(ctx, h);
}
+HPyAPI_FUNC(HPy)
+HPy_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw)
+{
+ return ctx_CallTupleDict(ctx, callable, args, kw);
+}
+
HPyAPI_FUNC(HPyListBuilder)
HPyListBuilder_New(HPyContext ctx, HPy_ssize_t initial_size)
{
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_ctx.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_ctx.h
index 044c93ffdf..115a47ccd7 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_ctx.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_ctx.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.ctx.autogen_ctx_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
@@ -138,6 +140,9 @@ struct _HPyContext_s {
HPy (*ctx_InPlaceAnd)(HPyContext ctx, HPy h1, HPy h2);
HPy (*ctx_InPlaceXor)(HPyContext ctx, HPy h1, HPy h2);
HPy (*ctx_InPlaceOr)(HPyContext ctx, HPy h1, HPy h2);
+ int (*ctx_Callable_Check)(HPyContext ctx, HPy h);
+ HPy (*ctx_CallTupleDict)(HPyContext ctx, HPy callable, HPy args, HPy kw);
+ void (*ctx_FatalError)(HPyContext ctx, const char *message);
void (*ctx_Err_SetString)(HPyContext ctx, HPy h_type, const char *message);
void (*ctx_Err_SetObject)(HPyContext ctx, HPy h_type, HPy h_value);
int (*ctx_Err_Occurred)(HPyContext ctx);
@@ -183,7 +188,7 @@ struct _HPyContext_s {
int (*ctx_List_Append)(HPyContext ctx, HPy h_list, HPy h_item);
int (*ctx_Dict_Check)(HPyContext ctx, HPy h);
HPy (*ctx_Dict_New)(HPyContext ctx);
- void (*ctx_FatalError)(HPyContext ctx, const char *message);
+ int (*ctx_Tuple_Check)(HPyContext ctx, HPy h);
HPy (*ctx_Tuple_FromArray)(HPyContext ctx, HPy items[], HPy_ssize_t n);
HPy (*ctx_FromPyObject)(HPyContext ctx, cpy_PyObject *obj);
cpy_PyObject *(*ctx_AsPyObject)(HPyContext ctx, HPy h);
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_hpyfunc_trampolines.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_hpyfunc_trampolines.h
index 70b46972b3..60a10b4051 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_hpyfunc_trampolines.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_hpyfunc_trampolines.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.hpyfunc.autogen_hpyfunc_trampoline_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_trampolines.h b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_trampolines.h
index 1bf0dbaef8..179423863c 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_trampolines.h
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/include/universal/autogen_trampolines.h
@@ -2,7 +2,9 @@
/*
DO NOT EDIT THIS FILE!
- This file is automatically generated by tools/autogen.py from tools/public_api.h.
+ This file is automatically generated by hpy.tools.autogen.trampolines.autogen_trampolines_h
+ See also hpy.tools.autogen and hpy/tools/public_api.h
+
Run this to regenerate:
make autogen
@@ -228,6 +230,14 @@ static inline HPy HPy_InPlaceOr(HPyContext ctx, HPy h1, HPy h2) {
return ctx->ctx_InPlaceOr ( ctx, h1, h2 );
}
+static inline int HPyCallable_Check(HPyContext ctx, HPy h) {
+ return ctx->ctx_Callable_Check ( ctx, h );
+}
+
+static inline HPy HPy_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw) {
+ return ctx->ctx_CallTupleDict ( ctx, callable, args, kw );
+}
+
static inline void HPyErr_SetString(HPyContext ctx, HPy h_type, const char *message) {
ctx->ctx_Err_SetString ( ctx, h_type, message );
}
@@ -404,6 +414,10 @@ static inline HPy HPyDict_New(HPyContext ctx) {
return ctx->ctx_Dict_New ( ctx );
}
+static inline int HPyTuple_Check(HPyContext ctx, HPy h) {
+ return ctx->ctx_Tuple_Check ( ctx, h );
+}
+
static inline HPy HPyTuple_FromArray(HPyContext ctx, HPy items[], HPy_ssize_t n) {
return ctx->ctx_Tuple_FromArray ( ctx, items, n );
}
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/src/runtime/ctx_call.c b/pypy/module/_hpy_universal/_vendored/hpy/devel/src/runtime/ctx_call.c
new file mode 100644
index 0000000000..e117121512
--- /dev/null
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/src/runtime/ctx_call.c
@@ -0,0 +1,38 @@
+#include <Python.h>
+#include "hpy.h"
+
+#ifdef HPY_UNIVERSAL_ABI
+ // for _h2py and _py2h
+# include "handles.h"
+#endif
+
+_HPy_HIDDEN HPy
+ctx_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw)
+{
+ PyObject *obj;
+ if (!HPy_IsNull(args) && !HPyTuple_Check(ctx, args)) {
+ HPyErr_SetString(ctx, ctx->h_TypeError,
+ "HPy_CallTupleDict requires args to be a tuple or null handle");
+ return HPy_NULL;
+ }
+ if (!HPy_IsNull(kw) && !HPyDict_Check(ctx, kw)) {
+ HPyErr_SetString(ctx, ctx->h_TypeError,
+ "HPy_CallTupleDict requires kw to be a dict or null handle");
+ return HPy_NULL;
+ }
+ if (HPy_IsNull(kw)) {
+ obj = PyObject_CallObject(_h2py(callable), _h2py(args));
+ }
+ else if (!HPy_IsNull(args)){
+ obj = PyObject_Call(_h2py(callable), _h2py(args), _h2py(kw));
+ }
+ else {
+ // args is null, but kw is not, so we need to create an empty args tuple
+ // for CPython's PyObject_Call
+ HPy items[] = {};
+ HPy empty_tuple = HPyTuple_FromArray(ctx, items, 0);
+ obj = PyObject_Call(_h2py(callable), _h2py(empty_tuple), _h2py(kw));
+ HPy_Close(ctx, empty_tuple);
+ }
+ return _py2h(obj);
+}
diff --git a/pypy/module/_hpy_universal/_vendored/hpy/devel/version.py b/pypy/module/_hpy_universal/_vendored/hpy/devel/version.py
index d8b8f5c099..82e7ded629 100644
--- a/pypy/module/_hpy_universal/_vendored/hpy/devel/version.py
+++ b/pypy/module/_hpy_universal/_vendored/hpy/devel/version.py
@@ -1,4 +1,4 @@
# automatically generated by setup.py:get_scm_config()
-__version__ = "0.1.dev875+g7c832a2"
-__git_revision__ = "7c832a2"
+__version__ = "0.1.dev959+geb07982"
+__git_revision__ = "eb07982"
diff --git a/pypy/module/_hpy_universal/interp_call.py b/pypy/module/_hpy_universal/interp_call.py
new file mode 100644
index 0000000000..17315fc9f0
--- /dev/null
+++ b/pypy/module/_hpy_universal/interp_call.py
@@ -0,0 +1,21 @@
+from pypy.interpreter.error import oefmt
+from .apiset import API
+from . import handles
+
+@API.func("HPy HPy_CallTupleDict(HPyContext ctx, HPy callable, HPy args, HPy kw)")
+def HPy_CallTupleDict(space, ctx, h_callable, h_args, h_kw):
+ w_callable = handles.deref(space, h_callable)
+ w_args = handles.deref(space, h_args) if h_args else None
+ w_kw = handles.deref(space, h_kw) if h_kw else None
+
+ # Check the types here, as space.call would allow any iterable/mapping
+ if w_args and not space.isinstance_w(w_args, space.w_tuple):
+ raise oefmt(space.w_TypeError,
+ "HPy_CallTupleDict requires args to be a tuple or null handle")
+ if w_kw and not space.isinstance_w(w_kw, space.w_dict):
+ raise oefmt(space.w_TypeError,
+ "HPy_CallTupleDict requires kw to be a dict or null handle")
+
+ # Note: both w_args and w_kw are allowed to be None
+ w_result = space.call(w_callable, w_args, w_kw)
+ return handles.new(space, w_result)
diff --git a/pypy/module/_hpy_universal/interp_hpy.py b/pypy/module/_hpy_universal/interp_hpy.py
index 525aaa5db2..acee9d43b9 100644
--- a/pypy/module/_hpy_universal/interp_hpy.py
+++ b/pypy/module/_hpy_universal/interp_hpy.py
@@ -19,6 +19,7 @@ from pypy.module._hpy_universal import (
interp_unicode,
interp_float,
interp_bytes,
+ interp_call,
interp_dict,
interp_list,
interp_tuple,
@@ -43,6 +44,10 @@ def create_hpy_module(space, name, origin, lib, initfunc_ptr):
state = space.fromcache(State)
initfunc_ptr = rffi.cast(llapi.HPyInitFunc, initfunc_ptr)
h_module = initfunc_ptr(state.ctx)
+ if not h_module:
+ raise oefmt(space.w_SystemError,
+ "initialization of %s failed without raising an exception",
+ name)
return handles.consume(space, h_module)
def descr_load_from_spec(space, w_spec):
diff --git a/pypy/module/_hpy_universal/interp_long.py b/pypy/module/_hpy_universal/interp_long.py
index c6f2c83556..f096db0fd7 100644
--- a/pypy/module/_hpy_universal/interp_long.py
+++ b/pypy/module/_hpy_universal/interp_long.py
@@ -98,7 +98,8 @@ def HPyLong_AsLongLong(space, ctx, h):
def HPyLong_AsUnsignedLongLong(space, ctx, h):
w_long = handles.deref(space, h)
try:
- return rffi.cast(rffi.ULONGLONG, space.r_ulonglong_w(w_long))
+ return rffi.cast(rffi.ULONGLONG, space.r_ulonglong_w(
+ w_long, allow_conversion=False))
except OperationError as e:
if e.match(space, space.w_ValueError):
e.w_type = space.w_OverflowError
@@ -126,4 +127,4 @@ def HPyLong_AsSize_t(space, ctx, h):
error_value=API.cast("ssize_t", -1))
def HPyLong_AsSsize_t(space, ctx, h):
w_long = handles.deref(space, h)
- return space.int_w(w_long)
+ return space.int_w(w_long, allow_conversion=False)
diff --git a/pypy/module/_hpy_universal/interp_module.py b/pypy/module/_hpy_universal/interp_module.py
index 97be1d7a49..34d831e5e9 100644
--- a/pypy/module/_hpy_universal/interp_module.py
+++ b/pypy/module/_hpy_universal/interp_module.py
@@ -1,7 +1,6 @@
from rpython.rtyper.lltypesystem import lltype, rffi
-from rpython.rlib.rarithmetic import widen
from pypy.interpreter.error import OperationError, oefmt
-from pypy.interpreter.module import Module
+from pypy.interpreter.module import Module, init_extra_module_attrs
from pypy.module._hpy_universal.apiset import API
from pypy.module._hpy_universal import llapi
from pypy.module._hpy_universal import handles
@@ -21,8 +20,8 @@ def HPyModule_Create(space, ctx, hpydef):
attach_legacy_methods(space, pymethods, w_mod, modname)
else:
raise oefmt(space.w_RuntimeError,
- "Module %s contains legacy methods, but _hpy_universal "
- "was compiled without cpyext support", modname)
+ "Module %s contains legacy methods, but _hpy_universal "
+ "was compiled without cpyext support", modname)
#
# add the native HPy defines
if hpydef.c_defines:
@@ -38,6 +37,12 @@ def HPyModule_Create(space, ctx, hpydef):
space, name, sig, doc, hpymeth.c_impl, w_mod)
space.setattr(w_mod, space.newtext(w_extfunc.name), w_extfunc)
i += 1
+ if hpydef.c_m_doc:
+ w_doc = space.newtext(rffi.constcharp2str(hpydef.c_m_doc))
+ else:
+ w_doc = space.w_None
+ space.setattr(w_mod, space.newtext('__doc__'), w_doc)
+ init_extra_module_attrs(space, w_mod)
return handles.new(space, w_mod)
diff --git a/pypy/module/_hpy_universal/interp_object.py b/pypy/module/_hpy_universal/interp_object.py
index c3a28de4ba..ac559a225c 100644
--- a/pypy/module/_hpy_universal/interp_object.py
+++ b/pypy/module/_hpy_universal/interp_object.py
@@ -67,6 +67,12 @@ def HPy_SetAttr_s(space, ctx, h_obj, name, h_value):
return API.int(0)
+@API.func("int HPyCallable_Check(HPyContext ctx, HPy h)", error_value='CANNOT_FAIL')
+def HPyCallable_Check(space, ctx, h_obj):
+ w_obj = handles.deref(space, h_obj)
+ return API.int(space.is_true(space.callable(w_obj)))
+
+
@API.func("HPy HPy_GetItem(HPyContext ctx, HPy h_obj, HPy h_key)")
def HPy_GetItem(space, ctx, h_obj, h_key):
w_obj = handles.deref(space, h_obj)
diff --git a/pypy/module/_hpy_universal/interp_tuple.py b/pypy/module/_hpy_universal/interp_tuple.py
index bb38411545..f9560cbb44 100644
--- a/pypy/module/_hpy_universal/interp_tuple.py
+++ b/pypy/module/_hpy_universal/interp_tuple.py
@@ -8,3 +8,11 @@ def HPyTuple_FromArray(space, ctx, items, n):
items_w[i] = handles.deref(space, items[i])
w_result = space.newtuple(items_w)
return handles.new(space, w_result)
+
+@API.func("int HPyTuple_Check(HPyContext ctx, HPy h)", error_value='CANNOT_FAIL')
+def HPyTuple_Check(space, ctx, h):
+ w_obj = handles.deref(space, h)
+ w_obj_type = space.type(w_obj)
+ res = (space.is_w(w_obj_type, space.w_tuple) or
+ space.issubtype_w(w_obj_type, space.w_tuple))
+ return API.int(res)
diff --git a/pypy/module/_hpy_universal/llapi.py b/pypy/module/_hpy_universal/llapi.py
index 3f5259ede1..73cf80684b 100644
--- a/pypy/module/_hpy_universal/llapi.py
+++ b/pypy/module/_hpy_universal/llapi.py
@@ -191,6 +191,9 @@ struct _HPyContext_s {
void * ctx_InPlaceAnd;
void * ctx_InPlaceXor;
void * ctx_InPlaceOr;
+ void * ctx_Callable_Check;
+ void * ctx_CallTupleDict;
+ void * ctx_FatalError;
void * ctx_Err_SetString;
void * ctx_Err_SetObject;
void * ctx_Err_Occurred;
@@ -236,7 +239,7 @@ struct _HPyContext_s {
void * ctx_List_Append;
void * ctx_Dict_Check;
void * ctx_Dict_New;
- void * ctx_FatalError;
+ void * ctx_Tuple_Check;
void * ctx_Tuple_FromArray;
void * ctx_FromPyObject;
void * ctx_AsPyObject;
@@ -497,6 +500,11 @@ HPy_GE = 5
SIZEOF_HPyObject_HEAD = rffi.sizeof(cts.gettype('struct _HPyObject_head_s'))
# HPy API functions which are implemented directly in C
+pypy_HPy_FatalError = rffi.llexternal('pypy_HPy_FatalError',
+ [HPyContext, rffi.CCHARP],
+ lltype.Void,
+ compilation_info=eci, _nowrapper=True)
+
pypy_HPyErr_Occurred = rffi.llexternal('pypy_HPyErr_Occurred', [HPyContext],
rffi.INT_real,
compilation_info=eci, _nowrapper=True)
diff --git a/pypy/module/_hpy_universal/src/hpyerr.c b/pypy/module/_hpy_universal/src/hpyerr.c
index 3aa76c9802..47f478ea35 100644
--- a/pypy/module/_hpy_universal/src/hpyerr.c
+++ b/pypy/module/_hpy_universal/src/hpyerr.c
@@ -6,11 +6,17 @@
# include "src/exception.h"
#endif
+#include <stdio.h>
#include "universal/hpy.h"
#include "hpyerr.h"
#include "bridge.h"
+void pypy_HPy_FatalError(HPyContext ctx, const char *message)
+{
+ fprintf(stderr, "Fatal Python error: %s\n", message);
+ abort();
+}
int pypy_HPyErr_Occurred(HPyContext ctx)
{
diff --git a/pypy/module/_hpy_universal/src/hpyerr.h b/pypy/module/_hpy_universal/src/hpyerr.h
index 61e9765240..a7ce78fe1a 100644
--- a/pypy/module/_hpy_universal/src/hpyerr.h
+++ b/pypy/module/_hpy_universal/src/hpyerr.h
@@ -1,6 +1,7 @@
#include "src/precommondefs.h"
#include "universal/hpy.h"
+RPY_EXTERN void pypy_HPy_FatalError(HPyContext ctx, const char *message);
RPY_EXTERN int pypy_HPyErr_Occurred(HPyContext ctx);
RPY_EXTERN void pypy_HPyErr_SetString(HPyContext ctx, HPy type, const char *message);
RPY_EXTERN void pypy_HPyErr_SetObject(HPyContext ctx, HPy type, HPy value);
diff --git a/pypy/module/_hpy_universal/state.py b/pypy/module/_hpy_universal/state.py
index 7639f175cc..4739290b66 100644
--- a/pypy/module/_hpy_universal/state.py
+++ b/pypy/module/_hpy_universal/state.py
@@ -66,6 +66,7 @@ class State:
ctx_field = 'c_ctx_' + func.basename
setattr(self.ctx, ctx_field, funcptr)
+ self.ctx.c_ctx_FatalError = rffi.cast(rffi.VOIDP, llapi.pypy_HPy_FatalError)
self.ctx.c_ctx_Err_Occurred = rffi.cast(rffi.VOIDP, llapi.pypy_HPyErr_Occurred)
self.ctx.c_ctx_Err_SetString = rffi.cast(rffi.VOIDP, llapi.pypy_HPyErr_SetString)
self.ctx.c_ctx_Err_SetObject = rffi.cast(rffi.VOIDP, llapi.pypy_HPyErr_SetObject)
diff --git a/pypy/module/_hpy_universal/test/_vendored/conftest.py b/pypy/module/_hpy_universal/test/_vendored/conftest.py
index 06fe80d0f6..b601413f76 100644
--- a/pypy/module/_hpy_universal/test/_vendored/conftest.py
+++ b/pypy/module/_hpy_universal/test/_vendored/conftest.py
@@ -1,23 +1,6 @@
import pytest
-import sys
from .support import ExtensionCompiler
-disable = False
-
-if sys.platform == 'win32':
- # skip all tests on windows, see issue hpyproject/hpy#61
- disable = True
-
-def pytest_ignore_collect(path, config):
- if disable:
- return True
-
-def pytest_collect_file(path, parent):
- if disable:
- # We end up here when calling py.test .../test_foo.py directly
- # It's OK to kill the whole session with the following line
- pytest.skip("skipping on windows")
-
def pytest_addoption(parser):
parser.addoption(
"--compiler-v", action="store_true",
diff --git a/pypy/module/_hpy_universal/test/_vendored/support.py b/pypy/module/_hpy_universal/test/_vendored/support.py
index b14e00ea0f..fe0a91b23a 100644
--- a/pypy/module/_hpy_universal/test/_vendored/support.py
+++ b/pypy/module/_hpy_universal/test/_vendored/support.py
@@ -198,33 +198,35 @@ class ExtensionCompiler:
def make_module(self, ExtensionTemplate, main_src, name, extra_sources):
"""
- Compile&load a modulo into memory. This is NOT a proper import: e.g. the module
- is not put into sys.modules
+ Compile & load a module. This is NOT a proper import: e.g.
+ the module is not put into sys.modules
"""
- so_filename = self.compile_module(ExtensionTemplate, main_src, name,
- extra_sources)
- if self.hpy_abi == 'universal':
- return self.load_universal_module(name, so_filename)
- else:
- return self.load_cython_module(name, so_filename)
-
- def load_universal_module(self, name, so_filename):
- assert self.hpy_abi == 'universal'
- import hpy.universal
- spec = Spec(name, so_filename)
- return hpy.universal.load_from_spec(spec)
-
- def load_cython_module(self, name, so_filename):
- assert self.hpy_abi == 'cpython'
- # we've got a normal CPython module compiled with the CPython API/ABI,
- # let's load it normally. It is important to do the imports only here,
- # because this file will be imported also by PyPy tests which runs on
- # Python2
- import importlib.util
- from importlib.machinery import ExtensionFileLoader
- spec = importlib.util.spec_from_file_location(name, so_filename)
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
+ mod_filename = self.compile_module(
+ ExtensionTemplate, main_src, name, extra_sources)
+ return self.load_module(name, mod_filename)
+
+ def load_module(self, name, mod_filename):
+ # It is important to do the imports only here, because this file will
+ # be imported also by PyPy tests which runs on Python2
+ import importlib
+ import sys
+ import os
+ if name in sys.modules:
+ raise ValueError(
+ "Test module {!r} already present in sys.modules".format(name))
+ importlib.invalidate_caches()
+ mod_dir = os.path.dirname(mod_filename)
+ sys.path.insert(0, mod_dir)
+ try:
+ module = importlib.import_module(name)
+ assert sys.modules[name] is module
+ finally:
+ # assert that the module import didn't change the sys.path entry
+ # that was added above, then remove the entry.
+ assert sys.path[0] == mod_dir
+ del sys.path[0]
+ if name in sys.modules:
+ del sys.modules[name]
return module
@@ -242,9 +244,34 @@ class HPyTest:
return self.compiler.make_module(ExtensionTemplate, main_src, name,
extra_sources)
- def should_check_refcount(self):
- # defaults to True on CPython, but is set to False by e.g. PyPy
- return sys.implementation.name == 'cpython'
+ def supports_refcounts(self):
+ """ Returns True if the underlying Python implementation supports
+ reference counts.
+
+ By default returns True on CPython and False on other
+ implementations.
+ """
+ return sys.implementation.name == "cpython"
+
+ def supports_ordinary_make_module_imports(self):
+ """ Returns True if `.make_module(...)` loads modules using a
+ standard Python import mechanism (e.g. `importlib.import_module`).
+
+ By default returns True because the base implementation of
+ `.make_module(...)` uses an ordinary import. Sub-classes that
+ override `.make_module(...)` may also want to override this
+ method.
+ """
+ return True
+
+ def supports_sys_executable(self):
+ """ Returns True is `sys.executable` is set to a value that allows
+ a Python equivalent to the current Python to be launched via, e.g.,
+ `subprocess.run(...)`.
+
+ By default returns `True` if sys.executable is set to a true value.
+ """
+ return bool(getattr(sys, "executable", None))
# the few functions below are copied and adapted from cffi/ffiplatform.py
@@ -263,32 +290,41 @@ def c_compile(tmpdir, ext, hpy_devel, hpy_abi, compiler_verbose=0, debug=None):
os.environ[key] = value
return outputfilename
+
def _build(tmpdir, ext, hpy_devel, hpy_abi, compiler_verbose=0, debug=None):
# XXX compact but horrible :-(
from distutils.core import Distribution
- import distutils.errors, distutils.log
+ import distutils.errors
+ import distutils.log
#
dist = Distribution()
dist.parse_config_files()
- options = dist.get_option_dict('build_ext')
if debug is None:
debug = sys.flags.debug
- options['debug'] = ('ffiplatform', debug)
- options['force'] = ('ffiplatform', True)
- options['build_lib'] = ('ffiplatform', tmpdir)
- options['build_temp'] = ('ffiplatform', tmpdir)
- #
+ options_build_ext = dist.get_option_dict('build_ext')
+ options_build_ext['debug'] = ('ffiplatform', debug)
+ options_build_ext['force'] = ('ffiplatform', True)
+ options_build_ext['build_lib'] = ('ffiplatform', tmpdir)
+ options_build_ext['build_temp'] = ('ffiplatform', tmpdir)
+ options_build_py = dist.get_option_dict('build_py')
+ options_build_py['build_lib'] = ('ffiplatform', tmpdir)
+
# this is the equivalent of passing --hpy-abi from setup.py's command line
dist.hpy_abi = hpy_abi
- hpy_devel.fix_distribution(dist, hpy_ext_modules=[ext])
- #
+ dist.hpy_ext_modules = [ext]
+ hpy_devel.fix_distribution(dist)
+
old_level = distutils.log.set_threshold(0) or 0
try:
distutils.log.set_verbosity(compiler_verbose)
dist.run_command('build_ext')
cmd_obj = dist.get_command_obj('build_ext')
- [soname] = cmd_obj.get_outputs()
+ outputs = cmd_obj.get_outputs()
+ if hpy_abi == "cpython":
+ [mod_filename] = [x for x in outputs if not x.endswith(".py")]
+ else:
+ [mod_filename] = [x for x in outputs if x.endswith(".py")]
finally:
distutils.log.set_threshold(old_level)
- #
- return soname
+
+ return mod_filename
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_basic.py b/pypy/module/_hpy_universal/test/_vendored/test_00_basic.py
index 1b639e50bf..1b639e50bf 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_basic.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_00_basic.py
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_call.py b/pypy/module/_hpy_universal/test/_vendored/test_call.py
new file mode 100644
index 0000000000..18ec74b65e
--- /dev/null
+++ b/pypy/module/_hpy_universal/test/_vendored/test_call.py
@@ -0,0 +1,110 @@
+from .support import HPyTest
+
+
+class TestCall(HPyTest):
+ def argument_combinations(self, **items):
+ """ Returns all possible ways of expressing the given items as
+ arguments to a function.
+ """
+ items = list(items.items())
+ for i in range(len(items) + 1):
+ args = tuple(item[1] for item in items[:i])
+ kw = dict(items[i:])
+ yield {"args": args, "kw": kw}
+ if not args:
+ yield {"kw": kw}
+ if not kw:
+ yield {"args": args}
+ if not args and not kw:
+ yield {}
+
+ def test_hpy_calltupledict(self):
+ import pytest
+ mod = self.make_module("""
+ HPyDef_METH(call, "call", call_impl, HPyFunc_KEYWORDS)
+ static HPy call_impl(HPyContext ctx, HPy self,
+ HPy *args, HPy_ssize_t nargs, HPy kw)
+ {
+
+ HPy f, result;
+ HPy f_args = HPy_NULL;
+ HPy f_kw = HPy_NULL;
+ HPyTracker ht;
+ static const char *kwlist[] = { "f", "args", "kw", NULL };
+ if (!HPyArg_ParseKeywords(ctx, &ht, args, nargs, kw, "O|OO",
+ kwlist, &f, &f_args, &f_kw)) {
+ return HPy_NULL;
+ }
+ result = HPy_CallTupleDict(ctx, f, f_args, f_kw);
+ HPyTracker_Close(ctx, ht);
+ return result;
+ }
+ @EXPORT(call)
+ @INIT
+ """)
+
+ def f(a, b):
+ return a + b
+
+ def g():
+ return "this is g"
+
+ # test passing arguments with handles of the correct type --
+ # i.e. args is a tuple or a null handle, kw is a dict or a null handle.
+ for d in self.argument_combinations(a=1, b=2):
+ assert mod.call(f, **d) == 3
+ for d in self.argument_combinations(a=1):
+ with pytest.raises(TypeError):
+ mod.call(f, **d)
+ for d in self.argument_combinations():
+ with pytest.raises(TypeError):
+ mod.call(f, **d)
+ for d in self.argument_combinations():
+ assert mod.call(g, **d) == "this is g"
+ for d in self.argument_combinations(object=2):
+ assert mod.call(str, **d) == "2"
+ for d in self.argument_combinations():
+ with pytest.raises(TypeError):
+ mod.call("not callable", **d)
+ for d in self.argument_combinations(unknown=2):
+ with pytest.raises(TypeError):
+ mod.call("not callable", **d)
+
+ # test passing handles of the incorrect type as arguments
+ with pytest.raises(TypeError):
+ mod.call(f, args=[1, 2])
+ with pytest.raises(TypeError):
+ mod.call(f, args="string")
+ with pytest.raises(TypeError):
+ mod.call(f, args=1)
+ with pytest.raises(TypeError):
+ mod.call(f, args=None)
+ with pytest.raises(TypeError):
+ mod.call(f, kw=[1, 2])
+ with pytest.raises(TypeError):
+ mod.call(f, kw="string")
+ with pytest.raises(TypeError):
+ mod.call(f, kw=1)
+ with pytest.raises(TypeError):
+ mod.call(f, kw=None)
+
+ def test_hpycallable_check(self):
+ mod = self.make_module("""
+ HPyDef_METH(f, "f", f_impl, HPyFunc_O)
+ static HPy f_impl(HPyContext ctx, HPy self, HPy arg)
+ {
+ if (HPyCallable_Check(ctx, arg))
+ return HPy_Dup(ctx, ctx->h_True);
+ return HPy_Dup(ctx, ctx->h_False);
+ }
+ @EXPORT(f)
+ @INIT
+ """)
+
+ def f():
+ return "this is f"
+
+ assert mod.f(f) is True
+ assert mod.f(str) is True
+ assert mod.f("a") is False
+ assert mod.f(3) is False
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_cpy_compat.py b/pypy/module/_hpy_universal/test/_vendored/test_cpy_compat.py
index 4ebc8b40a9..95f6ea9182 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_cpy_compat.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_cpy_compat.py
@@ -3,7 +3,7 @@ from .support import HPyTest
class TestCPythonCompatibility(HPyTest):
- # One note about the should_check_refcount() in the tests below: on
+ # One note about the supports_refcounts() in the tests below: on
# CPython, handles are actually implemented as INCREF/DECREF, so we can
# check e.g. after an HPy_Dup the refcnt is += 1. However, on PyPy they
# are implemented in a completely different way which is unrelated to the
@@ -35,7 +35,7 @@ class TestCPythonCompatibility(HPyTest):
x = mod.f()
assert x[0] == 1234
assert len(x) == 2
- if self.should_check_refcount():
+ if self.supports_refcounts():
assert x == [1234, +1]
def test_aspyobject(self):
@@ -97,7 +97,7 @@ class TestCPythonCompatibility(HPyTest):
@INIT
""")
x = mod.f()
- if self.should_check_refcount():
+ if self.supports_refcounts():
assert x == -1
def test_hpy_dup(self):
@@ -123,7 +123,7 @@ class TestCPythonCompatibility(HPyTest):
@INIT
""")
x = mod.f()
- if self.should_check_refcount():
+ if self.supports_refcounts():
assert x == +1
def test_many_handles(self):
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_hpyerr.py b/pypy/module/_hpy_universal/test/_vendored/test_hpyerr.py
index e9d935dc68..c1152d1572 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_hpyerr.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_hpyerr.py
@@ -20,6 +20,8 @@ class TestErr(HPyTest):
mod.f()
def test_FatalError(self):
+ import os
+ import sys
mod = self.make_module("""
HPyDef_METH(f, "f", f_impl, HPyFunc_NOARGS)
static HPy f_impl(HPyContext ctx, HPy self)
@@ -33,8 +35,21 @@ class TestErr(HPyTest):
@EXPORT(f)
@INIT
""")
- # Calling mod.f() gives a fatal error, ending in abort().
- # How to check that? For now we just check that the above compiles
+ if not self.supports_sys_executable():
+ # if sys.executable is not available (e.g. inside pypy app-level)
+ # tests, then skip the rest of this test
+ return
+ # subprocess is not importable in pypy app-level tests
+ import subprocess
+ env = os.environ.copy()
+ env["PYTHONPATH"] = os.path.dirname(mod.__file__)
+ result = subprocess.run([
+ sys.executable,
+ "-c", "import {} as mod; mod.f()".format(mod.__name__)
+ ], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ assert result.returncode == -6
+ assert result.stdout == b""
+ assert result.stderr.startswith(b"Fatal Python error: boom!\n")
def test_HPyErr_Occurred(self):
import pytest
@@ -233,10 +248,6 @@ class TestErr(HPyTest):
check_exception(EnvironmentError)
check_exception(IOError)
- @pytest_collecting.mark.xfail(True, reason=(
- "Creating the unicode exceptions requires something like HPyCall"
- " and that isn't implemented yet."
- ))
def test_h_unicode_exceptions(self):
import pytest
mod = self.make_module("""
@@ -264,7 +275,7 @@ class TestErr(HPyTest):
HPy_Close(ctx, h_dict);
return HPy_NULL;
}
- h_err_value = HPy_Call(ctx, h_err, h_args, h_kw);
+ h_err_value = HPy_CallTupleDict(ctx, h_err, h_args, h_kw);
if (HPy_IsNull(h_err_value)) {
HPy_Close(ctx, h_dict);
HPy_Close(ctx, h_err);
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_hpylong.py b/pypy/module/_hpy_universal/test/_vendored/test_hpylong.py
index df6ea1441d..3808e1390e 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_hpylong.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_hpylong.py
@@ -3,6 +3,33 @@ from .support import HPyTest
class TestLong(HPyTest):
+ def magic_int(self, v):
+ """ Return an instance of a class that implements __int__
+ and returns value v.
+ """
+ class MagicInt(object):
+ def __int__(self):
+ return v
+ return MagicInt()
+
+ def magic_index(self, v):
+ """ Return an instance of a class that implements __index__
+ and returns value v.
+ """
+ class MagicIndex(object):
+ def __index__(self):
+ return v
+ return MagicIndex()
+
+ def python_supports_magic_index(self):
+ """ Return True if the Python version is 3.8 or later and thus
+ should support calling __index__ in the various HPyLong_As...
+ methods.
+ """
+ import sys
+ vi = sys.version_info
+ return (vi.major > 3 or (vi.major == 3 and vi.minor >= 8))
+
def test_Long_FromLong(self):
mod = self.make_module("""
HPyDef_METH(f, "f", f_impl, HPyFunc_NOARGS)
@@ -33,6 +60,9 @@ class TestLong(HPyTest):
assert mod.f(45) == 90
with pytest.raises(TypeError):
mod.f("this is not a number")
+ assert mod.f(self.magic_int(2)) == 4
+ if self.python_supports_magic_index():
+ assert mod.f(self.magic_index(2)) == 4
def test_Long_FromUnsignedLong(self):
mod = self.make_module("""
@@ -66,6 +96,10 @@ class TestLong(HPyTest):
mod.f(-91)
with pytest.raises(TypeError):
mod.f("this is not a number")
+ with pytest.raises(TypeError):
+ mod.f(self.magic_int(2))
+ with pytest.raises(TypeError):
+ mod.f(self.magic_index(2))
def test_Long_AsUnsignedLongMask(self):
import pytest
@@ -85,6 +119,9 @@ class TestLong(HPyTest):
assert mod.f(-1) == 2**64 - 1
with pytest.raises(TypeError):
mod.f("this is not a number")
+ assert mod.f(self.magic_int(2)) == 2
+ if self.python_supports_magic_index():
+ assert mod.f(self.magic_index(2)) == 2
def test_Long_FromLongLong(self):
mod = self.make_module("""
@@ -118,6 +155,9 @@ class TestLong(HPyTest):
assert mod.f(-2147483648) == -2147483648
with pytest.raises(TypeError):
mod.f("this is not a number")
+ assert mod.f(self.magic_int(2)) == 2
+ if self.python_supports_magic_index():
+ assert mod.f(self.magic_index(2)) == 2
def test_Long_FromUnsignedLongLong(self):
mod = self.make_module("""
@@ -152,6 +192,10 @@ class TestLong(HPyTest):
mod.f(-4294967296)
with pytest.raises(TypeError):
mod.f("this is not a number")
+ with pytest.raises(TypeError):
+ mod.f(self.magic_int(2))
+ with pytest.raises(TypeError):
+ mod.f(self.magic_index(2))
def test_Long_AsUnsignedLongLongMask(self):
import pytest
@@ -171,6 +215,9 @@ class TestLong(HPyTest):
assert mod.f(-1) == 2**64 - 1
with pytest.raises(TypeError):
mod.f("this is not a number")
+ assert mod.f(self.magic_int(2)) == 2
+ if self.python_supports_magic_index():
+ assert mod.f(self.magic_index(2)) == 2
def test_Long_FromSize_t(self):
mod = self.make_module("""
@@ -205,6 +252,10 @@ class TestLong(HPyTest):
mod.f(-2147483648)
with pytest.raises(TypeError):
mod.f("this is not a number")
+ with pytest.raises(TypeError):
+ mod.f(self.magic_int(2))
+ with pytest.raises(TypeError):
+ mod.f(self.magic_index(2))
def test_Long_FromSsize_t(self):
mod = self.make_module("""
@@ -241,3 +292,7 @@ class TestLong(HPyTest):
assert mod.f(-41) == -41
with pytest.raises(TypeError):
mod.f("this is not a number")
+ with pytest.raises(TypeError):
+ mod.f(self.magic_int(2))
+ with pytest.raises(TypeError):
+ mod.f(self.magic_index(2))
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_hpytuple.py b/pypy/module/_hpy_universal/test/_vendored/test_hpytuple.py
index f95080da8a..0498c37af5 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_hpytuple.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_hpytuple.py
@@ -2,6 +2,25 @@ from .support import HPyTest
class TestTuple(HPyTest):
+ def test_Check(self):
+ mod = self.make_module("""
+ HPyDef_METH(f, "f", f_impl, HPyFunc_O)
+ static HPy f_impl(HPyContext ctx, HPy self, HPy arg)
+ {
+ if (HPyTuple_Check(ctx, arg))
+ return HPy_Dup(ctx, ctx->h_True);
+ return HPy_Dup(ctx, ctx->h_False);
+ }
+ @EXPORT(f)
+ @INIT
+ """)
+ class MyTuple(tuple):
+ pass
+
+ assert mod.f(()) is True
+ assert mod.f([]) is False
+ assert mod.f(MyTuple()) is True
+
def test_FromArray(self):
mod = self.make_module("""
HPyDef_METH(f, "f", f_impl, HPyFunc_O)
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_hpytype.py b/pypy/module/_hpy_universal/test/_vendored/test_hpytype.py
index 8ccec4e191..4edb58aaf0 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_hpytype.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_hpytype.py
@@ -192,7 +192,7 @@ class TestType(HPyTest):
def test_refcount(self):
import pytest
import sys
- if not self.should_check_refcount():
+ if not self.supports_refcounts():
pytest.skip()
mod = self.make_module("""
@DEFINE_PointObject
diff --git a/pypy/module/_hpy_universal/test/_vendored/test_importing.py b/pypy/module/_hpy_universal/test/_vendored/test_importing.py
index 7d0e7cecab..94de0b11da 100644
--- a/pypy/module/_hpy_universal/test/_vendored/test_importing.py
+++ b/pypy/module/_hpy_universal/test/_vendored/test_importing.py
@@ -1,31 +1,19 @@
-import pytest as pytest_collecting
-
from .support import HPyTest
-# this function should probably goes somewhere into hpy.universal and/or and
-# hpy package and/or an import hook, or whatever. I do not want to think about
-# this now.
-def import_module_properly(mod):
- raise NotImplementedError("fix me eventually")
-
-# this was moved from support.py, where it did not belong
-## class HPyLoader(ExtensionFileLoader):
-## def create_module(self, spec):
-## import hpy.universal
-## return hpy.universal.load_from_spec(spec)
-
class TestImporting(HPyTest):
- @pytest_collecting.mark.xfail
def test_importing_attributes(self):
- import sys
- modname = 'mytest'
- so_filename = self.compile_module("""
+ import pytest
+ if not self.supports_ordinary_make_module_imports():
+ pytest.skip()
+ mod = self.make_module("""
@INIT
- """, name=modname)
- mod = import_module_properly(so_filename, modname)
- assert mod in sys.modules
+ """, name='mytest')
+ assert mod.__name__ == 'mytest'
+ assert mod.__package__ == ''
+ assert mod.__doc__ == 'some test for hpy'
assert mod.__loader__.name == 'mytest'
assert mod.__spec__.loader is mod.__loader__
+ assert mod.__spec__.name == 'mytest'
assert mod.__file__
diff --git a/pypy/module/_hpy_universal/test/support.py b/pypy/module/_hpy_universal/test/support.py
index db875bad68..7490887272 100644
--- a/pypy/module/_hpy_universal/test/support.py
+++ b/pypy/module/_hpy_universal/test/support.py
@@ -10,7 +10,6 @@ from pypy.module._hpy_universal._vendored.hpy.devel import HPyDevel
COMPILER_VERBOSE = False
-
class HPyAppTest(object):
"""
Base class for HPy app tests. This is used as a mixin, and individual
@@ -55,8 +54,9 @@ class HPyAppTest(object):
else:
items_w = space.unpackiterable(w_extra_sources)
extra_sources = [space.text_w(item) for item in items_w]
- so_filename = compiler.compile_module(ExtensionTemplate,
+ py_filename = compiler.compile_module(ExtensionTemplate,
source_template, name, extra_sources)
+ so_filename = py_filename.replace(".py", ".hpy.so")
w_mod = space.appexec([space.newtext(so_filename), space.newtext(name)],
"""(path, modname):
import _hpy_universal
@@ -66,9 +66,19 @@ class HPyAppTest(object):
return w_mod
self.w_make_module = self.space.wrap(interp2app(descr_make_module))
- def should_check_refcount(space):
+ def supports_refcounts(space):
+ return space.w_False
+ self.w_supports_refcounts = self.space.wrap(interp2app(supports_refcounts))
+
+ def supports_ordinary_make_module_imports(space):
+ return space.w_False
+ self.w_supports_ordinary_make_module_imports = self.space.wrap(
+ interp2app(supports_ordinary_make_module_imports))
+
+ def supports_sys_executable(space):
return space.w_False
- self.w_should_check_refcount = self.space.wrap(interp2app(should_check_refcount))
+ self.w_supports_sys_executable = self.space.wrap(
+ interp2app(supports_sys_executable))
self.w_compiler = self.space.appexec([], """():
class compiler:
diff --git a/pypy/module/_hpy_universal/test/test_extra.py b/pypy/module/_hpy_universal/test/test_extra.py
index ea72f8a36a..20512cf87a 100644
--- a/pypy/module/_hpy_universal/test/test_extra.py
+++ b/pypy/module/_hpy_universal/test/test_extra.py
@@ -30,6 +30,52 @@ class TestExtra(HPyTest):
- ListBuilder_Cancel is not tested
"""
+ def test_import_failure(self):
+ import pytest
+ with pytest.raises(RuntimeError):
+ self.make_module("""
+ HPy_MODINIT(test)
+ static HPy init_test_impl(HPyContext ctx)
+ {
+ HPyErr_SetString(ctx, ctx->h_RuntimeError, "foo");
+ return HPyLong_FromLong(ctx, 42);
+ }
+ """, name='test')
+
+ def test_import_bad_module(self):
+ import pytest
+ with pytest.raises(SystemError):
+ self.make_module("""
+ HPy_MODINIT(test)
+ static HPy init_test_impl(HPyContext ctx)
+ {
+ return HPy_NULL;
+ }
+ """, name='test')
+
+ def test_HPyModule_Create(self):
+ mod = self.make_module("""
+ HPyDef_METH(f, "f", f_impl, HPyFunc_NOARGS)
+ static HPy f_impl(HPyContext ctx, HPy self)
+ {
+ HPyModuleDef def = {
+ .m_name = "foo",
+ .m_doc = "Some doc",
+ .m_size = -1,
+ };
+ return HPyModule_Create(ctx, &def);
+ }
+ @EXPORT(f)
+ @INIT
+ """)
+ m = mod.f()
+ assert m.__name__ == "foo"
+ assert m.__doc__ == "Some doc"
+ assert m.__package__ is None
+ assert m.__loader__ is None
+ assert m.__spec__ is None
+ assert set(vars(m).keys()) == {
+ '__name__', '__doc__', '__package__', '__loader__', '__spec__'}
class TestExtraCPythonCompatibility(HPyTest):