diff options
author | Matti Picus <matti.picus@gmail.com> | 2021-03-31 20:45:53 +0300 |
---|---|---|
committer | Matti Picus <matti.picus@gmail.com> | 2021-03-31 20:45:53 +0300 |
commit | db5be4d0cb59068a2aa6c773ff241fde46fc29f0 (patch) | |
tree | 9302b383fac824e3930f47736d8edf0df3a867a5 | |
parent | merge default into py3.7 (diff) | |
parent | Merge branch 'branch/hpy-update-to-eb07982b6' into 'branch/hpy' (diff) | |
download | pypy-db5be4d0cb59068a2aa6c773ff241fde46fc29f0.tar.gz pypy-db5be4d0cb59068a2aa6c773ff241fde46fc29f0.tar.bz2 pypy-db5be4d0cb59068a2aa6c773ff241fde46fc29f0.zip |
merge hpy into py3.7
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): |