Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
test_f2py2e.py984 linesDownload Raw Back to tests
1import platform
2import re
3import shlex
4import subprocess
5import sys
6import textwrap
7from collections import namedtuple
8from pathlib import Path
9
10import pytest
11
12from numpy.f2py.f2py2e import main as f2pycli
13from numpy.testing._private.utils import NOGIL_BUILD
14
15from . import util
16
17#######################
18# F2PY Test utilities #
19######################
20
21# Tests for CLI commands which call meson will fail if no compilers are present, these are to be skipped
22
23def compiler_check_f2pycli():
24    if not util.has_fortran_compiler():
25        pytest.skip("CLI command needs a Fortran compiler")
26    else:
27        f2pycli()
28
29#########################
30# CLI utils and classes #
31#########################
32
33
34PPaths = namedtuple("PPaths", "finp, f90inp, pyf, wrap77, wrap90, cmodf")
35
36
37def get_io_paths(fname_inp, mname="untitled"):
38    """Takes in a temporary file for testing and returns the expected output and input paths
39
40    Here expected output is essentially one of any of the possible generated
41    files.
42
43    ..note::
44
45         Since this does not actually run f2py, none of these are guaranteed to
46         exist, and module names are typically incorrect
47
48    Parameters
49    ----------
50    fname_inp : str
51                The input filename
52    mname : str, optional
53                The name of the module, untitled by default
54
55    Returns
56    -------
57    genp : NamedTuple PPaths
58            The possible paths which are generated, not all of which exist
59    """
60    bpath = Path(fname_inp)
61    return PPaths(
62        finp=bpath.with_suffix(".f"),
63        f90inp=bpath.with_suffix(".f90"),
64        pyf=bpath.with_suffix(".pyf"),
65        wrap77=bpath.with_name(f"{mname}-f2pywrappers.f"),
66        wrap90=bpath.with_name(f"{mname}-f2pywrappers2.f90"),
67        cmodf=bpath.with_name(f"{mname}module.c"),
68    )
69
70
71################
72# CLI Fixtures #
73################
74
75
76@pytest.fixture(scope="session")
77def hello_world_f90(tmpdir_factory):
78    """Generates a single f90 file for testing"""
79    fdat = util.getpath("tests", "src", "cli", "hiworld.f90").read_text()
80    fn = tmpdir_factory.getbasetemp() / "hello.f90"
81    fn.write_text(fdat, encoding="ascii")
82    return fn
83
84
85@pytest.fixture(scope="session")
86def gh23598_warn(tmpdir_factory):
87    """F90 file for testing warnings in gh23598"""
88    fdat = util.getpath("tests", "src", "crackfortran", "gh23598Warn.f90").read_text()
89    fn = tmpdir_factory.getbasetemp() / "gh23598Warn.f90"
90    fn.write_text(fdat, encoding="ascii")
91    return fn
92
93
94@pytest.fixture(scope="session")
95def gh22819_cli(tmpdir_factory):
96    """F90 file for testing disallowed CLI arguments in ghff819"""
97    fdat = util.getpath("tests", "src", "cli", "gh_22819.pyf").read_text()
98    fn = tmpdir_factory.getbasetemp() / "gh_22819.pyf"
99    fn.write_text(fdat, encoding="ascii")
100    return fn
101
102
103@pytest.fixture(scope="session")
104def hello_world_f77(tmpdir_factory):
105    """Generates a single f77 file for testing"""
106    fdat = util.getpath("tests", "src", "cli", "hi77.f").read_text()
107    fn = tmpdir_factory.getbasetemp() / "hello.f"
108    fn.write_text(fdat, encoding="ascii")
109    return fn
110
111
112@pytest.fixture(scope="session")
113def retreal_f77(tmpdir_factory):
114    """Generates a single f77 file for testing"""
115    fdat = util.getpath("tests", "src", "return_real", "foo77.f").read_text()
116    fn = tmpdir_factory.getbasetemp() / "foo.f"
117    fn.write_text(fdat, encoding="ascii")
118    return fn
119
120@pytest.fixture(scope="session")
121def f2cmap_f90(tmpdir_factory):
122    """Generates a single f90 file for testing"""
123    fdat = util.getpath("tests", "src", "f2cmap", "isoFortranEnvMap.f90").read_text()
124    f2cmap = util.getpath("tests", "src", "f2cmap", ".f2py_f2cmap").read_text()
125    fn = tmpdir_factory.getbasetemp() / "f2cmap.f90"
126    fmap = tmpdir_factory.getbasetemp() / "mapfile"
127    fn.write_text(fdat, encoding="ascii")
128    fmap.write_text(f2cmap, encoding="ascii")
129    return fn
130
131#########
132# Tests #
133#########
134
135def test_gh22819_cli(capfd, gh22819_cli, monkeypatch):
136    """Check that module names are handled correctly
137    gh-22819
138    Essentially, the -m name cannot be used to import the module, so the module
139    named in the .pyf needs to be used instead
140
141    CLI :: -m and a .pyf file
142    """
143    ipath = Path(gh22819_cli)
144    monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath}".split())
145    with util.switchdir(ipath.parent):
146        f2pycli()
147        gen_paths = [item.name for item in ipath.parent.rglob("*") if item.is_file()]
148        assert "blahmodule.c" not in gen_paths  # shouldn't be generated
149        assert "blah-f2pywrappers.f" not in gen_paths
150        assert "test_22819-f2pywrappers.f" in gen_paths
151        assert "test_22819module.c" in gen_paths
152
153
154def test_gh22819_many_pyf(capfd, gh22819_cli, monkeypatch):
155    """Only one .pyf file allowed
156    gh-22819
157    CLI :: .pyf files
158    """
159    ipath = Path(gh22819_cli)
160    monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath} hello.pyf".split())
161    with util.switchdir(ipath.parent):
162        with pytest.raises(ValueError, match="Only one .pyf file per call"):
163            f2pycli()
164
165
166def test_gh23598_warn(capfd, gh23598_warn, monkeypatch):
167    foutl = get_io_paths(gh23598_warn, mname="test")
168    ipath = foutl.f90inp
169    monkeypatch.setattr(
170        sys, "argv",
171        f'f2py {ipath} -m test'.split())
172
173    with util.switchdir(ipath.parent):
174        f2pycli()  # Generate files
175        wrapper = foutl.wrap90.read_text()
176        assert "intproductf2pywrap, intpr" not in wrapper
177
178
179def test_gen_pyf(capfd, hello_world_f90, monkeypatch):
180    """Ensures that a signature file is generated via the CLI
181    CLI :: -h
182    """
183    ipath = Path(hello_world_f90)
184    opath = Path(hello_world_f90).stem + ".pyf"
185    monkeypatch.setattr(sys, "argv", f'f2py -h {opath} {ipath}'.split())
186
187    with util.switchdir(ipath.parent):
188        f2pycli()  # Generate wrappers
189        out, _ = capfd.readouterr()
190        assert "Saving signatures to file" in out
191        assert Path(f'{opath}').exists()
192
193
194def test_gen_pyf_stdout(capfd, hello_world_f90, monkeypatch):
195    """Ensures that a signature file can be dumped to stdout
196    CLI :: -h
197    """
198    ipath = Path(hello_world_f90)
199    monkeypatch.setattr(sys, "argv", f'f2py -h stdout {ipath}'.split())
200    with util.switchdir(ipath.parent):
201        f2pycli()
202        out, _ = capfd.readouterr()
203        assert "Saving signatures to file" in out
204        assert "function hi() ! in " in out
205
206
207def test_gen_pyf_no_overwrite(capfd, hello_world_f90, monkeypatch):
208    """Ensures that the CLI refuses to overwrite signature files
209    CLI :: -h without --overwrite-signature
210    """
211    ipath = Path(hello_world_f90)
212    monkeypatch.setattr(sys, "argv", f'f2py -h faker.pyf {ipath}'.split())
213
214    with util.switchdir(ipath.parent):
215        Path("faker.pyf").write_text("Fake news", encoding="ascii")
216        with pytest.raises(SystemExit):
217            f2pycli()  # Refuse to overwrite
218            _, err = capfd.readouterr()
219            assert "Use --overwrite-signature to overwrite" in err
220
221
222@pytest.mark.skipif(sys.version_info <= (3, 12), reason="Python 3.12 required")
223def test_untitled_cli(capfd, hello_world_f90, monkeypatch):
224    """Check that modules are named correctly
225
226    CLI :: defaults
227    """
228    ipath = Path(hello_world_f90)
229    monkeypatch.setattr(sys, "argv", f"f2py --backend meson -c {ipath}".split())
230    with util.switchdir(ipath.parent):
231        compiler_check_f2pycli()
232        out, _ = capfd.readouterr()
233        assert "untitledmodule.c" in out
234
235
236@pytest.mark.skipif((platform.system() != 'Linux') or (sys.version_info <= (3, 12)), reason='Compiler and 3.12 required')
237def test_no_py312_distutils_fcompiler(capfd, hello_world_f90, monkeypatch):
238    """Check that no distutils imports are performed on 3.12
239    CLI :: --fcompiler --help-link --backend distutils
240    """
241    MNAME = "hi"
242    foutl = get_io_paths(hello_world_f90, mname=MNAME)
243    ipath = foutl.f90inp
244    monkeypatch.setattr(
245        sys, "argv", f"f2py {ipath} -c --fcompiler=gfortran -m {MNAME}".split()
246    )
247    with util.switchdir(ipath.parent):
248        compiler_check_f2pycli()
249        out, _ = capfd.readouterr()
250        assert "--fcompiler cannot be used with meson" in out
251    monkeypatch.setattr(
252        sys, "argv", ["f2py", "--help-link"]
253    )
254    with util.switchdir(ipath.parent):
255        f2pycli()
256        out, _ = capfd.readouterr()
257        assert "Use --dep for meson builds" in out
258    MNAME = "hi2"  # Needs to be different for a new -c
259    monkeypatch.setattr(
260        sys, "argv", f"f2py {ipath} -c -m {MNAME} --backend distutils".split()
261    )
262    with util.switchdir(ipath.parent):
263        f2pycli()
264        out, _ = capfd.readouterr()
265        assert "Cannot use distutils backend with Python>=3.12" in out
266
267
268@pytest.mark.xfail
269def test_f2py_skip(capfd, retreal_f77, monkeypatch):
270    """Tests that functions can be skipped
271    CLI :: skip:
272    """
273    foutl = get_io_paths(retreal_f77, mname="test")
274    ipath = foutl.finp
275    toskip = "t0 t4 t8 sd s8 s4"
276    remaining = "td s0"
277    monkeypatch.setattr(
278        sys, "argv",
279        f'f2py {ipath} -m test skip: {toskip}'.split())
280
281    with util.switchdir(ipath.parent):
282        f2pycli()
283        out, err = capfd.readouterr()
284        for skey in toskip.split():
285            assert (
286                f'buildmodule: Could not found the body of interfaced routine "{skey}". Skipping.'
287                in err)
288        for rkey in remaining.split():
289            assert f'Constructing wrapper function "{rkey}"' in out
290
291
292def test_f2py_only(capfd, retreal_f77, monkeypatch):
293    """Test that functions can be kept by only:
294    CLI :: only:
295    """
296    foutl = get_io_paths(retreal_f77, mname="test")
297    ipath = foutl.finp
298    toskip = "t0 t4 t8 sd s8 s4"
299    tokeep = "td s0"
300    monkeypatch.setattr(
301        sys, "argv",
302        f'f2py {ipath} -m test only: {tokeep}'.split())
303
304    with util.switchdir(ipath.parent):
305        f2pycli()
306        out, err = capfd.readouterr()
307        for skey in toskip.split():
308            assert (
309                f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
310                in err)
311        for rkey in tokeep.split():
312            assert f'Constructing wrapper function "{rkey}"' in out
313
314
315def test_file_processing_switch(capfd, hello_world_f90, retreal_f77,
316                                monkeypatch):
317    """Tests that it is possible to return to file processing mode
318    CLI :: :
319    BUG: numpy-gh #20520
320    """
321    foutl = get_io_paths(retreal_f77, mname="test")
322    ipath = foutl.finp
323    toskip = "t0 t4 t8 sd s8 s4"
324    ipath2 = Path(hello_world_f90)
325    tokeep = "td s0 hi"  # hi is in ipath2
326    mname = "blah"
327    monkeypatch.setattr(
328        sys,
329        "argv",
330        f'f2py {ipath} -m {mname} only: {tokeep} : {ipath2}'.split(
331        ),
332    )
333
334    with util.switchdir(ipath.parent):
335        f2pycli()
336        out, err = capfd.readouterr()
337        for skey in toskip.split():
338            assert (
339                f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
340                in err)
341        for rkey in tokeep.split():
342            assert f'Constructing wrapper function "{rkey}"' in out
343
344
345def test_mod_gen_f77(capfd, hello_world_f90, monkeypatch):
346    """Checks the generation of files based on a module name
347    CLI :: -m
348    """
349    MNAME = "hi"
350    foutl = get_io_paths(hello_world_f90, mname=MNAME)
351    ipath = foutl.f90inp
352    monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME}'.split())
353    with util.switchdir(ipath.parent):
354        f2pycli()
355
356    # Always generate C module
357    assert Path.exists(foutl.cmodf)
358    # File contains a function, check for F77 wrappers
359    assert Path.exists(foutl.wrap77)
360
361
362def test_mod_gen_gh25263(capfd, hello_world_f77, monkeypatch):
363    """Check that pyf files are correctly generated with module structure
364    CLI :: -m <name> -h pyf_file
365    BUG: numpy-gh #20520
366    """
367    MNAME = "hi"
368    foutl = get_io_paths(hello_world_f77, mname=MNAME)
369    ipath = foutl.finp
370    monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME} -h hi.pyf'.split())
371    with util.switchdir(ipath.parent):
372        f2pycli()
373        with Path('hi.pyf').open() as hipyf:
374            pyfdat = hipyf.read()
375            assert "python module hi" in pyfdat
376
377
378def test_lower_cmod(capfd, hello_world_f77, monkeypatch):
379    """Lowers cases by flag or when -h is present
380
381    CLI :: --[no-]lower
382    """
383    foutl = get_io_paths(hello_world_f77, mname="test")
384    ipath = foutl.finp
385    capshi = re.compile(r"HI\(\)")
386    capslo = re.compile(r"hi\(\)")
387    # Case I: --lower is passed
388    monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m test --lower'.split())
389    with util.switchdir(ipath.parent):
390        f2pycli()
391        out, _ = capfd.readouterr()
392        assert capslo.search(out) is not None
393        assert capshi.search(out) is None
394    # Case II: --no-lower is passed
395    monkeypatch.setattr(sys, "argv",
396                        f'f2py {ipath} -m test --no-lower'.split())
397    with util.switchdir(ipath.parent):
398        f2pycli()
399        out, _ = capfd.readouterr()
400        assert capslo.search(out) is None
401        assert capshi.search(out) is not None
402
403
404def test_lower_sig(capfd, hello_world_f77, monkeypatch):
405    """Lowers cases in signature files by flag or when -h is present
406
407    CLI :: --[no-]lower -h
408    """
409    foutl = get_io_paths(hello_world_f77, mname="test")
410    ipath = foutl.finp
411    # Signature files
412    capshi = re.compile(r"Block: HI")
413    capslo = re.compile(r"Block: hi")
414    # Case I: --lower is implied by -h
415    # TODO: Clean up to prevent passing --overwrite-signature
416    monkeypatch.setattr(
417        sys,
418        "argv",
419        f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature'.split(),
420    )
421
422    with util.switchdir(ipath.parent):
423        f2pycli()
424        out, _ = capfd.readouterr()
425        assert capslo.search(out) is not None
426        assert capshi.search(out) is None
427
428    # Case II: --no-lower overrides -h
429    monkeypatch.setattr(
430        sys,
431        "argv",
432        f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature --no-lower'
433        .split(),
434    )
435
436    with util.switchdir(ipath.parent):
437        f2pycli()
438        out, _ = capfd.readouterr()
439        assert capslo.search(out) is None
440        assert capshi.search(out) is not None
441
442
443def test_build_dir(capfd, hello_world_f90, monkeypatch):
444    """Ensures that the build directory can be specified
445
446    CLI :: --build-dir
447    """
448    ipath = Path(hello_world_f90)
449    mname = "blah"
450    odir = "tttmp"
451    monkeypatch.setattr(sys, "argv",
452                        f'f2py -m {mname} {ipath} --build-dir {odir}'.split())
453
454    with util.switchdir(ipath.parent):
455        f2pycli()
456        out, _ = capfd.readouterr()
457        assert f"Wrote C/API module \"{mname}\"" in out
458
459
460def test_overwrite(capfd, hello_world_f90, monkeypatch):
461    """Ensures that the build directory can be specified
462
463    CLI :: --overwrite-signature
464    """
465    ipath = Path(hello_world_f90)
466    monkeypatch.setattr(
467        sys, "argv",
468        f'f2py -h faker.pyf {ipath} --overwrite-signature'.split())
469
470    with util.switchdir(ipath.parent):
471        Path("faker.pyf").write_text("Fake news", encoding="ascii")
472        f2pycli()
473        out, _ = capfd.readouterr()
474        assert "Saving signatures to file" in out
475
476
477def test_latexdoc(capfd, hello_world_f90, monkeypatch):
478    """Ensures that TeX documentation is written out
479
480    CLI :: --latex-doc
481    """
482    ipath = Path(hello_world_f90)
483    mname = "blah"
484    monkeypatch.setattr(sys, "argv",
485                        f'f2py -m {mname} {ipath} --latex-doc'.split())
486
487    with util.switchdir(ipath.parent):
488        f2pycli()
489        out, _ = capfd.readouterr()
490        assert "Documentation is saved to file" in out
491        with Path(f"{mname}module.tex").open() as otex:
492            assert "\\documentclass" in otex.read()
493
494
495def test_nolatexdoc(capfd, hello_world_f90, monkeypatch):
496    """Ensures that TeX documentation is written out
497
498    CLI :: --no-latex-doc
499    """
500    ipath = Path(hello_world_f90)
501    mname = "blah"
502    monkeypatch.setattr(sys, "argv",
503                        f'f2py -m {mname} {ipath} --no-latex-doc'.split())
504
505    with util.switchdir(ipath.parent):
506        f2pycli()
507        out, _ = capfd.readouterr()
508        assert "Documentation is saved to file" not in out
509
510
511def test_shortlatex(capfd, hello_world_f90, monkeypatch):
512    """Ensures that truncated documentation is written out
513
514    TODO: Test to ensure this has no effect without --latex-doc
515    CLI :: --latex-doc --short-latex
516    """
517    ipath = Path(hello_world_f90)
518    mname = "blah"
519    monkeypatch.setattr(
520        sys,
521        "argv",
522        f'f2py -m {mname} {ipath} --latex-doc --short-latex'.split(),
523    )
524
525    with util.switchdir(ipath.parent):
526        f2pycli()
527        out, _ = capfd.readouterr()
528        assert "Documentation is saved to file" in out
529        with Path(f"./{mname}module.tex").open() as otex:
530            assert "\\documentclass" not in otex.read()
531
532
533def test_restdoc(capfd, hello_world_f90, monkeypatch):
534    """Ensures that RsT documentation is written out
535
536    CLI :: --rest-doc
537    """
538    ipath = Path(hello_world_f90)
539    mname = "blah"
540    monkeypatch.setattr(sys, "argv",
541                        f'f2py -m {mname} {ipath} --rest-doc'.split())
542
543    with util.switchdir(ipath.parent):
544        f2pycli()
545        out, _ = capfd.readouterr()
546        assert "ReST Documentation is saved to file" in out
547        with Path(f"./{mname}module.rest").open() as orst:
548            assert r".. -*- rest -*-" in orst.read()
549
550
551def test_norestexdoc(capfd, hello_world_f90, monkeypatch):
552    """Ensures that TeX documentation is written out
553
554    CLI :: --no-rest-doc
555    """
556    ipath = Path(hello_world_f90)
557    mname = "blah"
558    monkeypatch.setattr(sys, "argv",
559                        f'f2py -m {mname} {ipath} --no-rest-doc'.split())
560
561    with util.switchdir(ipath.parent):
562        f2pycli()
563        out, _ = capfd.readouterr()
564        assert "ReST Documentation is saved to file" not in out
565
566
567def test_debugcapi(capfd, hello_world_f90, monkeypatch):
568    """Ensures that debugging wrappers are written
569
570    CLI :: --debug-capi
571    """
572    ipath = Path(hello_world_f90)
573    mname = "blah"
574    monkeypatch.setattr(sys, "argv",
575                        f'f2py -m {mname} {ipath} --debug-capi'.split())
576
577    with util.switchdir(ipath.parent):
578        f2pycli()
579        with Path(f"./{mname}module.c").open() as ocmod:
580            assert r"#define DEBUGCFUNCS" in ocmod.read()
581
582
583@pytest.mark.skip(reason="Consistently fails on CI; noisy so skip not xfail.")
584def test_debugcapi_bld(hello_world_f90, monkeypatch):
585    """Ensures that debugging wrappers work
586
587    CLI :: --debug-capi -c
588    """
589    ipath = Path(hello_world_f90)
590    mname = "blah"
591    monkeypatch.setattr(sys, "argv",
592                        f'f2py -m {mname} {ipath} -c --debug-capi'.split())
593
594    with util.switchdir(ipath.parent):
595        f2pycli()
596        cmd_run = shlex.split(f"{sys.executable} -c \"import blah; blah.hi()\"")
597        rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
598        eout = ' Hello World\n'
599        eerr = textwrap.dedent("""\
600debug-capi:Python C/API function blah.hi()
601debug-capi:float hi=:output,hidden,scalar
602debug-capi:hi=0
603debug-capi:Fortran subroutine `f2pywraphi(&hi)'
604debug-capi:hi=0
605debug-capi:Building return value.
606debug-capi:Python C/API function blah.hi: successful.
607debug-capi:Freeing memory.
608        """)
609        assert rout.stdout == eout
610        assert rout.stderr == eerr
611
612
613def test_wrapfunc_def(capfd, hello_world_f90, monkeypatch):
614    """Ensures that fortran subroutine wrappers for F77 are included by default
615
616    CLI :: --[no]-wrap-functions
617    """
618    # Implied
619    ipath = Path(hello_world_f90)
620    mname = "blah"
621    monkeypatch.setattr(sys, "argv", f'f2py -m {mname} {ipath}'.split())
622
623    with util.switchdir(ipath.parent):
624        f2pycli()
625    out, _ = capfd.readouterr()
626    assert r"Fortran 77 wrappers are saved to" in out
627
628    # Explicit
629    monkeypatch.setattr(sys, "argv",
630                        f'f2py -m {mname} {ipath} --wrap-functions'.split())
631
632    with util.switchdir(ipath.parent):
633        f2pycli()
634        out, _ = capfd.readouterr()
635        assert r"Fortran 77 wrappers are saved to" in out
636
637
638def test_nowrapfunc(capfd, hello_world_f90, monkeypatch):
639    """Ensures that fortran subroutine wrappers for F77 can be disabled
640
641    CLI :: --no-wrap-functions
642    """
643    ipath = Path(hello_world_f90)
644    mname = "blah"
645    monkeypatch.setattr(sys, "argv",
646                        f'f2py -m {mname} {ipath} --no-wrap-functions'.split())
647
648    with util.switchdir(ipath.parent):
649        f2pycli()
650        out, _ = capfd.readouterr()
651        assert r"Fortran 77 wrappers are saved to" not in out
652
653
654def test_inclheader(capfd, hello_world_f90, monkeypatch):
655    """Add to the include directories
656
657    CLI :: -include
658    TODO: Document this in the help string
659    """
660    ipath = Path(hello_world_f90)
661    mname = "blah"
662    monkeypatch.setattr(
663        sys,
664        "argv",
665        f'f2py -m {mname} {ipath} -include<stdbool.h> -include<stdio.h> '.
666        split(),
667    )
668
669    with util.switchdir(ipath.parent):
670        f2pycli()
671        with Path(f"./{mname}module.c").open() as ocmod:
672            ocmr = ocmod.read()
673            assert "#include <stdbool.h>" in ocmr
674            assert "#include <stdio.h>" in ocmr
675
676@pytest.mark.skipif((platform.system() != 'Linux'), reason='Compiler required')
677def test_cli_obj(capfd, hello_world_f90, monkeypatch):
678    """Ensures that the extra object can be specified when using meson backend
679    """
680    ipath = Path(hello_world_f90)
681    mname = "blah"
682    odir = "tttmp"
683    obj = "extra.o"
684    monkeypatch.setattr(sys, "argv",
685                        f'f2py --backend meson --build-dir {odir} -m {mname} -c {obj} {ipath}'.split())
686
687    with util.switchdir(ipath.parent):
688        Path(obj).touch()
689        compiler_check_f2pycli()
690        with Path(f"{odir}/meson.build").open() as mesonbuild:
691            mbld = mesonbuild.read()
692            assert "objects:" in mbld
693            assert f"'''{obj}'''" in mbld
694
695
696def test_inclpath():
697    """Add to the include directories
698
699    CLI :: --include-paths
700    """
701    # TODO: populate
702    pass
703
704
705def test_hlink():
706    """Add to the include directories
707
708    CLI :: --help-link
709    """
710    # TODO: populate
711    pass
712
713
714def test_f2cmap(capfd, f2cmap_f90, monkeypatch):
715    """Check that Fortran-to-Python KIND specs can be passed
716
717    CLI :: --f2cmap
718    """
719    ipath = Path(f2cmap_f90)
720    monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --f2cmap mapfile'.split())
721
722    with util.switchdir(ipath.parent):
723        f2pycli()
724        out, _ = capfd.readouterr()
725        assert "Reading f2cmap from 'mapfile' ..." in out
726        assert "Mapping \"real(kind=real32)\" to \"float\"" in out
727        assert "Mapping \"real(kind=real64)\" to \"double\"" in out
728        assert "Mapping \"integer(kind=int64)\" to \"long_long\"" in out
729        assert "Successfully applied user defined f2cmap changes" in out
730
731
732def test_quiet(capfd, hello_world_f90, monkeypatch):
733    """Reduce verbosity
734
735    CLI :: --quiet
736    """
737    ipath = Path(hello_world_f90)
738    monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --quiet'.split())
739
740    with util.switchdir(ipath.parent):
741        f2pycli()
742        out, _ = capfd.readouterr()
743        assert len(out) == 0
744
745
746def test_verbose(capfd, hello_world_f90, monkeypatch):
747    """Increase verbosity
748
749    CLI :: --verbose
750    """
751    ipath = Path(hello_world_f90)
752    monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --verbose'.split())
753
754    with util.switchdir(ipath.parent):
755        f2pycli()
756        out, _ = capfd.readouterr()
757        assert "analyzeline" in out
758
759
760def test_version(capfd, monkeypatch):
761    """Ensure version
762
763    CLI :: -v
764    """
765    monkeypatch.setattr(sys, "argv", ["f2py", "-v"])
766    # TODO: f2py2e should not call sys.exit() after printing the version
767    with pytest.raises(SystemExit):
768        f2pycli()
769        out, _ = capfd.readouterr()
770        import numpy as np
771        assert np.__version__ == out.strip()
772
773
774@pytest.mark.skip(reason="Consistently fails on CI; noisy so skip not xfail.")
775def test_npdistop(hello_world_f90, monkeypatch):
776    """
777    CLI :: -c
778    """
779    ipath = Path(hello_world_f90)
780    monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} -c'.split())
781
782    with util.switchdir(ipath.parent):
783        f2pycli()
784        cmd_run = shlex.split(f"{sys.executable} -c \"import blah; blah.hi()\"")
785        rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
786        eout = ' Hello World\n'
787        assert rout.stdout == eout
788
789
790@pytest.mark.skipif((platform.system() != 'Linux') or sys.version_info <= (3, 12),
791                    reason='Compiler and Python 3.12 or newer required')
792def test_no_freethreading_compatible(hello_world_f90, monkeypatch):
793    """
794    CLI :: --no-freethreading-compatible
795    """
796    ipath = Path(hello_world_f90)
797    monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} -c --no-freethreading-compatible'.split())
798
799    with util.switchdir(ipath.parent):
800        compiler_check_f2pycli()
801        cmd = f"{sys.executable} -c \"import blah; blah.hi();"
802        if NOGIL_BUILD:
803            cmd += "import sys; assert sys._is_gil_enabled() is True\""
804        else:
805            cmd += "\""
806        cmd_run = shlex.split(cmd)
807        rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
808        eout = ' Hello World\n'
809        assert rout.stdout == eout
810        if NOGIL_BUILD:
811            assert "The global interpreter lock (GIL) has been enabled to load module 'blah'" in rout.stderr
812        assert rout.returncode == 0
813
814
815@pytest.mark.skipif((platform.system() != 'Linux') or sys.version_info <= (3, 12),
816                    reason='Compiler and Python 3.12 or newer required')
817def test_freethreading_compatible(hello_world_f90, monkeypatch):
818    """
819    CLI :: --freethreading_compatible
820    """
821    ipath = Path(hello_world_f90)
822    monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} -c --freethreading-compatible'.split())
823
824    with util.switchdir(ipath.parent):
825        compiler_check_f2pycli()
826        cmd = f"{sys.executable} -c \"import blah; blah.hi();"
827        if NOGIL_BUILD:
828            cmd += "import sys; assert sys._is_gil_enabled() is False\""
829        else:
830            cmd += "\""
831        cmd_run = shlex.split(cmd)
832        rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
833        eout = ' Hello World\n'
834        assert rout.stdout == eout
835        assert rout.stderr == ""
836        assert rout.returncode == 0
837
838
839# Numpy distutils flags
840# TODO: These should be tested separately
841
842def test_npd_fcompiler():
843    """
844    CLI :: -c --fcompiler
845    """
846    # TODO: populate
847    pass
848
849
850def test_npd_compiler():
851    """
852    CLI :: -c --compiler
853    """
854    # TODO: populate
855    pass
856
857
858def test_npd_help_fcompiler():
859    """
860    CLI :: -c --help-fcompiler
861    """
862    # TODO: populate
863    pass
864
865
866def test_npd_f77exec():
867    """
868    CLI :: -c --f77exec
869    """
870    # TODO: populate
871    pass
872
873
874def test_npd_f90exec():
875    """
876    CLI :: -c --f90exec
877    """
878    # TODO: populate
879    pass
880
881
882def test_npd_f77flags():
883    """
884    CLI :: -c --f77flags
885    """
886    # TODO: populate
887    pass
888
889
890def test_npd_f90flags():
891    """
892    CLI :: -c --f90flags
893    """
894    # TODO: populate
895    pass
896
897
898def test_npd_opt():
899    """
900    CLI :: -c --opt
901    """
902    # TODO: populate
903    pass
904
905
906def test_npd_arch():
907    """
908    CLI :: -c --arch
909    """
910    # TODO: populate
911    pass
912
913
914def test_npd_noopt():
915    """
916    CLI :: -c --noopt
917    """
918    # TODO: populate
919    pass
920
921
922def test_npd_noarch():
923    """
924    CLI :: -c --noarch
925    """
926    # TODO: populate
927    pass
928
929
930def test_npd_debug():
931    """
932    CLI :: -c --debug
933    """
934    # TODO: populate
935    pass
936
937
938def test_npd_link_auto():
939    """
940    CLI :: -c --link-<resource>
941    """
942    # TODO: populate
943    pass
944
945
946def test_npd_lib():
947    """
948    CLI :: -c -L/path/to/lib/ -l<libname>
949    """
950    # TODO: populate
951    pass
952
953
954def test_npd_define():
955    """
956    CLI :: -D<define>
957    """
958    # TODO: populate
959    pass
960
961
962def test_npd_undefine():
963    """
964    CLI :: -U<name>
965    """
966    # TODO: populate
967    pass
968
969
970def test_npd_incl():
971    """
972    CLI :: -I/path/to/include/
973    """
974    # TODO: populate
975    pass
976
977
978def test_npd_linker():
979    """
980    CLI :: <filename>.o <filename>.so <filename>.a
981    """
982    # TODO: populate
983    pass
984 
codekingpro/portable-devtools · Team Ai