diff options
author | scoder <stefan_ml@behnel.de> | 2022-07-30 18:43:10 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-30 18:43:10 +0200 |
commit | 84afe5550e619cc1b40e9909e4a35234841ad366 (patch) | |
tree | 5282a2fb1528b4d1b829df2b923f193818f69db4 /Cython/TestUtils.py | |
parent | 9b69292ee4f5340994776cac4ba18c27c9578ea1 (diff) | |
download | cython-84afe5550e619cc1b40e9909e4a35234841ad366.tar.gz |
Allow C code assertions in tests by defining regular expressions in module directives. (GH-4938)
Diffstat (limited to 'Cython/TestUtils.py')
-rw-r--r-- | Cython/TestUtils.py | 70 |
1 files changed, 69 insertions, 1 deletions
diff --git a/Cython/TestUtils.py b/Cython/TestUtils.py index bb2070d39..8328a3d6f 100644 --- a/Cython/TestUtils.py +++ b/Cython/TestUtils.py @@ -1,12 +1,14 @@ from __future__ import absolute_import import os +import re import unittest import shlex import sys import tempfile import textwrap from io import open +from functools import partial from .Compiler import Errors from .CodeWriter import CodeWriter @@ -161,11 +163,64 @@ class TransformTest(CythonTest): return tree +# For the test C code validation, we have to take care that the test directives (and thus +# the match strings) do not just appear in (multiline) C code comments containing the original +# Cython source code. Thus, we discard the comments before matching. +# This seems a prime case for re.VERBOSE, but it seems to match some of the whitespace. +_strip_c_comments = partial(re.compile( + re.sub('\s+', '', r''' + /[*] ( + (?: [^*\n] | [*][^/] )* + [\n] + (?: [^*] | [*][^/] )* + ) [*]/ + ''') +).sub, '') + + class TreeAssertVisitor(VisitorTransform): # actually, a TreeVisitor would be enough, but this needs to run # as part of the compiler pipeline - def visit_CompilerDirectivesNode(self, node): + def __init__(self): + super(TreeAssertVisitor, self).__init__() + self._module_pos = None + self._c_patterns = [] + self._c_antipatterns = [] + + def create_c_file_validator(self): + patterns, antipatterns = self._c_patterns, self._c_antipatterns + + def fail(pos, pattern, found, file_path): + Errors.error(pos, "Pattern '%s' %s found in %s" %( + pattern, + 'was' if found else 'was not', + file_path, + )) + + def validate_c_file(result): + c_file = result.c_file + if not (patterns or antipatterns): + #print("No patterns defined for %s" % c_file) + return result + + with open(c_file, encoding='utf8') as f: + c_content = f.read() + c_content = _strip_c_comments(c_content) + + for pattern in patterns: + #print("Searching pattern '%s'" % pattern) + if not re.search(pattern, c_content): + fail(self._module_pos, pattern, found=False, file_path=c_file) + + for antipattern in antipatterns: + #print("Searching antipattern '%s'" % antipattern) + if re.search(antipattern, c_content): + fail(self._module_pos, antipattern, found=True, file_path=c_file) + + return validate_c_file + + def _check_directives(self, node): directives = node.directives if 'test_assert_path_exists' in directives: for path in directives['test_assert_path_exists']: @@ -179,6 +234,19 @@ class TreeAssertVisitor(VisitorTransform): Errors.error( node.pos, "Unexpected path '%s' found in result tree" % path) + if 'test_assert_c_code_has' in directives: + self._c_patterns.extend(directives['test_assert_c_code_has']) + if 'test_fail_if_c_code_has' in directives: + self._c_antipatterns.extend(directives['test_fail_if_c_code_has']) + + def visit_ModuleNode(self, node): + self._module_pos = node.pos + self._check_directives(node) + self.visitchildren(node) + return node + + def visit_CompilerDirectivesNode(self, node): + self._check_directives(node) self.visitchildren(node) return node |