test_f2py2e.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. import textwrap, re, sys, subprocess, shlex
  2. from pathlib import Path
  3. from collections import namedtuple
  4. import platform
  5. import pytest
  6. from . import util
  7. from numpy.f2py.f2py2e import main as f2pycli
  8. #########################
  9. # CLI utils and classes #
  10. #########################
  11. PPaths = namedtuple("PPaths", "finp, f90inp, pyf, wrap77, wrap90, cmodf")
  12. def get_io_paths(fname_inp, mname="untitled"):
  13. """Takes in a temporary file for testing and returns the expected output and input paths
  14. Here expected output is essentially one of any of the possible generated
  15. files.
  16. ..note::
  17. Since this does not actually run f2py, none of these are guaranteed to
  18. exist, and module names are typically incorrect
  19. Parameters
  20. ----------
  21. fname_inp : str
  22. The input filename
  23. mname : str, optional
  24. The name of the module, untitled by default
  25. Returns
  26. -------
  27. genp : NamedTuple PPaths
  28. The possible paths which are generated, not all of which exist
  29. """
  30. bpath = Path(fname_inp)
  31. return PPaths(
  32. finp=bpath.with_suffix(".f"),
  33. f90inp=bpath.with_suffix(".f90"),
  34. pyf=bpath.with_suffix(".pyf"),
  35. wrap77=bpath.with_name(f"{mname}-f2pywrappers.f"),
  36. wrap90=bpath.with_name(f"{mname}-f2pywrappers2.f90"),
  37. cmodf=bpath.with_name(f"{mname}module.c"),
  38. )
  39. ##############
  40. # CLI Fixtures and Tests #
  41. #############
  42. @pytest.fixture(scope="session")
  43. def hello_world_f90(tmpdir_factory):
  44. """Generates a single f90 file for testing"""
  45. fdat = util.getpath("tests", "src", "cli", "hiworld.f90").read_text()
  46. fn = tmpdir_factory.getbasetemp() / "hello.f90"
  47. fn.write_text(fdat, encoding="ascii")
  48. return fn
  49. @pytest.fixture(scope="session")
  50. def gh23598_warn(tmpdir_factory):
  51. """F90 file for testing warnings in gh23598"""
  52. fdat = util.getpath("tests", "src", "crackfortran", "gh23598Warn.f90").read_text()
  53. fn = tmpdir_factory.getbasetemp() / "gh23598Warn.f90"
  54. fn.write_text(fdat, encoding="ascii")
  55. return fn
  56. @pytest.fixture(scope="session")
  57. def gh22819_cli(tmpdir_factory):
  58. """F90 file for testing disallowed CLI arguments in ghff819"""
  59. fdat = util.getpath("tests", "src", "cli", "gh_22819.pyf").read_text()
  60. fn = tmpdir_factory.getbasetemp() / "gh_22819.pyf"
  61. fn.write_text(fdat, encoding="ascii")
  62. return fn
  63. @pytest.fixture(scope="session")
  64. def hello_world_f77(tmpdir_factory):
  65. """Generates a single f77 file for testing"""
  66. fdat = util.getpath("tests", "src", "cli", "hi77.f").read_text()
  67. fn = tmpdir_factory.getbasetemp() / "hello.f"
  68. fn.write_text(fdat, encoding="ascii")
  69. return fn
  70. @pytest.fixture(scope="session")
  71. def retreal_f77(tmpdir_factory):
  72. """Generates a single f77 file for testing"""
  73. fdat = util.getpath("tests", "src", "return_real", "foo77.f").read_text()
  74. fn = tmpdir_factory.getbasetemp() / "foo.f"
  75. fn.write_text(fdat, encoding="ascii")
  76. return fn
  77. @pytest.fixture(scope="session")
  78. def f2cmap_f90(tmpdir_factory):
  79. """Generates a single f90 file for testing"""
  80. fdat = util.getpath("tests", "src", "f2cmap", "isoFortranEnvMap.f90").read_text()
  81. f2cmap = util.getpath("tests", "src", "f2cmap", ".f2py_f2cmap").read_text()
  82. fn = tmpdir_factory.getbasetemp() / "f2cmap.f90"
  83. fmap = tmpdir_factory.getbasetemp() / "mapfile"
  84. fn.write_text(fdat, encoding="ascii")
  85. fmap.write_text(f2cmap, encoding="ascii")
  86. return fn
  87. def test_gh22819_cli(capfd, gh22819_cli, monkeypatch):
  88. """Check that module names are handled correctly
  89. gh-22819
  90. Essentially, the -m name cannot be used to import the module, so the module
  91. named in the .pyf needs to be used instead
  92. CLI :: -m and a .pyf file
  93. """
  94. ipath = Path(gh22819_cli)
  95. monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath}".split())
  96. with util.switchdir(ipath.parent):
  97. f2pycli()
  98. gen_paths = [item.name for item in ipath.parent.rglob("*") if item.is_file()]
  99. assert "blahmodule.c" not in gen_paths # shouldn't be generated
  100. assert "blah-f2pywrappers.f" not in gen_paths
  101. assert "test_22819-f2pywrappers.f" in gen_paths
  102. assert "test_22819module.c" in gen_paths
  103. assert "Ignoring blah"
  104. def test_gh22819_many_pyf(capfd, gh22819_cli, monkeypatch):
  105. """Only one .pyf file allowed
  106. gh-22819
  107. CLI :: .pyf files
  108. """
  109. ipath = Path(gh22819_cli)
  110. monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath} hello.pyf".split())
  111. with util.switchdir(ipath.parent):
  112. with pytest.raises(ValueError, match="Only one .pyf file per call"):
  113. f2pycli()
  114. def test_gh23598_warn(capfd, gh23598_warn, monkeypatch):
  115. foutl = get_io_paths(gh23598_warn, mname="test")
  116. ipath = foutl.f90inp
  117. monkeypatch.setattr(
  118. sys, "argv",
  119. f'f2py {ipath} -m test'.split())
  120. with util.switchdir(ipath.parent):
  121. f2pycli() # Generate files
  122. wrapper = foutl.wrap90.read_text()
  123. assert "intproductf2pywrap, intpr" not in wrapper
  124. def test_gen_pyf(capfd, hello_world_f90, monkeypatch):
  125. """Ensures that a signature file is generated via the CLI
  126. CLI :: -h
  127. """
  128. ipath = Path(hello_world_f90)
  129. opath = Path(hello_world_f90).stem + ".pyf"
  130. monkeypatch.setattr(sys, "argv", f'f2py -h {opath} {ipath}'.split())
  131. with util.switchdir(ipath.parent):
  132. f2pycli() # Generate wrappers
  133. out, _ = capfd.readouterr()
  134. assert "Saving signatures to file" in out
  135. assert Path(f'{opath}').exists()
  136. def test_gen_pyf_stdout(capfd, hello_world_f90, monkeypatch):
  137. """Ensures that a signature file can be dumped to stdout
  138. CLI :: -h
  139. """
  140. ipath = Path(hello_world_f90)
  141. monkeypatch.setattr(sys, "argv", f'f2py -h stdout {ipath}'.split())
  142. with util.switchdir(ipath.parent):
  143. f2pycli()
  144. out, _ = capfd.readouterr()
  145. assert "Saving signatures to file" in out
  146. assert "function hi() ! in " in out
  147. def test_gen_pyf_no_overwrite(capfd, hello_world_f90, monkeypatch):
  148. """Ensures that the CLI refuses to overwrite signature files
  149. CLI :: -h without --overwrite-signature
  150. """
  151. ipath = Path(hello_world_f90)
  152. monkeypatch.setattr(sys, "argv", f'f2py -h faker.pyf {ipath}'.split())
  153. with util.switchdir(ipath.parent):
  154. Path("faker.pyf").write_text("Fake news", encoding="ascii")
  155. with pytest.raises(SystemExit):
  156. f2pycli() # Refuse to overwrite
  157. _, err = capfd.readouterr()
  158. assert "Use --overwrite-signature to overwrite" in err
  159. @pytest.mark.skipif((platform.system() != 'Linux') or (sys.version_info <= (3, 12)),
  160. reason='Compiler and 3.12 required')
  161. def test_untitled_cli(capfd, hello_world_f90, monkeypatch):
  162. """Check that modules are named correctly
  163. CLI :: defaults
  164. """
  165. ipath = Path(hello_world_f90)
  166. monkeypatch.setattr(sys, "argv", f"f2py --backend meson -c {ipath}".split())
  167. with util.switchdir(ipath.parent):
  168. f2pycli()
  169. out, _ = capfd.readouterr()
  170. assert "untitledmodule.c" in out
  171. @pytest.mark.skipif((platform.system() != 'Linux') or (sys.version_info <= (3, 12)), reason='Compiler and 3.12 required')
  172. def test_no_py312_distutils_fcompiler(capfd, hello_world_f90, monkeypatch):
  173. """Check that no distutils imports are performed on 3.12
  174. CLI :: --fcompiler --help-link --backend distutils
  175. """
  176. MNAME = "hi"
  177. foutl = get_io_paths(hello_world_f90, mname=MNAME)
  178. ipath = foutl.f90inp
  179. monkeypatch.setattr(
  180. sys, "argv", f"f2py {ipath} -c --fcompiler=gfortran -m {MNAME}".split()
  181. )
  182. with util.switchdir(ipath.parent):
  183. f2pycli()
  184. out, _ = capfd.readouterr()
  185. assert "--fcompiler cannot be used with meson" in out
  186. monkeypatch.setattr(
  187. sys, "argv", f"f2py --help-link".split()
  188. )
  189. with util.switchdir(ipath.parent):
  190. f2pycli()
  191. out, _ = capfd.readouterr()
  192. assert "Use --dep for meson builds" in out
  193. MNAME = "hi2" # Needs to be different for a new -c
  194. monkeypatch.setattr(
  195. sys, "argv", f"f2py {ipath} -c -m {MNAME} --backend distutils".split()
  196. )
  197. with util.switchdir(ipath.parent):
  198. f2pycli()
  199. out, _ = capfd.readouterr()
  200. assert "Cannot use distutils backend with Python>=3.12" in out
  201. @pytest.mark.xfail
  202. def test_f2py_skip(capfd, retreal_f77, monkeypatch):
  203. """Tests that functions can be skipped
  204. CLI :: skip:
  205. """
  206. foutl = get_io_paths(retreal_f77, mname="test")
  207. ipath = foutl.finp
  208. toskip = "t0 t4 t8 sd s8 s4"
  209. remaining = "td s0"
  210. monkeypatch.setattr(
  211. sys, "argv",
  212. f'f2py {ipath} -m test skip: {toskip}'.split())
  213. with util.switchdir(ipath.parent):
  214. f2pycli()
  215. out, err = capfd.readouterr()
  216. for skey in toskip.split():
  217. assert (
  218. f'buildmodule: Could not found the body of interfaced routine "{skey}". Skipping.'
  219. in err)
  220. for rkey in remaining.split():
  221. assert f'Constructing wrapper function "{rkey}"' in out
  222. def test_f2py_only(capfd, retreal_f77, monkeypatch):
  223. """Test that functions can be kept by only:
  224. CLI :: only:
  225. """
  226. foutl = get_io_paths(retreal_f77, mname="test")
  227. ipath = foutl.finp
  228. toskip = "t0 t4 t8 sd s8 s4"
  229. tokeep = "td s0"
  230. monkeypatch.setattr(
  231. sys, "argv",
  232. f'f2py {ipath} -m test only: {tokeep}'.split())
  233. with util.switchdir(ipath.parent):
  234. f2pycli()
  235. out, err = capfd.readouterr()
  236. for skey in toskip.split():
  237. assert (
  238. f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
  239. in err)
  240. for rkey in tokeep.split():
  241. assert f'Constructing wrapper function "{rkey}"' in out
  242. def test_file_processing_switch(capfd, hello_world_f90, retreal_f77,
  243. monkeypatch):
  244. """Tests that it is possible to return to file processing mode
  245. CLI :: :
  246. BUG: numpy-gh #20520
  247. """
  248. foutl = get_io_paths(retreal_f77, mname="test")
  249. ipath = foutl.finp
  250. toskip = "t0 t4 t8 sd s8 s4"
  251. ipath2 = Path(hello_world_f90)
  252. tokeep = "td s0 hi" # hi is in ipath2
  253. mname = "blah"
  254. monkeypatch.setattr(
  255. sys,
  256. "argv",
  257. f'f2py {ipath} -m {mname} only: {tokeep} : {ipath2}'.split(
  258. ),
  259. )
  260. with util.switchdir(ipath.parent):
  261. f2pycli()
  262. out, err = capfd.readouterr()
  263. for skey in toskip.split():
  264. assert (
  265. f'buildmodule: Could not find the body of interfaced routine "{skey}". Skipping.'
  266. in err)
  267. for rkey in tokeep.split():
  268. assert f'Constructing wrapper function "{rkey}"' in out
  269. def test_mod_gen_f77(capfd, hello_world_f90, monkeypatch):
  270. """Checks the generation of files based on a module name
  271. CLI :: -m
  272. """
  273. MNAME = "hi"
  274. foutl = get_io_paths(hello_world_f90, mname=MNAME)
  275. ipath = foutl.f90inp
  276. monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME}'.split())
  277. with util.switchdir(ipath.parent):
  278. f2pycli()
  279. # Always generate C module
  280. assert Path.exists(foutl.cmodf)
  281. # File contains a function, check for F77 wrappers
  282. assert Path.exists(foutl.wrap77)
  283. def test_mod_gen_gh25263(capfd, hello_world_f77, monkeypatch):
  284. """Check that pyf files are correctly generated with module structure
  285. CLI :: -m <name> -h pyf_file
  286. BUG: numpy-gh #20520
  287. """
  288. MNAME = "hi"
  289. foutl = get_io_paths(hello_world_f77, mname=MNAME)
  290. ipath = foutl.finp
  291. monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m {MNAME} -h hi.pyf'.split())
  292. with util.switchdir(ipath.parent):
  293. f2pycli()
  294. with Path('hi.pyf').open() as hipyf:
  295. pyfdat = hipyf.read()
  296. assert "python module hi" in pyfdat
  297. def test_lower_cmod(capfd, hello_world_f77, monkeypatch):
  298. """Lowers cases by flag or when -h is present
  299. CLI :: --[no-]lower
  300. """
  301. foutl = get_io_paths(hello_world_f77, mname="test")
  302. ipath = foutl.finp
  303. capshi = re.compile(r"HI\(\)")
  304. capslo = re.compile(r"hi\(\)")
  305. # Case I: --lower is passed
  306. monkeypatch.setattr(sys, "argv", f'f2py {ipath} -m test --lower'.split())
  307. with util.switchdir(ipath.parent):
  308. f2pycli()
  309. out, _ = capfd.readouterr()
  310. assert capslo.search(out) is not None
  311. assert capshi.search(out) is None
  312. # Case II: --no-lower is passed
  313. monkeypatch.setattr(sys, "argv",
  314. f'f2py {ipath} -m test --no-lower'.split())
  315. with util.switchdir(ipath.parent):
  316. f2pycli()
  317. out, _ = capfd.readouterr()
  318. assert capslo.search(out) is None
  319. assert capshi.search(out) is not None
  320. def test_lower_sig(capfd, hello_world_f77, monkeypatch):
  321. """Lowers cases in signature files by flag or when -h is present
  322. CLI :: --[no-]lower -h
  323. """
  324. foutl = get_io_paths(hello_world_f77, mname="test")
  325. ipath = foutl.finp
  326. # Signature files
  327. capshi = re.compile(r"Block: HI")
  328. capslo = re.compile(r"Block: hi")
  329. # Case I: --lower is implied by -h
  330. # TODO: Clean up to prevent passing --overwrite-signature
  331. monkeypatch.setattr(
  332. sys,
  333. "argv",
  334. f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature'.split(),
  335. )
  336. with util.switchdir(ipath.parent):
  337. f2pycli()
  338. out, _ = capfd.readouterr()
  339. assert capslo.search(out) is not None
  340. assert capshi.search(out) is None
  341. # Case II: --no-lower overrides -h
  342. monkeypatch.setattr(
  343. sys,
  344. "argv",
  345. f'f2py {ipath} -h {foutl.pyf} -m test --overwrite-signature --no-lower'
  346. .split(),
  347. )
  348. with util.switchdir(ipath.parent):
  349. f2pycli()
  350. out, _ = capfd.readouterr()
  351. assert capslo.search(out) is None
  352. assert capshi.search(out) is not None
  353. def test_build_dir(capfd, hello_world_f90, monkeypatch):
  354. """Ensures that the build directory can be specified
  355. CLI :: --build-dir
  356. """
  357. ipath = Path(hello_world_f90)
  358. mname = "blah"
  359. odir = "tttmp"
  360. monkeypatch.setattr(sys, "argv",
  361. f'f2py -m {mname} {ipath} --build-dir {odir}'.split())
  362. with util.switchdir(ipath.parent):
  363. f2pycli()
  364. out, _ = capfd.readouterr()
  365. assert f"Wrote C/API module \"{mname}\"" in out
  366. def test_overwrite(capfd, hello_world_f90, monkeypatch):
  367. """Ensures that the build directory can be specified
  368. CLI :: --overwrite-signature
  369. """
  370. ipath = Path(hello_world_f90)
  371. monkeypatch.setattr(
  372. sys, "argv",
  373. f'f2py -h faker.pyf {ipath} --overwrite-signature'.split())
  374. with util.switchdir(ipath.parent):
  375. Path("faker.pyf").write_text("Fake news", encoding="ascii")
  376. f2pycli()
  377. out, _ = capfd.readouterr()
  378. assert "Saving signatures to file" in out
  379. def test_latexdoc(capfd, hello_world_f90, monkeypatch):
  380. """Ensures that TeX documentation is written out
  381. CLI :: --latex-doc
  382. """
  383. ipath = Path(hello_world_f90)
  384. mname = "blah"
  385. monkeypatch.setattr(sys, "argv",
  386. f'f2py -m {mname} {ipath} --latex-doc'.split())
  387. with util.switchdir(ipath.parent):
  388. f2pycli()
  389. out, _ = capfd.readouterr()
  390. assert "Documentation is saved to file" in out
  391. with Path(f"{mname}module.tex").open() as otex:
  392. assert "\\documentclass" in otex.read()
  393. def test_nolatexdoc(capfd, hello_world_f90, monkeypatch):
  394. """Ensures that TeX documentation is written out
  395. CLI :: --no-latex-doc
  396. """
  397. ipath = Path(hello_world_f90)
  398. mname = "blah"
  399. monkeypatch.setattr(sys, "argv",
  400. f'f2py -m {mname} {ipath} --no-latex-doc'.split())
  401. with util.switchdir(ipath.parent):
  402. f2pycli()
  403. out, _ = capfd.readouterr()
  404. assert "Documentation is saved to file" not in out
  405. def test_shortlatex(capfd, hello_world_f90, monkeypatch):
  406. """Ensures that truncated documentation is written out
  407. TODO: Test to ensure this has no effect without --latex-doc
  408. CLI :: --latex-doc --short-latex
  409. """
  410. ipath = Path(hello_world_f90)
  411. mname = "blah"
  412. monkeypatch.setattr(
  413. sys,
  414. "argv",
  415. f'f2py -m {mname} {ipath} --latex-doc --short-latex'.split(),
  416. )
  417. with util.switchdir(ipath.parent):
  418. f2pycli()
  419. out, _ = capfd.readouterr()
  420. assert "Documentation is saved to file" in out
  421. with Path(f"./{mname}module.tex").open() as otex:
  422. assert "\\documentclass" not in otex.read()
  423. def test_restdoc(capfd, hello_world_f90, monkeypatch):
  424. """Ensures that RsT documentation is written out
  425. CLI :: --rest-doc
  426. """
  427. ipath = Path(hello_world_f90)
  428. mname = "blah"
  429. monkeypatch.setattr(sys, "argv",
  430. f'f2py -m {mname} {ipath} --rest-doc'.split())
  431. with util.switchdir(ipath.parent):
  432. f2pycli()
  433. out, _ = capfd.readouterr()
  434. assert "ReST Documentation is saved to file" in out
  435. with Path(f"./{mname}module.rest").open() as orst:
  436. assert r".. -*- rest -*-" in orst.read()
  437. def test_norestexdoc(capfd, hello_world_f90, monkeypatch):
  438. """Ensures that TeX documentation is written out
  439. CLI :: --no-rest-doc
  440. """
  441. ipath = Path(hello_world_f90)
  442. mname = "blah"
  443. monkeypatch.setattr(sys, "argv",
  444. f'f2py -m {mname} {ipath} --no-rest-doc'.split())
  445. with util.switchdir(ipath.parent):
  446. f2pycli()
  447. out, _ = capfd.readouterr()
  448. assert "ReST Documentation is saved to file" not in out
  449. def test_debugcapi(capfd, hello_world_f90, monkeypatch):
  450. """Ensures that debugging wrappers are written
  451. CLI :: --debug-capi
  452. """
  453. ipath = Path(hello_world_f90)
  454. mname = "blah"
  455. monkeypatch.setattr(sys, "argv",
  456. f'f2py -m {mname} {ipath} --debug-capi'.split())
  457. with util.switchdir(ipath.parent):
  458. f2pycli()
  459. with Path(f"./{mname}module.c").open() as ocmod:
  460. assert r"#define DEBUGCFUNCS" in ocmod.read()
  461. @pytest.mark.xfail(reason="Consistently fails on CI.")
  462. def test_debugcapi_bld(hello_world_f90, monkeypatch):
  463. """Ensures that debugging wrappers work
  464. CLI :: --debug-capi -c
  465. """
  466. ipath = Path(hello_world_f90)
  467. mname = "blah"
  468. monkeypatch.setattr(sys, "argv",
  469. f'f2py -m {mname} {ipath} -c --debug-capi'.split())
  470. with util.switchdir(ipath.parent):
  471. f2pycli()
  472. cmd_run = shlex.split("python3 -c \"import blah; blah.hi()\"")
  473. rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
  474. eout = ' Hello World\n'
  475. eerr = textwrap.dedent("""\
  476. debug-capi:Python C/API function blah.hi()
  477. debug-capi:float hi=:output,hidden,scalar
  478. debug-capi:hi=0
  479. debug-capi:Fortran subroutine `f2pywraphi(&hi)'
  480. debug-capi:hi=0
  481. debug-capi:Building return value.
  482. debug-capi:Python C/API function blah.hi: successful.
  483. debug-capi:Freeing memory.
  484. """)
  485. assert rout.stdout == eout
  486. assert rout.stderr == eerr
  487. def test_wrapfunc_def(capfd, hello_world_f90, monkeypatch):
  488. """Ensures that fortran subroutine wrappers for F77 are included by default
  489. CLI :: --[no]-wrap-functions
  490. """
  491. # Implied
  492. ipath = Path(hello_world_f90)
  493. mname = "blah"
  494. monkeypatch.setattr(sys, "argv", f'f2py -m {mname} {ipath}'.split())
  495. with util.switchdir(ipath.parent):
  496. f2pycli()
  497. out, _ = capfd.readouterr()
  498. assert r"Fortran 77 wrappers are saved to" in out
  499. # Explicit
  500. monkeypatch.setattr(sys, "argv",
  501. f'f2py -m {mname} {ipath} --wrap-functions'.split())
  502. with util.switchdir(ipath.parent):
  503. f2pycli()
  504. out, _ = capfd.readouterr()
  505. assert r"Fortran 77 wrappers are saved to" in out
  506. def test_nowrapfunc(capfd, hello_world_f90, monkeypatch):
  507. """Ensures that fortran subroutine wrappers for F77 can be disabled
  508. CLI :: --no-wrap-functions
  509. """
  510. ipath = Path(hello_world_f90)
  511. mname = "blah"
  512. monkeypatch.setattr(sys, "argv",
  513. f'f2py -m {mname} {ipath} --no-wrap-functions'.split())
  514. with util.switchdir(ipath.parent):
  515. f2pycli()
  516. out, _ = capfd.readouterr()
  517. assert r"Fortran 77 wrappers are saved to" not in out
  518. def test_inclheader(capfd, hello_world_f90, monkeypatch):
  519. """Add to the include directories
  520. CLI :: -include
  521. TODO: Document this in the help string
  522. """
  523. ipath = Path(hello_world_f90)
  524. mname = "blah"
  525. monkeypatch.setattr(
  526. sys,
  527. "argv",
  528. f'f2py -m {mname} {ipath} -include<stdbool.h> -include<stdio.h> '.
  529. split(),
  530. )
  531. with util.switchdir(ipath.parent):
  532. f2pycli()
  533. with Path(f"./{mname}module.c").open() as ocmod:
  534. ocmr = ocmod.read()
  535. assert "#include <stdbool.h>" in ocmr
  536. assert "#include <stdio.h>" in ocmr
  537. def test_inclpath():
  538. """Add to the include directories
  539. CLI :: --include-paths
  540. """
  541. # TODO: populate
  542. pass
  543. def test_hlink():
  544. """Add to the include directories
  545. CLI :: --help-link
  546. """
  547. # TODO: populate
  548. pass
  549. def test_f2cmap(capfd, f2cmap_f90, monkeypatch):
  550. """Check that Fortran-to-Python KIND specs can be passed
  551. CLI :: --f2cmap
  552. """
  553. ipath = Path(f2cmap_f90)
  554. monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --f2cmap mapfile'.split())
  555. with util.switchdir(ipath.parent):
  556. f2pycli()
  557. out, _ = capfd.readouterr()
  558. assert "Reading f2cmap from 'mapfile' ..." in out
  559. assert "Mapping \"real(kind=real32)\" to \"float\"" in out
  560. assert "Mapping \"real(kind=real64)\" to \"double\"" in out
  561. assert "Mapping \"integer(kind=int64)\" to \"long_long\"" in out
  562. assert "Successfully applied user defined f2cmap changes" in out
  563. def test_quiet(capfd, hello_world_f90, monkeypatch):
  564. """Reduce verbosity
  565. CLI :: --quiet
  566. """
  567. ipath = Path(hello_world_f90)
  568. monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --quiet'.split())
  569. with util.switchdir(ipath.parent):
  570. f2pycli()
  571. out, _ = capfd.readouterr()
  572. assert len(out) == 0
  573. def test_verbose(capfd, hello_world_f90, monkeypatch):
  574. """Increase verbosity
  575. CLI :: --verbose
  576. """
  577. ipath = Path(hello_world_f90)
  578. monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} --verbose'.split())
  579. with util.switchdir(ipath.parent):
  580. f2pycli()
  581. out, _ = capfd.readouterr()
  582. assert "analyzeline" in out
  583. def test_version(capfd, monkeypatch):
  584. """Ensure version
  585. CLI :: -v
  586. """
  587. monkeypatch.setattr(sys, "argv", 'f2py -v'.split())
  588. # TODO: f2py2e should not call sys.exit() after printing the version
  589. with pytest.raises(SystemExit):
  590. f2pycli()
  591. out, _ = capfd.readouterr()
  592. import numpy as np
  593. assert np.__version__ == out.strip()
  594. @pytest.mark.xfail(reason="Consistently fails on CI.")
  595. def test_npdistop(hello_world_f90, monkeypatch):
  596. """
  597. CLI :: -c
  598. """
  599. ipath = Path(hello_world_f90)
  600. monkeypatch.setattr(sys, "argv", f'f2py -m blah {ipath} -c'.split())
  601. with util.switchdir(ipath.parent):
  602. f2pycli()
  603. cmd_run = shlex.split("python -c \"import blah; blah.hi()\"")
  604. rout = subprocess.run(cmd_run, capture_output=True, encoding='UTF-8')
  605. eout = ' Hello World\n'
  606. assert rout.stdout == eout
  607. # Numpy distutils flags
  608. # TODO: These should be tested separately
  609. def test_npd_fcompiler():
  610. """
  611. CLI :: -c --fcompiler
  612. """
  613. # TODO: populate
  614. pass
  615. def test_npd_compiler():
  616. """
  617. CLI :: -c --compiler
  618. """
  619. # TODO: populate
  620. pass
  621. def test_npd_help_fcompiler():
  622. """
  623. CLI :: -c --help-fcompiler
  624. """
  625. # TODO: populate
  626. pass
  627. def test_npd_f77exec():
  628. """
  629. CLI :: -c --f77exec
  630. """
  631. # TODO: populate
  632. pass
  633. def test_npd_f90exec():
  634. """
  635. CLI :: -c --f90exec
  636. """
  637. # TODO: populate
  638. pass
  639. def test_npd_f77flags():
  640. """
  641. CLI :: -c --f77flags
  642. """
  643. # TODO: populate
  644. pass
  645. def test_npd_f90flags():
  646. """
  647. CLI :: -c --f90flags
  648. """
  649. # TODO: populate
  650. pass
  651. def test_npd_opt():
  652. """
  653. CLI :: -c --opt
  654. """
  655. # TODO: populate
  656. pass
  657. def test_npd_arch():
  658. """
  659. CLI :: -c --arch
  660. """
  661. # TODO: populate
  662. pass
  663. def test_npd_noopt():
  664. """
  665. CLI :: -c --noopt
  666. """
  667. # TODO: populate
  668. pass
  669. def test_npd_noarch():
  670. """
  671. CLI :: -c --noarch
  672. """
  673. # TODO: populate
  674. pass
  675. def test_npd_debug():
  676. """
  677. CLI :: -c --debug
  678. """
  679. # TODO: populate
  680. pass
  681. def test_npd_link_auto():
  682. """
  683. CLI :: -c --link-<resource>
  684. """
  685. # TODO: populate
  686. pass
  687. def test_npd_lib():
  688. """
  689. CLI :: -c -L/path/to/lib/ -l<libname>
  690. """
  691. # TODO: populate
  692. pass
  693. def test_npd_define():
  694. """
  695. CLI :: -D<define>
  696. """
  697. # TODO: populate
  698. pass
  699. def test_npd_undefine():
  700. """
  701. CLI :: -U<name>
  702. """
  703. # TODO: populate
  704. pass
  705. def test_npd_incl():
  706. """
  707. CLI :: -I/path/to/include/
  708. """
  709. # TODO: populate
  710. pass
  711. def test_npd_linker():
  712. """
  713. CLI :: <filename>.o <filename>.so <filename>.a
  714. """
  715. # TODO: populate
  716. pass