Merging PR_218 openai_rev package with new streamlit chat app

This commit is contained in:
noptuno
2023-04-27 20:29:30 -04:00
parent 479b8d6d10
commit 355dee533b
8378 changed files with 2931636 additions and 3 deletions

View File

@@ -0,0 +1,34 @@
module ops_module
abstract interface
subroutine op(x, y, z)
integer, intent(in) :: x, y
integer, intent(out) :: z
end subroutine
end interface
contains
subroutine foo(x, y, r1, r2)
integer, intent(in) :: x, y
integer, intent(out) :: r1, r2
procedure (op) add1, add2
procedure (op), pointer::p
p=>add1
call p(x, y, r1)
p=>add2
call p(x, y, r2)
end subroutine
end module
subroutine add1(x, y, z)
integer, intent(in) :: x, y
integer, intent(out) :: z
z = x + y
end subroutine
subroutine add2(x, y, z)
integer, intent(in) :: x, y
integer, intent(out) :: z
z = x + 2 * y
end subroutine

View File

@@ -0,0 +1,6 @@
module test
abstract interface
subroutine foo()
end subroutine
end interface
end module test

View File

@@ -0,0 +1,230 @@
/*
* This file was auto-generated with f2py (version:2_1330) and hand edited by
* Pearu for testing purposes. Do not edit this file unless you know what you
* are doing!!!
*/
#ifdef __cplusplus
extern "C" {
#endif
/*********************** See f2py2e/cfuncs.py: includes ***********************/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "fortranobject.h"
#include <math.h>
static PyObject *wrap_error;
static PyObject *wrap_module;
/************************************ call ************************************/
static char doc_f2py_rout_wrap_call[] = "\
Function signature:\n\
arr = call(type_num,dims,intent,obj)\n\
Required arguments:\n"
" type_num : input int\n"
" dims : input int-sequence\n"
" intent : input int\n"
" obj : input python object\n"
"Return objects:\n"
" arr : array";
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;
int intent = 0;
PyArrayObject *capi_arr_tmp = NULL;
PyObject *arr_capi = Py_None;
int i;
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));
for (i=0;i<rank;++i) {
PyObject *tmp;
tmp = PySequence_GetItem(dims_capi, i);
if (tmp == NULL) {
goto fail;
}
dims[i] = (npy_intp)PyLong_AsLong(tmp);
Py_DECREF(tmp);
if (dims[i] == -1 && PyErr_Occurred()) {
goto fail;
}
}
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;
}
capi_buildvalue = Py_BuildValue("N",capi_arr_tmp);
free(dims);
return capi_buildvalue;
fail:
free(dims);
return NULL;
}
static char doc_f2py_rout_wrap_attrs[] = "\
Function signature:\n\
arr = array_attrs(arr)\n\
Required arguments:\n"
" arr : input array object\n"
"Return objects:\n"
" data : data address in hex\n"
" nd : int\n"
" dimensions : tuple\n"
" strides : tuple\n"
" base : python object\n"
" (kind,type,type_num,elsize,alignment) : 4-tuple\n"
" flags : int\n"
" itemsize : int\n"
;
static PyObject *f2py_rout_wrap_attrs(PyObject *capi_self,
PyObject *capi_args) {
PyObject *arr_capi = Py_None;
PyArrayObject *arr = NULL;
PyObject *dimensions = NULL;
PyObject *strides = NULL;
char s[100];
int i;
memset(s,0,100);
if (!PyArg_ParseTuple(capi_args,"O!|:wrap.attrs",
&PyArray_Type,&arr_capi))
return NULL;
arr = (PyArrayObject *)arr_capi;
sprintf(s,"%p",PyArray_DATA(arr));
dimensions = PyTuple_New(PyArray_NDIM(arr));
strides = PyTuple_New(PyArray_NDIM(arr));
for (i=0;i<PyArray_NDIM(arr);++i) {
PyTuple_SetItem(dimensions,i,PyLong_FromLong(PyArray_DIM(arr,i)));
PyTuple_SetItem(strides,i,PyLong_FromLong(PyArray_STRIDE(arr,i)));
}
return Py_BuildValue("siNNO(cciii)ii",s,PyArray_NDIM(arr),
dimensions,strides,
(PyArray_BASE(arr)==NULL?Py_None:PyArray_BASE(arr)),
PyArray_DESCR(arr)->kind,
PyArray_DESCR(arr)->type,
PyArray_TYPE(arr),
PyArray_ITEMSIZE(arr),
PyArray_DESCR(arr)->alignment,
PyArray_FLAGS(arr),
PyArray_ITEMSIZE(arr));
}
static PyMethodDef f2py_module_methods[] = {
{"call",f2py_rout_wrap_call,METH_VARARGS,doc_f2py_rout_wrap_call},
{"array_attrs",f2py_rout_wrap_attrs,METH_VARARGS,doc_f2py_rout_wrap_attrs},
{NULL,NULL}
};
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"test_array_from_pyobj_ext",
NULL,
-1,
f2py_module_methods,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC PyInit_test_array_from_pyobj_ext(void) {
PyObject *m,*d, *s;
m = wrap_module = PyModule_Create(&moduledef);
Py_SET_TYPE(&PyFortran_Type, &PyType_Type);
import_array();
if (PyErr_Occurred())
Py_FatalError("can't initialize module wrap (failed to import numpy)");
d = PyModule_GetDict(m);
s = PyUnicode_FromString("This module 'wrap' is auto-generated with f2py (version:2_1330).\nFunctions:\n"
" arr = call(type_num,dims,intent,obj)\n"
".");
PyDict_SetItemString(d, "__doc__", s);
wrap_error = PyErr_NewException ("wrap.error", NULL, NULL);
Py_DECREF(s);
#define ADDCONST(NAME, CONST) \
s = PyLong_FromLong(CONST); \
PyDict_SetItemString(d, NAME, s); \
Py_DECREF(s)
ADDCONST("F2PY_INTENT_IN", F2PY_INTENT_IN);
ADDCONST("F2PY_INTENT_INOUT", F2PY_INTENT_INOUT);
ADDCONST("F2PY_INTENT_OUT", F2PY_INTENT_OUT);
ADDCONST("F2PY_INTENT_HIDE", F2PY_INTENT_HIDE);
ADDCONST("F2PY_INTENT_CACHE", F2PY_INTENT_CACHE);
ADDCONST("F2PY_INTENT_COPY", F2PY_INTENT_COPY);
ADDCONST("F2PY_INTENT_C", F2PY_INTENT_C);
ADDCONST("F2PY_OPTIONAL", F2PY_OPTIONAL);
ADDCONST("F2PY_INTENT_INPLACE", F2PY_INTENT_INPLACE);
ADDCONST("NPY_BOOL", NPY_BOOL);
ADDCONST("NPY_BYTE", NPY_BYTE);
ADDCONST("NPY_UBYTE", NPY_UBYTE);
ADDCONST("NPY_SHORT", NPY_SHORT);
ADDCONST("NPY_USHORT", NPY_USHORT);
ADDCONST("NPY_INT", NPY_INT);
ADDCONST("NPY_UINT", NPY_UINT);
ADDCONST("NPY_INTP", NPY_INTP);
ADDCONST("NPY_UINTP", NPY_UINTP);
ADDCONST("NPY_LONG", NPY_LONG);
ADDCONST("NPY_ULONG", NPY_ULONG);
ADDCONST("NPY_LONGLONG", NPY_LONGLONG);
ADDCONST("NPY_ULONGLONG", NPY_ULONGLONG);
ADDCONST("NPY_FLOAT", NPY_FLOAT);
ADDCONST("NPY_DOUBLE", NPY_DOUBLE);
ADDCONST("NPY_LONGDOUBLE", NPY_LONGDOUBLE);
ADDCONST("NPY_CFLOAT", NPY_CFLOAT);
ADDCONST("NPY_CDOUBLE", NPY_CDOUBLE);
ADDCONST("NPY_CLONGDOUBLE", NPY_CLONGDOUBLE);
ADDCONST("NPY_OBJECT", NPY_OBJECT);
ADDCONST("NPY_STRING", NPY_STRING);
ADDCONST("NPY_UNICODE", NPY_UNICODE);
ADDCONST("NPY_VOID", NPY_VOID);
ADDCONST("NPY_NTYPES", NPY_NTYPES);
ADDCONST("NPY_NOTYPE", NPY_NOTYPE);
ADDCONST("NPY_USERDEF", NPY_USERDEF);
ADDCONST("CONTIGUOUS", NPY_ARRAY_C_CONTIGUOUS);
ADDCONST("FORTRAN", NPY_ARRAY_F_CONTIGUOUS);
ADDCONST("OWNDATA", NPY_ARRAY_OWNDATA);
ADDCONST("FORCECAST", NPY_ARRAY_FORCECAST);
ADDCONST("ENSURECOPY", NPY_ARRAY_ENSURECOPY);
ADDCONST("ENSUREARRAY", NPY_ARRAY_ENSUREARRAY);
ADDCONST("ALIGNED", NPY_ARRAY_ALIGNED);
ADDCONST("WRITEABLE", NPY_ARRAY_WRITEABLE);
ADDCONST("WRITEBACKIFCOPY", NPY_ARRAY_WRITEBACKIFCOPY);
ADDCONST("BEHAVED", NPY_ARRAY_BEHAVED);
ADDCONST("BEHAVED_NS", NPY_ARRAY_BEHAVED_NS);
ADDCONST("CARRAY", NPY_ARRAY_CARRAY);
ADDCONST("FARRAY", NPY_ARRAY_FARRAY);
ADDCONST("CARRAY_RO", NPY_ARRAY_CARRAY_RO);
ADDCONST("FARRAY_RO", NPY_ARRAY_FARRAY_RO);
ADDCONST("DEFAULT", NPY_ARRAY_DEFAULT);
ADDCONST("UPDATE_ALL", NPY_ARRAY_UPDATE_ALL);
#undef ADDCONST(
if (PyErr_Occurred())
Py_FatalError("can't initialize module wrap");
#ifdef F2PY_REPORT_ATEXIT
on_exit(f2py_report_on_exit,(void*)"array_from_pyobj.wrap.call");
#endif
return m;
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1 @@
dict(real=dict(rk="double"))

View File

@@ -0,0 +1,34 @@
subroutine sum(x, res)
implicit none
real, intent(in) :: x(:)
real, intent(out) :: res
integer :: i
!print *, "sum: size(x) = ", size(x)
res = 0.0
do i = 1, size(x)
res = res + x(i)
enddo
end subroutine sum
function fsum(x) result (res)
implicit none
real, intent(in) :: x(:)
real :: res
integer :: i
!print *, "fsum: size(x) = ", size(x)
res = 0.0
do i = 1, size(x)
res = res + x(i)
enddo
end function fsum

View File

@@ -0,0 +1,41 @@
module mod
contains
subroutine sum(x, res)
implicit none
real, intent(in) :: x(:)
real, intent(out) :: res
integer :: i
!print *, "sum: size(x) = ", size(x)
res = 0.0
do i = 1, size(x)
res = res + x(i)
enddo
end subroutine sum
function fsum(x) result (res)
implicit none
real, intent(in) :: x(:)
real :: res
integer :: i
!print *, "fsum: size(x) = ", size(x)
res = 0.0
do i = 1, size(x)
res = res + x(i)
enddo
end function fsum
end module mod

View File

@@ -0,0 +1,19 @@
subroutine sum_with_use(x, res)
use precision
implicit none
real(kind=rk), intent(in) :: x(:)
real(kind=rk), intent(out) :: res
integer :: i
!print *, "size(x) = ", size(x)
res = 0.0
do i = 1, size(x)
res = res + x(i)
enddo
end subroutine

View File

@@ -0,0 +1,4 @@
module precision
integer, parameter :: rk = selected_real_kind(8)
integer, parameter :: ik = selected_real_kind(4)
end module

View File

@@ -0,0 +1,6 @@
SUBROUTINE FOO()
INTEGER BAR(2, 3)
COMMON /BLOCK/ BAR
RETURN
END

View File

@@ -0,0 +1,62 @@
subroutine t(fun,a)
integer a
cf2py intent(out) a
external fun
call fun(a)
end
subroutine func(a)
cf2py intent(in,out) a
integer a
a = a + 11
end
subroutine func0(a)
cf2py intent(out) a
integer a
a = 11
end
subroutine t2(a)
cf2py intent(callback) fun
integer a
cf2py intent(out) a
external fun
call fun(a)
end
subroutine string_callback(callback, a)
external callback
double precision callback
double precision a
character*1 r
cf2py intent(out) a
r = 'r'
a = callback(r)
end
subroutine string_callback_array(callback, cu, lencu, a)
external callback
integer callback
integer lencu
character*8 cu(lencu)
integer a
cf2py intent(out) a
a = callback(cu, lencu)
end
subroutine hidden_callback(a, r)
external global_f
cf2py intent(callback, hide) global_f
integer a, r, global_f
cf2py intent(out) r
r = global_f(a)
end
subroutine hidden_callback2(a, r)
external global_f
integer a, r, global_f
cf2py intent(out) r
r = global_f(a)
end

View File

@@ -0,0 +1,7 @@
function gh17797(f, y) result(r)
external f
integer(8) :: r, f
integer(8), dimension(:) :: y
r = f(0)
r = r + sum(y)
end function gh17797

View File

@@ -0,0 +1,17 @@
! When gh18335_workaround is defined as an extension,
! the issue cannot be reproduced.
!subroutine gh18335_workaround(f, y)
! implicit none
! external f
! integer(kind=1) :: y(1)
! call f(y)
!end subroutine gh18335_workaround
function gh18335(f) result (r)
implicit none
external f
integer(kind=1) :: y(1), r
y(1) = 123
call f(y)
r = y(1)
end function gh18335

View File

@@ -0,0 +1,3 @@
SUBROUTINE HI
PRINT*, "HELLO WORLD"
END SUBROUTINE

View File

@@ -0,0 +1,3 @@
function hi()
print*, "Hello World"
end function

View File

@@ -0,0 +1,11 @@
SUBROUTINE INITCB
DOUBLE PRECISION LONG
CHARACTER STRING
INTEGER OK
COMMON /BLOCK/ LONG, STRING, OK
LONG = 1.0
STRING = '2'
OK = 3
RETURN
END

View File

@@ -0,0 +1,13 @@
module foo
public
type, private, bind(c) :: a
integer :: i
end type a
type, bind(c) :: b_
integer :: j
end type b_
public :: b_
type :: c
integer :: k
end type c
end module foo

View File

@@ -0,0 +1,6 @@
module foo
type bar
character(len = 4) :: text
end type bar
type(bar), parameter :: abar = bar('abar')
end module foo

View File

@@ -0,0 +1,16 @@
subroutine subb(k)
real(8), intent(inout) :: k(:)
k=k+1
endsubroutine
subroutine subc(w,k)
real(8), intent(in) :: w(:)
real(8), intent(out) :: k(size(w))
k=w+1
endsubroutine
function t0(value)
character value
character t0
t0 = value
endfunction

View File

@@ -0,0 +1,12 @@
integer(8) function external_as_statement(fcn)
implicit none
external fcn
integer(8) :: fcn
external_as_statement = fcn(0)
end
integer(8) function external_as_attribute(fcn)
implicit none
integer(8), external :: fcn
external_as_attribute = fcn(0)
end

View File

@@ -0,0 +1,13 @@
subroutine gh2848( &
! first 2 parameters
par1, par2,&
! last 2 parameters
par3, par4)
integer, intent(in) :: par1, par2
integer, intent(out) :: par3, par4
par3 = par1
par4 = par2
end subroutine gh2848

View File

@@ -0,0 +1,49 @@
module foo
type bar
character(len = 32) :: item
end type bar
interface operator(.item.)
module procedure item_int, item_real
end interface operator(.item.)
interface operator(==)
module procedure items_are_equal
end interface operator(==)
interface assignment(=)
module procedure get_int, get_real
end interface assignment(=)
contains
function item_int(val) result(elem)
integer, intent(in) :: val
type(bar) :: elem
write(elem%item, "(I32)") val
end function item_int
function item_real(val) result(elem)
real, intent(in) :: val
type(bar) :: elem
write(elem%item, "(1PE32.12)") val
end function item_real
function items_are_equal(val1, val2) result(equal)
type(bar), intent(in) :: val1, val2
logical :: equal
equal = (val1%item == val2%item)
end function items_are_equal
subroutine get_real(rval, item)
real, intent(out) :: rval
type(bar), intent(in) :: item
read(item%item, *) rval
end subroutine get_real
subroutine get_int(rval, item)
integer, intent(out) :: rval
type(bar), intent(in) :: item
read(item%item, *) rval
end subroutine get_int
end module foo

View File

@@ -0,0 +1,11 @@
module foo
private
integer :: a
public :: setA
integer :: b
contains
subroutine setA(v)
integer, intent(in) :: v
a = v
end subroutine setA
end module foo

View File

@@ -0,0 +1,10 @@
module foo
public
integer, private :: a
public :: setA
contains
subroutine setA(v)
integer, intent(in) :: v
a = v
end subroutine setA
end module foo

View File

@@ -0,0 +1,10 @@
module foo
public
integer, private :: a
integer :: b
contains
subroutine setA(v)
integer, intent(in) :: v
a = v
end subroutine setA
end module foo

View File

@@ -0,0 +1,4 @@
subroutine foo(x)
real(8), intent(in) :: x
! Écrit à l'écran la valeur de x
end subroutine

View File

@@ -0,0 +1 @@
dict(real=dict(real32='float', real64='double'), integer=dict(int64='long_long'))

View File

@@ -0,0 +1,9 @@
subroutine func1(n, x, res)
use, intrinsic :: iso_fortran_env, only: int64, real64
implicit none
integer(int64), intent(in) :: n
real(real64), intent(in) :: x(n)
real(real64), intent(out) :: res
Cf2py intent(hide) :: n
res = sum(x)
end

View File

@@ -0,0 +1,20 @@
subroutine selectedrealkind(p, r, res)
implicit none
integer, intent(in) :: p, r
!f2py integer :: r=0
integer, intent(out) :: res
res = selected_real_kind(p, r)
end subroutine
subroutine selectedintkind(p, res)
implicit none
integer, intent(in) :: p
integer, intent(out) :: res
res = selected_int_kind(p)
end subroutine

View File

@@ -0,0 +1,5 @@
subroutine bar11(a)
cf2py intent(out) a
integer a
a = 11
end

View File

@@ -0,0 +1,8 @@
module foo_fixed
contains
subroutine bar12(a)
!f2py intent(out) a
integer a
a = 12
end subroutine bar12
end module foo_fixed

View File

@@ -0,0 +1,8 @@
module foo_free
contains
subroutine bar13(a)
!f2py intent(out) a
integer a
a = 13
end subroutine bar13
end module foo_free

View File

@@ -0,0 +1,12 @@
module mod
integer :: i
integer :: x(4)
real, dimension(2,3) :: a
real, allocatable, dimension(:,:) :: b
contains
subroutine foo
integer :: k
k = 1
a(1,2) = a(1,2)+3
end subroutine foo
end module mod

View File

@@ -0,0 +1,7 @@
subroutine foo(is_, ie_, arr, tout)
implicit none
integer :: is_,ie_
real, intent(in) :: arr(is_:ie_)
real, intent(out) :: tout(is_:ie_)
tout = arr
end

View File

@@ -0,0 +1,57 @@
! Check that parameters are correct intercepted.
! Constants with comma separations are commonly
! used, for instance Pi = 3._dp
subroutine foo(x)
implicit none
integer, parameter :: sp = selected_real_kind(6)
integer, parameter :: dp = selected_real_kind(15)
integer, parameter :: ii = selected_int_kind(9)
integer, parameter :: il = selected_int_kind(18)
real(dp), intent(inout) :: x
dimension x(3)
real(sp), parameter :: three_s = 3._sp
real(dp), parameter :: three_d = 3._dp
integer(ii), parameter :: three_i = 3_ii
integer(il), parameter :: three_l = 3_il
x(1) = x(1) + x(2) * three_s * three_i + x(3) * three_d * three_l
x(2) = x(2) * three_s
x(3) = x(3) * three_l
return
end subroutine
subroutine foo_no(x)
implicit none
integer, parameter :: sp = selected_real_kind(6)
integer, parameter :: dp = selected_real_kind(15)
integer, parameter :: ii = selected_int_kind(9)
integer, parameter :: il = selected_int_kind(18)
real(dp), intent(inout) :: x
dimension x(3)
real(sp), parameter :: three_s = 3.
real(dp), parameter :: three_d = 3.
integer(ii), parameter :: three_i = 3
integer(il), parameter :: three_l = 3
x(1) = x(1) + x(2) * three_s * three_i + x(3) * three_d * three_l
x(2) = x(2) * three_s
x(3) = x(3) * three_l
return
end subroutine
subroutine foo_sum(x)
implicit none
integer, parameter :: sp = selected_real_kind(6)
integer, parameter :: dp = selected_real_kind(15)
integer, parameter :: ii = selected_int_kind(9)
integer, parameter :: il = selected_int_kind(18)
real(dp), intent(inout) :: x
dimension x(3)
real(sp), parameter :: three_s = 2._sp + 1._sp
real(dp), parameter :: three_d = 1._dp + 2._dp
integer(ii), parameter :: three_i = 2_ii + 1_ii
integer(il), parameter :: three_l = 1_il + 2_il
x(1) = x(1) + x(2) * three_s * three_i + x(3) * three_d * three_l
x(2) = x(2) * three_s
x(3) = x(3) * three_l
return
end subroutine

View File

@@ -0,0 +1,15 @@
! Check that parameters are correct intercepted.
! Constants with comma separations are commonly
! used, for instance Pi = 3._dp
subroutine foo_compound_int(x)
implicit none
integer, parameter :: ii = selected_int_kind(9)
integer(ii), intent(inout) :: x
dimension x(3)
integer(ii), parameter :: three = 3_ii
integer(ii), parameter :: two = 2_ii
integer(ii), parameter :: six = three * 1_ii * two
x(1) = x(1) + x(2) + x(3) * six
return
end subroutine

View File

@@ -0,0 +1,22 @@
! Check that parameters are correct intercepted.
! Constants with comma separations are commonly
! used, for instance Pi = 3._dp
subroutine foo_int(x)
implicit none
integer, parameter :: ii = selected_int_kind(9)
integer(ii), intent(inout) :: x
dimension x(3)
integer(ii), parameter :: three = 3_ii
x(1) = x(1) + x(2) + x(3) * three
return
end subroutine
subroutine foo_long(x)
implicit none
integer, parameter :: ii = selected_int_kind(18)
integer(ii), intent(inout) :: x
dimension x(3)
integer(ii), parameter :: three = 3_ii
x(1) = x(1) + x(2) + x(3) * three
return
end subroutine

View File

@@ -0,0 +1,23 @@
! Check that parameters are correct intercepted.
! Specifically that types of constants without
! compound kind specs are correctly inferred
! adapted Gibbs iteration code from pymc
! for this test case
subroutine foo_non_compound_int(x)
implicit none
integer, parameter :: ii = selected_int_kind(9)
integer(ii) maxiterates
parameter (maxiterates=2)
integer(ii) maxseries
parameter (maxseries=2)
integer(ii) wasize
parameter (wasize=maxiterates*maxseries)
integer(ii), intent(inout) :: x
dimension x(wasize)
x(1) = x(1) + x(2) + x(3) + x(4) * wasize
return
end subroutine

View File

@@ -0,0 +1,23 @@
! Check that parameters are correct intercepted.
! Constants with comma separations are commonly
! used, for instance Pi = 3._dp
subroutine foo_single(x)
implicit none
integer, parameter :: rp = selected_real_kind(6)
real(rp), intent(inout) :: x
dimension x(3)
real(rp), parameter :: three = 3._rp
x(1) = x(1) + x(2) + x(3) * three
return
end subroutine
subroutine foo_double(x)
implicit none
integer, parameter :: rp = selected_real_kind(15)
real(rp), intent(inout) :: x
dimension x(3)
real(rp), parameter :: three = 3._rp
x(1) = x(1) + x(2) + x(3) * three
return
end subroutine

View File

@@ -0,0 +1,14 @@
SUBROUTINE FOO(OUT1, OUT2, OUT3, OUT4, OUT5, OUT6)
CHARACTER SINGLE, DOUBLE, SEMICOL, EXCLA, OPENPAR, CLOSEPAR
PARAMETER (SINGLE="'", DOUBLE='"', SEMICOL=';', EXCLA="!",
1 OPENPAR="(", CLOSEPAR=")")
CHARACTER OUT1, OUT2, OUT3, OUT4, OUT5, OUT6
Cf2py intent(out) OUT1, OUT2, OUT3, OUT4, OUT5, OUT6
OUT1 = SINGLE
OUT2 = DOUBLE
OUT3 = SEMICOL
OUT4 = EXCLA
OUT5 = OPENPAR
OUT6 = CLOSEPAR
RETURN
END

View File

@@ -0,0 +1,9 @@
! Check that intent(in out) translates as intent(inout).
! The separation seems to be a common usage.
subroutine foo(x)
implicit none
real(4), intent(in out) :: x
dimension x(3)
x(1) = x(1) + x(2) + x(3)
return
end

View File

@@ -0,0 +1,45 @@
function t0(value)
character value
character t0
t0 = value
end
function t1(value)
character*1 value
character*1 t1
t1 = value
end
function t5(value)
character*5 value
character*5 t5
t5 = value
end
function ts(value)
character*(*) value
character*(*) ts
ts = value
end
subroutine s0(t0,value)
character value
character t0
cf2py intent(out) t0
t0 = value
end
subroutine s1(t1,value)
character*1 value
character*1 t1
cf2py intent(out) t1
t1 = value
end
subroutine s5(t5,value)
character*5 value
character*5 t5
cf2py intent(out) t5
t5 = value
end
subroutine ss(ts,value)
character*(*) value
character*10 ts
cf2py intent(out) ts
ts = value
end

View File

@@ -0,0 +1,48 @@
module f90_return_char
contains
function t0(value)
character :: value
character :: t0
t0 = value
end function t0
function t1(value)
character(len=1) :: value
character(len=1) :: t1
t1 = value
end function t1
function t5(value)
character(len=5) :: value
character(len=5) :: t5
t5 = value
end function t5
function ts(value)
character(len=*) :: value
character(len=10) :: ts
ts = value
end function ts
subroutine s0(t0,value)
character :: value
character :: t0
!f2py intent(out) t0
t0 = value
end subroutine s0
subroutine s1(t1,value)
character(len=1) :: value
character(len=1) :: t1
!f2py intent(out) t1
t1 = value
end subroutine s1
subroutine s5(t5,value)
character(len=5) :: value
character(len=5) :: t5
!f2py intent(out) t5
t5 = value
end subroutine s5
subroutine ss(ts,value)
character(len=*) :: value
character(len=10) :: ts
!f2py intent(out) ts
ts = value
end subroutine ss
end module f90_return_char

View File

@@ -0,0 +1,45 @@
function t0(value)
complex value
complex t0
t0 = value
end
function t8(value)
complex*8 value
complex*8 t8
t8 = value
end
function t16(value)
complex*16 value
complex*16 t16
t16 = value
end
function td(value)
double complex value
double complex td
td = value
end
subroutine s0(t0,value)
complex value
complex t0
cf2py intent(out) t0
t0 = value
end
subroutine s8(t8,value)
complex*8 value
complex*8 t8
cf2py intent(out) t8
t8 = value
end
subroutine s16(t16,value)
complex*16 value
complex*16 t16
cf2py intent(out) t16
t16 = value
end
subroutine sd(td,value)
double complex value
double complex td
cf2py intent(out) td
td = value
end

View File

@@ -0,0 +1,48 @@
module f90_return_complex
contains
function t0(value)
complex :: value
complex :: t0
t0 = value
end function t0
function t8(value)
complex(kind=4) :: value
complex(kind=4) :: t8
t8 = value
end function t8
function t16(value)
complex(kind=8) :: value
complex(kind=8) :: t16
t16 = value
end function t16
function td(value)
double complex :: value
double complex :: td
td = value
end function td
subroutine s0(t0,value)
complex :: value
complex :: t0
!f2py intent(out) t0
t0 = value
end subroutine s0
subroutine s8(t8,value)
complex(kind=4) :: value
complex(kind=4) :: t8
!f2py intent(out) t8
t8 = value
end subroutine s8
subroutine s16(t16,value)
complex(kind=8) :: value
complex(kind=8) :: t16
!f2py intent(out) t16
t16 = value
end subroutine s16
subroutine sd(td,value)
double complex :: value
double complex :: td
!f2py intent(out) td
td = value
end subroutine sd
end module f90_return_complex

View File

@@ -0,0 +1,56 @@
function t0(value)
integer value
integer t0
t0 = value
end
function t1(value)
integer*1 value
integer*1 t1
t1 = value
end
function t2(value)
integer*2 value
integer*2 t2
t2 = value
end
function t4(value)
integer*4 value
integer*4 t4
t4 = value
end
function t8(value)
integer*8 value
integer*8 t8
t8 = value
end
subroutine s0(t0,value)
integer value
integer t0
cf2py intent(out) t0
t0 = value
end
subroutine s1(t1,value)
integer*1 value
integer*1 t1
cf2py intent(out) t1
t1 = value
end
subroutine s2(t2,value)
integer*2 value
integer*2 t2
cf2py intent(out) t2
t2 = value
end
subroutine s4(t4,value)
integer*4 value
integer*4 t4
cf2py intent(out) t4
t4 = value
end
subroutine s8(t8,value)
integer*8 value
integer*8 t8
cf2py intent(out) t8
t8 = value
end

View File

@@ -0,0 +1,59 @@
module f90_return_integer
contains
function t0(value)
integer :: value
integer :: t0
t0 = value
end function t0
function t1(value)
integer(kind=1) :: value
integer(kind=1) :: t1
t1 = value
end function t1
function t2(value)
integer(kind=2) :: value
integer(kind=2) :: t2
t2 = value
end function t2
function t4(value)
integer(kind=4) :: value
integer(kind=4) :: t4
t4 = value
end function t4
function t8(value)
integer(kind=8) :: value
integer(kind=8) :: t8
t8 = value
end function t8
subroutine s0(t0,value)
integer :: value
integer :: t0
!f2py intent(out) t0
t0 = value
end subroutine s0
subroutine s1(t1,value)
integer(kind=1) :: value
integer(kind=1) :: t1
!f2py intent(out) t1
t1 = value
end subroutine s1
subroutine s2(t2,value)
integer(kind=2) :: value
integer(kind=2) :: t2
!f2py intent(out) t2
t2 = value
end subroutine s2
subroutine s4(t4,value)
integer(kind=4) :: value
integer(kind=4) :: t4
!f2py intent(out) t4
t4 = value
end subroutine s4
subroutine s8(t8,value)
integer(kind=8) :: value
integer(kind=8) :: t8
!f2py intent(out) t8
t8 = value
end subroutine s8
end module f90_return_integer

View File

@@ -0,0 +1,56 @@
function t0(value)
logical value
logical t0
t0 = value
end
function t1(value)
logical*1 value
logical*1 t1
t1 = value
end
function t2(value)
logical*2 value
logical*2 t2
t2 = value
end
function t4(value)
logical*4 value
logical*4 t4
t4 = value
end
c function t8(value)
c logical*8 value
c logical*8 t8
c t8 = value
c end
subroutine s0(t0,value)
logical value
logical t0
cf2py intent(out) t0
t0 = value
end
subroutine s1(t1,value)
logical*1 value
logical*1 t1
cf2py intent(out) t1
t1 = value
end
subroutine s2(t2,value)
logical*2 value
logical*2 t2
cf2py intent(out) t2
t2 = value
end
subroutine s4(t4,value)
logical*4 value
logical*4 t4
cf2py intent(out) t4
t4 = value
end
c subroutine s8(t8,value)
c logical*8 value
c logical*8 t8
cf2py intent(out) t8
c t8 = value
c end

View File

@@ -0,0 +1,59 @@
module f90_return_logical
contains
function t0(value)
logical :: value
logical :: t0
t0 = value
end function t0
function t1(value)
logical(kind=1) :: value
logical(kind=1) :: t1
t1 = value
end function t1
function t2(value)
logical(kind=2) :: value
logical(kind=2) :: t2
t2 = value
end function t2
function t4(value)
logical(kind=4) :: value
logical(kind=4) :: t4
t4 = value
end function t4
function t8(value)
logical(kind=8) :: value
logical(kind=8) :: t8
t8 = value
end function t8
subroutine s0(t0,value)
logical :: value
logical :: t0
!f2py intent(out) t0
t0 = value
end subroutine s0
subroutine s1(t1,value)
logical(kind=1) :: value
logical(kind=1) :: t1
!f2py intent(out) t1
t1 = value
end subroutine s1
subroutine s2(t2,value)
logical(kind=2) :: value
logical(kind=2) :: t2
!f2py intent(out) t2
t2 = value
end subroutine s2
subroutine s4(t4,value)
logical(kind=4) :: value
logical(kind=4) :: t4
!f2py intent(out) t4
t4 = value
end subroutine s4
subroutine s8(t8,value)
logical(kind=8) :: value
logical(kind=8) :: t8
!f2py intent(out) t8
t8 = value
end subroutine s8
end module f90_return_logical

View File

@@ -0,0 +1,45 @@
function t0(value)
real value
real t0
t0 = value
end
function t4(value)
real*4 value
real*4 t4
t4 = value
end
function t8(value)
real*8 value
real*8 t8
t8 = value
end
function td(value)
double precision value
double precision td
td = value
end
subroutine s0(t0,value)
real value
real t0
cf2py intent(out) t0
t0 = value
end
subroutine s4(t4,value)
real*4 value
real*4 t4
cf2py intent(out) t4
t4 = value
end
subroutine s8(t8,value)
real*8 value
real*8 t8
cf2py intent(out) t8
t8 = value
end
subroutine sd(td,value)
double precision value
double precision td
cf2py intent(out) td
td = value
end

View File

@@ -0,0 +1,48 @@
module f90_return_real
contains
function t0(value)
real :: value
real :: t0
t0 = value
end function t0
function t4(value)
real(kind=4) :: value
real(kind=4) :: t4
t4 = value
end function t4
function t8(value)
real(kind=8) :: value
real(kind=8) :: t8
t8 = value
end function t8
function td(value)
double precision :: value
double precision :: td
td = value
end function td
subroutine s0(t0,value)
real :: value
real :: t0
!f2py intent(out) t0
t0 = value
end subroutine s0
subroutine s4(t4,value)
real(kind=4) :: value
real(kind=4) :: t4
!f2py intent(out) t4
t4 = value
end subroutine s4
subroutine s8(t8,value)
real(kind=8) :: value
real(kind=8) :: t8
!f2py intent(out) t8
t8 = value
end subroutine s8
subroutine sd(td,value)
double precision :: value
double precision :: td
!f2py intent(out) td
td = value
end subroutine sd
end module f90_return_real

View File

@@ -0,0 +1,44 @@
subroutine foo(a, n, m, b)
implicit none
real, intent(in) :: a(n, m)
integer, intent(in) :: n, m
real, intent(out) :: b(size(a, 1))
integer :: i
do i = 1, size(b)
b(i) = sum(a(i,:))
enddo
end subroutine
subroutine trans(x,y)
implicit none
real, intent(in), dimension(:,:) :: x
real, intent(out), dimension( size(x,2), size(x,1) ) :: y
integer :: N, M, i, j
N = size(x,1)
M = size(x,2)
DO i=1,N
do j=1,M
y(j,i) = x(i,j)
END DO
END DO
end subroutine trans
subroutine flatten(x,y)
implicit none
real, intent(in), dimension(:,:) :: x
real, intent(out), dimension( size(x) ) :: y
integer :: N, M, i, j, k
N = size(x,1)
M = size(x,2)
k = 1
DO i=1,N
do j=1,M
y(k) = x(i,j)
k = k + 1
END DO
END DO
end subroutine flatten

View File

@@ -0,0 +1,29 @@
MODULE char_test
CONTAINS
SUBROUTINE change_strings(strings, n_strs, out_strings)
IMPLICIT NONE
! Inputs
INTEGER, INTENT(IN) :: n_strs
CHARACTER, INTENT(IN), DIMENSION(2,n_strs) :: strings
CHARACTER, INTENT(OUT), DIMENSION(2,n_strs) :: out_strings
!f2py INTEGER, INTENT(IN) :: n_strs
!f2py CHARACTER, INTENT(IN), DIMENSION(2,n_strs) :: strings
!f2py CHARACTER, INTENT(OUT), DIMENSION(2,n_strs) :: strings
! Misc.
INTEGER*4 :: j
DO j=1, n_strs
out_strings(1,j) = strings(1,j)
out_strings(2,j) = 'A'
END DO
END SUBROUTINE change_strings
END MODULE char_test

View File

@@ -0,0 +1,34 @@
function sint(s) result(i)
implicit none
character(len=*) :: s
integer :: j, i
i = 0
do j=len(s), 1, -1
if (.not.((i.eq.0).and.(s(j:j).eq.' '))) then
i = i + ichar(s(j:j)) * 10 ** (j - 1)
endif
end do
return
end function sint
function test_in_bytes4(a) result (i)
implicit none
integer :: sint
character(len=4) :: a
integer :: i
i = sint(a)
a(1:1) = 'A'
return
end function test_in_bytes4
function test_inout_bytes4(a) result (i)
implicit none
integer :: sint
character(len=4), intent(inout) :: a
integer :: i
if (a(1:1).ne.' ') then
a(1:1) = 'E'
endif
i = sint(a)
return
end function test_inout_bytes4

View File

@@ -0,0 +1,9 @@
MODULE string_test
character(len=8) :: string
character string77 * 8
character(len=12), dimension(5,7) :: strarr
character strarr77(5,7) * 12
END MODULE string_test

View File

@@ -0,0 +1,12 @@
C FILE: STRING.F
SUBROUTINE FOO(A,B,C,D)
CHARACTER*5 A, B
CHARACTER*(*) C,D
Cf2py intent(in) a,c
Cf2py intent(inout) b,d
A(1:1) = 'A'
B(1:1) = 'B'
C(1:1) = 'C'
D(1:1) = 'D'
END
C END OF FILE STRING.F

View File

@@ -0,0 +1,9 @@
module fortfuncs
implicit none
contains
subroutine square(x,y)
integer, intent(in), value :: x
integer, intent(out) :: y
y = x*x
end subroutine square
end module fortfuncs

View File

@@ -0,0 +1,25 @@
from pathlib import Path
import pytest
import textwrap
from . import util
from numpy.f2py import crackfortran
from numpy.testing import IS_WASM
@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
class TestAbstractInterface(util.F2PyTest):
sources = [util.getpath("tests", "src", "abstract_interface", "foo.f90")]
skip = ["add1", "add2"]
def test_abstract_interface(self):
assert self.module.ops_module.foo(3, 5) == (8, 13)
def test_parse_abstract_interface(self):
# Test gh18403
fpath = util.getpath("tests", "src", "abstract_interface",
"gh18403_mod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
assert len(mod[0]["body"]) == 1
assert mod[0]["body"][0]["block"] == "abstract interface"

View File

@@ -0,0 +1,686 @@
import os
import sys
import copy
import platform
import pytest
import numpy as np
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():
"""
Build the required testing extension module
"""
global wrap
# Check compiler availability first
if not util.has_c_compiler():
pytest.skip("No C compiler available")
if wrap is None:
config_code = """
config.add_extension('test_array_from_pyobj_ext',
sources=['wrapmodule.c', 'fortranobject.c'],
define_macros=[])
"""
d = os.path.dirname(__file__)
src = [
util.getpath("tests", "src", "array_from_pyobj", "wrapmodule.c"),
util.getpath("src", "fortranobject.c"),
util.getpath("src", "fortranobject.h"),
]
wrap = util.build_module_distutils(src, config_code,
"test_array_from_pyobj_ext")
def flags_info(arr):
flags = wrap.array_attrs(arr)[6]
return flags2names(flags)
def flags2names(flags):
info = []
for flagname in [
"CONTIGUOUS",
"FORTRAN",
"OWNDATA",
"ENSURECOPY",
"ENSUREARRAY",
"ALIGNED",
"NOTSWAPPED",
"WRITEABLE",
"WRITEBACKIFCOPY",
"UPDATEIFCOPY",
"BEHAVED",
"BEHAVED_RO",
"CARRAY",
"FARRAY",
]:
if abs(flags) & getattr(wrap, flagname, 0):
info.append(flagname)
return info
class Intent:
def __init__(self, intent_list=[]):
self.intent_list = intent_list[:]
flags = 0
for i in intent_list:
if i == "optional":
flags |= wrap.F2PY_OPTIONAL
else:
flags |= getattr(wrap, "F2PY_INTENT_" + i.upper())
self.flags = flags
def __getattr__(self, name):
name = name.lower()
if name == "in_":
name = "in"
return self.__class__(self.intent_list + [name])
def __str__(self):
return "intent(%s)" % (",".join(self.intent_list))
def __repr__(self):
return "Intent(%r)" % (self.intent_list)
def is_intent(self, *names):
for name in names:
if name not in self.intent_list:
return False
return True
def is_intent_exact(self, *names):
return len(self.intent_list) == len(names) and self.is_intent(*names)
intent = Intent()
_type_names = [
"BOOL",
"BYTE",
"UBYTE",
"SHORT",
"USHORT",
"INT",
"UINT",
"LONG",
"ULONG",
"LONGLONG",
"ULONGLONG",
"FLOAT",
"DOUBLE",
"CFLOAT",
"STRING1",
"STRING5",
"CHARACTER",
]
_cast_dict = {"BOOL": ["BOOL"]}
_cast_dict["BYTE"] = _cast_dict["BOOL"] + ["BYTE"]
_cast_dict["UBYTE"] = _cast_dict["BOOL"] + ["UBYTE"]
_cast_dict["BYTE"] = ["BYTE"]
_cast_dict["UBYTE"] = ["UBYTE"]
_cast_dict["SHORT"] = _cast_dict["BYTE"] + ["UBYTE", "SHORT"]
_cast_dict["USHORT"] = _cast_dict["UBYTE"] + ["BYTE", "USHORT"]
_cast_dict["INT"] = _cast_dict["SHORT"] + ["USHORT", "INT"]
_cast_dict["UINT"] = _cast_dict["USHORT"] + ["SHORT", "UINT"]
_cast_dict["LONG"] = _cast_dict["INT"] + ["LONG"]
_cast_dict["ULONG"] = _cast_dict["UINT"] + ["ULONG"]
_cast_dict["LONGLONG"] = _cast_dict["LONG"] + ["LONGLONG"]
_cast_dict["ULONGLONG"] = _cast_dict["ULONG"] + ["ULONGLONG"]
_cast_dict["FLOAT"] = _cast_dict["SHORT"] + ["USHORT", "FLOAT"]
_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
# when numpy gains an aligned allocator the tests could be enabled again
#
# Furthermore, on macOS ARM64, LONGDOUBLE is an alias for DOUBLE.
if ((np.intp().dtype.itemsize != 4 or np.clongdouble().dtype.alignment <= 8)
and sys.platform != "win32"
and (platform.system(), platform.processor()) != ("Darwin", "arm")):
_type_names.extend(["LONGDOUBLE", "CDOUBLE", "CLONGDOUBLE"])
_cast_dict["LONGDOUBLE"] = _cast_dict["LONG"] + [
"ULONG",
"FLOAT",
"DOUBLE",
"LONGDOUBLE",
]
_cast_dict["CLONGDOUBLE"] = _cast_dict["LONGDOUBLE"] + [
"CFLOAT",
"CDOUBLE",
"CLONGDOUBLE",
]
_cast_dict["CDOUBLE"] = _cast_dict["DOUBLE"] + ["CFLOAT", "CDOUBLE"]
class Type:
_type_cache = {}
def __new__(cls, name):
if isinstance(name, np.dtype):
dtype0 = name
name = None
for n, i in typeinfo.items():
if not isinstance(i, type) and dtype0.type is i.type:
name = n
break
obj = cls._type_cache.get(name.upper(), None)
if obj is not None:
return obj
obj = object.__new__(cls)
obj._init(name)
cls._type_cache[name.upper()] = obj
return obj
def _init(self, name):
self.NAME = name.upper()
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.type = info.type
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]]
def all_types(self):
return [self.__class__(_m) for _m in _type_names]
def smaller_types(self):
bits = typeinfo[self.NAME].alignment
types = []
for name in _type_names:
if typeinfo[name].alignment < bits:
types.append(Type(name))
return types
def equal_types(self):
bits = typeinfo[self.NAME].alignment
types = []
for name in _type_names:
if name == self.NAME:
continue
if typeinfo[name].alignment == bits:
types.append(Type(name))
return types
def larger_types(self):
bits = typeinfo[self.NAME].alignment
types = []
for name in _type_names:
if typeinfo[name].alignment > bits:
types.append(Type(name))
return types
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
self.intent = intent
self.obj_copy = copy.deepcopy(obj)
self.obj = obj
# arr.dtypechar may be different from typ.dtypechar
self.arr = wrap.call(typ.type_num,
typ.elsize,
dims, intent.flags, obj)
assert isinstance(self.arr, np.ndarray)
self.arr_attr = wrap.array_attrs(self.arr)
if len(dims) > 1:
if self.intent.is_intent("c"):
assert (intent.flags & wrap.F2PY_INTENT_C)
assert not self.arr.flags["FORTRAN"]
assert self.arr.flags["CONTIGUOUS"]
assert (not self.arr_attr[6] & wrap.FORTRAN)
else:
assert (not intent.flags & wrap.F2PY_INTENT_C)
assert self.arr.flags["FORTRAN"]
assert not self.arr.flags["CONTIGUOUS"]
assert (self.arr_attr[6] & wrap.FORTRAN)
if obj is None:
self.pyarr = None
self.pyarr_attr = None
return
if intent.is_intent("cache"):
assert isinstance(obj, np.ndarray), repr(type(obj))
self.pyarr = np.array(obj).reshape(*dims).copy()
else:
self.pyarr = np.array(
np.array(obj, dtype=typ.dtypechar).reshape(*dims),
order=self.intent.is_intent("c") and "C" or "F",
)
assert self.pyarr.dtype == typ
self.pyarr.setflags(write=self.arr.flags["WRITEABLE"])
assert self.pyarr.flags["OWNDATA"], (obj, intent)
self.pyarr_attr = wrap.array_attrs(self.pyarr)
if len(dims) > 1:
if self.intent.is_intent("c"):
assert not self.pyarr.flags["FORTRAN"]
assert self.pyarr.flags["CONTIGUOUS"]
assert (not self.pyarr_attr[6] & wrap.FORTRAN)
else:
assert self.pyarr.flags["FORTRAN"]
assert not self.pyarr.flags["CONTIGUOUS"]
assert (self.pyarr_attr[6] & wrap.FORTRAN)
assert self.arr_attr[1] == self.pyarr_attr[1] # nd
assert self.arr_attr[2] == self.pyarr_attr[2] # dimensions
if self.arr_attr[1] <= 1:
assert self.arr_attr[3] == self.pyarr_attr[3], repr((
self.arr_attr[3],
self.pyarr_attr[3],
self.arr.tobytes(),
self.pyarr.tobytes(),
)) # strides
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],
flags2names(0 * self.arr_attr[6] - self.pyarr_attr[6]),
flags2names(self.arr_attr[6]),
intent,
)) # flags
if intent.is_intent("cache"):
assert self.arr_attr[5][3] >= self.type.elsize
else:
assert self.arr_attr[5][3] == self.type.elsize
assert (self.arr_equal(self.pyarr, self.arr))
if isinstance(self.obj, np.ndarray):
if typ.elsize == Type(obj.dtype).elsize:
if not intent.is_intent("copy") and self.arr_attr[1] <= 1:
assert self.has_shared_memory()
def arr_equal(self, arr1, arr2):
if arr1.shape != arr2.shape:
return False
return (arr1 == arr2).all()
def __str__(self):
return str(self.arr)
def has_shared_memory(self):
"""Check that created array shares data with input array."""
if self.obj is self.arr:
return True
if not isinstance(self.obj, np.ndarray):
return False
obj_attr = wrap.array_attrs(self.obj)
return obj_attr[0] == self.arr_attr[0]
class TestIntent:
def test_in_out(self):
assert str(intent.in_.out) == "intent(in,out)"
assert intent.in_.c.is_intent("c")
assert not intent.in_.c.is_intent_exact("c")
assert intent.in_.c.is_intent_exact("c", "in")
assert intent.in_.c.is_intent_exact("in", "c")
assert not intent.in_.is_intent("c")
class TestSharedMemory:
@pytest.fixture(autouse=True, scope="class", params=_type_names)
def setup_type(self, request):
request.cls.type = Type(request.param)
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()
def test_in_from_2casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num2seq, dtype=t.dtype)
a = self.array([len(self.num2seq)], intent.in_, obj)
if t.elsize == self.type.elsize:
assert a.has_shared_memory(), repr((self.type.dtype, t.dtype))
else:
assert not a.has_shared_memory()
@pytest.mark.parametrize("write", ["w", "ro"])
@pytest.mark.parametrize("order", ["C", "F"])
@pytest.mark.parametrize("inp", ["2seq", "23seq"])
def test_in_nocopy(self, write, order, inp):
"""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'))
a = self.array(obj.shape,
((order == 'C' and intent.in_.c) or intent.in_), obj)
assert a.has_shared_memory()
def test_inout_2seq(self):
obj = np.array(self.num2seq, dtype=self.type.dtype)
a = self.array([len(self.num2seq)], intent.inout, obj)
assert a.has_shared_memory()
try:
a = self.array([2], intent.in_.inout, self.num2seq)
except TypeError as msg:
if not str(msg).startswith(
"failed to initialize intent(inout|inplace|cache) array"):
raise
else:
raise SystemError("intent(inout) should have failed on sequence")
def test_f_inout_23seq(self):
obj = np.array(self.num23seq, dtype=self.type.dtype, order="F")
shape = (len(self.num23seq), len(self.num23seq[0]))
a = self.array(shape, intent.in_.inout, obj)
assert a.has_shared_memory()
obj = np.array(self.num23seq, dtype=self.type.dtype, order="C")
shape = (len(self.num23seq), len(self.num23seq[0]))
try:
a = self.array(shape, intent.in_.inout, obj)
except ValueError as msg:
if not str(msg).startswith(
"failed to initialize intent(inout) array"):
raise
else:
raise SystemError(
"intent(inout) should have failed on improper array")
def test_c_inout_23seq(self):
obj = np.array(self.num23seq, dtype=self.type.dtype)
shape = (len(self.num23seq), len(self.num23seq[0]))
a = self.array(shape, intent.in_.c.inout, obj)
assert a.has_shared_memory()
def test_in_copy_from_2casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num2seq, dtype=t.dtype)
a = self.array([len(self.num2seq)], intent.in_.copy, obj)
assert not a.has_shared_memory()
def test_c_in_from_23seq(self):
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_,
self.num23seq)
assert not a.has_shared_memory()
def test_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype)
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_, obj)
assert not a.has_shared_memory()
def test_f_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype, order="F")
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_, obj)
if t.elsize == self.type.elsize:
assert a.has_shared_memory()
else:
assert not a.has_shared_memory()
def test_c_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype)
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_.c, obj)
if t.elsize == self.type.elsize:
assert a.has_shared_memory()
else:
assert not a.has_shared_memory()
def test_f_copy_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype, order="F")
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_.copy,
obj)
assert not a.has_shared_memory()
def test_c_copy_in_from_23casttype(self):
for t in self.type.cast_types():
obj = np.array(self.num23seq, dtype=t.dtype)
a = self.array(
[len(self.num23seq), len(self.num23seq[0])], intent.in_.c.copy,
obj)
assert not a.has_shared_memory()
def test_in_cache_from_2casttype(self):
for t in self.type.all_types():
if t.elsize != self.type.elsize:
continue
obj = np.array(self.num2seq, dtype=t.dtype)
shape = (len(self.num2seq), )
a = self.array(shape, intent.in_.c.cache, obj)
assert a.has_shared_memory()
a = self.array(shape, intent.in_.cache, obj)
assert a.has_shared_memory()
obj = np.array(self.num2seq, dtype=t.dtype, order="F")
a = self.array(shape, intent.in_.c.cache, obj)
assert a.has_shared_memory()
a = self.array(shape, intent.in_.cache, obj)
assert a.has_shared_memory(), repr(t.dtype)
try:
a = self.array(shape, intent.in_.cache, obj[::-1])
except ValueError as msg:
if not str(msg).startswith(
"failed to initialize intent(cache) array"):
raise
else:
raise SystemError(
"intent(cache) should have failed on multisegmented array")
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)
shape = (len(self.num2seq), )
try:
self.array(shape, intent.in_.cache, obj) # Should succeed
except ValueError as msg:
if not str(msg).startswith(
"failed to initialize intent(cache) array"):
raise
else:
raise SystemError(
"intent(cache) should have failed on smaller array")
def test_cache_hidden(self):
shape = (2, )
a = self.array(shape, intent.cache.hide, None)
assert a.arr.shape == shape
shape = (2, 3)
a = self.array(shape, intent.cache.hide, None)
assert a.arr.shape == shape
shape = (-1, 3)
try:
a = self.array(shape, intent.cache.hide, None)
except ValueError as msg:
if not str(msg).startswith(
"failed to create intent(cache|hide)|optional array"):
raise
else:
raise SystemError(
"intent(cache) should have failed on undefined dimensions")
def test_hidden(self):
shape = (2, )
a = self.array(shape, intent.hide, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
shape = (2, 3)
a = self.array(shape, intent.hide, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert a.arr.flags["FORTRAN"] and not a.arr.flags["CONTIGUOUS"]
shape = (2, 3)
a = self.array(shape, intent.c.hide, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert not a.arr.flags["FORTRAN"] and a.arr.flags["CONTIGUOUS"]
shape = (-1, 3)
try:
a = self.array(shape, intent.hide, None)
except ValueError as msg:
if not str(msg).startswith(
"failed to create intent(cache|hide)|optional array"):
raise
else:
raise SystemError(
"intent(hide) should have failed on undefined dimensions")
def test_optional_none(self):
shape = (2, )
a = self.array(shape, intent.optional, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
shape = (2, 3)
a = self.array(shape, intent.optional, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert a.arr.flags["FORTRAN"] and not a.arr.flags["CONTIGUOUS"]
shape = (2, 3)
a = self.array(shape, intent.c.optional, None)
assert a.arr.shape == shape
assert a.arr_equal(a.arr, np.zeros(shape, dtype=self.type.dtype))
assert not a.arr.flags["FORTRAN"] and a.arr.flags["CONTIGUOUS"]
def test_optional_from_2seq(self):
obj = self.num2seq
shape = (len(obj), )
a = self.array(shape, intent.optional, obj)
assert a.arr.shape == shape
assert not a.has_shared_memory()
def test_optional_from_23seq(self):
obj = self.num23seq
shape = (len(obj), len(obj[0]))
a = self.array(shape, intent.optional, obj)
assert a.arr.shape == shape
assert not a.has_shared_memory()
a = self.array(shape, intent.optional.c, obj)
assert a.arr.shape == shape
assert not a.has_shared_memory()
def test_inplace(self):
obj = np.array(self.num23seq, dtype=self.type.dtype)
assert not obj.flags["FORTRAN"] and obj.flags["CONTIGUOUS"]
shape = obj.shape
a = self.array(shape, intent.inplace, obj)
assert obj[1][2] == a.arr[1][2], repr((obj, a.arr))
a.arr[1][2] = 54
assert obj[1][2] == a.arr[1][2] == np.array(54, dtype=self.type.dtype)
assert a.arr is obj
assert obj.flags["FORTRAN"] # obj attributes are changed inplace!
assert not obj.flags["CONTIGUOUS"]
def test_inplace_from_casttype(self):
for t in self.type.cast_types():
if t is self.type:
continue
obj = np.array(self.num23seq, dtype=t.dtype)
assert obj.dtype.type == t.type
assert obj.dtype.type is not self.type.type
assert not obj.flags["FORTRAN"] and obj.flags["CONTIGUOUS"]
shape = obj.shape
a = self.array(shape, intent.inplace, obj)
assert obj[1][2] == a.arr[1][2], repr((obj, a.arr))
a.arr[1][2] = 54
assert obj[1][2] == a.arr[1][2] == np.array(54,
dtype=self.type.dtype)
assert a.arr is obj
assert obj.flags["FORTRAN"] # obj attributes changed inplace!
assert not obj.flags["CONTIGUOUS"]
assert obj.dtype.type is self.type.type # obj changed inplace!

View File

@@ -0,0 +1,49 @@
import os
import pytest
import tempfile
from . import util
class TestAssumedShapeSumExample(util.F2PyTest):
sources = [
util.getpath("tests", "src", "assumed_shape", "foo_free.f90"),
util.getpath("tests", "src", "assumed_shape", "foo_use.f90"),
util.getpath("tests", "src", "assumed_shape", "precision.f90"),
util.getpath("tests", "src", "assumed_shape", "foo_mod.f90"),
util.getpath("tests", "src", "assumed_shape", ".f2py_f2cmap"),
]
@pytest.mark.slow
def test_all(self):
r = self.module.fsum([1, 2])
assert r == 3
r = self.module.sum([1, 2])
assert r == 3
r = self.module.sum_with_use([1, 2])
assert r == 3
r = self.module.mod.sum([1, 2])
assert r == 3
r = self.module.mod.fsum([1, 2])
assert r == 3
class TestF2cmapOption(TestAssumedShapeSumExample):
def setup_method(self):
# Use a custom file name for .f2py_f2cmap
self.sources = list(self.sources)
f2cmap_src = self.sources.pop(-1)
self.f2cmap_file = tempfile.NamedTemporaryFile(delete=False)
with open(f2cmap_src, "rb") as f:
self.f2cmap_file.write(f.read())
self.f2cmap_file.close()
self.sources.append(self.f2cmap_file.name)
self.options = ["--f2cmap", self.f2cmap_file.name]
super().setup_method()
def teardown_method(self):
os.unlink(self.f2cmap_file.name)

View File

@@ -0,0 +1,17 @@
import sys
import pytest
from . import util
from numpy.testing import IS_PYPY
class TestBlockDocString(util.F2PyTest):
sources = [util.getpath("tests", "src", "block_docstring", "foo.f")]
@pytest.mark.skipif(sys.platform == "win32",
reason="Fails with MinGW64 Gfortran (Issue #9673)")
@pytest.mark.xfail(IS_PYPY,
reason="PyPy cannot modify tp_doc after PyType_Ready")
def test_block_docstring(self):
expected = "bar : 'i'-array(2,3)\n"
assert self.module.block.__doc__ == expected

View File

@@ -0,0 +1,230 @@
import math
import textwrap
import sys
import pytest
import threading
import traceback
import time
import numpy as np
from numpy.testing import IS_PYPY
from . import util
class TestF77Callback(util.F2PyTest):
sources = [util.getpath("tests", "src", "callback", "foo.f")]
@pytest.mark.parametrize("name", "t,t2".split(","))
def test_all(self, name):
self.check_function(name)
@pytest.mark.xfail(IS_PYPY,
reason="PyPy cannot modify tp_doc after PyType_Ready")
def test_docstring(self):
expected = textwrap.dedent("""\
a = t(fun,[fun_extra_args])
Wrapper for ``t``.
Parameters
----------
fun : call-back function
Other Parameters
----------------
fun_extra_args : input tuple, optional
Default: ()
Returns
-------
a : int
Notes
-----
Call-back functions::
def fun(): return a
Return objects:
a : int
""")
assert self.module.t.__doc__ == expected
def check_function(self, name):
t = getattr(self.module, name)
r = t(lambda: 4)
assert r == 4
r = t(lambda a: 5, fun_extra_args=(6, ))
assert r == 5
r = t(lambda a: a, fun_extra_args=(6, ))
assert r == 6
r = t(lambda a: 5 + a, fun_extra_args=(7, ))
assert r == 12
r = t(lambda a: math.degrees(a), fun_extra_args=(math.pi, ))
assert r == 180
r = t(math.degrees, fun_extra_args=(math.pi, ))
assert r == 180
r = t(self.module.func, fun_extra_args=(6, ))
assert r == 17
r = t(self.module.func0)
assert r == 11
r = t(self.module.func0._cpointer)
assert r == 11
class A:
def __call__(self):
return 7
def mth(self):
return 9
a = A()
r = t(a)
assert r == 7
r = t(a.mth)
assert r == 9
@pytest.mark.skipif(sys.platform == 'win32',
reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_string_callback(self):
def callback(code):
if code == "r":
return 0
else:
return 1
f = getattr(self.module, "string_callback")
r = f(callback)
assert r == 0
@pytest.mark.skipif(sys.platform == 'win32',
reason='Fails with MinGW64 Gfortran (Issue #9673)')
def test_string_callback_array(self):
# See gh-10027
cu1 = np.zeros((1, ), "S8")
cu2 = np.zeros((1, 8), "c")
cu3 = np.array([""], "S8")
def callback(cu, lencu):
if cu.shape != (lencu,):
return 1
if cu.dtype != "S8":
return 2
if not np.all(cu == b""):
return 3
return 0
f = getattr(self.module, "string_callback_array")
for cu in [cu1, cu2, cu3]:
res = f(callback, cu, cu.size)
assert res == 0
def test_threadsafety(self):
# Segfaults if the callback handling is not threadsafe
errors = []
def cb():
# Sleep here to make it more likely for another thread
# to call their callback at the same time.
time.sleep(1e-3)
# Check reentrancy
r = self.module.t(lambda: 123)
assert r == 123
return 42
def runner(name):
try:
for j in range(50):
r = self.module.t(cb)
assert r == 42
self.check_function(name)
except Exception:
errors.append(traceback.format_exc())
threads = [
threading.Thread(target=runner, args=(arg, ))
for arg in ("t", "t2") for n in range(20)
]
for t in threads:
t.start()
for t in threads:
t.join()
errors = "\n\n".join(errors)
if errors:
raise AssertionError(errors)
def test_hidden_callback(self):
try:
self.module.hidden_callback(2)
except Exception as msg:
assert str(msg).startswith("Callback global_f not defined")
try:
self.module.hidden_callback2(2)
except Exception as msg:
assert str(msg).startswith("cb: Callback global_f not defined")
self.module.global_f = lambda x: x + 1
r = self.module.hidden_callback(2)
assert r == 3
self.module.global_f = lambda x: x + 2
r = self.module.hidden_callback(2)
assert r == 4
del self.module.global_f
try:
self.module.hidden_callback(2)
except Exception as msg:
assert str(msg).startswith("Callback global_f not defined")
self.module.global_f = lambda x=0: x + 3
r = self.module.hidden_callback(2)
assert r == 5
# reproducer of gh18341
r = self.module.hidden_callback2(2)
assert r == 3
class TestF77CallbackPythonTLS(TestF77Callback):
"""
Callback tests using Python thread-local storage instead of
compiler-provided
"""
options = ["-DF2PY_USE_PYTHON_TLS"]
class TestF90Callback(util.F2PyTest):
sources = [util.getpath("tests", "src", "callback", "gh17797.f90")]
def test_gh17797(self):
def incr(x):
return x + 123
y = np.array([1, 2, 3], dtype=np.int64)
r = self.module.gh17797(incr, y)
assert r == 123 + 1 + 2 + 3
class TestGH18335(util.F2PyTest):
"""The reproduction of the reported issue requires specific input that
extensions may break the issue conditions, so the reproducer is
implemented as a separate test class. Do not extend this test with
other tests!
"""
sources = [util.getpath("tests", "src", "callback", "gh18335.f90")]
def test_gh18335(self):
def foo(x):
x[0] += 1
r = self.module.gh18335(foo)
assert r == 123 + 1

View File

@@ -0,0 +1,592 @@
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'))
class TestStringScalarArr(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "scalar_string.f90")]
@pytest.mark.slow
def test_char(self):
for out in (self.module.string_test.string,
self.module.string_test.string77):
expected = ()
assert out.shape == expected
expected = '|S8'
assert out.dtype == expected
@pytest.mark.slow
def test_char_arr(self):
for out in (self.module.string_test.strarr,
self.module.string_test.strarr77):
expected = (5,7)
assert out.shape == expected
expected = '|S12'
assert out.dtype == expected

View File

@@ -0,0 +1,18 @@
import os
import sys
import pytest
import numpy as np
from . import util
class TestCommonBlock(util.F2PyTest):
sources = [util.getpath("tests", "src", "common", "block.f")]
@pytest.mark.skipif(sys.platform == "win32",
reason="Fails with MinGW64 Gfortran (Issue #9673)")
def test_common_block(self):
self.module.initcb()
assert self.module.block.long_bn == np.array(1.0, dtype=np.float64)
assert self.module.block.string_bn == np.array("2", dtype="|S1")
assert self.module.block.ok == np.array(3, dtype=np.int32)

View File

@@ -0,0 +1,117 @@
"""See https://github.com/numpy/numpy/pull/11937.
"""
import sys
import os
import uuid
from importlib import import_module
import pytest
import numpy.f2py
from . import util
def setup_module():
if not util.has_c_compiler():
pytest.skip("Needs C compiler")
if not util.has_f77_compiler():
pytest.skip("Needs FORTRAN 77 compiler")
# extra_args can be a list (since gh-11937) or string.
# also test absence of extra_args
@pytest.mark.parametrize("extra_args",
[["--noopt", "--debug"], "--noopt --debug", ""])
@pytest.mark.leaks_references(reason="Imported module seems never deleted.")
def test_f2py_init_compile(extra_args):
# flush through the f2py __init__ compile() function code path as a
# crude test for input handling following migration from
# exec_command() to subprocess.check_output() in gh-11937
# the Fortran 77 syntax requires 6 spaces before any commands, but
# more space may be added/
fsource = """
integer function foo()
foo = 10 + 5
return
end
"""
# use various helper functions in util.py to enable robust build /
# compile and reimport cycle in test suite
moddir = util.get_module_dir()
modname = util.get_temp_module_name()
cwd = os.getcwd()
target = os.path.join(moddir, str(uuid.uuid4()) + ".f")
# try running compile() with and without a source_fn provided so
# that the code path where a temporary file for writing Fortran
# source is created is also explored
for source_fn in [target, None]:
# mimic the path changing behavior used by build_module() in
# util.py, but don't actually use build_module() because it has
# its own invocation of subprocess that circumvents the
# f2py.compile code block under test
with util.switchdir(moddir):
ret_val = numpy.f2py.compile(fsource,
modulename=modname,
extra_args=extra_args,
source_fn=source_fn)
# check for compile success return value
assert ret_val == 0
# we are not currently able to import the Python-Fortran
# interface module on Windows / Appveyor, even though we do get
# successful compilation on that platform with Python 3.x
if sys.platform != "win32":
# check for sensible result of Fortran function; that means
# we can import the module name in Python and retrieve the
# result of the sum operation
return_check = import_module(modname)
calc_result = return_check.foo()
assert calc_result == 15
# Removal from sys.modules, is not as such necessary. Even with
# removal, the module (dict) stays alive.
del sys.modules[modname]
def test_f2py_init_compile_failure():
# verify an appropriate integer status value returned by
# f2py.compile() when invalid Fortran is provided
ret_val = numpy.f2py.compile(b"invalid")
assert ret_val == 1
def test_f2py_init_compile_bad_cmd():
# verify that usage of invalid command in f2py.compile() returns
# status value of 127 for historic consistency with exec_command()
# error handling
# patch the sys Python exe path temporarily to induce an OSError
# downstream NOTE: how bad of an idea is this patching?
try:
temp = sys.executable
sys.executable = "does not exist"
# the OSError should take precedence over invalid Fortran
ret_val = numpy.f2py.compile(b"invalid")
assert ret_val == 127
finally:
sys.executable = temp
@pytest.mark.parametrize(
"fsource",
[
"program test_f2py\nend program test_f2py",
b"program test_f2py\nend program test_f2py",
],
)
def test_compile_from_strings(tmpdir, fsource):
# Make sure we can compile str and bytes gh-12796
with util.switchdir(tmpdir):
ret_val = numpy.f2py.compile(fsource,
modulename="test_compile_from_strings",
extension=".f90")
assert ret_val == 0

View File

@@ -0,0 +1,278 @@
import importlib
import codecs
import unicodedata
import pytest
import numpy as np
from numpy.f2py.crackfortran import markinnerspaces
from . import util
from numpy.f2py import crackfortran
import textwrap
class TestNoSpace(util.F2PyTest):
# issue gh-15035: add handling for endsubroutine, endfunction with no space
# between "end" and the block name
sources = [util.getpath("tests", "src", "crackfortran", "gh15035.f")]
def test_module(self):
k = np.array([1, 2, 3], dtype=np.float64)
w = np.array([1, 2, 3], dtype=np.float64)
self.module.subb(k)
assert np.allclose(k, w + 1)
self.module.subc([w, k])
assert np.allclose(k, w + 1)
assert self.module.t0("23") == b"2"
class TestPublicPrivate:
def test_defaultPrivate(self):
fpath = util.getpath("tests", "src", "crackfortran", "privatemod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "private" in mod["vars"]["a"]["attrspec"]
assert "public" not in mod["vars"]["a"]["attrspec"]
assert "private" in mod["vars"]["b"]["attrspec"]
assert "public" not in mod["vars"]["b"]["attrspec"]
assert "private" not in mod["vars"]["seta"]["attrspec"]
assert "public" in mod["vars"]["seta"]["attrspec"]
def test_defaultPublic(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "publicmod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "private" in mod["vars"]["a"]["attrspec"]
assert "public" not in mod["vars"]["a"]["attrspec"]
assert "private" not in mod["vars"]["seta"]["attrspec"]
assert "public" in mod["vars"]["seta"]["attrspec"]
def test_access_type(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "accesstype.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
tt = mod[0]['vars']
assert set(tt['a']['attrspec']) == {'private', 'bind(c)'}
assert set(tt['b_']['attrspec']) == {'public', 'bind(c)'}
assert set(tt['c']['attrspec']) == {'public'}
class TestModuleProcedure():
def test_moduleOperators(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "operators.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert "body" in mod and len(mod["body"]) == 9
assert mod["body"][1]["name"] == "operator(.item.)"
assert "implementedby" in mod["body"][1]
assert mod["body"][1]["implementedby"] == \
["item_int", "item_real"]
assert mod["body"][2]["name"] == "operator(==)"
assert "implementedby" in mod["body"][2]
assert mod["body"][2]["implementedby"] == ["items_are_equal"]
assert mod["body"][3]["name"] == "assignment(=)"
assert "implementedby" in mod["body"][3]
assert mod["body"][3]["implementedby"] == \
["get_int", "get_real"]
def test_notPublicPrivate(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "pubprivmod.f90")
mod = crackfortran.crackfortran([str(fpath)])
assert len(mod) == 1
mod = mod[0]
assert mod['vars']['a']['attrspec'] == ['private', ]
assert mod['vars']['b']['attrspec'] == ['public', ]
assert mod['vars']['seta']['attrspec'] == ['public', ]
class TestExternal(util.F2PyTest):
# issue gh-17859: add external attribute support
sources = [util.getpath("tests", "src", "crackfortran", "gh17859.f")]
def test_external_as_statement(self):
def incr(x):
return x + 123
r = self.module.external_as_statement(incr)
assert r == 123
def test_external_as_attribute(self):
def incr(x):
return x + 123
r = self.module.external_as_attribute(incr)
assert r == 123
class TestCrackFortran(util.F2PyTest):
# gh-2848: commented lines between parameters in subroutine parameter lists
sources = [util.getpath("tests", "src", "crackfortran", "gh2848.f90")]
def test_gh2848(self):
r = self.module.gh2848(1, 2)
assert r == (1, 2)
class TestMarkinnerspaces:
# gh-14118: markinnerspaces does not handle multiple quotations
def test_do_not_touch_normal_spaces(self):
test_list = ["a ", " a", "a b c", "'abcdefghij'"]
for i in test_list:
assert markinnerspaces(i) == i
def test_one_relevant_space(self):
assert markinnerspaces("a 'b c' \\' \\'") == "a 'b@_@c' \\' \\'"
assert markinnerspaces(r'a "b c" \" \"') == r'a "b@_@c" \" \"'
def test_ignore_inner_quotes(self):
assert markinnerspaces("a 'b c\" \" d' e") == "a 'b@_@c\"@_@\"@_@d' e"
assert markinnerspaces("a \"b c' ' d\" e") == "a \"b@_@c'@_@'@_@d\" e"
def test_multiple_relevant_spaces(self):
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.
There exists two usage cases where analyzing dimensions
specifications are important.
In the first case, the size of output arrays must be defined based
on the inputs to a Fortran function. Because Fortran supports
arbitrary bases for indexing, for instance, `arr(lower:upper)`,
f2py has to evaluate an expression `upper - lower + 1` where
`lower` and `upper` are arbitrary expressions of input parameters.
The evaluation is performed in C, so f2py has to translate Fortran
expressions to valid C expressions (an alternative approach is
that a developer specifies the corresponding C expressions in a
.pyf file).
In the second case, when user provides an input array with a given
size but some hidden parameters used in dimensions specifications
need to be determined based on the input array size. This is a
harder problem because f2py has to solve the inverse problem: find
a parameter `p` such that `upper(p) - lower(p) + 1` equals to the
size of input array. In the case when this equation cannot be
solved (e.g. because the input array size is wrong), raise an
error before calling the Fortran function (that otherwise would
likely crash Python process when the size of input arrays is
wrong). f2py currently supports this case only when the equation
is linear with respect to unknown parameter.
"""
suffix = ".f90"
code_template = textwrap.dedent("""
function get_arr_size_{count}(a, n) result (length)
integer, intent(in) :: n
integer, dimension({dimspec}), intent(out) :: a
integer length
length = size(a)
end function
subroutine get_inv_arr_size_{count}(a, n)
integer :: n
! the value of n is computed in f2py wrapper
!f2py intent(out) n
integer, dimension({dimspec}), intent(in) :: a
if (a({first}).gt.0) then
print*, "a=", a
endif
end subroutine
""")
linear_dimspecs = [
"n", "2*n", "2:n", "n/2", "5 - n/2", "3*n:20", "n*(n+1):n*(n+5)",
"2*n, n"
]
nonlinear_dimspecs = ["2*n:3*n*n+2*n"]
all_dimspecs = linear_dimspecs + nonlinear_dimspecs
code = ""
for count, dimspec in enumerate(all_dimspecs):
lst = [(d.split(":")[0] if ":" in d else "1") for d in dimspec.split(',')]
code += code_template.format(
count=count,
dimspec=dimspec,
first=", ".join(lst),
)
@pytest.mark.parametrize("dimspec", all_dimspecs)
def test_array_size(self, dimspec):
count = self.all_dimspecs.index(dimspec)
get_arr_size = getattr(self.module, f"get_arr_size_{count}")
for n in [1, 2, 3, 4, 5]:
sz, a = get_arr_size(n)
assert a.size == sz
@pytest.mark.parametrize("dimspec", all_dimspecs)
def test_inv_array_size(self, dimspec):
count = self.all_dimspecs.index(dimspec)
get_arr_size = getattr(self.module, f"get_arr_size_{count}")
get_inv_arr_size = getattr(self.module, f"get_inv_arr_size_{count}")
for n in [1, 2, 3, 4, 5]:
sz, a = get_arr_size(n)
if dimspec in self.nonlinear_dimspecs:
# one must specify n as input, the call we'll ensure
# that a and n are compatible:
n1 = get_inv_arr_size(a, n)
else:
# in case of linear dependence, n can be determined
# from the shape of a:
n1 = get_inv_arr_size(a)
# n1 may be different from n (for instance, when `a` size
# is a function of some `n` fraction) but it must produce
# the same sized array
sz1, _ = get_arr_size(n1)
assert sz == sz1, (n, n1, sz, sz1)
class TestModuleDeclaration:
def test_dependencies(self, tmp_path):
fpath = util.getpath("tests", "src", "crackfortran", "foo_deps.f90")
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"
with f_path.open('w', encoding=encoding) as ff:
ff.write("""
subroutine foo()
end subroutine foo
""")
mod = crackfortran.crackfortran([str(f_path)])
assert mod[0]['name'] == 'foo'
class TestUnicodeComment(util.F2PyTest):
sources = [util.getpath("tests", "src", "crackfortran", "unicode_comment.f90")]
@pytest.mark.skipif(
(importlib.util.find_spec("charset_normalizer") is None),
reason="test requires charset_normalizer which is not installed",
)
def test_encoding_comment(self):
self.module.foo(3)

View File

@@ -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', 'code'))
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

View File

@@ -0,0 +1,15 @@
from . import util
import numpy as np
class TestF2Cmap(util.F2PyTest):
sources = [
util.getpath("tests", "src", "f2cmap", "isoFortranEnvMap.f90"),
util.getpath("tests", "src", "f2cmap", ".f2py_f2cmap")
]
# gh-15095
def test_long_long_map(self):
inp = np.ones(3)
out = self.module.func1(inp)
exp_out = 3
assert out == exp_out

View File

@@ -0,0 +1,769 @@
import textwrap, re, sys, subprocess, shlex
from pathlib import Path
from collections import namedtuple
import pytest
from . import util
from numpy.f2py.f2py2e import main as f2pycli
#########################
# CLI utils and classes #
#########################
PPaths = namedtuple("PPaths", "finp, f90inp, pyf, wrap77, wrap90, cmodf")
def get_io_paths(fname_inp, mname="untitled"):
"""Takes in a temporary file for testing and returns the expected output and input paths
Here expected output is essentially one of any of the possible generated
files.
..note::
Since this does not actually run f2py, none of these are guaranteed to
exist, and module names are typically incorrect
Parameters
----------
fname_inp : str
The input filename
mname : str, optional
The name of the module, untitled by default
Returns
-------
genp : NamedTuple PPaths
The possible paths which are generated, not all of which exist
"""
bpath = Path(fname_inp)
return PPaths(
finp=bpath.with_suffix(".f"),
f90inp=bpath.with_suffix(".f90"),
pyf=bpath.with_suffix(".pyf"),
wrap77=bpath.with_name(f"{mname}-f2pywrappers.f"),
wrap90=bpath.with_name(f"{mname}-f2pywrappers2.f90"),
cmodf=bpath.with_name(f"{mname}module.c"),
)
##############
# CLI Fixtures and Tests #
#############
@pytest.fixture(scope="session")
def hello_world_f90(tmpdir_factory):
"""Generates a single f90 file for testing"""
fdat = util.getpath("tests", "src", "cli", "hiworld.f90").read_text()
fn = tmpdir_factory.getbasetemp() / "hello.f90"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def hello_world_f77(tmpdir_factory):
"""Generates a single f77 file for testing"""
fdat = util.getpath("tests", "src", "cli", "hi77.f").read_text()
fn = tmpdir_factory.getbasetemp() / "hello.f"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def retreal_f77(tmpdir_factory):
"""Generates a single f77 file for testing"""
fdat = util.getpath("tests", "src", "return_real", "foo77.f").read_text()
fn = tmpdir_factory.getbasetemp() / "foo.f"
fn.write_text(fdat, encoding="ascii")
return fn
@pytest.fixture(scope="session")
def f2cmap_f90(tmpdir_factory):
"""Generates a single f90 file for testing"""
fdat = util.getpath("tests", "src", "f2cmap", "isoFortranEnvMap.f90").read_text()
f2cmap = util.getpath("tests", "src", "f2cmap", ".f2py_f2cmap").read_text()
fn = tmpdir_factory.getbasetemp() / "f2cmap.f90"
fmap = tmpdir_factory.getbasetemp() / "mapfile"
fn.write_text(fdat, encoding="ascii")
fmap.write_text(f2cmap, encoding="ascii")
return fn
def test_gen_pyf(capfd, hello_world_f90, monkeypatch):
"""Ensures that a signature file is generated via the CLI
CLI :: -h
"""
ipath = Path(hello_world_f90)
opath = Path(hello_world_f90).stem + ".pyf"
monkeypatch.setattr(sys, "argv", f'f2py -h {opath} {ipath}'.split())
with util.switchdir(ipath.parent):
f2pycli() # Generate wrappers
out, _ = capfd.readouterr()
assert "Saving signatures to file" in out
assert Path(f'{opath}').exists()
def test_gen_pyf_stdout(capfd, hello_world_f90, monkeypatch):
"""Ensures that a signature file can be dumped to stdout
CLI :: -h
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -h stdout {ipath}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Saving signatures to file" in out
assert "function hi() ! in " in out
def test_gen_pyf_no_overwrite(capfd, hello_world_f90, monkeypatch):
"""Ensures that the CLI refuses to overwrite signature files
CLI :: -h without --overwrite-signature
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -h faker.pyf {ipath}'.split())
with util.switchdir(ipath.parent):
Path("faker.pyf").write_text("Fake news", encoding="ascii")
with pytest.raises(SystemExit):
f2pycli() # Refuse to overwrite
_, err = capfd.readouterr()
assert "Use --overwrite-signature to overwrite" in err
@pytest.mark.xfail
def test_f2py_skip(capfd, retreal_f77, monkeypatch):
"""Tests that functions can be skipped
CLI :: skip:
"""
foutl = get_io_paths(retreal_f77, mname="test")
ipath = foutl.finp
toskip = "t0 t4 t8 sd s8 s4"
remaining = "td s0"
monkeypatch.setattr(
sys, "argv",
f'f2py {ipath} -m test skip: {toskip}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, err = capfd.readouterr()
for skey in toskip.split():
assert (
f'buildmodule: Could not found the body of interfaced routine "{skey}". Skipping.'
in err)
for rkey in remaining.split():
assert f'Constructing wrapper function "{rkey}"' in out
def test_f2py_only(capfd, retreal_f77, monkeypatch):
"""Test that functions can be kept by only:
CLI :: only:
"""
foutl = get_io_paths(retreal_f77, mname="test")
ipath = foutl.finp
toskip = "t0 t4 t8 sd s8 s4"
tokeep = "td s0"
monkeypatch.setattr(
sys, "argv",
f'f2py {ipath} -m test only: {tokeep}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, err = capfd.readouterr()
for skey in toskip.split():
assert (
f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
in err)
for rkey in tokeep.split():
assert f'Constructing wrapper function "{rkey}"' in out
def test_file_processing_switch(capfd, hello_world_f90, retreal_f77,
monkeypatch):
"""Tests that it is possible to return to file processing mode
CLI :: :
BUG: numpy-gh #20520
"""
foutl = get_io_paths(retreal_f77, mname="test")
ipath = foutl.finp
toskip = "t0 t4 t8 sd s8 s4"
ipath2 = Path(hello_world_f90)
tokeep = "td s0 hi" # hi is in ipath2
mname = "blah"
monkeypatch.setattr(
sys,
"argv",
f'f2py {ipath} -m {mname} only: {tokeep} : {ipath2}'.split(
),
)
with util.switchdir(ipath.parent):
f2pycli()
out, err = capfd.readouterr()
for skey in toskip.split():
assert (
f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
in err)
for rkey in tokeep.split():
assert f'Constructing wrapper function "{rkey}"' in out
def test_mod_gen_f77(capfd, hello_world_f90, monkeypatch):
"""Checks the generation of files based on a module name
CLI :: -m
"""
MNAME = "hi"
foutl = get_io_paths(hello_world_f90, mname=MNAME)
ipath = foutl.f90inp
monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME}'.split())
with util.switchdir(ipath.parent):
f2pycli()
# Always generate C module
assert Path.exists(foutl.cmodf)
# File contains a function, check for F77 wrappers
assert Path.exists(foutl.wrap77)
def test_lower_cmod(capfd, hello_world_f77, monkeypatch):
"""Lowers cases by flag or when -h is present
CLI :: --[no-]lower
"""
foutl = get_io_paths(hello_world_f77, mname="test")
ipath = foutl.finp
capshi = re.compile(r"HI\(\)")
capslo = re.compile(r"hi\(\)")
# Case I: --lower is passed
monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m test --lower'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is not None
assert capshi.search(out) is None
# Case II: --no-lower is passed
monkeypatch.setattr(sys, "argv",
f'f2py {ipath} -m test --no-lower'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is None
assert capshi.search(out) is not None
def test_lower_sig(capfd, hello_world_f77, monkeypatch):
"""Lowers cases in signature files by flag or when -h is present
CLI :: --[no-]lower -h
"""
foutl = get_io_paths(hello_world_f77, mname="test")
ipath = foutl.finp
# Signature files
capshi = re.compile(r"Block: HI")
capslo = re.compile(r"Block: hi")
# Case I: --lower is implied by -h
# TODO: Clean up to prevent passing --overwrite-signature
monkeypatch.setattr(
sys,
"argv",
f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature'.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is not None
assert capshi.search(out) is None
# Case II: --no-lower overrides -h
monkeypatch.setattr(
sys,
"argv",
f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature --no-lower'
.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert capslo.search(out) is None
assert capshi.search(out) is not None
def test_build_dir(capfd, hello_world_f90, monkeypatch):
"""Ensures that the build directory can be specified
CLI :: --build-dir
"""
ipath = Path(hello_world_f90)
mname = "blah"
odir = "tttmp"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --build-dir {odir}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert f"Wrote C/API module \"{mname}\"" in out
def test_overwrite(capfd, hello_world_f90, monkeypatch):
"""Ensures that the build directory can be specified
CLI :: --overwrite-signature
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(
sys, "argv",
f'f2py -h faker.pyf {ipath} --overwrite-signature'.split())
with util.switchdir(ipath.parent):
Path("faker.pyf").write_text("Fake news", encoding="ascii")
f2pycli()
out, _ = capfd.readouterr()
assert "Saving signatures to file" in out
def test_latexdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that TeX documentation is written out
CLI :: --latex-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --latex-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Documentation is saved to file" in out
with Path(f"{mname}module.tex").open() as otex:
assert "\\documentclass" in otex.read()
def test_nolatexdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that TeX documentation is written out
CLI :: --no-latex-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --no-latex-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Documentation is saved to file" not in out
def test_shortlatex(capfd, hello_world_f90, monkeypatch):
"""Ensures that truncated documentation is written out
TODO: Test to ensure this has no effect without --latex-doc
CLI :: --latex-doc --short-latex
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(
sys,
"argv",
f'f2py -m {mname} {ipath} --latex-doc --short-latex'.split(),
)
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Documentation is saved to file" in out
with Path(f"./{mname}module.tex").open() as otex:
assert "\\documentclass" not in otex.read()
def test_restdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that RsT documentation is written out
CLI :: --rest-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --rest-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "ReST Documentation is saved to file" in out
with Path(f"./{mname}module.rest").open() as orst:
assert r".. -*- rest -*-" in orst.read()
def test_norestexdoc(capfd, hello_world_f90, monkeypatch):
"""Ensures that TeX documentation is written out
CLI :: --no-rest-doc
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --no-rest-doc'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "ReST Documentation is saved to file" not in out
def test_debugcapi(capfd, hello_world_f90, monkeypatch):
"""Ensures that debugging wrappers are written
CLI :: --debug-capi
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --debug-capi'.split())
with util.switchdir(ipath.parent):
f2pycli()
with Path(f"./{mname}module.c").open() as ocmod:
assert r"#define DEBUGCFUNCS" in ocmod.read()
@pytest.mark.xfail(reason="Consistently fails on CI.")
def test_debugcapi_bld(hello_world_f90, monkeypatch):
"""Ensures that debugging wrappers work
CLI :: --debug-capi -c
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} -c --debug-capi'.split())
with util.switchdir(ipath.parent):
f2pycli()
cmd_run = shlex.split("python3 -c \"import blah; blah.hi()\"")
rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
eout = ' Hello World\n'
eerr = textwrap.dedent("""\
debug-capi:Python C/API function blah.hi()
debug-capi:float hi=:output,hidden,scalar
debug-capi:hi=0
debug-capi:Fortran subroutine `f2pywraphi(&hi)'
debug-capi:hi=0
debug-capi:Building return value.
debug-capi:Python C/API function blah.hi: successful.
debug-capi:Freeing memory.
""")
assert rout.stdout == eout
assert rout.stderr == eerr
def test_wrapfunc_def(capfd, hello_world_f90, monkeypatch):
"""Ensures that fortran subroutine wrappers for F77 are included by default
CLI :: --[no]-wrap-functions
"""
# Implied
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv", f'f2py -m {mname} {ipath}'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert r"Fortran 77 wrappers are saved to" in out
# Explicit
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --wrap-functions'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert r"Fortran 77 wrappers are saved to" in out
def test_nowrapfunc(capfd, hello_world_f90, monkeypatch):
"""Ensures that fortran subroutine wrappers for F77 can be disabled
CLI :: --no-wrap-functions
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(sys, "argv",
f'f2py -m {mname} {ipath} --no-wrap-functions'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert r"Fortran 77 wrappers are saved to" not in out
def test_inclheader(capfd, hello_world_f90, monkeypatch):
"""Add to the include directories
CLI :: -include
TODO: Document this in the help string
"""
ipath = Path(hello_world_f90)
mname = "blah"
monkeypatch.setattr(
sys,
"argv",
f'f2py -m {mname} {ipath} -include<stdbool.h> -include<stdio.h> '.
split(),
)
with util.switchdir(ipath.parent):
f2pycli()
with Path(f"./{mname}module.c").open() as ocmod:
ocmr = ocmod.read()
assert "#include <stdbool.h>" in ocmr
assert "#include <stdio.h>" in ocmr
def test_inclpath():
"""Add to the include directories
CLI :: --include-paths
"""
# TODO: populate
pass
def test_hlink():
"""Add to the include directories
CLI :: --help-link
"""
# TODO: populate
pass
def test_f2cmap(capfd, f2cmap_f90, monkeypatch):
"""Check that Fortran-to-Python KIND specs can be passed
CLI :: --f2cmap
"""
ipath = Path(f2cmap_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --f2cmap mapfile'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "Reading f2cmap from 'mapfile' ..." in out
assert "Mapping \"real(kind=real32)\" to \"float\"" in out
assert "Mapping \"real(kind=real64)\" to \"double\"" in out
assert "Mapping \"integer(kind=int64)\" to \"long_long\"" in out
assert "Successfully applied user defined f2cmap changes" in out
def test_quiet(capfd, hello_world_f90, monkeypatch):
"""Reduce verbosity
CLI :: --quiet
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --quiet'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert len(out) == 0
def test_verbose(capfd, hello_world_f90, monkeypatch):
"""Increase verbosity
CLI :: --verbose
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --verbose'.split())
with util.switchdir(ipath.parent):
f2pycli()
out, _ = capfd.readouterr()
assert "analyzeline" in out
def test_version(capfd, monkeypatch):
"""Ensure version
CLI :: -v
"""
monkeypatch.setattr(sys, "argv", 'f2py -v'.split())
# TODO: f2py2e should not call sys.exit() after printing the version
with pytest.raises(SystemExit):
f2pycli()
out, _ = capfd.readouterr()
import numpy as np
assert np.__version__ == out.strip()
@pytest.mark.xfail(reason="Consistently fails on CI.")
def test_npdistop(hello_world_f90, monkeypatch):
"""
CLI :: -c
"""
ipath = Path(hello_world_f90)
monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} -c'.split())
with util.switchdir(ipath.parent):
f2pycli()
cmd_run = shlex.split("python -c \"import blah; blah.hi()\"")
rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
eout = ' Hello World\n'
assert rout.stdout == eout
# Numpy distutils flags
# TODO: These should be tested separately
def test_npd_fcompiler():
"""
CLI :: -c --fcompiler
"""
# TODO: populate
pass
def test_npd_compiler():
"""
CLI :: -c --compiler
"""
# TODO: populate
pass
def test_npd_help_fcompiler():
"""
CLI :: -c --help-fcompiler
"""
# TODO: populate
pass
def test_npd_f77exec():
"""
CLI :: -c --f77exec
"""
# TODO: populate
pass
def test_npd_f90exec():
"""
CLI :: -c --f90exec
"""
# TODO: populate
pass
def test_npd_f77flags():
"""
CLI :: -c --f77flags
"""
# TODO: populate
pass
def test_npd_f90flags():
"""
CLI :: -c --f90flags
"""
# TODO: populate
pass
def test_npd_opt():
"""
CLI :: -c --opt
"""
# TODO: populate
pass
def test_npd_arch():
"""
CLI :: -c --arch
"""
# TODO: populate
pass
def test_npd_noopt():
"""
CLI :: -c --noopt
"""
# TODO: populate
pass
def test_npd_noarch():
"""
CLI :: -c --noarch
"""
# TODO: populate
pass
def test_npd_debug():
"""
CLI :: -c --debug
"""
# TODO: populate
pass
def test_npd_link_auto():
"""
CLI :: -c --link-<resource>
"""
# TODO: populate
pass
def test_npd_lib():
"""
CLI :: -c -L/path/to/lib/ -l<libname>
"""
# TODO: populate
pass
def test_npd_define():
"""
CLI :: -D<define>
"""
# TODO: populate
pass
def test_npd_undefine():
"""
CLI :: -U<name>
"""
# TODO: populate
pass
def test_npd_incl():
"""
CLI :: -I/path/to/include/
"""
# TODO: populate
pass
def test_npd_linker():
"""
CLI :: <filename>.o <filename>.so <filename>.a
"""
# TODO: populate
pass

View File

@@ -0,0 +1,26 @@
import os
import pytest
from numpy.f2py.crackfortran import (
_selected_int_kind_func as selected_int_kind,
_selected_real_kind_func as selected_real_kind,
)
from . import util
class TestKind(util.F2PyTest):
sources = [util.getpath("tests", "src", "kind", "foo.f90")]
def test_all(self):
selectedrealkind = self.module.selectedrealkind
selectedintkind = self.module.selectedintkind
for i in range(40):
assert selectedintkind(i) == selected_int_kind(
i
), f"selectedintkind({i}): expected {selected_int_kind(i)!r} but got {selectedintkind(i)!r}"
for i in range(20):
assert selectedrealkind(i) == selected_real_kind(
i
), f"selectedrealkind({i}): expected {selected_real_kind(i)!r} but got {selectedrealkind(i)!r}"

View File

@@ -0,0 +1,33 @@
import os
import textwrap
import pytest
from numpy.testing import IS_PYPY
from . import util
class TestMixed(util.F2PyTest):
sources = [
util.getpath("tests", "src", "mixed", "foo.f"),
util.getpath("tests", "src", "mixed", "foo_fixed.f90"),
util.getpath("tests", "src", "mixed", "foo_free.f90"),
]
def test_all(self):
assert self.module.bar11() == 11
assert self.module.foo_fixed.bar12() == 12
assert self.module.foo_free.bar13() == 13
@pytest.mark.xfail(IS_PYPY,
reason="PyPy cannot modify tp_doc after PyType_Ready")
def test_docstring(self):
expected = textwrap.dedent("""\
a = bar11()
Wrapper for ``bar11``.
Returns
-------
a : int
""")
assert self.module.bar11.__doc__ == expected

View File

@@ -0,0 +1,27 @@
import os
import sys
import pytest
import textwrap
from . import util
from numpy.testing import IS_PYPY
class TestModuleDocString(util.F2PyTest):
sources = [
util.getpath("tests", "src", "module_data",
"module_data_docstring.f90")
]
@pytest.mark.skipif(sys.platform == "win32",
reason="Fails with MinGW64 Gfortran (Issue #9673)")
@pytest.mark.xfail(IS_PYPY,
reason="PyPy cannot modify tp_doc after PyType_Ready")
def test_module_docstring(self):
assert self.module.mod.__doc__ == textwrap.dedent("""\
i : 'i'-scalar
x : 'i'-array(4)
a : 'f'-array(2,3)
b : 'f'-array(-1,-1), not allocated\x00
foo()\n
Wrapper for ``foo``.\n\n""")

View File

@@ -0,0 +1,112 @@
import os
import pytest
import numpy as np
from . import util
class TestParameters(util.F2PyTest):
# Check that intent(in out) translates as intent(inout)
sources = [
util.getpath("tests", "src", "parameter", "constant_real.f90"),
util.getpath("tests", "src", "parameter", "constant_integer.f90"),
util.getpath("tests", "src", "parameter", "constant_both.f90"),
util.getpath("tests", "src", "parameter", "constant_compound.f90"),
util.getpath("tests", "src", "parameter", "constant_non_compound.f90"),
]
@pytest.mark.slow
def test_constant_real_single(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.float32)[::2]
pytest.raises(ValueError, self.module.foo_single, x)
# check values with contiguous array
x = np.arange(3, dtype=np.float32)
self.module.foo_single(x)
assert np.allclose(x, [0 + 1 + 2 * 3, 1, 2])
@pytest.mark.slow
def test_constant_real_double(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.float64)[::2]
pytest.raises(ValueError, self.module.foo_double, x)
# check values with contiguous array
x = np.arange(3, dtype=np.float64)
self.module.foo_double(x)
assert np.allclose(x, [0 + 1 + 2 * 3, 1, 2])
@pytest.mark.slow
def test_constant_compound_int(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.int32)[::2]
pytest.raises(ValueError, self.module.foo_compound_int, x)
# check values with contiguous array
x = np.arange(3, dtype=np.int32)
self.module.foo_compound_int(x)
assert np.allclose(x, [0 + 1 + 2 * 6, 1, 2])
@pytest.mark.slow
def test_constant_non_compound_int(self):
# check values
x = np.arange(4, dtype=np.int32)
self.module.foo_non_compound_int(x)
assert np.allclose(x, [0 + 1 + 2 + 3 * 4, 1, 2, 3])
@pytest.mark.slow
def test_constant_integer_int(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.int32)[::2]
pytest.raises(ValueError, self.module.foo_int, x)
# check values with contiguous array
x = np.arange(3, dtype=np.int32)
self.module.foo_int(x)
assert np.allclose(x, [0 + 1 + 2 * 3, 1, 2])
@pytest.mark.slow
def test_constant_integer_long(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.int64)[::2]
pytest.raises(ValueError, self.module.foo_long, x)
# check values with contiguous array
x = np.arange(3, dtype=np.int64)
self.module.foo_long(x)
assert np.allclose(x, [0 + 1 + 2 * 3, 1, 2])
@pytest.mark.slow
def test_constant_both(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.float64)[::2]
pytest.raises(ValueError, self.module.foo, x)
# check values with contiguous array
x = np.arange(3, dtype=np.float64)
self.module.foo(x)
assert np.allclose(x, [0 + 1 * 3 * 3 + 2 * 3 * 3, 1 * 3, 2 * 3])
@pytest.mark.slow
def test_constant_no(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.float64)[::2]
pytest.raises(ValueError, self.module.foo_no, x)
# check values with contiguous array
x = np.arange(3, dtype=np.float64)
self.module.foo_no(x)
assert np.allclose(x, [0 + 1 * 3 * 3 + 2 * 3 * 3, 1 * 3, 2 * 3])
@pytest.mark.slow
def test_constant_sum(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.float64)[::2]
pytest.raises(ValueError, self.module.foo_sum, x)
# check values with contiguous array
x = np.arange(3, dtype=np.float64)
self.module.foo_sum(x)
assert np.allclose(x, [0 + 1 * 3 * 3 + 2 * 3 * 3, 1 * 3, 2 * 3])

View File

@@ -0,0 +1,16 @@
"""See https://github.com/numpy/numpy/pull/10676.
"""
import sys
import pytest
from . import util
class TestQuotedCharacter(util.F2PyTest):
sources = [util.getpath("tests", "src", "quoted_character", "foo.f")]
@pytest.mark.skipif(sys.platform == "win32",
reason="Fails with MinGW64 Gfortran (Issue #9673)")
def test_quoted_character(self):
assert self.module.foo() == (b"'", b'"', b";", b"!", b"(", b")")

View File

@@ -0,0 +1,66 @@
import os
import pytest
import numpy as np
from . import util
class TestIntentInOut(util.F2PyTest):
# Check that intent(in out) translates as intent(inout)
sources = [util.getpath("tests", "src", "regression", "inout.f90")]
@pytest.mark.slow
def test_inout(self):
# non-contiguous should raise error
x = np.arange(6, dtype=np.float32)[::2]
pytest.raises(ValueError, self.module.foo, x)
# check values with contiguous array
x = np.arange(3, dtype=np.float32)
self.module.foo(x)
assert np.allclose(x, [3, 1, 2])
class TestNegativeBounds(util.F2PyTest):
# Check that negative bounds work correctly
sources = [util.getpath("tests", "src", "negative_bounds", "issue_20853.f90")]
@pytest.mark.slow
def test_negbound(self):
xvec = np.arange(12)
xlow = -6
xhigh = 4
# Calculate the upper bound,
# Keeping the 1 index in mind
def ubound(xl, xh):
return xh - xl + 1
rval = self.module.foo(is_=xlow, ie_=xhigh,
arr=xvec[:ubound(xlow, xhigh)])
expval = np.arange(11, dtype = np.float32)
assert np.allclose(rval, expval)
class TestNumpyVersionAttribute(util.F2PyTest):
# Check that th attribute __f2py_numpy_version__ is present
# in the compiled module and that has the value np.__version__.
sources = [util.getpath("tests", "src", "regression", "inout.f90")]
@pytest.mark.slow
def test_numpy_version_attribute(self):
# Check that self.module has an attribute named "__f2py_numpy_version__"
assert hasattr(self.module, "__f2py_numpy_version__")
# Check that the attribute __f2py_numpy_version__ is a string
assert isinstance(self.module.__f2py_numpy_version__, str)
# Check that __f2py_numpy_version__ has the value numpy.__version__
assert np.__version__ == self.module.__f2py_numpy_version__
def test_include_path():
incdir = np.f2py.get_include()
fnames_in_dir = os.listdir(incdir)
for fname in ("fortranobject.c", "fortranobject.h"):
assert fname in fnames_in_dir

View File

@@ -0,0 +1,45 @@
import pytest
from numpy import array
from . import util
import platform
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"
r = t("ab")
assert r == b"a"
r = t(array("ab"))
assert r == b"a"
r = t(array(77, "u1"))
assert r == b"M"
elif tname in ["ts", "ss"]:
assert t(23) == b"23"
assert t("123456789abcdef") == b"123456789a"
elif tname in ["t5", "s5"]:
assert t(23) == b"23"
assert t("ab") == b"ab"
assert t("123456789abcdef") == b"12345"
else:
raise NotImplementedError
class TestFReturnCharacter(TestReturnCharacter):
sources = [
util.getpath("tests", "src", "return_character", "foo77.f"),
util.getpath("tests", "src", "return_character", "foo90.f90"),
]
@pytest.mark.xfail(IS_S390X, reason="callback returns ' '")
@pytest.mark.parametrize("name", "t0,t1,t5,s0,s1,s5,ss".split(","))
def test_all_f77(self, name):
self.check_function(getattr(self.module, name), name)
@pytest.mark.xfail(IS_S390X, reason="callback returns ' '")
@pytest.mark.parametrize("name", "t0,t1,t5,ts,s0,s1,s5,ss".split(","))
def test_all_f90(self, name):
self.check_function(getattr(self.module.f90_return_char, name), name)

View File

@@ -0,0 +1,65 @@
import pytest
from numpy import array
from . import util
class TestReturnComplex(util.F2PyTest):
def check_function(self, t, tname):
if tname in ["t0", "t8", "s0", "s8"]:
err = 1e-5
else:
err = 0.0
assert abs(t(234j) - 234.0j) <= err
assert abs(t(234.6) - 234.6) <= err
assert abs(t(234) - 234.0) <= err
assert abs(t(234.6 + 3j) - (234.6 + 3j)) <= err
# assert abs(t('234')-234.)<=err
# assert abs(t('234.6')-234.6)<=err
assert abs(t(-234) + 234.0) <= err
assert abs(t([234]) - 234.0) <= err
assert abs(t((234, )) - 234.0) <= err
assert abs(t(array(234)) - 234.0) <= err
assert abs(t(array(23 + 4j, "F")) - (23 + 4j)) <= err
assert abs(t(array([234])) - 234.0) <= err
assert abs(t(array([[234]])) - 234.0) <= err
assert abs(t(array([234]).astype("b")) + 22.0) <= err
assert abs(t(array([234], "h")) - 234.0) <= err
assert abs(t(array([234], "i")) - 234.0) <= err
assert abs(t(array([234], "l")) - 234.0) <= err
assert abs(t(array([234], "q")) - 234.0) <= err
assert abs(t(array([234], "f")) - 234.0) <= err
assert abs(t(array([234], "d")) - 234.0) <= err
assert abs(t(array([234 + 3j], "F")) - (234 + 3j)) <= err
assert abs(t(array([234], "D")) - 234.0) <= err
# pytest.raises(TypeError, t, array([234], 'a1'))
pytest.raises(TypeError, t, "abc")
pytest.raises(IndexError, t, [])
pytest.raises(IndexError, t, ())
pytest.raises(TypeError, t, t)
pytest.raises(TypeError, t, {})
try:
r = t(10**400)
assert repr(r) in ["(inf+0j)", "(Infinity+0j)"]
except OverflowError:
pass
class TestFReturnComplex(TestReturnComplex):
sources = [
util.getpath("tests", "src", "return_complex", "foo77.f"),
util.getpath("tests", "src", "return_complex", "foo90.f90"),
]
@pytest.mark.parametrize("name", "t0,t8,t16,td,s0,s8,s16,sd".split(","))
def test_all_f77(self, name):
self.check_function(getattr(self.module, name), name)
@pytest.mark.parametrize("name", "t0,t8,t16,td,s0,s8,s16,sd".split(","))
def test_all_f90(self, name):
self.check_function(getattr(self.module.f90_return_complex, name),
name)

View File

@@ -0,0 +1,55 @@
import pytest
from numpy import array
from . import util
class TestReturnInteger(util.F2PyTest):
def check_function(self, t, tname):
assert t(123) == 123
assert t(123.6) == 123
assert t("123") == 123
assert t(-123) == -123
assert t([123]) == 123
assert t((123, )) == 123
assert t(array(123)) == 123
assert t(array([123])) == 123
assert t(array([[123]])) == 123
assert t(array([123], "b")) == 123
assert t(array([123], "h")) == 123
assert t(array([123], "i")) == 123
assert t(array([123], "l")) == 123
assert t(array([123], "B")) == 123
assert t(array([123], "f")) == 123
assert t(array([123], "d")) == 123
# pytest.raises(ValueError, t, array([123],'S3'))
pytest.raises(ValueError, t, "abc")
pytest.raises(IndexError, t, [])
pytest.raises(IndexError, t, ())
pytest.raises(Exception, t, t)
pytest.raises(Exception, t, {})
if tname in ["t8", "s8"]:
pytest.raises(OverflowError, t, 100000000000000000000000)
pytest.raises(OverflowError, t, 10000000011111111111111.23)
class TestFReturnInteger(TestReturnInteger):
sources = [
util.getpath("tests", "src", "return_integer", "foo77.f"),
util.getpath("tests", "src", "return_integer", "foo90.f90"),
]
@pytest.mark.parametrize("name",
"t0,t1,t2,t4,t8,s0,s1,s2,s4,s8".split(","))
def test_all_f77(self, name):
self.check_function(getattr(self.module, name), name)
@pytest.mark.parametrize("name",
"t0,t1,t2,t4,t8,s0,s1,s2,s4,s8".split(","))
def test_all_f90(self, name):
self.check_function(getattr(self.module.f90_return_integer, name),
name)

View File

@@ -0,0 +1,64 @@
import pytest
from numpy import array
from . import util
class TestReturnLogical(util.F2PyTest):
def check_function(self, t):
assert t(True) == 1
assert t(False) == 0
assert t(0) == 0
assert t(None) == 0
assert t(0.0) == 0
assert t(0j) == 0
assert t(1j) == 1
assert t(234) == 1
assert t(234.6) == 1
assert t(234.6 + 3j) == 1
assert t("234") == 1
assert t("aaa") == 1
assert t("") == 0
assert t([]) == 0
assert t(()) == 0
assert t({}) == 0
assert t(t) == 1
assert t(-234) == 1
assert t(10**100) == 1
assert t([234]) == 1
assert t((234, )) == 1
assert t(array(234)) == 1
assert t(array([234])) == 1
assert t(array([[234]])) == 1
assert t(array([127], "b")) == 1
assert t(array([234], "h")) == 1
assert t(array([234], "i")) == 1
assert t(array([234], "l")) == 1
assert t(array([234], "f")) == 1
assert t(array([234], "d")) == 1
assert t(array([234 + 3j], "F")) == 1
assert t(array([234], "D")) == 1
assert t(array(0)) == 0
assert t(array([0])) == 0
assert t(array([[0]])) == 0
assert t(array([0j])) == 0
assert t(array([1])) == 1
pytest.raises(ValueError, t, array([0, 0]))
class TestFReturnLogical(TestReturnLogical):
sources = [
util.getpath("tests", "src", "return_logical", "foo77.f"),
util.getpath("tests", "src", "return_logical", "foo90.f90"),
]
@pytest.mark.slow
@pytest.mark.parametrize("name", "t0,t1,t2,t4,s0,s1,s2,s4".split(","))
def test_all_f77(self, name):
self.check_function(getattr(self.module, name))
@pytest.mark.slow
@pytest.mark.parametrize("name",
"t0,t1,t2,t4,t8,s0,s1,s2,s4,s8".split(","))
def test_all_f90(self, name):
self.check_function(getattr(self.module.f90_return_logical, name))

View File

@@ -0,0 +1,109 @@
import platform
import pytest
import numpy as np
from numpy import array
from . import util
class TestReturnReal(util.F2PyTest):
def check_function(self, t, tname):
if tname in ["t0", "t4", "s0", "s4"]:
err = 1e-5
else:
err = 0.0
assert abs(t(234) - 234.0) <= err
assert abs(t(234.6) - 234.6) <= err
assert abs(t("234") - 234) <= err
assert abs(t("234.6") - 234.6) <= err
assert abs(t(-234) + 234) <= err
assert abs(t([234]) - 234) <= err
assert abs(t((234, )) - 234.0) <= err
assert abs(t(array(234)) - 234.0) <= err
assert abs(t(array([234])) - 234.0) <= err
assert abs(t(array([[234]])) - 234.0) <= err
assert abs(t(array([234]).astype("b")) + 22) <= err
assert abs(t(array([234], "h")) - 234.0) <= err
assert abs(t(array([234], "i")) - 234.0) <= err
assert abs(t(array([234], "l")) - 234.0) <= err
assert abs(t(array([234], "B")) - 234.0) <= err
assert abs(t(array([234], "f")) - 234.0) <= err
assert abs(t(array([234], "d")) - 234.0) <= err
if tname in ["t0", "t4", "s0", "s4"]:
assert t(1e200) == t(1e300) # inf
# pytest.raises(ValueError, t, array([234], 'S1'))
pytest.raises(ValueError, t, "abc")
pytest.raises(IndexError, t, [])
pytest.raises(IndexError, t, ())
pytest.raises(Exception, t, t)
pytest.raises(Exception, t, {})
try:
r = t(10**400)
assert repr(r) in ["inf", "Infinity"]
except OverflowError:
pass
@pytest.mark.skipif(
platform.system() == "Darwin",
reason="Prone to error when run with numpy/f2py/tests on mac os, "
"but not when run in isolation",
)
@pytest.mark.skipif(
np.dtype(np.intp).itemsize < 8,
reason="32-bit builds are buggy"
)
class TestCReturnReal(TestReturnReal):
suffix = ".pyf"
module_name = "c_ext_return_real"
code = """
python module c_ext_return_real
usercode \'\'\'
float t4(float value) { return value; }
void s4(float *t4, float value) { *t4 = value; }
double t8(double value) { return value; }
void s8(double *t8, double value) { *t8 = value; }
\'\'\'
interface
function t4(value)
real*4 intent(c) :: t4,value
end
function t8(value)
real*8 intent(c) :: t8,value
end
subroutine s4(t4,value)
intent(c) s4
real*4 intent(out) :: t4
real*4 intent(c) :: value
end
subroutine s8(t8,value)
intent(c) s8
real*8 intent(out) :: t8
real*8 intent(c) :: value
end
end interface
end python module c_ext_return_real
"""
@pytest.mark.parametrize("name", "t4,t8,s4,s8".split(","))
def test_all(self, name):
self.check_function(getattr(self.module, name), name)
class TestFReturnReal(TestReturnReal):
sources = [
util.getpath("tests", "src", "return_real", "foo77.f"),
util.getpath("tests", "src", "return_real", "foo90.f90"),
]
@pytest.mark.parametrize("name", "t0,t4,t8,td,s0,s4,s8,sd".split(","))
def test_all_f77(self, name):
self.check_function(getattr(self.module, name), name)
@pytest.mark.parametrize("name", "t0,t4,t8,td,s0,s4,s8,sd".split(","))
def test_all_f90(self, name):
self.check_function(getattr(self.module.f90_return_real, name), name)

View File

@@ -0,0 +1,74 @@
import platform
import pytest
import numpy as np
from . import util
@pytest.mark.skipif(
platform.system() == "Darwin",
reason="Prone to error when run with numpy/f2py/tests on mac os, "
"but not when run in isolation",
)
@pytest.mark.skipif(
np.dtype(np.intp).itemsize < 8,
reason="32-bit builds are buggy"
)
class TestMultiline(util.F2PyTest):
suffix = ".pyf"
module_name = "multiline"
code = f"""
python module {module_name}
usercode '''
void foo(int* x) {{
char dummy = ';';
*x = 42;
}}
'''
interface
subroutine foo(x)
intent(c) foo
integer intent(out) :: x
end subroutine foo
end interface
end python module {module_name}
"""
def test_multiline(self):
assert self.module.foo() == 42
@pytest.mark.skipif(
platform.system() == "Darwin",
reason="Prone to error when run with numpy/f2py/tests on mac os, "
"but not when run in isolation",
)
@pytest.mark.skipif(
np.dtype(np.intp).itemsize < 8,
reason="32-bit builds are buggy"
)
class TestCallstatement(util.F2PyTest):
suffix = ".pyf"
module_name = "callstatement"
code = f"""
python module {module_name}
usercode '''
void foo(int* x) {{
}}
'''
interface
subroutine foo(x)
intent(c) foo
integer intent(out) :: x
callprotoargument int*
callstatement {{ &
; &
x = 42; &
}}
end subroutine foo
end interface
end python module {module_name}
"""
def test_callstatement(self):
assert self.module.foo() == 42

View File

@@ -0,0 +1,45 @@
import os
import pytest
import numpy as np
from . import util
class TestSizeSumExample(util.F2PyTest):
sources = [util.getpath("tests", "src", "size", "foo.f90")]
@pytest.mark.slow
def test_all(self):
r = self.module.foo([[]])
assert r == [0]
r = self.module.foo([[1, 2]])
assert r == [3]
r = self.module.foo([[1, 2], [3, 4]])
assert np.allclose(r, [3, 7])
r = self.module.foo([[1, 2], [3, 4], [5, 6]])
assert np.allclose(r, [3, 7, 11])
@pytest.mark.slow
def test_transpose(self):
r = self.module.trans([[]])
assert np.allclose(r.T, np.array([[]]))
r = self.module.trans([[1, 2]])
assert np.allclose(r, [[1.], [2.]])
r = self.module.trans([[1, 2, 3], [4, 5, 6]])
assert np.allclose(r, [[1, 4], [2, 5], [3, 6]])
@pytest.mark.slow
def test_flatten(self):
r = self.module.flatten([[]])
assert np.allclose(r, [])
r = self.module.flatten([[1, 2]])
assert np.allclose(r, [1, 2])
r = self.module.flatten([[1, 2, 3], [4, 5, 6]])
assert np.allclose(r, [1, 2, 3, 4, 5, 6])

View File

@@ -0,0 +1,100 @@
import os
import pytest
import textwrap
import numpy as np
from . import util
class TestString(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "char.f90")]
@pytest.mark.slow
def test_char(self):
strings = np.array(["ab", "cd", "ef"], dtype="c").T
inp, out = self.module.char_test.change_strings(
strings, strings.shape[1])
assert inp == pytest.approx(strings)
expected = strings.copy()
expected[1, :] = "AAA"
assert out == pytest.approx(expected)
class TestDocStringArguments(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "string.f")]
def test_example(self):
a = np.array(b"123\0\0")
b = np.array(b"123\0\0")
c = np.array(b"123")
d = np.array(b"123")
self.module.foo(a, b, c, d)
assert a.tobytes() == b"123\0\0"
assert b.tobytes() == b"B23\0\0"
assert c.tobytes() == b"123"
assert d.tobytes() == b"D23"
class TestFixedString(util.F2PyTest):
sources = [util.getpath("tests", "src", "string", "fixed_string.f90")]
@staticmethod
def _sint(s, start=0, end=None):
"""Return the content of a string buffer as integer value.
For example:
_sint('1234') -> 4321
_sint('123A') -> 17321
"""
if isinstance(s, np.ndarray):
s = s.tobytes()
elif isinstance(s, str):
s = s.encode()
assert isinstance(s, bytes)
if end is None:
end = len(s)
i = 0
for j in range(start, min(end, len(s))):
i += s[j] * 10**j
return i
def _get_input(self, intent="in"):
if intent in ["in"]:
yield ""
yield "1"
yield "1234"
yield "12345"
yield b""
yield b"\0"
yield b"1"
yield b"\01"
yield b"1\0"
yield b"1234"
yield b"12345"
yield np.ndarray((), np.bytes_, buffer=b"") # array(b'', dtype='|S0')
yield np.array(b"") # array(b'', dtype='|S1')
yield np.array(b"\0")
yield np.array(b"1")
yield np.array(b"1\0")
yield np.array(b"\01")
yield np.array(b"1234")
yield np.array(b"123\0")
yield np.array(b"12345")
def test_intent_in(self):
for s in self._get_input():
r = self.module.test_in_bytes4(s)
# also checks that s is not changed inplace
expected = self._sint(s, end=4)
assert r == expected, s
def test_intent_inout(self):
for s in self._get_input(intent="inout"):
rest = self._sint(s, start=4)
r = self.module.test_inout_bytes4(s)
expected = self._sint(s, end=4)
assert r == expected
# check that the rest of input string is preserved
assert rest == self._sint(s, start=4)

View File

@@ -0,0 +1,494 @@
import pytest
from numpy.f2py.symbolic import (
Expr,
Op,
ArithOp,
Language,
as_symbol,
as_number,
as_string,
as_array,
as_complex,
as_terms,
as_factors,
eliminate_quotes,
insert_quotes,
fromstring,
as_expr,
as_apply,
as_numer_denom,
as_ternary,
as_ref,
as_deref,
normalize,
as_eq,
as_ne,
as_lt,
as_gt,
as_le,
as_ge,
)
from . import util
class TestSymbolic(util.F2PyTest):
def test_eliminate_quotes(self):
def worker(s):
r, d = eliminate_quotes(s)
s1 = insert_quotes(r, d)
assert s1 == s
for kind in ["", "mykind_"]:
worker(kind + '"1234" // "ABCD"')
worker(kind + '"1234" // ' + kind + '"ABCD"')
worker(kind + "\"1234\" // 'ABCD'")
worker(kind + '"1234" // ' + kind + "'ABCD'")
worker(kind + '"1\\"2\'AB\'34"')
worker("a = " + kind + "'1\\'2\"AB\"34'")
def test_sanity(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
assert x.op == Op.SYMBOL
assert repr(x) == "Expr(Op.SYMBOL, 'x')"
assert x == x
assert x != y
assert hash(x) is not None
n = as_number(123)
m = as_number(456)
assert n.op == Op.INTEGER
assert repr(n) == "Expr(Op.INTEGER, (123, 4))"
assert n == n
assert n != m
assert hash(n) is not None
fn = as_number(12.3)
fm = as_number(45.6)
assert fn.op == Op.REAL
assert repr(fn) == "Expr(Op.REAL, (12.3, 4))"
assert fn == fn
assert fn != fm
assert hash(fn) is not None
c = as_complex(1, 2)
c2 = as_complex(3, 4)
assert c.op == Op.COMPLEX
assert repr(c) == ("Expr(Op.COMPLEX, (Expr(Op.INTEGER, (1, 4)),"
" Expr(Op.INTEGER, (2, 4))))")
assert c == c
assert c != c2
assert hash(c) is not None
s = as_string("'123'")
s2 = as_string('"ABC"')
assert s.op == Op.STRING
assert repr(s) == "Expr(Op.STRING, (\"'123'\", 1))", repr(s)
assert s == s
assert s != s2
a = as_array((n, m))
b = as_array((n, ))
assert a.op == Op.ARRAY
assert repr(a) == ("Expr(Op.ARRAY, (Expr(Op.INTEGER, (123, 4)),"
" Expr(Op.INTEGER, (456, 4))))")
assert a == a
assert a != b
t = as_terms(x)
u = as_terms(y)
assert t.op == Op.TERMS
assert repr(t) == "Expr(Op.TERMS, {Expr(Op.SYMBOL, 'x'): 1})"
assert t == t
assert t != u
assert hash(t) is not None
v = as_factors(x)
w = as_factors(y)
assert v.op == Op.FACTORS
assert repr(v) == "Expr(Op.FACTORS, {Expr(Op.SYMBOL, 'x'): 1})"
assert v == v
assert w != v
assert hash(v) is not None
t = as_ternary(x, y, z)
u = as_ternary(x, z, y)
assert t.op == Op.TERNARY
assert t == t
assert t != u
assert hash(t) is not None
e = as_eq(x, y)
f = as_lt(x, y)
assert e.op == Op.RELATIONAL
assert e == e
assert e != f
assert hash(e) is not None
def test_tostring_fortran(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
n = as_number(123)
m = as_number(456)
a = as_array((n, m))
c = as_complex(n, m)
assert str(x) == "x"
assert str(n) == "123"
assert str(a) == "[123, 456]"
assert str(c) == "(123, 456)"
assert str(Expr(Op.TERMS, {x: 1})) == "x"
assert str(Expr(Op.TERMS, {x: 2})) == "2 * x"
assert str(Expr(Op.TERMS, {x: -1})) == "-x"
assert str(Expr(Op.TERMS, {x: -2})) == "-2 * x"
assert str(Expr(Op.TERMS, {x: 1, y: 1})) == "x + y"
assert str(Expr(Op.TERMS, {x: -1, y: -1})) == "-x - y"
assert str(Expr(Op.TERMS, {x: 2, y: 3})) == "2 * x + 3 * y"
assert str(Expr(Op.TERMS, {x: -2, y: 3})) == "-2 * x + 3 * y"
assert str(Expr(Op.TERMS, {x: 2, y: -3})) == "2 * x - 3 * y"
assert str(Expr(Op.FACTORS, {x: 1})) == "x"
assert str(Expr(Op.FACTORS, {x: 2})) == "x ** 2"
assert str(Expr(Op.FACTORS, {x: -1})) == "x ** -1"
assert str(Expr(Op.FACTORS, {x: -2})) == "x ** -2"
assert str(Expr(Op.FACTORS, {x: 1, y: 1})) == "x * y"
assert str(Expr(Op.FACTORS, {x: 2, y: 3})) == "x ** 2 * y ** 3"
v = Expr(Op.FACTORS, {x: 2, Expr(Op.TERMS, {x: 1, y: 1}): 3})
assert str(v) == "x ** 2 * (x + y) ** 3", str(v)
v = Expr(Op.FACTORS, {x: 2, Expr(Op.FACTORS, {x: 1, y: 1}): 3})
assert str(v) == "x ** 2 * (x * y) ** 3", str(v)
assert str(Expr(Op.APPLY, ("f", (), {}))) == "f()"
assert str(Expr(Op.APPLY, ("f", (x, ), {}))) == "f(x)"
assert str(Expr(Op.APPLY, ("f", (x, y), {}))) == "f(x, y)"
assert str(Expr(Op.INDEXING, ("f", x))) == "f[x]"
assert str(as_ternary(x, y, z)) == "merge(y, z, x)"
assert str(as_eq(x, y)) == "x .eq. y"
assert str(as_ne(x, y)) == "x .ne. y"
assert str(as_lt(x, y)) == "x .lt. y"
assert str(as_le(x, y)) == "x .le. y"
assert str(as_gt(x, y)) == "x .gt. y"
assert str(as_ge(x, y)) == "x .ge. y"
def test_tostring_c(self):
language = Language.C
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
n = as_number(123)
assert Expr(Op.FACTORS, {x: 2}).tostring(language=language) == "x * x"
assert (Expr(Op.FACTORS, {
x + y: 2
}).tostring(language=language) == "(x + y) * (x + y)")
assert Expr(Op.FACTORS, {
x: 12
}).tostring(language=language) == "pow(x, 12)"
assert as_apply(ArithOp.DIV, x,
y).tostring(language=language) == "x / y"
assert (as_apply(ArithOp.DIV, x,
x + y).tostring(language=language) == "x / (x + y)")
assert (as_apply(ArithOp.DIV, x - y, x +
y).tostring(language=language) == "(x - y) / (x + y)")
assert (x + (x - y) / (x + y) +
n).tostring(language=language) == "123 + x + (x - y) / (x + y)"
assert as_ternary(x, y, z).tostring(language=language) == "(x?y:z)"
assert as_eq(x, y).tostring(language=language) == "x == y"
assert as_ne(x, y).tostring(language=language) == "x != y"
assert as_lt(x, y).tostring(language=language) == "x < y"
assert as_le(x, y).tostring(language=language) == "x <= y"
assert as_gt(x, y).tostring(language=language) == "x > y"
assert as_ge(x, y).tostring(language=language) == "x >= y"
def test_operations(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
assert x + x == Expr(Op.TERMS, {x: 2})
assert x - x == Expr(Op.INTEGER, (0, 4))
assert x + y == Expr(Op.TERMS, {x: 1, y: 1})
assert x - y == Expr(Op.TERMS, {x: 1, y: -1})
assert x * x == Expr(Op.FACTORS, {x: 2})
assert x * y == Expr(Op.FACTORS, {x: 1, y: 1})
assert +x == x
assert -x == Expr(Op.TERMS, {x: -1}), repr(-x)
assert 2 * x == Expr(Op.TERMS, {x: 2})
assert 2 + x == Expr(Op.TERMS, {x: 1, as_number(1): 2})
assert 2 * x + 3 * y == Expr(Op.TERMS, {x: 2, y: 3})
assert (x + y) * 2 == Expr(Op.TERMS, {x: 2, y: 2})
assert x**2 == Expr(Op.FACTORS, {x: 2})
assert (x + y)**2 == Expr(
Op.TERMS,
{
Expr(Op.FACTORS, {x: 2}): 1,
Expr(Op.FACTORS, {y: 2}): 1,
Expr(Op.FACTORS, {
x: 1,
y: 1
}): 2,
},
)
assert (x + y) * x == x**2 + x * y
assert (x + y)**2 == x**2 + 2 * x * y + y**2
assert (x + y)**2 + (x - y)**2 == 2 * x**2 + 2 * y**2
assert (x + y) * z == x * z + y * z
assert z * (x + y) == x * z + y * z
assert (x / 2) == as_apply(ArithOp.DIV, x, as_number(2))
assert (2 * x / 2) == x
assert (3 * x / 2) == as_apply(ArithOp.DIV, 3 * x, as_number(2))
assert (4 * x / 2) == 2 * x
assert (5 * x / 2) == as_apply(ArithOp.DIV, 5 * x, as_number(2))
assert (6 * x / 2) == 3 * x
assert ((3 * 5) * x / 6) == as_apply(ArithOp.DIV, 5 * x, as_number(2))
assert (30 * x**2 * y**4 / (24 * x**3 * y**3)) == as_apply(
ArithOp.DIV, 5 * y, 4 * x)
assert ((15 * x / 6) / 5) == as_apply(ArithOp.DIV, x,
as_number(2)), (15 * x / 6) / 5
assert (x / (5 / x)) == as_apply(ArithOp.DIV, x**2, as_number(5))
assert (x / 2.0) == Expr(Op.TERMS, {x: 0.5})
s = as_string('"ABC"')
t = as_string('"123"')
assert s // t == Expr(Op.STRING, ('"ABC123"', 1))
assert s // x == Expr(Op.CONCAT, (s, x))
assert x // s == Expr(Op.CONCAT, (x, s))
c = as_complex(1.0, 2.0)
assert -c == as_complex(-1.0, -2.0)
assert c + c == as_expr((1 + 2j) * 2)
assert c * c == as_expr((1 + 2j)**2)
def test_substitute(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
a = as_array((x, y))
assert x.substitute({x: y}) == y
assert (x + y).substitute({x: z}) == y + z
assert (x * y).substitute({x: z}) == y * z
assert (x**4).substitute({x: z}) == z**4
assert (x / y).substitute({x: z}) == z / y
assert x.substitute({x: y + z}) == y + z
assert a.substitute({x: y + z}) == as_array((y + z, y))
assert as_ternary(x, y,
z).substitute({x: y + z}) == as_ternary(y + z, y, z)
assert as_eq(x, y).substitute({x: y + z}) == as_eq(y + z, y)
def test_fromstring(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
f = as_symbol("f")
s = as_string('"ABC"')
t = as_string('"123"')
a = as_array((x, y))
assert fromstring("x") == x
assert fromstring("+ x") == x
assert fromstring("- x") == -x
assert fromstring("x + y") == x + y
assert fromstring("x + 1") == x + 1
assert fromstring("x * y") == x * y
assert fromstring("x * 2") == x * 2
assert fromstring("x / y") == x / y
assert fromstring("x ** 2", language=Language.Python) == x**2
assert fromstring("x ** 2 ** 3", language=Language.Python) == x**2**3
assert fromstring("(x + y) * z") == (x + y) * z
assert fromstring("f(x)") == f(x)
assert fromstring("f(x,y)") == f(x, y)
assert fromstring("f[x]") == f[x]
assert fromstring("f[x][y]") == f[x][y]
assert fromstring('"ABC"') == s
assert (normalize(
fromstring('"ABC" // "123" ',
language=Language.Fortran)) == s // t)
assert fromstring('f("ABC")') == f(s)
assert fromstring('MYSTRKIND_"ABC"') == as_string('"ABC"', "MYSTRKIND")
assert fromstring("(/x, y/)") == a, fromstring("(/x, y/)")
assert fromstring("f((/x, y/))") == f(a)
assert fromstring("(/(x+y)*z/)") == as_array(((x + y) * z, ))
assert fromstring("123") == as_number(123)
assert fromstring("123_2") == as_number(123, 2)
assert fromstring("123_myintkind") == as_number(123, "myintkind")
assert fromstring("123.0") == as_number(123.0, 4)
assert fromstring("123.0_4") == as_number(123.0, 4)
assert fromstring("123.0_8") == as_number(123.0, 8)
assert fromstring("123.0e0") == as_number(123.0, 4)
assert fromstring("123.0d0") == as_number(123.0, 8)
assert fromstring("123d0") == as_number(123.0, 8)
assert fromstring("123e-0") == as_number(123.0, 4)
assert fromstring("123d+0") == as_number(123.0, 8)
assert fromstring("123.0_myrealkind") == as_number(123.0, "myrealkind")
assert fromstring("3E4") == as_number(30000.0, 4)
assert fromstring("(1, 2)") == as_complex(1, 2)
assert fromstring("(1e2, PI)") == as_complex(as_number(100.0),
as_symbol("PI"))
assert fromstring("[1, 2]") == as_array((as_number(1), as_number(2)))
assert fromstring("POINT(x, y=1)") == as_apply(as_symbol("POINT"),
x,
y=as_number(1))
assert fromstring(
'PERSON(name="John", age=50, shape=(/34, 23/))') == as_apply(
as_symbol("PERSON"),
name=as_string('"John"'),
age=as_number(50),
shape=as_array((as_number(34), as_number(23))),
)
assert fromstring("x?y:z") == as_ternary(x, y, z)
assert fromstring("*x") == as_deref(x)
assert fromstring("**x") == as_deref(as_deref(x))
assert fromstring("&x") == as_ref(x)
assert fromstring("(*x) * (*y)") == as_deref(x) * as_deref(y)
assert fromstring("(*x) * *y") == as_deref(x) * as_deref(y)
assert fromstring("*x * *y") == as_deref(x) * as_deref(y)
assert fromstring("*x**y") == as_deref(x) * as_deref(y)
assert fromstring("x == y") == as_eq(x, y)
assert fromstring("x != y") == as_ne(x, y)
assert fromstring("x < y") == as_lt(x, y)
assert fromstring("x > y") == as_gt(x, y)
assert fromstring("x <= y") == as_le(x, y)
assert fromstring("x >= y") == as_ge(x, y)
assert fromstring("x .eq. y", language=Language.Fortran) == as_eq(x, y)
assert fromstring("x .ne. y", language=Language.Fortran) == as_ne(x, y)
assert fromstring("x .lt. y", language=Language.Fortran) == as_lt(x, y)
assert fromstring("x .gt. y", language=Language.Fortran) == as_gt(x, y)
assert fromstring("x .le. y", language=Language.Fortran) == as_le(x, y)
assert fromstring("x .ge. y", language=Language.Fortran) == as_ge(x, y)
def test_traverse(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
f = as_symbol("f")
# Use traverse to substitute a symbol
def replace_visit(s, r=z):
if s == x:
return r
assert x.traverse(replace_visit) == z
assert y.traverse(replace_visit) == y
assert z.traverse(replace_visit) == z
assert (f(y)).traverse(replace_visit) == f(y)
assert (f(x)).traverse(replace_visit) == f(z)
assert (f[y]).traverse(replace_visit) == f[y]
assert (f[z]).traverse(replace_visit) == f[z]
assert (x + y + z).traverse(replace_visit) == (2 * z + y)
assert (x +
f(y, x - z)).traverse(replace_visit) == (z +
f(y, as_number(0)))
assert as_eq(x, y).traverse(replace_visit) == as_eq(z, y)
# Use traverse to collect symbols, method 1
function_symbols = set()
symbols = set()
def collect_symbols(s):
if s.op is Op.APPLY:
oper = s.data[0]
function_symbols.add(oper)
if oper in symbols:
symbols.remove(oper)
elif s.op is Op.SYMBOL and s not in function_symbols:
symbols.add(s)
(x + f(y, x - z)).traverse(collect_symbols)
assert function_symbols == {f}
assert symbols == {x, y, z}
# Use traverse to collect symbols, method 2
def collect_symbols2(expr, symbols):
if expr.op is Op.SYMBOL:
symbols.add(expr)
symbols = set()
(x + f(y, x - z)).traverse(collect_symbols2, symbols)
assert symbols == {x, y, z, f}
# Use traverse to partially collect symbols
def collect_symbols3(expr, symbols):
if expr.op is Op.APPLY:
# skip traversing function calls
return expr
if expr.op is Op.SYMBOL:
symbols.add(expr)
symbols = set()
(x + f(y, x - z)).traverse(collect_symbols3, symbols)
assert symbols == {x}
def test_linear_solve(self):
x = as_symbol("x")
y = as_symbol("y")
z = as_symbol("z")
assert x.linear_solve(x) == (as_number(1), as_number(0))
assert (x + 1).linear_solve(x) == (as_number(1), as_number(1))
assert (2 * x).linear_solve(x) == (as_number(2), as_number(0))
assert (2 * x + 3).linear_solve(x) == (as_number(2), as_number(3))
assert as_number(3).linear_solve(x) == (as_number(0), as_number(3))
assert y.linear_solve(x) == (as_number(0), y)
assert (y * z).linear_solve(x) == (as_number(0), y * z)
assert (x + y).linear_solve(x) == (as_number(1), y)
assert (z * x + y).linear_solve(x) == (z, y)
assert ((z + y) * x + y).linear_solve(x) == (z + y, y)
assert (z * y * x + y).linear_solve(x) == (z * y, y)
pytest.raises(RuntimeError, lambda: (x * x).linear_solve(x))
def test_as_numer_denom(self):
x = as_symbol("x")
y = as_symbol("y")
n = as_number(123)
assert as_numer_denom(x) == (x, as_number(1))
assert as_numer_denom(x / n) == (x, n)
assert as_numer_denom(n / x) == (n, x)
assert as_numer_denom(x / y) == (x, y)
assert as_numer_denom(x * y) == (x * y, as_number(1))
assert as_numer_denom(n + x / y) == (x + n * y, y)
assert as_numer_denom(n + x / (y - x / n)) == (y * n**2, y * n - x)
def test_polynomial_atoms(self):
x = as_symbol("x")
y = as_symbol("y")
n = as_number(123)
assert x.polynomial_atoms() == {x}
assert n.polynomial_atoms() == set()
assert (y[x]).polynomial_atoms() == {y[x]}
assert (y(x)).polynomial_atoms() == {y(x)}
assert (y(x) + x).polynomial_atoms() == {y(x), x}
assert (y(x) * x[y]).polynomial_atoms() == {y(x), x[y]}
assert (y(x)**x).polynomial_atoms() == {y(x)}

View File

@@ -0,0 +1,14 @@
import os
import pytest
from . import util
class TestValueAttr(util.F2PyTest):
sources = [util.getpath("tests", "src", "value_attrspec", "gh21665.f90")]
# gh-21665
def test_long_long_map(self):
inp = 2
out = self.module.fortfuncs.square(inp)
exp_out = 4
assert out == exp_out

View File

@@ -0,0 +1,419 @@
"""
Utility functions for
- building and importing modules on test time, using a temporary location
- detecting if compilers are present
- determining paths to tests
"""
import os
import sys
import subprocess
import tempfile
import shutil
import atexit
import textwrap
import re
import pytest
import contextlib
import numpy
from pathlib import Path
from numpy.compat import asbytes, asstr
from numpy.testing import temppath, IS_WASM
from importlib import import_module
#
# Maintaining a temporary module directory
#
_module_dir = None
_module_num = 5403
def _cleanup():
global _module_dir
if _module_dir is not None:
try:
sys.path.remove(_module_dir)
except ValueError:
pass
try:
shutil.rmtree(_module_dir)
except OSError:
pass
_module_dir = None
def get_module_dir():
global _module_dir
if _module_dir is None:
_module_dir = tempfile.mkdtemp()
atexit.register(_cleanup)
if _module_dir not in sys.path:
sys.path.insert(0, _module_dir)
return _module_dir
def get_temp_module_name():
# Assume single-threaded, and the module dir usable only by this thread
global _module_num
get_module_dir()
name = "_test_ext_module_%d" % _module_num
_module_num += 1
if name in sys.modules:
# this should not be possible, but check anyway
raise RuntimeError("Temporary module name already in use.")
return name
def _memoize(func):
memo = {}
def wrapper(*a, **kw):
key = repr((a, kw))
if key not in memo:
try:
memo[key] = func(*a, **kw)
except Exception as e:
memo[key] = e
raise
ret = memo[key]
if isinstance(ret, Exception):
raise ret
return ret
wrapper.__name__ = func.__name__
return wrapper
#
# Building modules
#
@_memoize
def build_module(source_files, options=[], skip=[], only=[], module_name=None):
"""
Compile and import a f2py module, built from the given files.
"""
code = f"import sys; sys.path = {sys.path!r}; import numpy.f2py; numpy.f2py.main()"
d = get_module_dir()
# Copy files
dst_sources = []
f2py_sources = []
for fn in source_files:
if not os.path.isfile(fn):
raise RuntimeError("%s is not a file" % fn)
dst = os.path.join(d, os.path.basename(fn))
shutil.copyfile(fn, dst)
dst_sources.append(dst)
base, ext = os.path.splitext(dst)
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()
f2py_opts = ["-c", "-m", module_name] + options + f2py_sources
if skip:
f2py_opts += ["skip:"] + skip
if only:
f2py_opts += ["only:"] + only
# Build
cwd = os.getcwd()
try:
os.chdir(d)
cmd = [sys.executable, "-c", code] + f2py_opts
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = p.communicate()
if p.returncode != 0:
raise RuntimeError("Running f2py failed: %s\n%s" %
(cmd[4:], asstr(out)))
finally:
os.chdir(cwd)
# Partial cleanup
for fn in dst_sources:
os.unlink(fn)
# Import
return import_module(module_name)
@_memoize
def build_code(source_code,
options=[],
skip=[],
only=[],
suffix=None,
module_name=None):
"""
Compile and import Fortran code using f2py.
"""
if suffix is None:
suffix = ".f"
with temppath(suffix=suffix) as path:
with open(path, "w") as f:
f.write(source_code)
return build_module([path],
options=options,
skip=skip,
only=only,
module_name=module_name)
#
# 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(br"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]
#
# Building with distutils
#
@_memoize
def build_module_distutils(source_files, config_code, module_name, **kw):
"""
Build a module via distutils and import it.
"""
d = get_module_dir()
# Copy files
dst_sources = []
for fn in source_files:
if not os.path.isfile(fn):
raise RuntimeError("%s is not a file" % fn)
dst = os.path.join(d, os.path.basename(fn))
shutil.copyfile(fn, dst)
dst_sources.append(dst)
# Build script
config_code = textwrap.dedent(config_code).replace("\n", "\n ")
code = fr"""
import os
import sys
sys.path = {repr(sys.path)}
def configuration(parent_name='',top_path=None):
from numpy.distutils.misc_util import Configuration
config = Configuration('', parent_name, top_path)
{config_code}
return config
if __name__ == "__main__":
from numpy.distutils.core import setup
setup(configuration=configuration)
"""
script = os.path.join(d, get_temp_module_name() + ".py")
dst_sources.append(script)
with open(script, "wb") as f:
f.write(asbytes(code))
# Build
cwd = os.getcwd()
try:
os.chdir(d)
cmd = [sys.executable, script, "build_ext", "-i"]
p = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, err = p.communicate()
if p.returncode != 0:
raise RuntimeError("Running distutils build failed: %s\n%s" %
(cmd[4:], asstr(out)))
finally:
os.chdir(cwd)
# Partial cleanup
for fn in dst_sources:
os.unlink(fn)
# Import
__import__(module_name)
return sys.modules[module_name]
#
# Unittest convenience
#
class F2PyTest:
code = None
sources = None
options = []
skip = []
only = []
suffix = ".f"
module = None
@property
def module_name(self):
cls = type(self)
return f'_{cls.__module__.rsplit(".",1)[-1]}_{cls.__name__}_ext_module'
def setup_method(self):
if sys.platform == "win32":
pytest.skip("Fails with MinGW64 Gfortran (Issue #9673)")
if self.module is not None:
return
# Check compiler availability first
if not has_c_compiler():
pytest.skip("No C compiler available")
codes = []
if self.sources:
codes.extend(self.sources)
if self.code is not None:
codes.append(self.suffix)
needs_f77 = False
needs_f90 = False
needs_pyf = False
for fn in codes:
if str(fn).endswith(".f"):
needs_f77 = True
elif str(fn).endswith(".f90"):
needs_f90 = True
elif str(fn).endswith(".pyf"):
needs_pyf = True
if needs_f77 and not has_f77_compiler():
pytest.skip("No Fortran 77 compiler available")
if needs_f90 and not has_f90_compiler():
pytest.skip("No Fortran 90 compiler available")
if needs_pyf and not (has_f90_compiler() or has_f77_compiler()):
pytest.skip("No Fortran compiler available")
# Build the module
if self.code is not None:
self.module = build_code(
self.code,
options=self.options,
skip=self.skip,
only=self.only,
suffix=self.suffix,
module_name=self.module_name,
)
if self.sources is not None:
self.module = build_module(
self.sources,
options=self.options,
skip=self.skip,
only=self.only,
module_name=self.module_name,
)
#
# Helper functions
#
def getpath(*a):
# Package root
d = Path(numpy.f2py.__file__).parent.resolve()
return d.joinpath(*a)
@contextlib.contextmanager
def switchdir(path):
curpath = Path.cwd()
os.chdir(path)
try:
yield
finally:
os.chdir(curpath)