diff options
Diffstat (limited to 'numpy/f2py/tests')
-rw-r--r-- | numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c | 7 | ||||
-rw-r--r-- | numpy/f2py/tests/test_array_from_pyobj.py | 81 | ||||
-rw-r--r-- | numpy/f2py/tests/test_callback.py | 22 | ||||
-rw-r--r-- | numpy/f2py/tests/test_character.py | 570 | ||||
-rw-r--r-- | numpy/f2py/tests/test_crackfortran.py | 31 | ||||
-rw-r--r-- | numpy/f2py/tests/test_docs.py | 55 | ||||
-rw-r--r-- | numpy/f2py/tests/test_return_character.py | 2 | ||||
-rw-r--r-- | numpy/f2py/tests/util.py | 10 |
8 files changed, 749 insertions, 29 deletions
diff --git a/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c b/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c index c8ae7b9dc..9a8b4a752 100644 --- a/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c +++ b/numpy/f2py/tests/src/array_from_pyobj/wrapmodule.c @@ -33,6 +33,7 @@ static PyObject *f2py_rout_wrap_call(PyObject *capi_self, PyObject *capi_args) { PyObject * volatile capi_buildvalue = NULL; int type_num = 0; + int elsize = 0; npy_intp *dims = NULL; PyObject *dims_capi = Py_None; int rank = 0; @@ -41,8 +42,8 @@ static PyObject *f2py_rout_wrap_call(PyObject *capi_self, PyObject *arr_capi = Py_None; int i; - if (!PyArg_ParseTuple(capi_args,"iOiO|:wrap.call",\ - &type_num,&dims_capi,&intent,&arr_capi)) + if (!PyArg_ParseTuple(capi_args,"iiOiO|:wrap.call",\ + &type_num,&elsize,&dims_capi,&intent,&arr_capi)) return NULL; rank = PySequence_Length(dims_capi); dims = malloc(rank*sizeof(npy_intp)); @@ -58,7 +59,7 @@ static PyObject *f2py_rout_wrap_call(PyObject *capi_self, goto fail; } } - capi_arr_tmp = array_from_pyobj(type_num,dims,rank,intent|F2PY_INTENT_OUT,arr_capi); + capi_arr_tmp = ndarray_from_pyobj(type_num,elsize,dims,rank,intent|F2PY_INTENT_OUT,arr_capi,"wrap.call failed"); if (capi_arr_tmp == NULL) { free(dims); return NULL; diff --git a/numpy/f2py/tests/test_array_from_pyobj.py b/numpy/f2py/tests/test_array_from_pyobj.py index 5a084bc3e..2b8c8defc 100644 --- a/numpy/f2py/tests/test_array_from_pyobj.py +++ b/numpy/f2py/tests/test_array_from_pyobj.py @@ -6,11 +6,18 @@ import pytest import numpy as np -from numpy.core.multiarray import typeinfo +from numpy.testing import assert_, assert_equal +from numpy.core.multiarray import typeinfo as _typeinfo from . import util wrap = None +# Extend core typeinfo with CHARACTER to test dtype('c') +_ti = _typeinfo['STRING'] +typeinfo = dict( + CHARACTER=type(_ti)(('c', _ti.num, 8, _ti.alignment, _ti.type)), + **_typeinfo) + def setup_module(): """ @@ -56,6 +63,7 @@ def flags2names(flags): "NOTSWAPPED", "WRITEABLE", "WRITEBACKIFCOPY", + "UPDATEIFCOPY", "BEHAVED", "BEHAVED_RO", "CARRAY", @@ -116,6 +124,9 @@ _type_names = [ "FLOAT", "DOUBLE", "CFLOAT", + "STRING1", + "STRING5", + "CHARACTER", ] _cast_dict = {"BOOL": ["BOOL"]} @@ -139,6 +150,10 @@ _cast_dict["DOUBLE"] = _cast_dict["INT"] + ["UINT", "FLOAT", "DOUBLE"] _cast_dict["CFLOAT"] = _cast_dict["FLOAT"] + ["CFLOAT"] +_cast_dict['STRING1'] = ['STRING1'] +_cast_dict['STRING5'] = ['STRING5'] +_cast_dict['CHARACTER'] = ['CHARACTER'] + # 32 bit system malloc typically does not provide the alignment required by # 16 byte long double types this means the inout intent cannot be satisfied # and several tests fail as the alignment flag can be randomly true or fals @@ -184,14 +199,33 @@ class Type: def _init(self, name): self.NAME = name.upper() - info = typeinfo[self.NAME] - self.type_num = getattr(wrap, "NPY_" + self.NAME) + + if self.NAME == 'CHARACTER': + info = typeinfo[self.NAME] + self.type_num = getattr(wrap, 'NPY_STRING') + self.elsize = 1 + self.dtype = np.dtype('c') + elif self.NAME.startswith('STRING'): + info = typeinfo[self.NAME[:6]] + self.type_num = getattr(wrap, 'NPY_STRING') + self.elsize = int(self.NAME[6:] or 0) + self.dtype = np.dtype(f'S{self.elsize}') + else: + info = typeinfo[self.NAME] + self.type_num = getattr(wrap, 'NPY_' + self.NAME) + self.elsize = info.bits // 8 + self.dtype = np.dtype(info.type) + assert self.type_num == info.num - self.dtype = np.dtype(info.type) self.type = info.type - self.elsize = info.bits / 8 self.dtypechar = info.char + def __repr__(self): + return (f"Type({self.NAME})|type_num={self.type_num}," + f" dtype={self.dtype}," + f" type={self.type}, elsize={self.elsize}," + f" dtypechar={self.dtypechar}") + def cast_types(self): return [self.__class__(_m) for _m in _cast_dict[self.NAME]] @@ -226,6 +260,11 @@ class Type: class Array: + + def __repr__(self): + return (f'Array({self.type}, {self.dims}, {self.intent},' + f' {self.obj})|arr={self.arr}') + def __init__(self, typ, dims, intent, obj): self.type = typ self.dims = dims @@ -234,7 +273,9 @@ class Array: self.obj = obj # arr.dtypechar may be different from typ.dtypechar - self.arr = wrap.call(typ.type_num, dims, intent.flags, obj) + self.arr = wrap.call(typ.type_num, + typ.elsize, + dims, intent.flags, obj) assert isinstance(self.arr, np.ndarray) @@ -289,7 +330,9 @@ class Array: self.arr.tobytes(), self.pyarr.tobytes(), )) # strides - assert self.arr_attr[5][-2:] == self.pyarr_attr[5][-2:] # descr + assert self.arr_attr[5][-2:] == self.pyarr_attr[5][-2:], repr(( + self.arr_attr[5], self.pyarr_attr[5] + )) # descr assert self.arr_attr[6] == self.pyarr_attr[6], repr(( self.arr_attr[6], self.pyarr_attr[6], @@ -338,8 +381,6 @@ class TestIntent: class TestSharedMemory: - num2seq = [1, 2] - num23seq = [[1, 2, 3], [4, 5, 6]] @pytest.fixture(autouse=True, scope="class", params=_type_names) def setup_type(self, request): @@ -347,6 +388,21 @@ class TestSharedMemory: request.cls.array = lambda self, dims, intent, obj: Array( Type(request.param), dims, intent, obj) + @property + def num2seq(self): + if self.type.NAME.startswith('STRING'): + elsize = self.type.elsize + return ['1' * elsize, '2' * elsize] + return [1, 2] + + @property + def num23seq(self): + if self.type.NAME.startswith('STRING'): + elsize = self.type.elsize + return [['1' * elsize, '2' * elsize, '3' * elsize], + ['4' * elsize, '5' * elsize, '6' * elsize]] + return [[1, 2, 3], [4, 5, 6]] + def test_in_from_2seq(self): a = self.array([2], intent.in_, self.num2seq) assert not a.has_shared_memory() @@ -367,9 +423,9 @@ class TestSharedMemory: """Test if intent(in) array can be passed without copies""" seq = getattr(self, "num" + inp) obj = np.array(seq, dtype=self.type.dtype, order=order) - obj.setflags(write=(write == "w")) + obj.setflags(write=(write == 'w')) a = self.array(obj.shape, - ((order == "C" and intent.in_.c) or intent.in_), obj) + ((order == 'C' and intent.in_.c) or intent.in_), obj) assert a.has_shared_memory() def test_inout_2seq(self): @@ -496,6 +552,9 @@ class TestSharedMemory: def test_in_cache_from_2casttype_failure(self): for t in self.type.all_types(): + if t.NAME == 'STRING': + # string elsize is 0, so skipping the test + continue if t.elsize >= self.type.elsize: continue obj = np.array(self.num2seq, dtype=t.dtype) diff --git a/numpy/f2py/tests/test_callback.py b/numpy/f2py/tests/test_callback.py index 4e91430fd..335b78413 100644 --- a/numpy/f2py/tests/test_callback.py +++ b/numpy/f2py/tests/test_callback.py @@ -84,8 +84,8 @@ class TestF77Callback(util.F2PyTest): r = t(a.mth) assert r == 9 - @pytest.mark.skipif(sys.platform == "win32", - reason="Fails with MinGW64 Gfortran (Issue #9673)") + @pytest.mark.skipif(sys.platform == 'win32', + reason='Fails with MinGW64 Gfortran (Issue #9673)') def test_string_callback(self): def callback(code): if code == "r": @@ -97,24 +97,27 @@ class TestF77Callback(util.F2PyTest): r = f(callback) assert r == 0 - @pytest.mark.skipif(sys.platform == "win32", - reason="Fails with MinGW64 Gfortran (Issue #9673)") + @pytest.mark.skipif(sys.platform == 'win32', + reason='Fails with MinGW64 Gfortran (Issue #9673)') def test_string_callback_array(self): # See gh-10027 - cu = np.zeros((1, 8), "S1") + cu1 = np.zeros((1, ), "S8") + cu2 = np.zeros((1, 8), "c") + cu3 = np.array([""], "S8") def callback(cu, lencu): - if cu.shape != (lencu, 8): + if cu.shape != (lencu,): return 1 - if cu.dtype != "S1": + if cu.dtype != "S8": return 2 if not np.all(cu == b""): return 3 return 0 f = getattr(self.module, "string_callback_array") - res = f(callback, cu, len(cu)) - assert res == 0 + for cu in [cu1, cu2, cu3]: + res = f(callback, cu, len(cu)) + assert res == 0 def test_threadsafety(self): # Segfaults if the callback handling is not threadsafe @@ -223,6 +226,5 @@ class TestGH18335(util.F2PyTest): def foo(x): x[0] += 1 - y = np.array([1, 2, 3], dtype=np.int8) r = self.module.gh18335(foo) assert r == 123 + 1 diff --git a/numpy/f2py/tests/test_character.py b/numpy/f2py/tests/test_character.py new file mode 100644 index 000000000..b54b4d981 --- /dev/null +++ b/numpy/f2py/tests/test_character.py @@ -0,0 +1,570 @@ +import pytest +import textwrap +from numpy.testing import assert_array_equal, assert_equal, assert_raises +import numpy as np +from numpy.f2py.tests import util + + +class TestCharacterString(util.F2PyTest): + # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py'] + suffix = '.f90' + fprefix = 'test_character_string' + length_list = ['1', '3', 'star'] + + code = '' + for length in length_list: + fsuffix = length + clength = dict(star='(*)').get(length, length) + + code += textwrap.dedent(f""" + + subroutine {fprefix}_input_{fsuffix}(c, o, n) + character*{clength}, intent(in) :: c + integer n + !f2py integer, depend(c), intent(hide) :: n = slen(c) + integer*1, dimension(n) :: o + !f2py intent(out) o + o = transfer(c, o) + end subroutine {fprefix}_input_{fsuffix} + + subroutine {fprefix}_output_{fsuffix}(c, o, n) + character*{clength}, intent(out) :: c + integer n + integer*1, dimension(n), intent(in) :: o + !f2py integer, depend(o), intent(hide) :: n = len(o) + c = transfer(o, c) + end subroutine {fprefix}_output_{fsuffix} + + subroutine {fprefix}_array_input_{fsuffix}(c, o, m, n) + integer m, i, n + character*{clength}, intent(in), dimension(m) :: c + !f2py integer, depend(c), intent(hide) :: m = len(c) + !f2py integer, depend(c), intent(hide) :: n = f2py_itemsize(c) + integer*1, dimension(m, n), intent(out) :: o + do i=1,m + o(i, :) = transfer(c(i), o(i, :)) + end do + end subroutine {fprefix}_array_input_{fsuffix} + + subroutine {fprefix}_array_output_{fsuffix}(c, o, m, n) + character*{clength}, intent(out), dimension(m) :: c + integer n + integer*1, dimension(m, n), intent(in) :: o + !f2py character(f2py_len=n) :: c + !f2py integer, depend(o), intent(hide) :: m = len(o) + !f2py integer, depend(o), intent(hide) :: n = shape(o, 1) + do i=1,m + c(i) = transfer(o(i, :), c(i)) + end do + end subroutine {fprefix}_array_output_{fsuffix} + + subroutine {fprefix}_2d_array_input_{fsuffix}(c, o, m1, m2, n) + integer m1, m2, i, j, n + character*{clength}, intent(in), dimension(m1, m2) :: c + !f2py integer, depend(c), intent(hide) :: m1 = len(c) + !f2py integer, depend(c), intent(hide) :: m2 = shape(c, 1) + !f2py integer, depend(c), intent(hide) :: n = f2py_itemsize(c) + integer*1, dimension(m1, m2, n), intent(out) :: o + do i=1,m1 + do j=1,m2 + o(i, j, :) = transfer(c(i, j), o(i, j, :)) + end do + end do + end subroutine {fprefix}_2d_array_input_{fsuffix} + """) + + @pytest.mark.parametrize("length", length_list) + def test_input(self, length): + fsuffix = {'(*)': 'star'}.get(length, length) + f = getattr(self.module, self.fprefix + '_input_' + fsuffix) + + a = {'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length] + + assert_array_equal(f(a), np.array(list(map(ord, a)), dtype='u1')) + + @pytest.mark.parametrize("length", length_list[:-1]) + def test_output(self, length): + fsuffix = length + f = getattr(self.module, self.fprefix + '_output_' + fsuffix) + + a = {'1': 'a', '3': 'abc'}[length] + + assert_array_equal(f(np.array(list(map(ord, a)), dtype='u1')), + a.encode()) + + @pytest.mark.parametrize("length", length_list) + def test_array_input(self, length): + fsuffix = length + f = getattr(self.module, self.fprefix + '_array_input_' + fsuffix) + + a = np.array([{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length], + {'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length], + ], dtype='S') + + expected = np.array([[c for c in s] for s in a], dtype='u1') + assert_array_equal(f(a), expected) + + @pytest.mark.parametrize("length", length_list) + def test_array_output(self, length): + fsuffix = length + f = getattr(self.module, self.fprefix + '_array_output_' + fsuffix) + + expected = np.array( + [{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length], + {'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length]], dtype='S') + + a = np.array([[c for c in s] for s in expected], dtype='u1') + assert_array_equal(f(a), expected) + + @pytest.mark.parametrize("length", length_list) + def test_2d_array_input(self, length): + fsuffix = length + f = getattr(self.module, self.fprefix + '_2d_array_input_' + fsuffix) + + a = np.array([[{'1': 'a', '3': 'abc', 'star': 'abcde' * 3}[length], + {'1': 'A', '3': 'ABC', 'star': 'ABCDE' * 3}[length]], + [{'1': 'f', '3': 'fgh', 'star': 'fghij' * 3}[length], + {'1': 'F', '3': 'FGH', 'star': 'FGHIJ' * 3}[length]]], + dtype='S') + expected = np.array([[[c for c in item] for item in row] for row in a], + dtype='u1', order='F') + assert_array_equal(f(a), expected) + + +class TestCharacter(util.F2PyTest): + # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py'] + suffix = '.f90' + fprefix = 'test_character' + + code = textwrap.dedent(f""" + subroutine {fprefix}_input(c, o) + character, intent(in) :: c + integer*1 o + !f2py intent(out) o + o = transfer(c, o) + end subroutine {fprefix}_input + + subroutine {fprefix}_output(c, o) + character :: c + integer*1, intent(in) :: o + !f2py intent(out) c + c = transfer(o, c) + end subroutine {fprefix}_output + + subroutine {fprefix}_input_output(c, o) + character, intent(in) :: c + character o + !f2py intent(out) o + o = c + end subroutine {fprefix}_input_output + + subroutine {fprefix}_inout(c, n) + character :: c, n + !f2py intent(in) n + !f2py intent(inout) c + c = n + end subroutine {fprefix}_inout + + function {fprefix}_return(o) result (c) + character :: c + character, intent(in) :: o + c = transfer(o, c) + end function {fprefix}_return + + subroutine {fprefix}_array_input(c, o) + character, intent(in) :: c(3) + integer*1 o(3) + !f2py intent(out) o + integer i + do i=1,3 + o(i) = transfer(c(i), o(i)) + end do + end subroutine {fprefix}_array_input + + subroutine {fprefix}_2d_array_input(c, o) + character, intent(in) :: c(2, 3) + integer*1 o(2, 3) + !f2py intent(out) o + integer i, j + do i=1,2 + do j=1,3 + o(i, j) = transfer(c(i, j), o(i, j)) + end do + end do + end subroutine {fprefix}_2d_array_input + + subroutine {fprefix}_array_output(c, o) + character :: c(3) + integer*1, intent(in) :: o(3) + !f2py intent(out) c + do i=1,3 + c(i) = transfer(o(i), c(i)) + end do + end subroutine {fprefix}_array_output + + subroutine {fprefix}_array_inout(c, n) + character :: c(3), n(3) + !f2py intent(in) n(3) + !f2py intent(inout) c(3) + do i=1,3 + c(i) = n(i) + end do + end subroutine {fprefix}_array_inout + + subroutine {fprefix}_2d_array_inout(c, n) + character :: c(2, 3), n(2, 3) + !f2py intent(in) n(2, 3) + !f2py intent(inout) c(2. 3) + integer i, j + do i=1,2 + do j=1,3 + c(i, j) = n(i, j) + end do + end do + end subroutine {fprefix}_2d_array_inout + + function {fprefix}_array_return(o) result (c) + character, dimension(3) :: c + character, intent(in) :: o(3) + do i=1,3 + c(i) = o(i) + end do + end function {fprefix}_array_return + + function {fprefix}_optional(o) result (c) + character, intent(in) :: o + !f2py character o = "a" + character :: c + c = o + end function {fprefix}_optional + """) + + @pytest.mark.parametrize("dtype", ['c', 'S1']) + def test_input(self, dtype): + f = getattr(self.module, self.fprefix + '_input') + + assert_equal(f(np.array('a', dtype=dtype)), ord('a')) + assert_equal(f(np.array(b'a', dtype=dtype)), ord('a')) + assert_equal(f(np.array(['a'], dtype=dtype)), ord('a')) + assert_equal(f(np.array('abc', dtype=dtype)), ord('a')) + assert_equal(f(np.array([['a']], dtype=dtype)), ord('a')) + + def test_input_varia(self): + f = getattr(self.module, self.fprefix + '_input') + + assert_equal(f('a'), ord('a')) + assert_equal(f(b'a'), ord(b'a')) + assert_equal(f(''), 0) + assert_equal(f(b''), 0) + assert_equal(f(b'\0'), 0) + assert_equal(f('ab'), ord('a')) + assert_equal(f(b'ab'), ord('a')) + assert_equal(f(['a']), ord('a')) + + assert_equal(f(np.array(b'a')), ord('a')) + assert_equal(f(np.array([b'a'])), ord('a')) + a = np.array('a') + assert_equal(f(a), ord('a')) + a = np.array(['a']) + assert_equal(f(a), ord('a')) + + try: + f([]) + except IndexError as msg: + if not str(msg).endswith(' got 0-list'): + raise + else: + raise SystemError(f'{f.__name__} should have failed on empty list') + + try: + f(97) + except TypeError as msg: + if not str(msg).endswith(' got int instance'): + raise + else: + raise SystemError(f'{f.__name__} should have failed on int value') + + @pytest.mark.parametrize("dtype", ['c', 'S1', 'U1']) + def test_array_input(self, dtype): + f = getattr(self.module, self.fprefix + '_array_input') + + assert_array_equal(f(np.array(['a', 'b', 'c'], dtype=dtype)), + np.array(list(map(ord, 'abc')), dtype='i1')) + assert_array_equal(f(np.array([b'a', b'b', b'c'], dtype=dtype)), + np.array(list(map(ord, 'abc')), dtype='i1')) + + def test_array_input_varia(self): + f = getattr(self.module, self.fprefix + '_array_input') + assert_array_equal(f(['a', 'b', 'c']), + np.array(list(map(ord, 'abc')), dtype='i1')) + assert_array_equal(f([b'a', b'b', b'c']), + np.array(list(map(ord, 'abc')), dtype='i1')) + + try: + f(['a', 'b', 'c', 'd']) + except ValueError as msg: + if not str(msg).endswith( + 'th dimension must be fixed to 3 but got 4'): + raise + else: + raise SystemError( + f'{f.__name__} should have failed on wrong input') + + @pytest.mark.parametrize("dtype", ['c', 'S1', 'U1']) + def test_2d_array_input(self, dtype): + f = getattr(self.module, self.fprefix + '_2d_array_input') + + a = np.array([['a', 'b', 'c'], + ['d', 'e', 'f']], dtype=dtype, order='F') + expected = a.view(np.uint32 if dtype == 'U1' else np.uint8) + assert_array_equal(f(a), expected) + + def test_output(self): + f = getattr(self.module, self.fprefix + '_output') + + assert_equal(f(ord(b'a')), b'a') + assert_equal(f(0), b'\0') + + def test_array_output(self): + f = getattr(self.module, self.fprefix + '_array_output') + + assert_array_equal(f(list(map(ord, 'abc'))), + np.array(list('abc'), dtype='S1')) + + def test_input_output(self): + f = getattr(self.module, self.fprefix + '_input_output') + + assert_equal(f(b'a'), b'a') + assert_equal(f('a'), b'a') + assert_equal(f(''), b'\0') + + @pytest.mark.parametrize("dtype", ['c', 'S1']) + def test_inout(self, dtype): + f = getattr(self.module, self.fprefix + '_inout') + + a = np.array(list('abc'), dtype=dtype) + f(a, 'A') + assert_array_equal(a, np.array(list('Abc'), dtype=a.dtype)) + f(a[1:], 'B') + assert_array_equal(a, np.array(list('ABc'), dtype=a.dtype)) + + a = np.array(['abc'], dtype=dtype) + f(a, 'A') + assert_array_equal(a, np.array(['Abc'], dtype=a.dtype)) + + def test_inout_varia(self): + f = getattr(self.module, self.fprefix + '_inout') + a = np.array('abc', dtype='S3') + f(a, 'A') + assert_array_equal(a, np.array('Abc', dtype=a.dtype)) + + a = np.array(['abc'], dtype='S3') + f(a, 'A') + assert_array_equal(a, np.array(['Abc'], dtype=a.dtype)) + + try: + f('abc', 'A') + except ValueError as msg: + if not str(msg).endswith(' got 3-str'): + raise + else: + raise SystemError(f'{f.__name__} should have failed on str value') + + @pytest.mark.parametrize("dtype", ['c', 'S1']) + def test_array_inout(self, dtype): + f = getattr(self.module, self.fprefix + '_array_inout') + n = np.array(['A', 'B', 'C'], dtype=dtype, order='F') + + a = np.array(['a', 'b', 'c'], dtype=dtype, order='F') + f(a, n) + assert_array_equal(a, n) + + a = np.array(['a', 'b', 'c', 'd'], dtype=dtype) + f(a[1:], n) + assert_array_equal(a, np.array(['a', 'A', 'B', 'C'], dtype=dtype)) + + a = np.array([['a', 'b', 'c']], dtype=dtype, order='F') + f(a, n) + assert_array_equal(a, np.array([['A', 'B', 'C']], dtype=dtype)) + + a = np.array(['a', 'b', 'c', 'd'], dtype=dtype, order='F') + try: + f(a, n) + except ValueError as msg: + if not str(msg).endswith( + 'th dimension must be fixed to 3 but got 4'): + raise + else: + raise SystemError( + f'{f.__name__} should have failed on wrong input') + + @pytest.mark.parametrize("dtype", ['c', 'S1']) + def test_2d_array_inout(self, dtype): + f = getattr(self.module, self.fprefix + '_2d_array_inout') + n = np.array([['A', 'B', 'C'], + ['D', 'E', 'F']], + dtype=dtype, order='F') + a = np.array([['a', 'b', 'c'], + ['d', 'e', 'f']], + dtype=dtype, order='F') + f(a, n) + assert_array_equal(a, n) + + def test_return(self): + f = getattr(self.module, self.fprefix + '_return') + + assert_equal(f('a'), b'a') + + @pytest.mark.skip('fortran function returning array segfaults') + def test_array_return(self): + f = getattr(self.module, self.fprefix + '_array_return') + + a = np.array(list('abc'), dtype='S1') + assert_array_equal(f(a), a) + + def test_optional(self): + f = getattr(self.module, self.fprefix + '_optional') + + assert_equal(f(), b"a") + assert_equal(f(b'B'), b"B") + + +class TestMiscCharacter(util.F2PyTest): + # options = ['--debug-capi', '--build-dir', '/tmp/test-build-f2py'] + suffix = '.f90' + fprefix = 'test_misc_character' + + code = textwrap.dedent(f""" + subroutine {fprefix}_gh18684(x, y, m) + character(len=5), dimension(m), intent(in) :: x + character*5, dimension(m), intent(out) :: y + integer i, m + !f2py integer, intent(hide), depend(x) :: m = f2py_len(x) + do i=1,m + y(i) = x(i) + end do + end subroutine {fprefix}_gh18684 + + subroutine {fprefix}_gh6308(x, i) + integer i + !f2py check(i>=0 && i<12) i + character*5 name, x + common name(12) + name(i + 1) = x + end subroutine {fprefix}_gh6308 + + subroutine {fprefix}_gh4519(x) + character(len=*), intent(in) :: x(:) + !f2py intent(out) x + integer :: i + do i=1, size(x) + print*, "x(",i,")=", x(i) + end do + end subroutine {fprefix}_gh4519 + + pure function {fprefix}_gh3425(x) result (y) + character(len=*), intent(in) :: x + character(len=len(x)) :: y + integer :: i + do i = 1, len(x) + j = iachar(x(i:i)) + if (j>=iachar("a") .and. j<=iachar("z") ) then + y(i:i) = achar(j-32) + else + y(i:i) = x(i:i) + endif + end do + end function {fprefix}_gh3425 + + subroutine {fprefix}_character_bc_new(x, y, z) + character, intent(in) :: x + character, intent(out) :: y + !f2py character, depend(x) :: y = x + !f2py character, dimension((x=='a'?1:2)), depend(x), intent(out) :: z + character, dimension(*) :: z + !f2py character, optional, check(x == 'a' || x == 'b') :: x = 'a' + !f2py callstatement (*f2py_func)(&x, &y, z) + !f2py callprotoargument character*, character*, character* + if (y.eq.x) then + y = x + else + y = 'e' + endif + z(1) = 'c' + end subroutine {fprefix}_character_bc_new + + subroutine {fprefix}_character_bc_old(x, y, z) + character, intent(in) :: x + character, intent(out) :: y + !f2py character, depend(x) :: y = x[0] + !f2py character, dimension((*x=='a'?1:2)), depend(x), intent(out) :: z + character, dimension(*) :: z + !f2py character, optional, check(*x == 'a' || x[0] == 'b') :: x = 'a' + !f2py callstatement (*f2py_func)(x, y, z) + !f2py callprotoargument char*, char*, char* + if (y.eq.x) then + y = x + else + y = 'e' + endif + z(1) = 'c' + end subroutine {fprefix}_character_bc_old + """) + + def test_gh18684(self): + # Test character(len=5) and character*5 usages + f = getattr(self.module, self.fprefix + '_gh18684') + x = np.array(["abcde", "fghij"], dtype='S5') + y = f(x) + + assert_array_equal(x, y) + + def test_gh6308(self): + # Test character string array in a common block + f = getattr(self.module, self.fprefix + '_gh6308') + + assert_equal(self.module._BLNK_.name.dtype, np.dtype('S5')) + assert_equal(len(self.module._BLNK_.name), 12) + f("abcde", 0) + assert_equal(self.module._BLNK_.name[0], b"abcde") + f("12345", 5) + assert_equal(self.module._BLNK_.name[5], b"12345") + + def test_gh4519(self): + # Test array of assumed length strings + f = getattr(self.module, self.fprefix + '_gh4519') + + for x, expected in [ + ('a', dict(shape=(), dtype=np.dtype('S1'))), + ('text', dict(shape=(), dtype=np.dtype('S4'))), + (np.array(['1', '2', '3'], dtype='S1'), + dict(shape=(3,), dtype=np.dtype('S1'))), + (['1', '2', '34'], + dict(shape=(3,), dtype=np.dtype('S2'))), + (['', ''], dict(shape=(2,), dtype=np.dtype('S1')))]: + r = f(x) + for k, v in expected.items(): + assert_equal(getattr(r, k), v) + + def test_gh3425(self): + # Test returning a copy of assumed length string + f = getattr(self.module, self.fprefix + '_gh3425') + # f is equivalent to bytes.upper + + assert_equal(f('abC'), b'ABC') + assert_equal(f(''), b'') + assert_equal(f('abC12d'), b'ABC12D') + + @pytest.mark.parametrize("state", ['new', 'old']) + def test_character_bc(self, state): + f = getattr(self.module, self.fprefix + '_character_bc_' + state) + + c, a = f() + assert_equal(c, b'a') + assert_equal(len(a), 1) + + c, a = f(b'b') + assert_equal(c, b'b') + assert_equal(len(a), 2) + + assert_raises(Exception, lambda: f(b'c')) diff --git a/numpy/f2py/tests/test_crackfortran.py b/numpy/f2py/tests/test_crackfortran.py index ea618bf33..53edfc091 100644 --- a/numpy/f2py/tests/test_crackfortran.py +++ b/numpy/f2py/tests/test_crackfortran.py @@ -1,3 +1,4 @@ +import codecs import pytest import numpy as np from numpy.f2py.crackfortran import markinnerspaces @@ -18,7 +19,7 @@ class TestNoSpace(util.F2PyTest): assert np.allclose(k, w + 1) self.module.subc([w, k]) assert np.allclose(k, w + 1) - assert self.module.t0(23) == b"2" + assert self.module.t0("23") == b"2" class TestPublicPrivate: @@ -122,7 +123,6 @@ class TestMarkinnerspaces: assert markinnerspaces("a 'b c' 'd e'") == "a 'b@_@c' 'd@_@e'" assert markinnerspaces(r'a "b c" "d e"') == r'a "b@_@c" "d@_@e"' - class TestDimSpec(util.F2PyTest): """This test suite tests various expressions that are used as dimension specifications. @@ -231,3 +231,30 @@ class TestModuleDeclaration: mod = crackfortran.crackfortran([str(fpath)]) assert len(mod) == 1 assert mod[0]["vars"]["abar"]["="] == "bar('abar')" + +class TestEval(util.F2PyTest): + def test_eval_scalar(self): + eval_scalar = crackfortran._eval_scalar + + assert eval_scalar('123', {}) == '123' + assert eval_scalar('12 + 3', {}) == '15' + assert eval_scalar('a + b', dict(a=1, b=2)) == '3' + assert eval_scalar('"123"', {}) == "'123'" + + +class TestFortranReader(util.F2PyTest): + @pytest.mark.parametrize("encoding", + ['ascii', 'utf-8', 'utf-16', 'utf-32']) + def test_input_encoding(self, tmp_path, encoding): + # gh-635 + f_path = tmp_path / f"input_with_{encoding}_encoding.f90" + # explicit BOM is required for UTF8 + bom = {'utf-8': codecs.BOM_UTF8}.get(encoding, b'') + with f_path.open('w', encoding=encoding) as ff: + ff.write(bom.decode(encoding) + + """ + subroutine foo() + end subroutine foo + """) + mod = crackfortran.crackfortran([str(f_path)]) + assert mod[0]['name'] == 'foo' diff --git a/numpy/f2py/tests/test_docs.py b/numpy/f2py/tests/test_docs.py new file mode 100644 index 000000000..4aa5f5f5c --- /dev/null +++ b/numpy/f2py/tests/test_docs.py @@ -0,0 +1,55 @@ +import os +import pytest +import numpy as np +from numpy.testing import assert_array_equal, assert_equal +from . import util + + +def get_docdir(): + # assuming that documentation tests are run from a source + # directory + return os.path.abspath(os.path.join( + os.path.dirname(__file__), + '..', '..', '..', + 'doc', 'source', 'f2py')) + + +pytestmark = pytest.mark.skipif( + not os.path.isdir(get_docdir()), + reason=('Could not find f2py documentation sources' + f' ({get_docdir()} does not exists)')) + + +def _path(*a): + return os.path.join(*((get_docdir(),) + a)) + + +class TestDocAdvanced(util.F2PyTest): + # options = ['--debug-capi', '--build-dir', '/tmp/build-f2py'] + sources = [_path('asterisk1.f90'), _path('asterisk2.f90'), + _path('ftype.f')] + + def test_asterisk1(self): + foo = getattr(self.module, 'foo1') + assert_equal(foo(), b'123456789A12') + + def test_asterisk2(self): + foo = getattr(self.module, 'foo2') + assert_equal(foo(2), b'12') + assert_equal(foo(12), b'123456789A12') + assert_equal(foo(24), b'123456789A123456789B') + + def test_ftype(self): + ftype = self.module + ftype.foo() + assert_equal(ftype.data.a, 0) + ftype.data.a = 3 + ftype.data.x = [1, 2, 3] + assert_equal(ftype.data.a, 3) + assert_array_equal(ftype.data.x, + np.array([1, 2, 3], dtype=np.float32)) + ftype.data.x[1] = 45 + assert_array_equal(ftype.data.x, + np.array([1, 45, 3], dtype=np.float32)) + + # TODO: implement test methods for other example Fortran codes diff --git a/numpy/f2py/tests/test_return_character.py b/numpy/f2py/tests/test_return_character.py index 21055faef..36c1f10f4 100644 --- a/numpy/f2py/tests/test_return_character.py +++ b/numpy/f2py/tests/test_return_character.py @@ -10,7 +10,7 @@ IS_S390X = platform.machine() == "s390x" class TestReturnCharacter(util.F2PyTest): def check_function(self, t, tname): if tname in ["t0", "t1", "s0", "s1"]: - assert t(23) == b"2" + assert t("23") == b"2" r = t("ab") assert r == b"a" r = t(array("ab")) diff --git a/numpy/f2py/tests/util.py b/numpy/f2py/tests/util.py index ae81bbfc4..5f815f9d7 100644 --- a/numpy/f2py/tests/util.py +++ b/numpy/f2py/tests/util.py @@ -58,7 +58,7 @@ def get_module_dir(): def get_temp_module_name(): # Assume single-threaded, and the module dir usable only by this thread global _module_num - d = get_module_dir() + get_module_dir() name = "_test_ext_module_%d" % _module_num _module_num += 1 if name in sys.modules: @@ -117,6 +117,8 @@ def build_module(source_files, options=[], skip=[], only=[], module_name=None): if ext in (".f90", ".f", ".c", ".pyf"): f2py_sources.append(dst) + assert f2py_sources + # Prepare options if module_name is None: module_name = get_temp_module_name() @@ -332,7 +334,11 @@ class F2PyTest: only = [] suffix = ".f" module = None - module_name = None + + @property + def module_name(self): + cls = type(self) + return f'_{cls.__module__.rsplit(".",1)[-1]}_{cls.__name__}_ext_module' def setup(self): if sys.platform == "win32": |