summaryrefslogtreecommitdiff
path: root/numpy/f2py/tests/test_crackfortran.py
diff options
context:
space:
mode:
Diffstat (limited to 'numpy/f2py/tests/test_crackfortran.py')
-rw-r--r--numpy/f2py/tests/test_crackfortran.py78
1 files changed, 73 insertions, 5 deletions
diff --git a/numpy/f2py/tests/test_crackfortran.py b/numpy/f2py/tests/test_crackfortran.py
index dcf8760db..49bfc13af 100644
--- a/numpy/f2py/tests/test_crackfortran.py
+++ b/numpy/f2py/tests/test_crackfortran.py
@@ -1,7 +1,10 @@
+import importlib
import codecs
+import time
+import unicodedata
import pytest
import numpy as np
-from numpy.f2py.crackfortran import markinnerspaces
+from numpy.f2py.crackfortran import markinnerspaces, nameargspattern
from . import util
from numpy.f2py import crackfortran
import textwrap
@@ -132,6 +135,7 @@ class TestMarkinnerspaces:
assert markinnerspaces("a 'b c' 'd e'") == "a 'b@_@c' 'd@_@e'"
assert markinnerspaces(r'a "b c" "d e"') == r'a "b@_@c" "d@_@e"'
+
class TestDimSpec(util.F2PyTest):
"""This test suite tests various expressions that are used as dimension
specifications.
@@ -241,6 +245,7 @@ class TestModuleDeclaration:
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
@@ -257,13 +262,76 @@ class TestFortranReader(util.F2PyTest):
def test_input_encoding(self, tmp_path, encoding):
# gh-635
f_path = tmp_path / f"input_with_{encoding}_encoding.f90"
- # explicit BOM is required for UTF8
- bom = {'utf-8': codecs.BOM_UTF8}.get(encoding, b'')
with f_path.open('w', encoding=encoding) as ff:
- ff.write(bom.decode(encoding) +
- """
+ 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)
+
+
+class TestNameArgsPatternBacktracking:
+ @pytest.mark.parametrize(
+ ['adversary'],
+ [
+ ('@)@bind@(@',),
+ ('@)@bind @(@',),
+ ('@)@bind foo bar baz@(@',)
+ ]
+ )
+ def test_nameargspattern_backtracking(self, adversary):
+ '''address ReDOS vulnerability:
+ https://github.com/numpy/numpy/issues/23338'''
+ trials_per_batch = 12
+ batches_per_regex = 4
+ start_reps, end_reps = 15, 25
+ for ii in range(start_reps, end_reps):
+ repeated_adversary = adversary * ii
+ # test times in small batches.
+ # this gives us more chances to catch a bad regex
+ # while still catching it before too long if it is bad
+ for _ in range(batches_per_regex):
+ times = []
+ for _ in range(trials_per_batch):
+ t0 = time.perf_counter()
+ mtch = nameargspattern.search(repeated_adversary)
+ times.append(time.perf_counter() - t0)
+ # our pattern should be much faster than 0.2s per search
+ # it's unlikely that a bad regex will pass even on fast CPUs
+ assert np.median(times) < 0.2
+ assert not mtch
+ # if the adversary is capped with @)@, it becomes acceptable
+ # according to the old version of the regex.
+ # that should still be true.
+ good_version_of_adversary = repeated_adversary + '@)@'
+ assert nameargspattern.search(good_version_of_adversary)
+
+
+class TestFunctionReturn(util.F2PyTest):
+ sources = [util.getpath("tests", "src", "crackfortran", "gh23598.f90")]
+
+ def test_function_rettype(self):
+ # gh-23598
+ assert self.module.intproduct(3, 4) == 12
+
+
+class TestFortranGroupCounters(util.F2PyTest):
+ def test_end_if_comment(self):
+ # gh-23533
+ fpath = util.getpath("tests", "src", "crackfortran", "gh23533.f")
+ try:
+ crackfortran.crackfortran([str(fpath)])
+ except Exception as exc:
+ assert False, f"'crackfortran.crackfortran' raised an exception {exc}"