Adding all project files
This commit is contained in:
parent
6c9e127bdc
commit
cd4316ad0f
42289 changed files with 8009643 additions and 0 deletions
0
venv/Lib/site-packages/~umpy/distutils/tests/__init__.py
Normal file
0
venv/Lib/site-packages/~umpy/distutils/tests/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,74 @@
|
|||
'''Tests for numpy.distutils.build_ext.'''
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from textwrap import indent, dedent
|
||||
import pytest
|
||||
from numpy.testing import IS_WASM
|
||||
|
||||
@pytest.mark.skipif(IS_WASM, reason="cannot start subprocess in wasm")
|
||||
@pytest.mark.slow
|
||||
def test_multi_fortran_libs_link(tmp_path):
|
||||
'''
|
||||
Ensures multiple "fake" static libraries are correctly linked.
|
||||
see gh-18295
|
||||
'''
|
||||
|
||||
# We need to make sure we actually have an f77 compiler.
|
||||
# This is nontrivial, so we'll borrow the utilities
|
||||
# from f2py tests:
|
||||
from numpy.distutils.tests.utilities import has_f77_compiler
|
||||
if not has_f77_compiler():
|
||||
pytest.skip('No F77 compiler found')
|
||||
|
||||
# make some dummy sources
|
||||
with open(tmp_path / '_dummy1.f', 'w') as fid:
|
||||
fid.write(indent(dedent('''\
|
||||
FUNCTION dummy_one()
|
||||
RETURN
|
||||
END FUNCTION'''), prefix=' '*6))
|
||||
with open(tmp_path / '_dummy2.f', 'w') as fid:
|
||||
fid.write(indent(dedent('''\
|
||||
FUNCTION dummy_two()
|
||||
RETURN
|
||||
END FUNCTION'''), prefix=' '*6))
|
||||
with open(tmp_path / '_dummy.c', 'w') as fid:
|
||||
# doesn't need to load - just needs to exist
|
||||
fid.write('int PyInit_dummyext;')
|
||||
|
||||
# make a setup file
|
||||
with open(tmp_path / 'setup.py', 'w') as fid:
|
||||
srctree = os.path.join(os.path.dirname(__file__), '..', '..', '..')
|
||||
fid.write(dedent(f'''\
|
||||
def configuration(parent_package="", top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
config = Configuration("", parent_package, top_path)
|
||||
config.add_library("dummy1", sources=["_dummy1.f"])
|
||||
config.add_library("dummy2", sources=["_dummy2.f"])
|
||||
config.add_extension("dummyext", sources=["_dummy.c"], libraries=["dummy1", "dummy2"])
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.path.insert(0, r"{srctree}")
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path="").todict())'''))
|
||||
|
||||
# build the test extensino and "install" into a temporary directory
|
||||
build_dir = tmp_path
|
||||
subprocess.check_call([sys.executable, 'setup.py', 'build', 'install',
|
||||
'--prefix', str(tmp_path / 'installdir'),
|
||||
'--record', str(tmp_path / 'tmp_install_log.txt'),
|
||||
],
|
||||
cwd=str(build_dir),
|
||||
)
|
||||
# get the path to the so
|
||||
so = None
|
||||
with open(tmp_path /'tmp_install_log.txt') as fid:
|
||||
for line in fid:
|
||||
if 'dummyext' in line:
|
||||
so = line.strip()
|
||||
break
|
||||
assert so is not None
|
|
@ -0,0 +1,808 @@
|
|||
import re, textwrap, os
|
||||
from os import sys, path
|
||||
from distutils.errors import DistutilsError
|
||||
|
||||
is_standalone = __name__ == '__main__' and __package__ is None
|
||||
if is_standalone:
|
||||
import unittest, contextlib, tempfile, shutil
|
||||
sys.path.append(path.abspath(path.join(path.dirname(__file__), "..")))
|
||||
from ccompiler_opt import CCompilerOpt
|
||||
|
||||
# from numpy/testing/_private/utils.py
|
||||
@contextlib.contextmanager
|
||||
def tempdir(*args, **kwargs):
|
||||
tmpdir = tempfile.mkdtemp(*args, **kwargs)
|
||||
try:
|
||||
yield tmpdir
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
def assert_(expr, msg=''):
|
||||
if not expr:
|
||||
raise AssertionError(msg)
|
||||
else:
|
||||
from numpy.distutils.ccompiler_opt import CCompilerOpt
|
||||
from numpy.testing import assert_, tempdir
|
||||
|
||||
# architectures and compilers to test
|
||||
arch_compilers = dict(
|
||||
x86 = ("gcc", "clang", "icc", "iccw", "msvc"),
|
||||
x64 = ("gcc", "clang", "icc", "iccw", "msvc"),
|
||||
ppc64 = ("gcc", "clang"),
|
||||
ppc64le = ("gcc", "clang"),
|
||||
armhf = ("gcc", "clang"),
|
||||
aarch64 = ("gcc", "clang", "fcc"),
|
||||
s390x = ("gcc", "clang"),
|
||||
noarch = ("gcc",)
|
||||
)
|
||||
|
||||
class FakeCCompilerOpt(CCompilerOpt):
|
||||
fake_info = ""
|
||||
def __init__(self, trap_files="", trap_flags="", *args, **kwargs):
|
||||
self.fake_trap_files = trap_files
|
||||
self.fake_trap_flags = trap_flags
|
||||
CCompilerOpt.__init__(self, None, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return textwrap.dedent("""\
|
||||
<<<<
|
||||
march : {}
|
||||
compiler : {}
|
||||
----------------
|
||||
{}
|
||||
>>>>
|
||||
""").format(self.cc_march, self.cc_name, self.report())
|
||||
|
||||
def dist_compile(self, sources, flags, **kwargs):
|
||||
assert(isinstance(sources, list))
|
||||
assert(isinstance(flags, list))
|
||||
if self.fake_trap_files:
|
||||
for src in sources:
|
||||
if re.match(self.fake_trap_files, src):
|
||||
self.dist_error("source is trapped by a fake interface")
|
||||
if self.fake_trap_flags:
|
||||
for f in flags:
|
||||
if re.match(self.fake_trap_flags, f):
|
||||
self.dist_error("flag is trapped by a fake interface")
|
||||
# fake objects
|
||||
return zip(sources, [' '.join(flags)] * len(sources))
|
||||
|
||||
def dist_info(self):
|
||||
return FakeCCompilerOpt.fake_info
|
||||
|
||||
@staticmethod
|
||||
def dist_log(*args, stderr=False):
|
||||
pass
|
||||
|
||||
class _Test_CCompilerOpt:
|
||||
arch = None # x86_64
|
||||
cc = None # gcc
|
||||
|
||||
def setup_class(self):
|
||||
FakeCCompilerOpt.conf_nocache = True
|
||||
self._opt = None
|
||||
|
||||
def nopt(self, *args, **kwargs):
|
||||
FakeCCompilerOpt.fake_info = (self.arch, self.cc, "")
|
||||
return FakeCCompilerOpt(*args, **kwargs)
|
||||
|
||||
def opt(self):
|
||||
if not self._opt:
|
||||
self._opt = self.nopt()
|
||||
return self._opt
|
||||
|
||||
def march(self):
|
||||
return self.opt().cc_march
|
||||
|
||||
def cc_name(self):
|
||||
return self.opt().cc_name
|
||||
|
||||
def get_targets(self, targets, groups, **kwargs):
|
||||
FakeCCompilerOpt.conf_target_groups = groups
|
||||
opt = self.nopt(
|
||||
cpu_baseline=kwargs.get("baseline", "min"),
|
||||
cpu_dispatch=kwargs.get("dispatch", "max"),
|
||||
trap_files=kwargs.get("trap_files", ""),
|
||||
trap_flags=kwargs.get("trap_flags", "")
|
||||
)
|
||||
with tempdir() as tmpdir:
|
||||
file = os.path.join(tmpdir, "test_targets.c")
|
||||
with open(file, 'w') as f:
|
||||
f.write(targets)
|
||||
gtargets = []
|
||||
gflags = {}
|
||||
fake_objects = opt.try_dispatch([file])
|
||||
for source, flags in fake_objects:
|
||||
gtar = path.basename(source).split('.')[1:-1]
|
||||
glen = len(gtar)
|
||||
if glen == 0:
|
||||
gtar = "baseline"
|
||||
elif glen == 1:
|
||||
gtar = gtar[0].upper()
|
||||
else:
|
||||
# converting multi-target into parentheses str format to be equivalent
|
||||
# to the configuration statements syntax.
|
||||
gtar = ('('+' '.join(gtar)+')').upper()
|
||||
gtargets.append(gtar)
|
||||
gflags[gtar] = flags
|
||||
|
||||
has_baseline, targets = opt.sources_status[file]
|
||||
targets = targets + ["baseline"] if has_baseline else targets
|
||||
# convert tuple that represent multi-target into parentheses str format
|
||||
targets = [
|
||||
'('+' '.join(tar)+')' if isinstance(tar, tuple) else tar
|
||||
for tar in targets
|
||||
]
|
||||
if len(targets) != len(gtargets) or not all(t in gtargets for t in targets):
|
||||
raise AssertionError(
|
||||
"'sources_status' returns different targets than the compiled targets\n"
|
||||
"%s != %s" % (targets, gtargets)
|
||||
)
|
||||
# return targets from 'sources_status' since the order is matters
|
||||
return targets, gflags
|
||||
|
||||
def arg_regex(self, **kwargs):
|
||||
map2origin = dict(
|
||||
x64 = "x86",
|
||||
ppc64le = "ppc64",
|
||||
aarch64 = "armhf",
|
||||
clang = "gcc",
|
||||
)
|
||||
march = self.march(); cc_name = self.cc_name()
|
||||
map_march = map2origin.get(march, march)
|
||||
map_cc = map2origin.get(cc_name, cc_name)
|
||||
for key in (
|
||||
march, cc_name, map_march, map_cc,
|
||||
march + '_' + cc_name,
|
||||
map_march + '_' + cc_name,
|
||||
march + '_' + map_cc,
|
||||
map_march + '_' + map_cc,
|
||||
) :
|
||||
regex = kwargs.pop(key, None)
|
||||
if regex is not None:
|
||||
break
|
||||
if regex:
|
||||
if isinstance(regex, dict):
|
||||
for k, v in regex.items():
|
||||
if v[-1:] not in ')}$?\\.+*':
|
||||
regex[k] = v + '$'
|
||||
else:
|
||||
assert(isinstance(regex, str))
|
||||
if regex[-1:] not in ')}$?\\.+*':
|
||||
regex += '$'
|
||||
return regex
|
||||
|
||||
def expect(self, dispatch, baseline="", **kwargs):
|
||||
match = self.arg_regex(**kwargs)
|
||||
if match is None:
|
||||
return
|
||||
opt = self.nopt(
|
||||
cpu_baseline=baseline, cpu_dispatch=dispatch,
|
||||
trap_files=kwargs.get("trap_files", ""),
|
||||
trap_flags=kwargs.get("trap_flags", "")
|
||||
)
|
||||
features = ' '.join(opt.cpu_dispatch_names())
|
||||
if not match:
|
||||
if len(features) != 0:
|
||||
raise AssertionError(
|
||||
'expected empty features, not "%s"' % features
|
||||
)
|
||||
return
|
||||
if not re.match(match, features, re.IGNORECASE):
|
||||
raise AssertionError(
|
||||
'dispatch features "%s" not match "%s"' % (features, match)
|
||||
)
|
||||
|
||||
def expect_baseline(self, baseline, dispatch="", **kwargs):
|
||||
match = self.arg_regex(**kwargs)
|
||||
if match is None:
|
||||
return
|
||||
opt = self.nopt(
|
||||
cpu_baseline=baseline, cpu_dispatch=dispatch,
|
||||
trap_files=kwargs.get("trap_files", ""),
|
||||
trap_flags=kwargs.get("trap_flags", "")
|
||||
)
|
||||
features = ' '.join(opt.cpu_baseline_names())
|
||||
if not match:
|
||||
if len(features) != 0:
|
||||
raise AssertionError(
|
||||
'expected empty features, not "%s"' % features
|
||||
)
|
||||
return
|
||||
if not re.match(match, features, re.IGNORECASE):
|
||||
raise AssertionError(
|
||||
'baseline features "%s" not match "%s"' % (features, match)
|
||||
)
|
||||
|
||||
def expect_flags(self, baseline, dispatch="", **kwargs):
|
||||
match = self.arg_regex(**kwargs)
|
||||
if match is None:
|
||||
return
|
||||
opt = self.nopt(
|
||||
cpu_baseline=baseline, cpu_dispatch=dispatch,
|
||||
trap_files=kwargs.get("trap_files", ""),
|
||||
trap_flags=kwargs.get("trap_flags", "")
|
||||
)
|
||||
flags = ' '.join(opt.cpu_baseline_flags())
|
||||
if not match:
|
||||
if len(flags) != 0:
|
||||
raise AssertionError(
|
||||
'expected empty flags not "%s"' % flags
|
||||
)
|
||||
return
|
||||
if not re.match(match, flags):
|
||||
raise AssertionError(
|
||||
'flags "%s" not match "%s"' % (flags, match)
|
||||
)
|
||||
|
||||
def expect_targets(self, targets, groups={}, **kwargs):
|
||||
match = self.arg_regex(**kwargs)
|
||||
if match is None:
|
||||
return
|
||||
targets, _ = self.get_targets(targets=targets, groups=groups, **kwargs)
|
||||
targets = ' '.join(targets)
|
||||
if not match:
|
||||
if len(targets) != 0:
|
||||
raise AssertionError(
|
||||
'expected empty targets, not "%s"' % targets
|
||||
)
|
||||
return
|
||||
if not re.match(match, targets, re.IGNORECASE):
|
||||
raise AssertionError(
|
||||
'targets "%s" not match "%s"' % (targets, match)
|
||||
)
|
||||
|
||||
def expect_target_flags(self, targets, groups={}, **kwargs):
|
||||
match_dict = self.arg_regex(**kwargs)
|
||||
if match_dict is None:
|
||||
return
|
||||
assert(isinstance(match_dict, dict))
|
||||
_, tar_flags = self.get_targets(targets=targets, groups=groups)
|
||||
|
||||
for match_tar, match_flags in match_dict.items():
|
||||
if match_tar not in tar_flags:
|
||||
raise AssertionError(
|
||||
'expected to find target "%s"' % match_tar
|
||||
)
|
||||
flags = tar_flags[match_tar]
|
||||
if not match_flags:
|
||||
if len(flags) != 0:
|
||||
raise AssertionError(
|
||||
'expected to find empty flags in target "%s"' % match_tar
|
||||
)
|
||||
if not re.match(match_flags, flags):
|
||||
raise AssertionError(
|
||||
'"%s" flags "%s" not match "%s"' % (match_tar, flags, match_flags)
|
||||
)
|
||||
|
||||
def test_interface(self):
|
||||
wrong_arch = "ppc64" if self.arch != "ppc64" else "x86"
|
||||
wrong_cc = "clang" if self.cc != "clang" else "icc"
|
||||
opt = self.opt()
|
||||
assert_(getattr(opt, "cc_on_" + self.arch))
|
||||
assert_(not getattr(opt, "cc_on_" + wrong_arch))
|
||||
assert_(getattr(opt, "cc_is_" + self.cc))
|
||||
assert_(not getattr(opt, "cc_is_" + wrong_cc))
|
||||
|
||||
def test_args_empty(self):
|
||||
for baseline, dispatch in (
|
||||
("", "none"),
|
||||
(None, ""),
|
||||
("none +none", "none - none"),
|
||||
("none -max", "min - max"),
|
||||
("+vsx2 -VSX2", "vsx avx2 avx512f -max"),
|
||||
("max -vsx - avx + avx512f neon -MAX ",
|
||||
"min -min + max -max -vsx + avx2 -avx2 +NONE")
|
||||
) :
|
||||
opt = self.nopt(cpu_baseline=baseline, cpu_dispatch=dispatch)
|
||||
assert(len(opt.cpu_baseline_names()) == 0)
|
||||
assert(len(opt.cpu_dispatch_names()) == 0)
|
||||
|
||||
def test_args_validation(self):
|
||||
if self.march() == "unknown":
|
||||
return
|
||||
# check sanity of argument's validation
|
||||
for baseline, dispatch in (
|
||||
("unkown_feature - max +min", "unknown max min"), # unknowing features
|
||||
("#avx2", "$vsx") # groups and polices aren't acceptable
|
||||
) :
|
||||
try:
|
||||
self.nopt(cpu_baseline=baseline, cpu_dispatch=dispatch)
|
||||
raise AssertionError("excepted an exception for invalid arguments")
|
||||
except DistutilsError:
|
||||
pass
|
||||
|
||||
def test_skip(self):
|
||||
# only takes what platform supports and skip the others
|
||||
# without casing exceptions
|
||||
self.expect(
|
||||
"sse vsx neon",
|
||||
x86="sse", ppc64="vsx", armhf="neon", unknown=""
|
||||
)
|
||||
self.expect(
|
||||
"sse41 avx avx2 vsx2 vsx3 neon_vfpv4 asimd",
|
||||
x86 = "sse41 avx avx2",
|
||||
ppc64 = "vsx2 vsx3",
|
||||
armhf = "neon_vfpv4 asimd",
|
||||
unknown = ""
|
||||
)
|
||||
# any features in cpu_dispatch must be ignored if it's part of baseline
|
||||
self.expect(
|
||||
"sse neon vsx", baseline="sse neon vsx",
|
||||
x86="", ppc64="", armhf=""
|
||||
)
|
||||
self.expect(
|
||||
"avx2 vsx3 asimdhp", baseline="avx2 vsx3 asimdhp",
|
||||
x86="", ppc64="", armhf=""
|
||||
)
|
||||
|
||||
def test_implies(self):
|
||||
# baseline combining implied features, so we count
|
||||
# on it instead of testing 'feature_implies()'' directly
|
||||
self.expect_baseline(
|
||||
"fma3 avx2 asimd vsx3",
|
||||
# .* between two spaces can validate features in between
|
||||
x86 = "sse .* sse41 .* fma3.*avx2",
|
||||
ppc64 = "vsx vsx2 vsx3",
|
||||
armhf = "neon neon_fp16 neon_vfpv4 asimd"
|
||||
)
|
||||
"""
|
||||
special cases
|
||||
"""
|
||||
# in icc and msvc, FMA3 and AVX2 can't be separated
|
||||
# both need to implies each other, same for avx512f & cd
|
||||
for f0, f1 in (
|
||||
("fma3", "avx2"),
|
||||
("avx512f", "avx512cd"),
|
||||
):
|
||||
diff = ".* sse42 .* %s .*%s$" % (f0, f1)
|
||||
self.expect_baseline(f0,
|
||||
x86_gcc=".* sse42 .* %s$" % f0,
|
||||
x86_icc=diff, x86_iccw=diff
|
||||
)
|
||||
self.expect_baseline(f1,
|
||||
x86_gcc=".* avx .* %s$" % f1,
|
||||
x86_icc=diff, x86_iccw=diff
|
||||
)
|
||||
# in msvc, following features can't be separated too
|
||||
for f in (("fma3", "avx2"), ("avx512f", "avx512cd", "avx512_skx")):
|
||||
for ff in f:
|
||||
self.expect_baseline(ff,
|
||||
x86_msvc=".*%s" % ' '.join(f)
|
||||
)
|
||||
|
||||
# in ppc64le VSX and VSX2 can't be separated
|
||||
self.expect_baseline("vsx", ppc64le="vsx vsx2")
|
||||
# in aarch64 following features can't be separated
|
||||
for f in ("neon", "neon_fp16", "neon_vfpv4", "asimd"):
|
||||
self.expect_baseline(f, aarch64="neon neon_fp16 neon_vfpv4 asimd")
|
||||
|
||||
def test_args_options(self):
|
||||
# max & native
|
||||
for o in ("max", "native"):
|
||||
if o == "native" and self.cc_name() == "msvc":
|
||||
continue
|
||||
self.expect(o,
|
||||
trap_files=".*cpu_(sse|vsx|neon|vx).c",
|
||||
x86="", ppc64="", armhf="", s390x=""
|
||||
)
|
||||
self.expect(o,
|
||||
trap_files=".*cpu_(sse3|vsx2|neon_vfpv4|vxe).c",
|
||||
x86="sse sse2", ppc64="vsx", armhf="neon neon_fp16",
|
||||
aarch64="", ppc64le="", s390x="vx"
|
||||
)
|
||||
self.expect(o,
|
||||
trap_files=".*cpu_(popcnt|vsx3).c",
|
||||
x86="sse .* sse41", ppc64="vsx vsx2",
|
||||
armhf="neon neon_fp16 .* asimd .*",
|
||||
s390x="vx vxe vxe2"
|
||||
)
|
||||
self.expect(o,
|
||||
x86_gcc=".* xop fma4 .* avx512f .* avx512_knl avx512_knm avx512_skx .*",
|
||||
# in icc, xop and fam4 aren't supported
|
||||
x86_icc=".* avx512f .* avx512_knl avx512_knm avx512_skx .*",
|
||||
x86_iccw=".* avx512f .* avx512_knl avx512_knm avx512_skx .*",
|
||||
# in msvc, avx512_knl avx512_knm aren't supported
|
||||
x86_msvc=".* xop fma4 .* avx512f .* avx512_skx .*",
|
||||
armhf=".* asimd asimdhp asimddp .*",
|
||||
ppc64="vsx vsx2 vsx3 vsx4.*",
|
||||
s390x="vx vxe vxe2.*"
|
||||
)
|
||||
# min
|
||||
self.expect("min",
|
||||
x86="sse sse2", x64="sse sse2 sse3",
|
||||
armhf="", aarch64="neon neon_fp16 .* asimd",
|
||||
ppc64="", ppc64le="vsx vsx2", s390x=""
|
||||
)
|
||||
self.expect(
|
||||
"min", trap_files=".*cpu_(sse2|vsx2).c",
|
||||
x86="", ppc64le=""
|
||||
)
|
||||
# an exception must triggered if native flag isn't supported
|
||||
# when option "native" is activated through the args
|
||||
try:
|
||||
self.expect("native",
|
||||
trap_flags=".*(-march=native|-xHost|/QxHost|-mcpu=a64fx).*",
|
||||
x86=".*", ppc64=".*", armhf=".*", s390x=".*", aarch64=".*",
|
||||
)
|
||||
if self.march() != "unknown":
|
||||
raise AssertionError(
|
||||
"excepted an exception for %s" % self.march()
|
||||
)
|
||||
except DistutilsError:
|
||||
if self.march() == "unknown":
|
||||
raise AssertionError("excepted no exceptions")
|
||||
|
||||
def test_flags(self):
|
||||
self.expect_flags(
|
||||
"sse sse2 vsx vsx2 neon neon_fp16 vx vxe",
|
||||
x86_gcc="-msse -msse2", x86_icc="-msse -msse2",
|
||||
x86_iccw="/arch:SSE2",
|
||||
x86_msvc="/arch:SSE2" if self.march() == "x86" else "",
|
||||
ppc64_gcc= "-mcpu=power8",
|
||||
ppc64_clang="-mcpu=power8",
|
||||
armhf_gcc="-mfpu=neon-fp16 -mfp16-format=ieee",
|
||||
aarch64="",
|
||||
s390x="-mzvector -march=arch12"
|
||||
)
|
||||
# testing normalize -march
|
||||
self.expect_flags(
|
||||
"asimd",
|
||||
aarch64="",
|
||||
armhf_gcc=r"-mfp16-format=ieee -mfpu=neon-fp-armv8 -march=armv8-a\+simd"
|
||||
)
|
||||
self.expect_flags(
|
||||
"asimdhp",
|
||||
aarch64_gcc=r"-march=armv8.2-a\+fp16",
|
||||
armhf_gcc=r"-mfp16-format=ieee -mfpu=neon-fp-armv8 -march=armv8.2-a\+fp16"
|
||||
)
|
||||
self.expect_flags(
|
||||
"asimddp", aarch64_gcc=r"-march=armv8.2-a\+dotprod"
|
||||
)
|
||||
self.expect_flags(
|
||||
# asimdfhm implies asimdhp
|
||||
"asimdfhm", aarch64_gcc=r"-march=armv8.2-a\+fp16\+fp16fml"
|
||||
)
|
||||
self.expect_flags(
|
||||
"asimddp asimdhp asimdfhm",
|
||||
aarch64_gcc=r"-march=armv8.2-a\+dotprod\+fp16\+fp16fml"
|
||||
)
|
||||
self.expect_flags(
|
||||
"vx vxe vxe2",
|
||||
s390x=r"-mzvector -march=arch13"
|
||||
)
|
||||
|
||||
def test_targets_exceptions(self):
|
||||
for targets in (
|
||||
"bla bla", "/*@targets",
|
||||
"/*@targets */",
|
||||
"/*@targets unknown */",
|
||||
"/*@targets $unknown_policy avx2 */",
|
||||
"/*@targets #unknown_group avx2 */",
|
||||
"/*@targets $ */",
|
||||
"/*@targets # vsx */",
|
||||
"/*@targets #$ vsx */",
|
||||
"/*@targets vsx avx2 ) */",
|
||||
"/*@targets vsx avx2 (avx2 */",
|
||||
"/*@targets vsx avx2 () */",
|
||||
"/*@targets vsx avx2 ($autovec) */", # no features
|
||||
"/*@targets vsx avx2 (xxx) */",
|
||||
"/*@targets vsx avx2 (baseline) */",
|
||||
) :
|
||||
try:
|
||||
self.expect_targets(
|
||||
targets,
|
||||
x86="", armhf="", ppc64="", s390x=""
|
||||
)
|
||||
if self.march() != "unknown":
|
||||
raise AssertionError(
|
||||
"excepted an exception for %s" % self.march()
|
||||
)
|
||||
except DistutilsError:
|
||||
if self.march() == "unknown":
|
||||
raise AssertionError("excepted no exceptions")
|
||||
|
||||
def test_targets_syntax(self):
|
||||
for targets in (
|
||||
"/*@targets $keep_baseline sse vsx neon vx*/",
|
||||
"/*@targets,$keep_baseline,sse,vsx,neon vx*/",
|
||||
"/*@targets*$keep_baseline*sse*vsx*neon*vx*/",
|
||||
"""
|
||||
/*
|
||||
** @targets
|
||||
** $keep_baseline, sse vsx,neon, vx
|
||||
*/
|
||||
""",
|
||||
"""
|
||||
/*
|
||||
************@targets****************
|
||||
** $keep_baseline, sse vsx, neon, vx
|
||||
************************************
|
||||
*/
|
||||
""",
|
||||
"""
|
||||
/*
|
||||
/////////////@targets/////////////////
|
||||
//$keep_baseline//sse//vsx//neon//vx
|
||||
/////////////////////////////////////
|
||||
*/
|
||||
""",
|
||||
"""
|
||||
/*
|
||||
@targets
|
||||
$keep_baseline
|
||||
SSE VSX NEON VX*/
|
||||
"""
|
||||
) :
|
||||
self.expect_targets(targets,
|
||||
x86="sse", ppc64="vsx", armhf="neon", s390x="vx", unknown=""
|
||||
)
|
||||
|
||||
def test_targets(self):
|
||||
# test skipping baseline features
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
sse sse2 sse41 avx avx2 avx512f
|
||||
vsx vsx2 vsx3 vsx4
|
||||
neon neon_fp16 asimdhp asimddp
|
||||
vx vxe vxe2
|
||||
*/
|
||||
""",
|
||||
baseline="avx vsx2 asimd vx vxe",
|
||||
x86="avx512f avx2", armhf="asimddp asimdhp", ppc64="vsx4 vsx3",
|
||||
s390x="vxe2"
|
||||
)
|
||||
# test skipping non-dispatch features
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
sse41 avx avx2 avx512f
|
||||
vsx2 vsx3 vsx4
|
||||
asimd asimdhp asimddp
|
||||
vx vxe vxe2
|
||||
*/
|
||||
""",
|
||||
baseline="", dispatch="sse41 avx2 vsx2 asimd asimddp vxe2",
|
||||
x86="avx2 sse41", armhf="asimddp asimd", ppc64="vsx2", s390x="vxe2"
|
||||
)
|
||||
# test skipping features that not supported
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
sse2 sse41 avx2 avx512f
|
||||
vsx2 vsx3 vsx4
|
||||
neon asimdhp asimddp
|
||||
vx vxe vxe2
|
||||
*/
|
||||
""",
|
||||
baseline="",
|
||||
trap_files=".*(avx2|avx512f|vsx3|vsx4|asimddp|vxe2).c",
|
||||
x86="sse41 sse2", ppc64="vsx2", armhf="asimdhp neon",
|
||||
s390x="vxe vx"
|
||||
)
|
||||
# test skipping features that implies each other
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
sse sse2 avx fma3 avx2 avx512f avx512cd
|
||||
vsx vsx2 vsx3
|
||||
neon neon_vfpv4 neon_fp16 neon_fp16 asimd asimdhp
|
||||
asimddp asimdfhm
|
||||
*/
|
||||
""",
|
||||
baseline="",
|
||||
x86_gcc="avx512cd avx512f avx2 fma3 avx sse2",
|
||||
x86_msvc="avx512cd avx2 avx sse2",
|
||||
x86_icc="avx512cd avx2 avx sse2",
|
||||
x86_iccw="avx512cd avx2 avx sse2",
|
||||
ppc64="vsx3 vsx2 vsx",
|
||||
ppc64le="vsx3 vsx2",
|
||||
armhf="asimdfhm asimddp asimdhp asimd neon_vfpv4 neon_fp16 neon",
|
||||
aarch64="asimdfhm asimddp asimdhp asimd"
|
||||
)
|
||||
|
||||
def test_targets_policies(self):
|
||||
# 'keep_baseline', generate objects for baseline features
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
$keep_baseline
|
||||
sse2 sse42 avx2 avx512f
|
||||
vsx2 vsx3
|
||||
neon neon_vfpv4 asimd asimddp
|
||||
vx vxe vxe2
|
||||
*/
|
||||
""",
|
||||
baseline="sse41 avx2 vsx2 asimd vsx3 vxe",
|
||||
x86="avx512f avx2 sse42 sse2",
|
||||
ppc64="vsx3 vsx2",
|
||||
armhf="asimddp asimd neon_vfpv4 neon",
|
||||
# neon, neon_vfpv4, asimd implies each other
|
||||
aarch64="asimddp asimd",
|
||||
s390x="vxe2 vxe vx"
|
||||
)
|
||||
# 'keep_sort', leave the sort as-is
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
$keep_baseline $keep_sort
|
||||
avx512f sse42 avx2 sse2
|
||||
vsx2 vsx3
|
||||
asimd neon neon_vfpv4 asimddp
|
||||
vxe vxe2
|
||||
*/
|
||||
""",
|
||||
x86="avx512f sse42 avx2 sse2",
|
||||
ppc64="vsx2 vsx3",
|
||||
armhf="asimd neon neon_vfpv4 asimddp",
|
||||
# neon, neon_vfpv4, asimd implies each other
|
||||
aarch64="asimd asimddp",
|
||||
s390x="vxe vxe2"
|
||||
)
|
||||
# 'autovec', skipping features that can't be
|
||||
# vectorized by the compiler
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
$keep_baseline $keep_sort $autovec
|
||||
avx512f avx2 sse42 sse41 sse2
|
||||
vsx3 vsx2
|
||||
asimddp asimd neon_vfpv4 neon
|
||||
*/
|
||||
""",
|
||||
x86_gcc="avx512f avx2 sse42 sse41 sse2",
|
||||
x86_icc="avx512f avx2 sse42 sse41 sse2",
|
||||
x86_iccw="avx512f avx2 sse42 sse41 sse2",
|
||||
x86_msvc="avx512f avx2 sse2"
|
||||
if self.march() == 'x86' else "avx512f avx2",
|
||||
ppc64="vsx3 vsx2",
|
||||
armhf="asimddp asimd neon_vfpv4 neon",
|
||||
# neon, neon_vfpv4, asimd implies each other
|
||||
aarch64="asimddp asimd"
|
||||
)
|
||||
for policy in ("$maxopt", "$autovec"):
|
||||
# 'maxopt' and autovec set the max acceptable optimization flags
|
||||
self.expect_target_flags(
|
||||
"/*@targets baseline %s */" % policy,
|
||||
gcc={"baseline":".*-O3.*"}, icc={"baseline":".*-O3.*"},
|
||||
iccw={"baseline":".*/O3.*"}, msvc={"baseline":".*/O2.*"},
|
||||
unknown={"baseline":".*"}
|
||||
)
|
||||
|
||||
# 'werror', force compilers to treat warnings as errors
|
||||
self.expect_target_flags(
|
||||
"/*@targets baseline $werror */",
|
||||
gcc={"baseline":".*-Werror.*"}, icc={"baseline":".*-Werror.*"},
|
||||
iccw={"baseline":".*/Werror.*"}, msvc={"baseline":".*/WX.*"},
|
||||
unknown={"baseline":".*"}
|
||||
)
|
||||
|
||||
def test_targets_groups(self):
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets $keep_baseline baseline #test_group */
|
||||
""",
|
||||
groups=dict(
|
||||
test_group=("""
|
||||
$keep_baseline
|
||||
asimddp sse2 vsx2 avx2 vsx3
|
||||
avx512f asimdhp
|
||||
""")
|
||||
),
|
||||
x86="avx512f avx2 sse2 baseline",
|
||||
ppc64="vsx3 vsx2 baseline",
|
||||
armhf="asimddp asimdhp baseline"
|
||||
)
|
||||
# test skip duplicating and sorting
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
* sse42 avx avx512f
|
||||
* #test_group_1
|
||||
* vsx2
|
||||
* #test_group_2
|
||||
* asimddp asimdfhm
|
||||
*/
|
||||
""",
|
||||
groups=dict(
|
||||
test_group_1=("""
|
||||
VSX2 vsx3 asimd avx2 SSE41
|
||||
"""),
|
||||
test_group_2=("""
|
||||
vsx2 vsx3 asImd aVx2 sse41
|
||||
""")
|
||||
),
|
||||
x86="avx512f avx2 avx sse42 sse41",
|
||||
ppc64="vsx3 vsx2",
|
||||
# vsx2 part of the default baseline of ppc64le, option ("min")
|
||||
ppc64le="vsx3",
|
||||
armhf="asimdfhm asimddp asimd",
|
||||
# asimd part of the default baseline of aarch64, option ("min")
|
||||
aarch64="asimdfhm asimddp"
|
||||
)
|
||||
|
||||
def test_targets_multi(self):
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
(avx512_clx avx512_cnl) (asimdhp asimddp)
|
||||
*/
|
||||
""",
|
||||
x86=r"\(avx512_clx avx512_cnl\)",
|
||||
armhf=r"\(asimdhp asimddp\)",
|
||||
)
|
||||
# test skipping implied features and auto-sort
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets
|
||||
f16c (sse41 avx sse42) (sse3 avx2 avx512f)
|
||||
vsx2 (vsx vsx3 vsx2)
|
||||
(neon neon_vfpv4 asimd asimdhp asimddp)
|
||||
*/
|
||||
""",
|
||||
x86="avx512f f16c avx",
|
||||
ppc64="vsx3 vsx2",
|
||||
ppc64le="vsx3", # vsx2 part of baseline
|
||||
armhf=r"\(asimdhp asimddp\)",
|
||||
)
|
||||
# test skipping implied features and keep sort
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets $keep_sort
|
||||
(sse41 avx sse42) (sse3 avx2 avx512f)
|
||||
(vsx vsx3 vsx2)
|
||||
(asimddp neon neon_vfpv4 asimd asimdhp)
|
||||
(vx vxe vxe2)
|
||||
*/
|
||||
""",
|
||||
x86="avx avx512f",
|
||||
ppc64="vsx3",
|
||||
armhf=r"\(asimdhp asimddp\)",
|
||||
s390x="vxe2"
|
||||
)
|
||||
# test compiler variety and avoiding duplicating
|
||||
self.expect_targets(
|
||||
"""
|
||||
/*@targets $keep_sort
|
||||
fma3 avx2 (fma3 avx2) (avx2 fma3) avx2 fma3
|
||||
*/
|
||||
""",
|
||||
x86_gcc=r"fma3 avx2 \(fma3 avx2\)",
|
||||
x86_icc="avx2", x86_iccw="avx2",
|
||||
x86_msvc="avx2"
|
||||
)
|
||||
|
||||
def new_test(arch, cc):
|
||||
if is_standalone: return textwrap.dedent("""\
|
||||
class TestCCompilerOpt_{class_name}(_Test_CCompilerOpt, unittest.TestCase):
|
||||
arch = '{arch}'
|
||||
cc = '{cc}'
|
||||
def __init__(self, methodName="runTest"):
|
||||
unittest.TestCase.__init__(self, methodName)
|
||||
self.setup_class()
|
||||
""").format(
|
||||
class_name=arch + '_' + cc, arch=arch, cc=cc
|
||||
)
|
||||
return textwrap.dedent("""\
|
||||
class TestCCompilerOpt_{class_name}(_Test_CCompilerOpt):
|
||||
arch = '{arch}'
|
||||
cc = '{cc}'
|
||||
""").format(
|
||||
class_name=arch + '_' + cc, arch=arch, cc=cc
|
||||
)
|
||||
"""
|
||||
if 1 and is_standalone:
|
||||
FakeCCompilerOpt.fake_info = "x86_icc"
|
||||
cco = FakeCCompilerOpt(None, cpu_baseline="avx2")
|
||||
print(' '.join(cco.cpu_baseline_names()))
|
||||
print(cco.cpu_baseline_flags())
|
||||
unittest.main()
|
||||
sys.exit()
|
||||
"""
|
||||
for arch, compilers in arch_compilers.items():
|
||||
for cc in compilers:
|
||||
exec(new_test(arch, cc))
|
||||
|
||||
if is_standalone:
|
||||
unittest.main()
|
|
@ -0,0 +1,176 @@
|
|||
import unittest
|
||||
from os import sys, path
|
||||
|
||||
is_standalone = __name__ == '__main__' and __package__ is None
|
||||
if is_standalone:
|
||||
sys.path.append(path.abspath(path.join(path.dirname(__file__), "..")))
|
||||
from ccompiler_opt import CCompilerOpt
|
||||
else:
|
||||
from numpy.distutils.ccompiler_opt import CCompilerOpt
|
||||
|
||||
arch_compilers = dict(
|
||||
x86 = ("gcc", "clang", "icc", "iccw", "msvc"),
|
||||
x64 = ("gcc", "clang", "icc", "iccw", "msvc"),
|
||||
ppc64 = ("gcc", "clang"),
|
||||
ppc64le = ("gcc", "clang"),
|
||||
armhf = ("gcc", "clang"),
|
||||
aarch64 = ("gcc", "clang"),
|
||||
narch = ("gcc",)
|
||||
)
|
||||
|
||||
class FakeCCompilerOpt(CCompilerOpt):
|
||||
fake_info = ("arch", "compiler", "extra_args")
|
||||
def __init__(self, *args, **kwargs):
|
||||
CCompilerOpt.__init__(self, None, **kwargs)
|
||||
def dist_compile(self, sources, flags, **kwargs):
|
||||
return sources
|
||||
def dist_info(self):
|
||||
return FakeCCompilerOpt.fake_info
|
||||
@staticmethod
|
||||
def dist_log(*args, stderr=False):
|
||||
pass
|
||||
|
||||
class _TestConfFeatures(FakeCCompilerOpt):
|
||||
"""A hook to check the sanity of configured features
|
||||
- before it called by the abstract class '_Feature'
|
||||
"""
|
||||
|
||||
def conf_features_partial(self):
|
||||
conf_all = self.conf_features
|
||||
for feature_name, feature in conf_all.items():
|
||||
self.test_feature(
|
||||
"attribute conf_features",
|
||||
conf_all, feature_name, feature
|
||||
)
|
||||
|
||||
conf_partial = FakeCCompilerOpt.conf_features_partial(self)
|
||||
for feature_name, feature in conf_partial.items():
|
||||
self.test_feature(
|
||||
"conf_features_partial()",
|
||||
conf_partial, feature_name, feature
|
||||
)
|
||||
return conf_partial
|
||||
|
||||
def test_feature(self, log, search_in, feature_name, feature_dict):
|
||||
error_msg = (
|
||||
"during validate '{}' within feature '{}', "
|
||||
"march '{}' and compiler '{}'\n>> "
|
||||
).format(log, feature_name, self.cc_march, self.cc_name)
|
||||
|
||||
if not feature_name.isupper():
|
||||
raise AssertionError(error_msg + "feature name must be in uppercase")
|
||||
|
||||
for option, val in feature_dict.items():
|
||||
self.test_option_types(error_msg, option, val)
|
||||
self.test_duplicates(error_msg, option, val)
|
||||
|
||||
self.test_implies(error_msg, search_in, feature_name, feature_dict)
|
||||
self.test_group(error_msg, search_in, feature_name, feature_dict)
|
||||
self.test_extra_checks(error_msg, search_in, feature_name, feature_dict)
|
||||
|
||||
def test_option_types(self, error_msg, option, val):
|
||||
for tp, available in (
|
||||
((str, list), (
|
||||
"implies", "headers", "flags", "group", "detect", "extra_checks"
|
||||
)),
|
||||
((str,), ("disable",)),
|
||||
((int,), ("interest",)),
|
||||
((bool,), ("implies_detect",)),
|
||||
((bool, type(None)), ("autovec",)),
|
||||
) :
|
||||
found_it = option in available
|
||||
if not found_it:
|
||||
continue
|
||||
if not isinstance(val, tp):
|
||||
error_tp = [t.__name__ for t in (*tp,)]
|
||||
error_tp = ' or '.join(error_tp)
|
||||
raise AssertionError(error_msg +
|
||||
"expected '%s' type for option '%s' not '%s'" % (
|
||||
error_tp, option, type(val).__name__
|
||||
))
|
||||
break
|
||||
|
||||
if not found_it:
|
||||
raise AssertionError(error_msg + "invalid option name '%s'" % option)
|
||||
|
||||
def test_duplicates(self, error_msg, option, val):
|
||||
if option not in (
|
||||
"implies", "headers", "flags", "group", "detect", "extra_checks"
|
||||
) : return
|
||||
|
||||
if isinstance(val, str):
|
||||
val = val.split()
|
||||
|
||||
if len(val) != len(set(val)):
|
||||
raise AssertionError(error_msg + "duplicated values in option '%s'" % option)
|
||||
|
||||
def test_implies(self, error_msg, search_in, feature_name, feature_dict):
|
||||
if feature_dict.get("disabled") is not None:
|
||||
return
|
||||
implies = feature_dict.get("implies", "")
|
||||
if not implies:
|
||||
return
|
||||
if isinstance(implies, str):
|
||||
implies = implies.split()
|
||||
|
||||
if feature_name in implies:
|
||||
raise AssertionError(error_msg + "feature implies itself")
|
||||
|
||||
for impl in implies:
|
||||
impl_dict = search_in.get(impl)
|
||||
if impl_dict is not None:
|
||||
if "disable" in impl_dict:
|
||||
raise AssertionError(error_msg + "implies disabled feature '%s'" % impl)
|
||||
continue
|
||||
raise AssertionError(error_msg + "implies non-exist feature '%s'" % impl)
|
||||
|
||||
def test_group(self, error_msg, search_in, feature_name, feature_dict):
|
||||
if feature_dict.get("disabled") is not None:
|
||||
return
|
||||
group = feature_dict.get("group", "")
|
||||
if not group:
|
||||
return
|
||||
if isinstance(group, str):
|
||||
group = group.split()
|
||||
|
||||
for f in group:
|
||||
impl_dict = search_in.get(f)
|
||||
if not impl_dict or "disable" in impl_dict:
|
||||
continue
|
||||
raise AssertionError(error_msg +
|
||||
"in option 'group', '%s' already exists as a feature name" % f
|
||||
)
|
||||
|
||||
def test_extra_checks(self, error_msg, search_in, feature_name, feature_dict):
|
||||
if feature_dict.get("disabled") is not None:
|
||||
return
|
||||
extra_checks = feature_dict.get("extra_checks", "")
|
||||
if not extra_checks:
|
||||
return
|
||||
if isinstance(extra_checks, str):
|
||||
extra_checks = extra_checks.split()
|
||||
|
||||
for f in extra_checks:
|
||||
impl_dict = search_in.get(f)
|
||||
if not impl_dict or "disable" in impl_dict:
|
||||
continue
|
||||
raise AssertionError(error_msg +
|
||||
"in option 'extra_checks', extra test case '%s' already exists as a feature name" % f
|
||||
)
|
||||
|
||||
class TestConfFeatures(unittest.TestCase):
|
||||
def __init__(self, methodName="runTest"):
|
||||
unittest.TestCase.__init__(self, methodName)
|
||||
self._setup()
|
||||
|
||||
def _setup(self):
|
||||
FakeCCompilerOpt.conf_nocache = True
|
||||
|
||||
def test_features(self):
|
||||
for arch, compilers in arch_compilers.items():
|
||||
for cc in compilers:
|
||||
FakeCCompilerOpt.fake_info = (arch, cc, "")
|
||||
_TestConfFeatures()
|
||||
|
||||
if is_standalone:
|
||||
unittest.main()
|
|
@ -0,0 +1,217 @@
|
|||
import os
|
||||
import pytest
|
||||
import sys
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
from numpy.distutils import exec_command
|
||||
from numpy.distutils.exec_command import get_pythonexe
|
||||
from numpy.testing import tempdir, assert_, assert_warns, IS_WASM
|
||||
|
||||
|
||||
# In python 3 stdout, stderr are text (unicode compliant) devices, so to
|
||||
# emulate them import StringIO from the io module.
|
||||
from io import StringIO
|
||||
|
||||
class redirect_stdout:
|
||||
"""Context manager to redirect stdout for exec_command test."""
|
||||
def __init__(self, stdout=None):
|
||||
self._stdout = stdout or sys.stdout
|
||||
|
||||
def __enter__(self):
|
||||
self.old_stdout = sys.stdout
|
||||
sys.stdout = self._stdout
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self._stdout.flush()
|
||||
sys.stdout = self.old_stdout
|
||||
# note: closing sys.stdout won't close it.
|
||||
self._stdout.close()
|
||||
|
||||
class redirect_stderr:
|
||||
"""Context manager to redirect stderr for exec_command test."""
|
||||
def __init__(self, stderr=None):
|
||||
self._stderr = stderr or sys.stderr
|
||||
|
||||
def __enter__(self):
|
||||
self.old_stderr = sys.stderr
|
||||
sys.stderr = self._stderr
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self._stderr.flush()
|
||||
sys.stderr = self.old_stderr
|
||||
# note: closing sys.stderr won't close it.
|
||||
self._stderr.close()
|
||||
|
||||
class emulate_nonposix:
|
||||
"""Context manager to emulate os.name != 'posix' """
|
||||
def __init__(self, osname='non-posix'):
|
||||
self._new_name = osname
|
||||
|
||||
def __enter__(self):
|
||||
self._old_name = os.name
|
||||
os.name = self._new_name
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
os.name = self._old_name
|
||||
|
||||
|
||||
def test_exec_command_stdout():
|
||||
# Regression test for gh-2999 and gh-2915.
|
||||
# There are several packages (nose, scipy.weave.inline, Sage inline
|
||||
# Fortran) that replace stdout, in which case it doesn't have a fileno
|
||||
# method. This is tested here, with a do-nothing command that fails if the
|
||||
# presence of fileno() is assumed in exec_command.
|
||||
|
||||
# The code has a special case for posix systems, so if we are on posix test
|
||||
# both that the special case works and that the generic code works.
|
||||
|
||||
# Test posix version:
|
||||
with redirect_stdout(StringIO()):
|
||||
with redirect_stderr(TemporaryFile()):
|
||||
with assert_warns(DeprecationWarning):
|
||||
exec_command.exec_command("cd '.'")
|
||||
|
||||
if os.name == 'posix':
|
||||
# Test general (non-posix) version:
|
||||
with emulate_nonposix():
|
||||
with redirect_stdout(StringIO()):
|
||||
with redirect_stderr(TemporaryFile()):
|
||||
with assert_warns(DeprecationWarning):
|
||||
exec_command.exec_command("cd '.'")
|
||||
|
||||
def test_exec_command_stderr():
|
||||
# Test posix version:
|
||||
with redirect_stdout(TemporaryFile(mode='w+')):
|
||||
with redirect_stderr(StringIO()):
|
||||
with assert_warns(DeprecationWarning):
|
||||
exec_command.exec_command("cd '.'")
|
||||
|
||||
if os.name == 'posix':
|
||||
# Test general (non-posix) version:
|
||||
with emulate_nonposix():
|
||||
with redirect_stdout(TemporaryFile()):
|
||||
with redirect_stderr(StringIO()):
|
||||
with assert_warns(DeprecationWarning):
|
||||
exec_command.exec_command("cd '.'")
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
|
||||
class TestExecCommand:
|
||||
def setup_method(self):
|
||||
self.pyexe = get_pythonexe()
|
||||
|
||||
def check_nt(self, **kws):
|
||||
s, o = exec_command.exec_command('cmd /C echo path=%path%')
|
||||
assert_(s == 0)
|
||||
assert_(o != '')
|
||||
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "import sys;sys.stderr.write(sys.platform)"' % self.pyexe)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'win32')
|
||||
|
||||
def check_posix(self, **kws):
|
||||
s, o = exec_command.exec_command("echo Hello", **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Hello')
|
||||
|
||||
s, o = exec_command.exec_command('echo $AAA', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == '')
|
||||
|
||||
s, o = exec_command.exec_command('echo "$AAA"', AAA='Tere', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Tere')
|
||||
|
||||
s, o = exec_command.exec_command('echo "$AAA"', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == '')
|
||||
|
||||
if 'BBB' not in os.environ:
|
||||
os.environ['BBB'] = 'Hi'
|
||||
s, o = exec_command.exec_command('echo "$BBB"', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Hi')
|
||||
|
||||
s, o = exec_command.exec_command('echo "$BBB"', BBB='Hey', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Hey')
|
||||
|
||||
s, o = exec_command.exec_command('echo "$BBB"', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Hi')
|
||||
|
||||
del os.environ['BBB']
|
||||
|
||||
s, o = exec_command.exec_command('echo "$BBB"', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == '')
|
||||
|
||||
|
||||
s, o = exec_command.exec_command('this_is_not_a_command', **kws)
|
||||
assert_(s != 0)
|
||||
assert_(o != '')
|
||||
|
||||
s, o = exec_command.exec_command('echo path=$PATH', **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o != '')
|
||||
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "import sys,os;sys.stderr.write(os.name)"' %
|
||||
self.pyexe, **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'posix')
|
||||
|
||||
def check_basic(self, *kws):
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "raise \'Ignore me.\'"' % self.pyexe, **kws)
|
||||
assert_(s != 0)
|
||||
assert_(o != '')
|
||||
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "import sys;sys.stderr.write(\'0\');'
|
||||
'sys.stderr.write(\'1\');sys.stderr.write(\'2\')"' %
|
||||
self.pyexe, **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == '012')
|
||||
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "import sys;sys.exit(15)"' % self.pyexe, **kws)
|
||||
assert_(s == 15)
|
||||
assert_(o == '')
|
||||
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "print(\'Heipa\'")' % self.pyexe, **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Heipa')
|
||||
|
||||
def check_execute_in(self, **kws):
|
||||
with tempdir() as tmpdir:
|
||||
fn = "file"
|
||||
tmpfile = os.path.join(tmpdir, fn)
|
||||
with open(tmpfile, 'w') as f:
|
||||
f.write('Hello')
|
||||
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "f = open(\'%s\', \'r\'); f.close()"' %
|
||||
(self.pyexe, fn), **kws)
|
||||
assert_(s != 0)
|
||||
assert_(o != '')
|
||||
s, o = exec_command.exec_command(
|
||||
'"%s" -c "f = open(\'%s\', \'r\'); print(f.read()); '
|
||||
'f.close()"' % (self.pyexe, fn), execute_in=tmpdir, **kws)
|
||||
assert_(s == 0)
|
||||
assert_(o == 'Hello')
|
||||
|
||||
def test_basic(self):
|
||||
with redirect_stdout(StringIO()):
|
||||
with redirect_stderr(StringIO()):
|
||||
with assert_warns(DeprecationWarning):
|
||||
if os.name == "posix":
|
||||
self.check_posix(use_tee=0)
|
||||
self.check_posix(use_tee=1)
|
||||
elif os.name == "nt":
|
||||
self.check_nt(use_tee=0)
|
||||
self.check_nt(use_tee=1)
|
||||
self.check_execute_in(use_tee=0)
|
||||
self.check_execute_in(use_tee=1)
|
|
@ -0,0 +1,43 @@
|
|||
from numpy.testing import assert_
|
||||
import numpy.distutils.fcompiler
|
||||
|
||||
customizable_flags = [
|
||||
('f77', 'F77FLAGS'),
|
||||
('f90', 'F90FLAGS'),
|
||||
('free', 'FREEFLAGS'),
|
||||
('arch', 'FARCH'),
|
||||
('debug', 'FDEBUG'),
|
||||
('flags', 'FFLAGS'),
|
||||
('linker_so', 'LDFLAGS'),
|
||||
]
|
||||
|
||||
|
||||
def test_fcompiler_flags(monkeypatch):
|
||||
monkeypatch.setenv('NPY_DISTUTILS_APPEND_FLAGS', '0')
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='none')
|
||||
flag_vars = fc.flag_vars.clone(lambda *args, **kwargs: None)
|
||||
|
||||
for opt, envvar in customizable_flags:
|
||||
new_flag = '-dummy-{}-flag'.format(opt)
|
||||
prev_flags = getattr(flag_vars, opt)
|
||||
|
||||
monkeypatch.setenv(envvar, new_flag)
|
||||
new_flags = getattr(flag_vars, opt)
|
||||
|
||||
monkeypatch.delenv(envvar)
|
||||
assert_(new_flags == [new_flag])
|
||||
|
||||
monkeypatch.setenv('NPY_DISTUTILS_APPEND_FLAGS', '1')
|
||||
|
||||
for opt, envvar in customizable_flags:
|
||||
new_flag = '-dummy-{}-flag'.format(opt)
|
||||
prev_flags = getattr(flag_vars, opt)
|
||||
monkeypatch.setenv(envvar, new_flag)
|
||||
new_flags = getattr(flag_vars, opt)
|
||||
|
||||
monkeypatch.delenv(envvar)
|
||||
if prev_flags is None:
|
||||
assert_(new_flags == [new_flag])
|
||||
else:
|
||||
assert_(new_flags == prev_flags + [new_flag])
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
from numpy.testing import assert_
|
||||
|
||||
import numpy.distutils.fcompiler
|
||||
|
||||
g77_version_strings = [
|
||||
('GNU Fortran 0.5.25 20010319 (prerelease)', '0.5.25'),
|
||||
('GNU Fortran (GCC 3.2) 3.2 20020814 (release)', '3.2'),
|
||||
('GNU Fortran (GCC) 3.3.3 20040110 (prerelease) (Debian)', '3.3.3'),
|
||||
('GNU Fortran (GCC) 3.3.3 (Debian 20040401)', '3.3.3'),
|
||||
('GNU Fortran (GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)) 3.2.2'
|
||||
' 20030222 (Red Hat Linux 3.2.2-5)', '3.2.2'),
|
||||
]
|
||||
|
||||
gfortran_version_strings = [
|
||||
('GNU Fortran 95 (GCC 4.0.3 20051023 (prerelease) (Debian 4.0.2-3))',
|
||||
'4.0.3'),
|
||||
('GNU Fortran 95 (GCC) 4.1.0', '4.1.0'),
|
||||
('GNU Fortran 95 (GCC) 4.2.0 20060218 (experimental)', '4.2.0'),
|
||||
('GNU Fortran (GCC) 4.3.0 20070316 (experimental)', '4.3.0'),
|
||||
('GNU Fortran (rubenvb-4.8.0) 4.8.0', '4.8.0'),
|
||||
('4.8.0', '4.8.0'),
|
||||
('4.0.3-7', '4.0.3'),
|
||||
("gfortran: warning: couldn't understand kern.osversion '14.1.0\n4.9.1",
|
||||
'4.9.1'),
|
||||
("gfortran: warning: couldn't understand kern.osversion '14.1.0\n"
|
||||
"gfortran: warning: yet another warning\n4.9.1",
|
||||
'4.9.1'),
|
||||
('GNU Fortran (crosstool-NG 8a21ab48) 7.2.0', '7.2.0')
|
||||
]
|
||||
|
||||
class TestG77Versions:
|
||||
def test_g77_version(self):
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='gnu')
|
||||
for vs, version in g77_version_strings:
|
||||
v = fc.version_match(vs)
|
||||
assert_(v == version, (vs, v))
|
||||
|
||||
def test_not_g77(self):
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='gnu')
|
||||
for vs, _ in gfortran_version_strings:
|
||||
v = fc.version_match(vs)
|
||||
assert_(v is None, (vs, v))
|
||||
|
||||
class TestGFortranVersions:
|
||||
def test_gfortran_version(self):
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='gnu95')
|
||||
for vs, version in gfortran_version_strings:
|
||||
v = fc.version_match(vs)
|
||||
assert_(v == version, (vs, v))
|
||||
|
||||
def test_not_gfortran(self):
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='gnu95')
|
||||
for vs, _ in g77_version_strings:
|
||||
v = fc.version_match(vs)
|
||||
assert_(v is None, (vs, v))
|
|
@ -0,0 +1,30 @@
|
|||
import numpy.distutils.fcompiler
|
||||
from numpy.testing import assert_
|
||||
|
||||
|
||||
intel_32bit_version_strings = [
|
||||
("Intel(R) Fortran Intel(R) 32-bit Compiler Professional for applications"
|
||||
"running on Intel(R) 32, Version 11.1", '11.1'),
|
||||
]
|
||||
|
||||
intel_64bit_version_strings = [
|
||||
("Intel(R) Fortran IA-64 Compiler Professional for applications"
|
||||
"running on IA-64, Version 11.0", '11.0'),
|
||||
("Intel(R) Fortran Intel(R) 64 Compiler Professional for applications"
|
||||
"running on Intel(R) 64, Version 11.1", '11.1')
|
||||
]
|
||||
|
||||
class TestIntelFCompilerVersions:
|
||||
def test_32bit_version(self):
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='intel')
|
||||
for vs, version in intel_32bit_version_strings:
|
||||
v = fc.version_match(vs)
|
||||
assert_(v == version)
|
||||
|
||||
|
||||
class TestIntelEM64TFCompilerVersions:
|
||||
def test_64bit_version(self):
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler='intelem')
|
||||
for vs, version in intel_64bit_version_strings:
|
||||
v = fc.version_match(vs)
|
||||
assert_(v == version)
|
|
@ -0,0 +1,22 @@
|
|||
from numpy.testing import assert_
|
||||
import numpy.distutils.fcompiler
|
||||
|
||||
nag_version_strings = [('nagfor', 'NAG Fortran Compiler Release '
|
||||
'6.2(Chiyoda) Build 6200', '6.2'),
|
||||
('nagfor', 'NAG Fortran Compiler Release '
|
||||
'6.1(Tozai) Build 6136', '6.1'),
|
||||
('nagfor', 'NAG Fortran Compiler Release '
|
||||
'6.0(Hibiya) Build 1021', '6.0'),
|
||||
('nagfor', 'NAG Fortran Compiler Release '
|
||||
'5.3.2(971)', '5.3.2'),
|
||||
('nag', 'NAGWare Fortran 95 compiler Release 5.1'
|
||||
'(347,355-367,375,380-383,389,394,399,401-402,407,'
|
||||
'431,435,437,446,459-460,463,472,494,496,503,508,'
|
||||
'511,517,529,555,557,565)', '5.1')]
|
||||
|
||||
class TestNagFCompilerVersions:
|
||||
def test_version_match(self):
|
||||
for comp, vs, version in nag_version_strings:
|
||||
fc = numpy.distutils.fcompiler.new_fcompiler(compiler=comp)
|
||||
v = fc.version_match(vs)
|
||||
assert_(v == version)
|
|
@ -0,0 +1,44 @@
|
|||
|
||||
from numpy.distutils.from_template import process_str
|
||||
from numpy.testing import assert_equal
|
||||
|
||||
|
||||
pyf_src = """
|
||||
python module foo
|
||||
<_rd=real,double precision>
|
||||
interface
|
||||
subroutine <s,d>foosub(tol)
|
||||
<_rd>, intent(in,out) :: tol
|
||||
end subroutine <s,d>foosub
|
||||
end interface
|
||||
end python module foo
|
||||
"""
|
||||
|
||||
expected_pyf = """
|
||||
python module foo
|
||||
interface
|
||||
subroutine sfoosub(tol)
|
||||
real, intent(in,out) :: tol
|
||||
end subroutine sfoosub
|
||||
subroutine dfoosub(tol)
|
||||
double precision, intent(in,out) :: tol
|
||||
end subroutine dfoosub
|
||||
end interface
|
||||
end python module foo
|
||||
"""
|
||||
|
||||
|
||||
def normalize_whitespace(s):
|
||||
"""
|
||||
Remove leading and trailing whitespace, and convert internal
|
||||
stretches of whitespace to a single space.
|
||||
"""
|
||||
return ' '.join(s.split())
|
||||
|
||||
|
||||
def test_from_template():
|
||||
"""Regression test for gh-10712."""
|
||||
pyf = process_str(pyf_src)
|
||||
normalized_pyf = normalize_whitespace(pyf)
|
||||
normalized_expected_pyf = normalize_whitespace(expected_pyf)
|
||||
assert_equal(normalized_pyf, normalized_expected_pyf)
|
34
venv/Lib/site-packages/~umpy/distutils/tests/test_log.py
Normal file
34
venv/Lib/site-packages/~umpy/distutils/tests/test_log.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
import io
|
||||
import re
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
import pytest
|
||||
|
||||
from numpy.distutils import log
|
||||
|
||||
|
||||
def setup_module():
|
||||
f = io.StringIO() # changing verbosity also logs here, capture that
|
||||
with redirect_stdout(f):
|
||||
log.set_verbosity(2, force=True) # i.e. DEBUG
|
||||
|
||||
|
||||
def teardown_module():
|
||||
log.set_verbosity(0, force=True) # the default
|
||||
|
||||
|
||||
r_ansi = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("func_name", ["error", "warn", "info", "debug"])
|
||||
def test_log_prefix(func_name):
|
||||
func = getattr(log, func_name)
|
||||
msg = f"{func_name} message"
|
||||
f = io.StringIO()
|
||||
with redirect_stdout(f):
|
||||
func(msg)
|
||||
out = f.getvalue()
|
||||
assert out # sanity check
|
||||
clean_out = r_ansi.sub("", out)
|
||||
line = next(line for line in clean_out.splitlines())
|
||||
assert line == f"{func_name.upper()}: {msg}"
|
|
@ -0,0 +1,42 @@
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
from numpy.distutils import mingw32ccompiler
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform != 'win32', reason='win32 only test')
|
||||
def test_build_import():
|
||||
'''Test the mingw32ccompiler.build_import_library, which builds a
|
||||
`python.a` from the MSVC `python.lib`
|
||||
'''
|
||||
|
||||
# make sure `nm.exe` exists and supports the current python version. This
|
||||
# can get mixed up when the PATH has a 64-bit nm but the python is 32-bit
|
||||
try:
|
||||
out = subprocess.check_output(['nm.exe', '--help'])
|
||||
except FileNotFoundError:
|
||||
pytest.skip("'nm.exe' not on path, is mingw installed?")
|
||||
supported = out[out.find(b'supported targets:'):]
|
||||
if sys.maxsize < 2**32:
|
||||
if b'pe-i386' not in supported:
|
||||
raise ValueError("'nm.exe' found but it does not support 32-bit "
|
||||
"dlls when using 32-bit python. Supported "
|
||||
"formats: '%s'" % supported)
|
||||
elif b'pe-x86-64' not in supported:
|
||||
raise ValueError("'nm.exe' found but it does not support 64-bit "
|
||||
"dlls when using 64-bit python. Supported "
|
||||
"formats: '%s'" % supported)
|
||||
# Hide the import library to force a build
|
||||
has_import_lib, fullpath = mingw32ccompiler._check_for_import_lib()
|
||||
if has_import_lib:
|
||||
shutil.move(fullpath, fullpath + '.bak')
|
||||
|
||||
try:
|
||||
# Whew, now we can actually test the function
|
||||
mingw32ccompiler.build_import_library()
|
||||
|
||||
finally:
|
||||
if has_import_lib:
|
||||
shutil.move(fullpath + '.bak', fullpath)
|
|
@ -0,0 +1,82 @@
|
|||
from os.path import join, sep, dirname
|
||||
|
||||
from numpy.distutils.misc_util import (
|
||||
appendpath, minrelpath, gpaths, get_shared_lib_extension, get_info
|
||||
)
|
||||
from numpy.testing import (
|
||||
assert_, assert_equal
|
||||
)
|
||||
|
||||
ajoin = lambda *paths: join(*((sep,)+paths))
|
||||
|
||||
class TestAppendpath:
|
||||
|
||||
def test_1(self):
|
||||
assert_equal(appendpath('prefix', 'name'), join('prefix', 'name'))
|
||||
assert_equal(appendpath('/prefix', 'name'), ajoin('prefix', 'name'))
|
||||
assert_equal(appendpath('/prefix', '/name'), ajoin('prefix', 'name'))
|
||||
assert_equal(appendpath('prefix', '/name'), join('prefix', 'name'))
|
||||
|
||||
def test_2(self):
|
||||
assert_equal(appendpath('prefix/sub', 'name'),
|
||||
join('prefix', 'sub', 'name'))
|
||||
assert_equal(appendpath('prefix/sub', 'sup/name'),
|
||||
join('prefix', 'sub', 'sup', 'name'))
|
||||
assert_equal(appendpath('/prefix/sub', '/prefix/name'),
|
||||
ajoin('prefix', 'sub', 'name'))
|
||||
|
||||
def test_3(self):
|
||||
assert_equal(appendpath('/prefix/sub', '/prefix/sup/name'),
|
||||
ajoin('prefix', 'sub', 'sup', 'name'))
|
||||
assert_equal(appendpath('/prefix/sub/sub2', '/prefix/sup/sup2/name'),
|
||||
ajoin('prefix', 'sub', 'sub2', 'sup', 'sup2', 'name'))
|
||||
assert_equal(appendpath('/prefix/sub/sub2', '/prefix/sub/sup/name'),
|
||||
ajoin('prefix', 'sub', 'sub2', 'sup', 'name'))
|
||||
|
||||
class TestMinrelpath:
|
||||
|
||||
def test_1(self):
|
||||
n = lambda path: path.replace('/', sep)
|
||||
assert_equal(minrelpath(n('aa/bb')), n('aa/bb'))
|
||||
assert_equal(minrelpath('..'), '..')
|
||||
assert_equal(minrelpath(n('aa/..')), '')
|
||||
assert_equal(minrelpath(n('aa/../bb')), 'bb')
|
||||
assert_equal(minrelpath(n('aa/bb/..')), 'aa')
|
||||
assert_equal(minrelpath(n('aa/bb/../..')), '')
|
||||
assert_equal(minrelpath(n('aa/bb/../cc/../dd')), n('aa/dd'))
|
||||
assert_equal(minrelpath(n('.././..')), n('../..'))
|
||||
assert_equal(minrelpath(n('aa/bb/.././../dd')), n('dd'))
|
||||
|
||||
class TestGpaths:
|
||||
|
||||
def test_gpaths(self):
|
||||
local_path = minrelpath(join(dirname(__file__), '..'))
|
||||
ls = gpaths('command/*.py', local_path)
|
||||
assert_(join(local_path, 'command', 'build_src.py') in ls, repr(ls))
|
||||
f = gpaths('system_info.py', local_path)
|
||||
assert_(join(local_path, 'system_info.py') == f[0], repr(f))
|
||||
|
||||
class TestSharedExtension:
|
||||
|
||||
def test_get_shared_lib_extension(self):
|
||||
import sys
|
||||
ext = get_shared_lib_extension(is_python_ext=False)
|
||||
if sys.platform.startswith('linux'):
|
||||
assert_equal(ext, '.so')
|
||||
elif sys.platform.startswith('gnukfreebsd'):
|
||||
assert_equal(ext, '.so')
|
||||
elif sys.platform.startswith('darwin'):
|
||||
assert_equal(ext, '.dylib')
|
||||
elif sys.platform.startswith('win'):
|
||||
assert_equal(ext, '.dll')
|
||||
# just check for no crash
|
||||
assert_(get_shared_lib_extension(is_python_ext=True))
|
||||
|
||||
|
||||
def test_installed_npymath_ini():
|
||||
# Regression test for gh-7707. If npymath.ini wasn't installed, then this
|
||||
# will give an error.
|
||||
info = get_info('npymath')
|
||||
|
||||
assert isinstance(info, dict)
|
||||
assert "define_macros" in info
|
|
@ -0,0 +1,84 @@
|
|||
import os
|
||||
|
||||
from numpy.distutils.npy_pkg_config import read_config, parse_flags
|
||||
from numpy.testing import temppath, assert_
|
||||
|
||||
simple = """\
|
||||
[meta]
|
||||
Name = foo
|
||||
Description = foo lib
|
||||
Version = 0.1
|
||||
|
||||
[default]
|
||||
cflags = -I/usr/include
|
||||
libs = -L/usr/lib
|
||||
"""
|
||||
simple_d = {'cflags': '-I/usr/include', 'libflags': '-L/usr/lib',
|
||||
'version': '0.1', 'name': 'foo'}
|
||||
|
||||
simple_variable = """\
|
||||
[meta]
|
||||
Name = foo
|
||||
Description = foo lib
|
||||
Version = 0.1
|
||||
|
||||
[variables]
|
||||
prefix = /foo/bar
|
||||
libdir = ${prefix}/lib
|
||||
includedir = ${prefix}/include
|
||||
|
||||
[default]
|
||||
cflags = -I${includedir}
|
||||
libs = -L${libdir}
|
||||
"""
|
||||
simple_variable_d = {'cflags': '-I/foo/bar/include', 'libflags': '-L/foo/bar/lib',
|
||||
'version': '0.1', 'name': 'foo'}
|
||||
|
||||
class TestLibraryInfo:
|
||||
def test_simple(self):
|
||||
with temppath('foo.ini') as path:
|
||||
with open(path, 'w') as f:
|
||||
f.write(simple)
|
||||
pkg = os.path.splitext(path)[0]
|
||||
out = read_config(pkg)
|
||||
|
||||
assert_(out.cflags() == simple_d['cflags'])
|
||||
assert_(out.libs() == simple_d['libflags'])
|
||||
assert_(out.name == simple_d['name'])
|
||||
assert_(out.version == simple_d['version'])
|
||||
|
||||
def test_simple_variable(self):
|
||||
with temppath('foo.ini') as path:
|
||||
with open(path, 'w') as f:
|
||||
f.write(simple_variable)
|
||||
pkg = os.path.splitext(path)[0]
|
||||
out = read_config(pkg)
|
||||
|
||||
assert_(out.cflags() == simple_variable_d['cflags'])
|
||||
assert_(out.libs() == simple_variable_d['libflags'])
|
||||
assert_(out.name == simple_variable_d['name'])
|
||||
assert_(out.version == simple_variable_d['version'])
|
||||
out.vars['prefix'] = '/Users/david'
|
||||
assert_(out.cflags() == '-I/Users/david/include')
|
||||
|
||||
class TestParseFlags:
|
||||
def test_simple_cflags(self):
|
||||
d = parse_flags("-I/usr/include")
|
||||
assert_(d['include_dirs'] == ['/usr/include'])
|
||||
|
||||
d = parse_flags("-I/usr/include -DFOO")
|
||||
assert_(d['include_dirs'] == ['/usr/include'])
|
||||
assert_(d['macros'] == ['FOO'])
|
||||
|
||||
d = parse_flags("-I /usr/include -DFOO")
|
||||
assert_(d['include_dirs'] == ['/usr/include'])
|
||||
assert_(d['macros'] == ['FOO'])
|
||||
|
||||
def test_simple_lflags(self):
|
||||
d = parse_flags("-L/usr/lib -lfoo -L/usr/lib -lbar")
|
||||
assert_(d['library_dirs'] == ['/usr/lib', '/usr/lib'])
|
||||
assert_(d['libraries'] == ['foo', 'bar'])
|
||||
|
||||
d = parse_flags("-L /usr/lib -lfoo -L/usr/lib -lbar")
|
||||
assert_(d['library_dirs'] == ['/usr/lib', '/usr/lib'])
|
||||
assert_(d['libraries'] == ['foo', 'bar'])
|
|
@ -0,0 +1,79 @@
|
|||
import pytest
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
|
||||
from numpy.distutils import _shell_utils
|
||||
from numpy.testing import IS_WASM
|
||||
|
||||
argv_cases = [
|
||||
[r'exe'],
|
||||
[r'path/exe'],
|
||||
[r'path\exe'],
|
||||
[r'\\server\path\exe'],
|
||||
[r'path to/exe'],
|
||||
[r'path to\exe'],
|
||||
|
||||
[r'exe', '--flag'],
|
||||
[r'path/exe', '--flag'],
|
||||
[r'path\exe', '--flag'],
|
||||
[r'path to/exe', '--flag'],
|
||||
[r'path to\exe', '--flag'],
|
||||
|
||||
# flags containing literal quotes in their name
|
||||
[r'path to/exe', '--flag-"quoted"'],
|
||||
[r'path to\exe', '--flag-"quoted"'],
|
||||
[r'path to/exe', '"--flag-quoted"'],
|
||||
[r'path to\exe', '"--flag-quoted"'],
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(params=[
|
||||
_shell_utils.WindowsParser,
|
||||
_shell_utils.PosixParser
|
||||
])
|
||||
def Parser(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def runner(Parser):
|
||||
if Parser != _shell_utils.NativeParser:
|
||||
pytest.skip('Unable to run with non-native parser')
|
||||
|
||||
if Parser == _shell_utils.WindowsParser:
|
||||
return lambda cmd: subprocess.check_output(cmd)
|
||||
elif Parser == _shell_utils.PosixParser:
|
||||
# posix has no non-shell string parsing
|
||||
return lambda cmd: subprocess.check_output(cmd, shell=True)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
|
||||
@pytest.mark.parametrize('argv', argv_cases)
|
||||
def test_join_matches_subprocess(Parser, runner, argv):
|
||||
"""
|
||||
Test that join produces strings understood by subprocess
|
||||
"""
|
||||
# invoke python to return its arguments as json
|
||||
cmd = [
|
||||
sys.executable, '-c',
|
||||
'import json, sys; print(json.dumps(sys.argv[1:]))'
|
||||
]
|
||||
joined = Parser.join(cmd + argv)
|
||||
json_out = runner(joined).decode()
|
||||
assert json.loads(json_out) == argv
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
|
||||
@pytest.mark.parametrize('argv', argv_cases)
|
||||
def test_roundtrip(Parser, argv):
|
||||
"""
|
||||
Test that split is the inverse operation of join
|
||||
"""
|
||||
try:
|
||||
joined = Parser.join(argv)
|
||||
assert argv == Parser.split(joined)
|
||||
except NotImplementedError:
|
||||
pytest.skip("Not implemented")
|
334
venv/Lib/site-packages/~umpy/distutils/tests/test_system_info.py
Normal file
334
venv/Lib/site-packages/~umpy/distutils/tests/test_system_info.py
Normal file
|
@ -0,0 +1,334 @@
|
|||
import os
|
||||
import shutil
|
||||
import pytest
|
||||
from tempfile import mkstemp, mkdtemp
|
||||
from subprocess import Popen, PIPE
|
||||
import importlib.metadata
|
||||
from distutils.errors import DistutilsError
|
||||
|
||||
from numpy.testing import assert_, assert_equal, assert_raises
|
||||
from numpy.distutils import ccompiler, customized_ccompiler
|
||||
from numpy.distutils.system_info import system_info, ConfigParser, mkl_info
|
||||
from numpy.distutils.system_info import AliasedOptionError
|
||||
from numpy.distutils.system_info import default_lib_dirs, default_include_dirs
|
||||
from numpy.distutils import _shell_utils
|
||||
|
||||
|
||||
try:
|
||||
if importlib.metadata.version('setuptools') >= '60':
|
||||
# pkg-resources gives deprecation warnings, and there may be more
|
||||
# issues. We only support setuptools <60
|
||||
pytest.skip("setuptools is too new", allow_module_level=True)
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
# we don't require `setuptools`; if it is not found, continue
|
||||
pass
|
||||
|
||||
|
||||
def get_class(name, notfound_action=1):
|
||||
"""
|
||||
notfound_action:
|
||||
0 - do nothing
|
||||
1 - display warning message
|
||||
2 - raise error
|
||||
"""
|
||||
cl = {'temp1': Temp1Info,
|
||||
'temp2': Temp2Info,
|
||||
'duplicate_options': DuplicateOptionInfo,
|
||||
}.get(name.lower(), _system_info)
|
||||
return cl()
|
||||
|
||||
simple_site = """
|
||||
[ALL]
|
||||
library_dirs = {dir1:s}{pathsep:s}{dir2:s}
|
||||
libraries = {lib1:s},{lib2:s}
|
||||
extra_compile_args = -I/fake/directory -I"/path with/spaces" -Os
|
||||
runtime_library_dirs = {dir1:s}
|
||||
|
||||
[temp1]
|
||||
library_dirs = {dir1:s}
|
||||
libraries = {lib1:s}
|
||||
runtime_library_dirs = {dir1:s}
|
||||
|
||||
[temp2]
|
||||
library_dirs = {dir2:s}
|
||||
libraries = {lib2:s}
|
||||
extra_link_args = -Wl,-rpath={lib2_escaped:s}
|
||||
rpath = {dir2:s}
|
||||
|
||||
[duplicate_options]
|
||||
mylib_libs = {lib1:s}
|
||||
libraries = {lib2:s}
|
||||
"""
|
||||
site_cfg = simple_site
|
||||
|
||||
fakelib_c_text = """
|
||||
/* This file is generated from numpy/distutils/testing/test_system_info.py */
|
||||
#include<stdio.h>
|
||||
void foo(void) {
|
||||
printf("Hello foo");
|
||||
}
|
||||
void bar(void) {
|
||||
printf("Hello bar");
|
||||
}
|
||||
"""
|
||||
|
||||
def have_compiler():
|
||||
""" Return True if there appears to be an executable compiler
|
||||
"""
|
||||
compiler = customized_ccompiler()
|
||||
try:
|
||||
cmd = compiler.compiler # Unix compilers
|
||||
except AttributeError:
|
||||
try:
|
||||
if not compiler.initialized:
|
||||
compiler.initialize() # MSVC is different
|
||||
except (DistutilsError, ValueError):
|
||||
return False
|
||||
cmd = [compiler.cc]
|
||||
try:
|
||||
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
p.stdout.close()
|
||||
p.stderr.close()
|
||||
p.wait()
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
HAVE_COMPILER = have_compiler()
|
||||
|
||||
|
||||
class _system_info(system_info):
|
||||
|
||||
def __init__(self,
|
||||
default_lib_dirs=default_lib_dirs,
|
||||
default_include_dirs=default_include_dirs,
|
||||
verbosity=1,
|
||||
):
|
||||
self.__class__.info = {}
|
||||
self.local_prefixes = []
|
||||
defaults = {'library_dirs': '',
|
||||
'include_dirs': '',
|
||||
'runtime_library_dirs': '',
|
||||
'rpath': '',
|
||||
'src_dirs': '',
|
||||
'search_static_first': "0",
|
||||
'extra_compile_args': '',
|
||||
'extra_link_args': ''}
|
||||
self.cp = ConfigParser(defaults)
|
||||
# We have to parse the config files afterwards
|
||||
# to have a consistent temporary filepath
|
||||
|
||||
def _check_libs(self, lib_dirs, libs, opt_libs, exts):
|
||||
"""Override _check_libs to return with all dirs """
|
||||
info = {'libraries': libs, 'library_dirs': lib_dirs}
|
||||
return info
|
||||
|
||||
|
||||
class Temp1Info(_system_info):
|
||||
"""For testing purposes"""
|
||||
section = 'temp1'
|
||||
|
||||
|
||||
class Temp2Info(_system_info):
|
||||
"""For testing purposes"""
|
||||
section = 'temp2'
|
||||
|
||||
class DuplicateOptionInfo(_system_info):
|
||||
"""For testing purposes"""
|
||||
section = 'duplicate_options'
|
||||
|
||||
|
||||
class TestSystemInfoReading:
|
||||
|
||||
def setup_method(self):
|
||||
""" Create the libraries """
|
||||
# Create 2 sources and 2 libraries
|
||||
self._dir1 = mkdtemp()
|
||||
self._src1 = os.path.join(self._dir1, 'foo.c')
|
||||
self._lib1 = os.path.join(self._dir1, 'libfoo.so')
|
||||
self._dir2 = mkdtemp()
|
||||
self._src2 = os.path.join(self._dir2, 'bar.c')
|
||||
self._lib2 = os.path.join(self._dir2, 'libbar.so')
|
||||
# Update local site.cfg
|
||||
global simple_site, site_cfg
|
||||
site_cfg = simple_site.format(**{
|
||||
'dir1': self._dir1,
|
||||
'lib1': self._lib1,
|
||||
'dir2': self._dir2,
|
||||
'lib2': self._lib2,
|
||||
'pathsep': os.pathsep,
|
||||
'lib2_escaped': _shell_utils.NativeParser.join([self._lib2])
|
||||
})
|
||||
# Write site.cfg
|
||||
fd, self._sitecfg = mkstemp()
|
||||
os.close(fd)
|
||||
with open(self._sitecfg, 'w') as fd:
|
||||
fd.write(site_cfg)
|
||||
# Write the sources
|
||||
with open(self._src1, 'w') as fd:
|
||||
fd.write(fakelib_c_text)
|
||||
with open(self._src2, 'w') as fd:
|
||||
fd.write(fakelib_c_text)
|
||||
# We create all class-instances
|
||||
|
||||
def site_and_parse(c, site_cfg):
|
||||
c.files = [site_cfg]
|
||||
c.parse_config_files()
|
||||
return c
|
||||
self.c_default = site_and_parse(get_class('default'), self._sitecfg)
|
||||
self.c_temp1 = site_and_parse(get_class('temp1'), self._sitecfg)
|
||||
self.c_temp2 = site_and_parse(get_class('temp2'), self._sitecfg)
|
||||
self.c_dup_options = site_and_parse(get_class('duplicate_options'),
|
||||
self._sitecfg)
|
||||
|
||||
def teardown_method(self):
|
||||
# Do each removal separately
|
||||
try:
|
||||
shutil.rmtree(self._dir1)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
shutil.rmtree(self._dir2)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
os.remove(self._sitecfg)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_all(self):
|
||||
# Read in all information in the ALL block
|
||||
tsi = self.c_default
|
||||
assert_equal(tsi.get_lib_dirs(), [self._dir1, self._dir2])
|
||||
assert_equal(tsi.get_libraries(), [self._lib1, self._lib2])
|
||||
assert_equal(tsi.get_runtime_lib_dirs(), [self._dir1])
|
||||
extra = tsi.calc_extra_info()
|
||||
assert_equal(extra['extra_compile_args'], ['-I/fake/directory', '-I/path with/spaces', '-Os'])
|
||||
|
||||
def test_temp1(self):
|
||||
# Read in all information in the temp1 block
|
||||
tsi = self.c_temp1
|
||||
assert_equal(tsi.get_lib_dirs(), [self._dir1])
|
||||
assert_equal(tsi.get_libraries(), [self._lib1])
|
||||
assert_equal(tsi.get_runtime_lib_dirs(), [self._dir1])
|
||||
|
||||
def test_temp2(self):
|
||||
# Read in all information in the temp2 block
|
||||
tsi = self.c_temp2
|
||||
assert_equal(tsi.get_lib_dirs(), [self._dir2])
|
||||
assert_equal(tsi.get_libraries(), [self._lib2])
|
||||
# Now from rpath and not runtime_library_dirs
|
||||
assert_equal(tsi.get_runtime_lib_dirs(key='rpath'), [self._dir2])
|
||||
extra = tsi.calc_extra_info()
|
||||
assert_equal(extra['extra_link_args'], ['-Wl,-rpath=' + self._lib2])
|
||||
|
||||
def test_duplicate_options(self):
|
||||
# Ensure that duplicates are raising an AliasedOptionError
|
||||
tsi = self.c_dup_options
|
||||
assert_raises(AliasedOptionError, tsi.get_option_single, "mylib_libs", "libraries")
|
||||
assert_equal(tsi.get_libs("mylib_libs", [self._lib1]), [self._lib1])
|
||||
assert_equal(tsi.get_libs("libraries", [self._lib2]), [self._lib2])
|
||||
|
||||
@pytest.mark.skipif(not HAVE_COMPILER, reason="Missing compiler")
|
||||
def test_compile1(self):
|
||||
# Compile source and link the first source
|
||||
c = customized_ccompiler()
|
||||
previousDir = os.getcwd()
|
||||
try:
|
||||
# Change directory to not screw up directories
|
||||
os.chdir(self._dir1)
|
||||
c.compile([os.path.basename(self._src1)], output_dir=self._dir1)
|
||||
# Ensure that the object exists
|
||||
assert_(os.path.isfile(self._src1.replace('.c', '.o')) or
|
||||
os.path.isfile(self._src1.replace('.c', '.obj')))
|
||||
finally:
|
||||
os.chdir(previousDir)
|
||||
|
||||
@pytest.mark.skipif(not HAVE_COMPILER, reason="Missing compiler")
|
||||
@pytest.mark.skipif('msvc' in repr(ccompiler.new_compiler()),
|
||||
reason="Fails with MSVC compiler ")
|
||||
def test_compile2(self):
|
||||
# Compile source and link the second source
|
||||
tsi = self.c_temp2
|
||||
c = customized_ccompiler()
|
||||
extra_link_args = tsi.calc_extra_info()['extra_link_args']
|
||||
previousDir = os.getcwd()
|
||||
try:
|
||||
# Change directory to not screw up directories
|
||||
os.chdir(self._dir2)
|
||||
c.compile([os.path.basename(self._src2)], output_dir=self._dir2,
|
||||
extra_postargs=extra_link_args)
|
||||
# Ensure that the object exists
|
||||
assert_(os.path.isfile(self._src2.replace('.c', '.o')))
|
||||
finally:
|
||||
os.chdir(previousDir)
|
||||
|
||||
HAS_MKL = "mkl_rt" in mkl_info().calc_libraries_info().get("libraries", [])
|
||||
|
||||
@pytest.mark.xfail(HAS_MKL, reason=("`[DEFAULT]` override doesn't work if "
|
||||
"numpy is built with MKL support"))
|
||||
def test_overrides(self):
|
||||
previousDir = os.getcwd()
|
||||
cfg = os.path.join(self._dir1, 'site.cfg')
|
||||
shutil.copy(self._sitecfg, cfg)
|
||||
try:
|
||||
os.chdir(self._dir1)
|
||||
# Check that the '[ALL]' section does not override
|
||||
# missing values from other sections
|
||||
info = mkl_info()
|
||||
lib_dirs = info.cp['ALL']['library_dirs'].split(os.pathsep)
|
||||
assert info.get_lib_dirs() != lib_dirs
|
||||
|
||||
# But if we copy the values to a '[mkl]' section the value
|
||||
# is correct
|
||||
with open(cfg) as fid:
|
||||
mkl = fid.read().replace('[ALL]', '[mkl]', 1)
|
||||
with open(cfg, 'w') as fid:
|
||||
fid.write(mkl)
|
||||
info = mkl_info()
|
||||
assert info.get_lib_dirs() == lib_dirs
|
||||
|
||||
# Also, the values will be taken from a section named '[DEFAULT]'
|
||||
with open(cfg) as fid:
|
||||
dflt = fid.read().replace('[mkl]', '[DEFAULT]', 1)
|
||||
with open(cfg, 'w') as fid:
|
||||
fid.write(dflt)
|
||||
info = mkl_info()
|
||||
assert info.get_lib_dirs() == lib_dirs
|
||||
finally:
|
||||
os.chdir(previousDir)
|
||||
|
||||
|
||||
def test_distutils_parse_env_order(monkeypatch):
|
||||
from numpy.distutils.system_info import _parse_env_order
|
||||
env = 'NPY_TESTS_DISTUTILS_PARSE_ENV_ORDER'
|
||||
|
||||
base_order = list('abcdef')
|
||||
|
||||
monkeypatch.setenv(env, 'b,i,e,f')
|
||||
order, unknown = _parse_env_order(base_order, env)
|
||||
assert len(order) == 3
|
||||
assert order == list('bef')
|
||||
assert len(unknown) == 1
|
||||
|
||||
# For when LAPACK/BLAS optimization is disabled
|
||||
monkeypatch.setenv(env, '')
|
||||
order, unknown = _parse_env_order(base_order, env)
|
||||
assert len(order) == 0
|
||||
assert len(unknown) == 0
|
||||
|
||||
for prefix in '^!':
|
||||
monkeypatch.setenv(env, f'{prefix}b,i,e')
|
||||
order, unknown = _parse_env_order(base_order, env)
|
||||
assert len(order) == 4
|
||||
assert order == list('acdf')
|
||||
assert len(unknown) == 1
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
monkeypatch.setenv(env, 'b,^e,i')
|
||||
_parse_env_order(base_order, env)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
monkeypatch.setenv(env, '!b,^e,i')
|
||||
_parse_env_order(base_order, env)
|
90
venv/Lib/site-packages/~umpy/distutils/tests/utilities.py
Normal file
90
venv/Lib/site-packages/~umpy/distutils/tests/utilities.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Kanged out of numpy.f2py.tests.util for test_build_ext
|
||||
from numpy.testing import IS_WASM
|
||||
import textwrap
|
||||
import shutil
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
#
|
||||
# Check if compilers are available at all...
|
||||
#
|
||||
|
||||
_compiler_status = None
|
||||
|
||||
|
||||
def _get_compiler_status():
|
||||
global _compiler_status
|
||||
if _compiler_status is not None:
|
||||
return _compiler_status
|
||||
|
||||
_compiler_status = (False, False, False)
|
||||
if IS_WASM:
|
||||
# Can't run compiler from inside WASM.
|
||||
return _compiler_status
|
||||
|
||||
# XXX: this is really ugly. But I don't know how to invoke Distutils
|
||||
# in a safer way...
|
||||
code = textwrap.dedent(
|
||||
f"""\
|
||||
import os
|
||||
import sys
|
||||
sys.path = {repr(sys.path)}
|
||||
|
||||
def configuration(parent_name='',top_path=None):
|
||||
global config
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
config = Configuration('', parent_name, top_path)
|
||||
return config
|
||||
|
||||
from numpy.distutils.core import setup
|
||||
setup(configuration=configuration)
|
||||
|
||||
config_cmd = config.get_config_cmd()
|
||||
have_c = config_cmd.try_compile('void foo() {{}}')
|
||||
print('COMPILERS:%%d,%%d,%%d' %% (have_c,
|
||||
config.have_f77c(),
|
||||
config.have_f90c()))
|
||||
sys.exit(99)
|
||||
"""
|
||||
)
|
||||
code = code % dict(syspath=repr(sys.path))
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
try:
|
||||
script = os.path.join(tmpdir, "setup.py")
|
||||
|
||||
with open(script, "w") as f:
|
||||
f.write(code)
|
||||
|
||||
cmd = [sys.executable, "setup.py", "config"]
|
||||
p = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=tmpdir
|
||||
)
|
||||
out, err = p.communicate()
|
||||
finally:
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
m = re.search(rb"COMPILERS:(\d+),(\d+),(\d+)", out)
|
||||
if m:
|
||||
_compiler_status = (
|
||||
bool(int(m.group(1))),
|
||||
bool(int(m.group(2))),
|
||||
bool(int(m.group(3))),
|
||||
)
|
||||
# Finished
|
||||
return _compiler_status
|
||||
|
||||
|
||||
def has_c_compiler():
|
||||
return _get_compiler_status()[0]
|
||||
|
||||
|
||||
def has_f77_compiler():
|
||||
return _get_compiler_status()[1]
|
||||
|
||||
|
||||
def has_f90_compiler():
|
||||
return _get_compiler_status()[2]
|
Loading…
Add table
Add a link
Reference in a new issue