summaryrefslogtreecommitdiff
path: root/numpy/f2py/tests/test_compile_function.py
blob: 40ea7997f7d7120160f692e15ea00d531eeebfd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""See https://github.com/numpy/numpy/pull/11937.

"""
from __future__ import division, absolute_import, print_function

import sys
import os
import uuid
from importlib import import_module
import pytest

import numpy.f2py

from numpy.testing import assert_equal
from . import util


def setup_module():
    if sys.platform == 'win32' and sys.version_info[0] < 3:
        pytest.skip('Fails with MinGW64 Gfortran (Issue #9673)')
    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
        try:
            os.chdir(moddir)
            ret_val = numpy.f2py.compile(
                fsource,
                modulename=modname,
                extra_args=extra_args,
                source_fn=source_fn
                )
        finally:
            os.chdir(cwd)

        # check for compile success return value
        assert_equal(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_equal(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_equal(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_equal(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
    cwd = os.getcwd()
    try:
        os.chdir(str(tmpdir))
        ret_val = numpy.f2py.compile(
                fsource,
                modulename='test_compile_from_strings',
                extension='.f90')
        assert_equal(ret_val, 0)
    finally:
        os.chdir(cwd)