test_extending.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. from importlib.util import spec_from_file_location, module_from_spec
  2. import os
  3. import pytest
  4. import shutil
  5. import subprocess
  6. import sys
  7. import sysconfig
  8. import warnings
  9. import numpy as np
  10. from numpy.testing import IS_WASM, IS_EDITABLE
  11. try:
  12. import cffi
  13. except ImportError:
  14. cffi = None
  15. if sys.flags.optimize > 1:
  16. # no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
  17. # cffi cannot succeed
  18. cffi = None
  19. try:
  20. with warnings.catch_warnings(record=True) as w:
  21. # numba issue gh-4733
  22. warnings.filterwarnings('always', '', DeprecationWarning)
  23. import numba
  24. except (ImportError, SystemError):
  25. # Certain numpy/numba versions trigger a SystemError due to a numba bug
  26. numba = None
  27. try:
  28. import cython
  29. from Cython.Compiler.Version import version as cython_version
  30. except ImportError:
  31. cython = None
  32. else:
  33. from numpy._utils import _pep440
  34. # Note: keep in sync with the one in pyproject.toml
  35. required_version = '3.0.6'
  36. if _pep440.parse(cython_version) < _pep440.Version(required_version):
  37. # too old or wrong cython, skip the test
  38. cython = None
  39. @pytest.mark.skipif(
  40. IS_EDITABLE,
  41. reason='Editable install cannot find .pxd headers'
  42. )
  43. @pytest.mark.skipif(
  44. sys.platform == "win32" and sys.maxsize < 2**32,
  45. reason="Failing in 32-bit Windows wheel build job, skip for now"
  46. )
  47. @pytest.mark.skipif(IS_WASM, reason="Can't start subprocess")
  48. @pytest.mark.skipif(cython is None, reason="requires cython")
  49. @pytest.mark.slow
  50. def test_cython(tmp_path):
  51. import glob
  52. # build the examples in a temporary directory
  53. srcdir = os.path.join(os.path.dirname(__file__), '..')
  54. shutil.copytree(srcdir, tmp_path / 'random')
  55. build_dir = tmp_path / 'random' / '_examples' / 'cython'
  56. target_dir = build_dir / "build"
  57. os.makedirs(target_dir, exist_ok=True)
  58. # Ensure we use the correct Python interpreter even when `meson` is
  59. # installed in a different Python environment (see gh-24956)
  60. native_file = str(build_dir / 'interpreter-native-file.ini')
  61. with open(native_file, 'w') as f:
  62. f.write("[binaries]\n")
  63. f.write(f"python = '{sys.executable}'\n")
  64. f.write(f"python3 = '{sys.executable}'")
  65. if sys.platform == "win32":
  66. subprocess.check_call(["meson", "setup",
  67. "--buildtype=release",
  68. "--vsenv", "--native-file", native_file,
  69. str(build_dir)],
  70. cwd=target_dir,
  71. )
  72. else:
  73. subprocess.check_call(["meson", "setup",
  74. "--native-file", native_file, str(build_dir)],
  75. cwd=target_dir
  76. )
  77. subprocess.check_call(["meson", "compile", "-vv"], cwd=target_dir)
  78. # gh-16162: make sure numpy's __init__.pxd was used for cython
  79. # not really part of this test, but it is a convenient place to check
  80. g = glob.glob(str(target_dir / "*" / "extending.pyx.c"))
  81. with open(g[0]) as fid:
  82. txt_to_find = 'NumPy API declarations from "numpy/__init__'
  83. for line in fid:
  84. if txt_to_find in line:
  85. break
  86. else:
  87. assert False, ("Could not find '{}' in C file, "
  88. "wrong pxd used".format(txt_to_find))
  89. # import without adding the directory to sys.path
  90. suffix = sysconfig.get_config_var('EXT_SUFFIX')
  91. def load(modname):
  92. so = (target_dir / modname).with_suffix(suffix)
  93. spec = spec_from_file_location(modname, so)
  94. mod = module_from_spec(spec)
  95. spec.loader.exec_module(mod)
  96. return mod
  97. # test that the module can be imported
  98. load("extending")
  99. load("extending_cpp")
  100. # actually test the cython c-extension
  101. extending_distributions = load("extending_distributions")
  102. from numpy.random import PCG64
  103. values = extending_distributions.uniforms_ex(PCG64(0), 10, 'd')
  104. assert values.shape == (10,)
  105. assert values.dtype == np.float64
  106. @pytest.mark.skipif(numba is None or cffi is None,
  107. reason="requires numba and cffi")
  108. def test_numba():
  109. from numpy.random._examples.numba import extending # noqa: F401
  110. @pytest.mark.skipif(cffi is None, reason="requires cffi")
  111. def test_cffi():
  112. from numpy.random._examples.cffi import extending # noqa: F401