summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_tools
diff options
context:
space:
mode:
authorEric Snow <ericsnowcurrently@gmail.com>2019-09-11 18:49:45 (GMT)
committerGitHub <noreply@github.com>2019-09-11 18:49:45 (GMT)
commitee536b2020b1f0baad1286dbd4345e13870324af (patch)
tree2486233603db05a76aaef863bd6639455e3dfef7 /Lib/test/test_tools
parent9936371af298d465095ae70bc9c2943b4b16eac4 (diff)
downloadcpython-ee536b2020b1f0baad1286dbd4345e13870324af.zip
cpython-ee536b2020b1f0baad1286dbd4345e13870324af.tar.gz
cpython-ee536b2020b1f0baad1286dbd4345e13870324af.tar.bz2
bpo-36876: Add a tool that identifies unsupported global C variables. (#15877)
Diffstat (limited to 'Lib/test/test_tools')
-rw-r--r--Lib/test/test_tools/__init__.py31
-rw-r--r--Lib/test/test_tools/test_c_analyzer/__init__.py15
-rw-r--r--Lib/test/test_tools/test_c_analyzer/__main__.py5
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/__init__.py0
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_files.py470
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_info.py194
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_known.py68
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_globals/__init__.py0
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_globals/test___main__.py296
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_globals/test_find.py332
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_globals/test_functional.py34
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_globals/test_show.py52
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_globals/test_supported.py96
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_parser/__init__.py0
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_parser/test_declarations.py795
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_parser/test_info.py208
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_parser/test_preprocessor.py1562
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_symbols/__init__.py0
-rw-r--r--Lib/test/test_tools/test_c_analyzer/test_c_symbols/test_info.py192
-rw-r--r--Lib/test/test_tools/test_c_analyzer/util.py60
20 files changed, 4401 insertions, 9 deletions
diff --git a/Lib/test/test_tools/__init__.py b/Lib/test/test_tools/__init__.py
index 4d0fca3..eb9acad 100644
--- a/Lib/test/test_tools/__init__.py
+++ b/Lib/test/test_tools/__init__.py
@@ -1,20 +1,33 @@
"""Support functions for testing scripts in the Tools directory."""
-import os
-import unittest
+import contextlib
import importlib
+import os.path
+import unittest
from test import support
-basepath = os.path.dirname( # <src/install dir>
- os.path.dirname( # Lib
- os.path.dirname( # test
- os.path.dirname(__file__)))) # test_tools
+basepath = os.path.normpath(
+ os.path.dirname( # <src/install dir>
+ os.path.dirname( # Lib
+ os.path.dirname( # test
+ os.path.dirname(__file__))))) # test_tools
toolsdir = os.path.join(basepath, 'Tools')
scriptsdir = os.path.join(toolsdir, 'scripts')
-def skip_if_missing():
- if not os.path.isdir(scriptsdir):
- raise unittest.SkipTest('scripts directory could not be found')
+def skip_if_missing(tool=None):
+ if tool:
+ tooldir = os.path.join(toolsdir, tool)
+ else:
+ tool = 'scripts'
+ tooldir = scriptsdir
+ if not os.path.isdir(tooldir):
+ raise unittest.SkipTest(f'{tool} directory could not be found')
+
+@contextlib.contextmanager
+def imports_under_tool(name, *subdirs):
+ tooldir = os.path.join(toolsdir, name, *subdirs)
+ with support.DirsOnSysPath(tooldir) as cm:
+ yield cm
def import_tool(toolname):
with support.DirsOnSysPath(scriptsdir):
diff --git a/Lib/test/test_tools/test_c_analyzer/__init__.py b/Lib/test/test_tools/test_c_analyzer/__init__.py
new file mode 100644
index 0000000..d0b4c04
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/__init__.py
@@ -0,0 +1,15 @@
+import contextlib
+import os.path
+import test.test_tools
+from test.support import load_package_tests
+
+
+@contextlib.contextmanager
+def tool_imports_for_tests():
+ test.test_tools.skip_if_missing('c-analyzer')
+ with test.test_tools.imports_under_tool('c-analyzer'):
+ yield
+
+
+def load_tests(*args):
+ return load_package_tests(os.path.dirname(__file__), *args)
diff --git a/Lib/test/test_tools/test_c_analyzer/__main__.py b/Lib/test/test_tools/test_c_analyzer/__main__.py
new file mode 100644
index 0000000..b5b017d
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/__main__.py
@@ -0,0 +1,5 @@
+from . import load_tests
+import unittest
+
+
+unittest.main()
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/__init__.py b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/__init__.py
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_files.py b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_files.py
new file mode 100644
index 0000000..6d14aea
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_files.py
@@ -0,0 +1,470 @@
+import os.path
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_analyzer_common.files import (
+ iter_files, _walk_tree, glob_tree,
+ )
+
+
+def fixpath(filename):
+ return filename.replace('/', os.path.sep)
+
+
+class IterFilesTests(unittest.TestCase):
+
+ maxDiff = None
+
+ _return_walk = None
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+ def set_files(self, *filesperroot):
+ roots = []
+ result = []
+ for root, files in filesperroot:
+ root = fixpath(root)
+ roots.append(root)
+ result.append([os.path.join(root, fixpath(f))
+ for f in files])
+ self._return_walk = result
+ return roots
+
+ def _walk(self, root, *, suffix=None, walk=None):
+ self.calls.append(('_walk', (root, suffix, walk)))
+ return iter(self._return_walk.pop(0))
+
+ def _glob(self, root, *, suffix=None):
+ self.calls.append(('_glob', (root, suffix)))
+ return iter(self._return_walk.pop(0))
+
+ def test_typical(self):
+ dirnames = self.set_files(
+ ('spam', ['file1.c', 'file2.c']),
+ ('eggs', ['ham/file3.h']),
+ )
+ suffixes = ('.c', '.h')
+
+ files = list(iter_files(dirnames, suffixes,
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file2.c'),
+ fixpath('eggs/ham/file3.h'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', None, _walk_tree)),
+ ('_walk', ('eggs', None, _walk_tree)),
+ ])
+
+ def test_single_root(self):
+ self._return_walk = [
+ [fixpath('spam/file1.c'), fixpath('spam/file2.c')],
+ ]
+
+ files = list(iter_files('spam', '.c',
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file2.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', '.c', _walk_tree)),
+ ])
+
+ def test_one_root(self):
+ self._return_walk = [
+ [fixpath('spam/file1.c'), fixpath('spam/file2.c')],
+ ]
+
+ files = list(iter_files(['spam'], '.c',
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file2.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', '.c', _walk_tree)),
+ ])
+
+ def test_multiple_roots(self):
+ dirnames = self.set_files(
+ ('spam', ['file1.c', 'file2.c']),
+ ('eggs', ['ham/file3.c']),
+ )
+
+ files = list(iter_files(dirnames, '.c',
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file2.c'),
+ fixpath('eggs/ham/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', '.c', _walk_tree)),
+ ('_walk', ('eggs', '.c', _walk_tree)),
+ ])
+
+ def test_no_roots(self):
+ files = list(iter_files([], '.c',
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [])
+ self.assertEqual(self.calls, [])
+
+ def test_single_suffix(self):
+ self._return_walk = [
+ [fixpath('spam/file1.c'),
+ fixpath('spam/eggs/file3.c'),
+ ],
+ ]
+
+ files = list(iter_files('spam', '.c',
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/eggs/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', '.c', _walk_tree)),
+ ])
+
+ def test_one_suffix(self):
+ self._return_walk = [
+ [fixpath('spam/file1.c'),
+ fixpath('spam/file1.h'),
+ fixpath('spam/file1.o'),
+ fixpath('spam/eggs/file3.c'),
+ ],
+ ]
+
+ files = list(iter_files('spam', ['.c'],
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/eggs/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', None, _walk_tree)),
+ ])
+
+ def test_multiple_suffixes(self):
+ self._return_walk = [
+ [fixpath('spam/file1.c'),
+ fixpath('spam/file1.h'),
+ fixpath('spam/file1.o'),
+ fixpath('spam/eggs/file3.c'),
+ ],
+ ]
+
+ files = list(iter_files('spam', ('.c', '.h'),
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file1.h'),
+ fixpath('spam/eggs/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', None, _walk_tree)),
+ ])
+
+ def test_no_suffix(self):
+ expected = [fixpath('spam/file1.c'),
+ fixpath('spam/file1.h'),
+ fixpath('spam/file1.o'),
+ fixpath('spam/eggs/file3.c'),
+ ]
+ for suffix in (None, '', ()):
+ with self.subTest(suffix):
+ self.calls.clear()
+ self._return_walk = [list(expected)]
+
+ files = list(iter_files('spam', suffix,
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, expected)
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', suffix, _walk_tree)),
+ ])
+
+ def test_relparent(self):
+ dirnames = self.set_files(
+ ('/x/y/z/spam', ['file1.c', 'file2.c']),
+ ('/x/y/z/eggs', ['ham/file3.c']),
+ )
+
+ files = list(iter_files(dirnames, '.c', fixpath('/x/y'),
+ _glob=self._glob,
+ _walk=self._walk))
+
+ self.assertEqual(files, [
+ fixpath('z/spam/file1.c'),
+ fixpath('z/spam/file2.c'),
+ fixpath('z/eggs/ham/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', (fixpath('/x/y/z/spam'), '.c', _walk_tree)),
+ ('_walk', (fixpath('/x/y/z/eggs'), '.c', _walk_tree)),
+ ])
+
+ def test_glob(self):
+ dirnames = self.set_files(
+ ('spam', ['file1.c', 'file2.c']),
+ ('eggs', ['ham/file3.c']),
+ )
+
+ files = list(iter_files(dirnames, '.c',
+ get_files=glob_tree,
+ _walk=self._walk,
+ _glob=self._glob))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file2.c'),
+ fixpath('eggs/ham/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_glob', ('spam', '.c')),
+ ('_glob', ('eggs', '.c')),
+ ])
+
+
+ def test_alt_walk_func(self):
+ dirnames = self.set_files(
+ ('spam', ['file1.c', 'file2.c']),
+ ('eggs', ['ham/file3.c']),
+ )
+ def get_files(root):
+ return None
+
+ files = list(iter_files(dirnames, '.c',
+ get_files=get_files,
+ _walk=self._walk,
+ _glob=self._glob))
+
+ self.assertEqual(files, [
+ fixpath('spam/file1.c'),
+ fixpath('spam/file2.c'),
+ fixpath('eggs/ham/file3.c'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_walk', ('spam', '.c', get_files)),
+ ('_walk', ('eggs', '.c', get_files)),
+ ])
+
+
+
+
+
+
+# def test_no_dirnames(self):
+# dirnames = []
+# filter_by_name = None
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [])
+# self.assertEqual(self.calls, [])
+#
+# def test_no_filter(self):
+# self._return_walk = [
+# [('spam', (), ('file1', 'file2.c', 'file3.h', 'file4.o')),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# ]
+# filter_by_name = None
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [
+# fixpath('spam/file1'),
+# fixpath('spam/file2.c'),
+# fixpath('spam/file3.h'),
+# fixpath('spam/file4.o'),
+# ])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ])
+#
+# def test_no_files(self):
+# self._return_walk = [
+# [('spam', (), ()),
+# ],
+# [(fixpath('eggs/ham'), (), ()),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# fixpath('eggs/ham'),
+# ]
+# filter_by_name = None
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ('_walk', (fixpath('eggs/ham'),)),
+# ])
+#
+# def test_tree(self):
+# self._return_walk = [
+# [('spam', ('sub1', 'sub2', 'sub3'), ('file1',)),
+# (fixpath('spam/sub1'), ('sub1sub1',), ('file2', 'file3')),
+# (fixpath('spam/sub1/sub1sub1'), (), ('file4',)),
+# (fixpath('spam/sub2'), (), ()),
+# (fixpath('spam/sub3'), (), ('file5',)),
+# ],
+# [(fixpath('eggs/ham'), (), ('file6',)),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# fixpath('eggs/ham'),
+# ]
+# filter_by_name = None
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [
+# fixpath('spam/file1'),
+# fixpath('spam/sub1/file2'),
+# fixpath('spam/sub1/file3'),
+# fixpath('spam/sub1/sub1sub1/file4'),
+# fixpath('spam/sub3/file5'),
+# fixpath('eggs/ham/file6'),
+# ])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ('_walk', (fixpath('eggs/ham'),)),
+# ])
+#
+# def test_filter_suffixes(self):
+# self._return_walk = [
+# [('spam', (), ('file1', 'file2.c', 'file3.h', 'file4.o')),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# ]
+# filter_by_name = ('.c', '.h')
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [
+# fixpath('spam/file2.c'),
+# fixpath('spam/file3.h'),
+# ])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ])
+#
+# def test_some_filtered(self):
+# self._return_walk = [
+# [('spam', (), ('file1', 'file2', 'file3', 'file4')),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# ]
+# def filter_by_name(filename, results=[False, True, False, True]):
+# self.calls.append(('filter_by_name', (filename,)))
+# return results.pop(0)
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [
+# fixpath('spam/file2'),
+# fixpath('spam/file4'),
+# ])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ('filter_by_name', ('file1',)),
+# ('filter_by_name', ('file2',)),
+# ('filter_by_name', ('file3',)),
+# ('filter_by_name', ('file4',)),
+# ])
+#
+# def test_none_filtered(self):
+# self._return_walk = [
+# [('spam', (), ('file1', 'file2', 'file3', 'file4')),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# ]
+# def filter_by_name(filename, results=[True, True, True, True]):
+# self.calls.append(('filter_by_name', (filename,)))
+# return results.pop(0)
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [
+# fixpath('spam/file1'),
+# fixpath('spam/file2'),
+# fixpath('spam/file3'),
+# fixpath('spam/file4'),
+# ])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ('filter_by_name', ('file1',)),
+# ('filter_by_name', ('file2',)),
+# ('filter_by_name', ('file3',)),
+# ('filter_by_name', ('file4',)),
+# ])
+#
+# def test_all_filtered(self):
+# self._return_walk = [
+# [('spam', (), ('file1', 'file2', 'file3', 'file4')),
+# ],
+# ]
+# dirnames = [
+# 'spam',
+# ]
+# def filter_by_name(filename, results=[False, False, False, False]):
+# self.calls.append(('filter_by_name', (filename,)))
+# return results.pop(0)
+#
+# files = list(iter_files(dirnames, filter_by_name,
+# _walk=self._walk))
+#
+# self.assertEqual(files, [])
+# self.assertEqual(self.calls, [
+# ('_walk', ('spam',)),
+# ('filter_by_name', ('file1',)),
+# ('filter_by_name', ('file2',)),
+# ('filter_by_name', ('file3',)),
+# ('filter_by_name', ('file4',)),
+# ])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_info.py b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_info.py
new file mode 100644
index 0000000..2d38671
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_info.py
@@ -0,0 +1,194 @@
+import string
+import unittest
+
+from ..util import PseudoStr, StrProxy, Object
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_analyzer_common.info import ID
+
+
+class IDTests(unittest.TestCase):
+
+ VALID_ARGS = (
+ 'x/y/z/spam.c',
+ 'func',
+ 'eggs',
+ )
+ VALID_KWARGS = dict(zip(ID._fields, VALID_ARGS))
+ VALID_EXPECTED = VALID_ARGS
+
+ def test_from_raw(self):
+ tests = [
+ ('', None),
+ (None, None),
+ ('spam', (None, None, 'spam')),
+ (('spam',), (None, None, 'spam')),
+ (('x/y/z/spam.c', 'spam'), ('x/y/z/spam.c', None, 'spam')),
+ (self.VALID_ARGS, self.VALID_EXPECTED),
+ (self.VALID_KWARGS, self.VALID_EXPECTED),
+ ]
+ for raw, expected in tests:
+ with self.subTest(raw):
+ id = ID.from_raw(raw)
+
+ self.assertEqual(id, expected)
+
+ def test_minimal(self):
+ id = ID(
+ filename=None,
+ funcname=None,
+ name='eggs',
+ )
+
+ self.assertEqual(id, (
+ None,
+ None,
+ 'eggs',
+ ))
+
+ def test_init_typical_global(self):
+ id = ID(
+ filename='x/y/z/spam.c',
+ funcname=None,
+ name='eggs',
+ )
+
+ self.assertEqual(id, (
+ 'x/y/z/spam.c',
+ None,
+ 'eggs',
+ ))
+
+ def test_init_typical_local(self):
+ id = ID(
+ filename='x/y/z/spam.c',
+ funcname='func',
+ name='eggs',
+ )
+
+ self.assertEqual(id, (
+ 'x/y/z/spam.c',
+ 'func',
+ 'eggs',
+ ))
+
+ def test_init_all_missing(self):
+ for value in ('', None):
+ with self.subTest(repr(value)):
+ id = ID(
+ filename=value,
+ funcname=value,
+ name=value,
+ )
+
+ self.assertEqual(id, (
+ None,
+ None,
+ None,
+ ))
+
+ def test_init_all_coerced(self):
+ tests = [
+ ('str subclass',
+ dict(
+ filename=PseudoStr('x/y/z/spam.c'),
+ funcname=PseudoStr('func'),
+ name=PseudoStr('eggs'),
+ ),
+ ('x/y/z/spam.c',
+ 'func',
+ 'eggs',
+ )),
+ ('non-str',
+ dict(
+ filename=StrProxy('x/y/z/spam.c'),
+ funcname=Object(),
+ name=('a', 'b', 'c'),
+ ),
+ ('x/y/z/spam.c',
+ '<object>',
+ "('a', 'b', 'c')",
+ )),
+ ]
+ for summary, kwargs, expected in tests:
+ with self.subTest(summary):
+ id = ID(**kwargs)
+
+ for field in ID._fields:
+ value = getattr(id, field)
+ self.assertIs(type(value), str)
+ self.assertEqual(tuple(id), expected)
+
+ def test_iterable(self):
+ id = ID(**self.VALID_KWARGS)
+
+ filename, funcname, name = id
+
+ values = (filename, funcname, name)
+ for value, expected in zip(values, self.VALID_EXPECTED):
+ self.assertEqual(value, expected)
+
+ def test_fields(self):
+ id = ID('a', 'b', 'z')
+
+ self.assertEqual(id.filename, 'a')
+ self.assertEqual(id.funcname, 'b')
+ self.assertEqual(id.name, 'z')
+
+ def test_validate_typical(self):
+ id = ID(
+ filename='x/y/z/spam.c',
+ funcname='func',
+ name='eggs',
+ )
+
+ id.validate() # This does not fail.
+
+ def test_validate_missing_field(self):
+ for field in ID._fields:
+ with self.subTest(field):
+ id = ID(**self.VALID_KWARGS)
+ id = id._replace(**{field: None})
+
+ if field == 'funcname':
+ id.validate() # The field can be missing (not set).
+ id = id._replace(filename=None)
+ id.validate() # Both fields can be missing (not set).
+ continue
+
+ with self.assertRaises(TypeError):
+ id.validate()
+
+ def test_validate_bad_field(self):
+ badch = tuple(c for c in string.punctuation + string.digits)
+ notnames = (
+ '1a',
+ 'a.b',
+ 'a-b',
+ '&a',
+ 'a++',
+ ) + badch
+ tests = [
+ ('filename', ()), # Any non-empty str is okay.
+ ('funcname', notnames),
+ ('name', notnames),
+ ]
+ seen = set()
+ for field, invalid in tests:
+ for value in invalid:
+ seen.add(value)
+ with self.subTest(f'{field}={value!r}'):
+ id = ID(**self.VALID_KWARGS)
+ id = id._replace(**{field: value})
+
+ with self.assertRaises(ValueError):
+ id.validate()
+
+ for field, invalid in tests:
+ valid = seen - set(invalid)
+ for value in valid:
+ with self.subTest(f'{field}={value!r}'):
+ id = ID(**self.VALID_KWARGS)
+ id = id._replace(**{field: value})
+
+ id.validate() # This does not fail.
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_known.py b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_known.py
new file mode 100644
index 0000000..215023d
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_analyzer_common/test_known.py
@@ -0,0 +1,68 @@
+import re
+import textwrap
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_parser.info import Variable
+ from c_analyzer_common.info import ID
+ from c_analyzer_common.known import from_file
+
+
+class FromFileTests(unittest.TestCase):
+
+ maxDiff = None
+
+ _return_read_tsv = ()
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+ def _read_tsv(self, *args):
+ self.calls.append(('_read_tsv', args))
+ return self._return_read_tsv
+
+ def test_typical(self):
+ lines = textwrap.dedent('''
+ filename funcname name kind declaration
+ file1.c - var1 variable static int
+ file1.c func1 local1 variable static int
+ file1.c - var2 variable int
+ file1.c func2 local2 variable char *
+ file2.c - var1 variable char *
+ ''').strip().splitlines()
+ lines = [re.sub(r'\s+', '\t', line, 4) for line in lines]
+ self._return_read_tsv = [tuple(v.strip() for v in line.split('\t'))
+ for line in lines[1:]]
+
+ known = from_file('spam.c', _read_tsv=self._read_tsv)
+
+ self.assertEqual(known, {
+ 'variables': {v.id: v for v in [
+ Variable.from_parts('file1.c', '', 'var1', 'static int'),
+ Variable.from_parts('file1.c', 'func1', 'local1', 'static int'),
+ Variable.from_parts('file1.c', '', 'var2', 'int'),
+ Variable.from_parts('file1.c', 'func2', 'local2', 'char *'),
+ Variable.from_parts('file2.c', '', 'var1', 'char *'),
+ ]},
+ })
+ self.assertEqual(self.calls, [
+ ('_read_tsv', ('spam.c', 'filename\tfuncname\tname\tkind\tdeclaration')),
+ ])
+
+ def test_empty(self):
+ self._return_read_tsv = []
+
+ known = from_file('spam.c', _read_tsv=self._read_tsv)
+
+ self.assertEqual(known, {
+ 'variables': {},
+ })
+ self.assertEqual(self.calls, [
+ ('_read_tsv', ('spam.c', 'filename\tfuncname\tname\tkind\tdeclaration')),
+ ])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_globals/__init__.py b/Lib/test/test_tools/test_c_analyzer/test_c_globals/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_globals/__init__.py
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_globals/test___main__.py b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test___main__.py
new file mode 100644
index 0000000..5f52c58
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test___main__.py
@@ -0,0 +1,296 @@
+import sys
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_analyzer_common import SOURCE_DIRS
+ from c_analyzer_common.known import DATA_FILE as KNOWN_FILE
+ from c_parser import info
+ import c_globals as cg
+ from c_globals.supported import IGNORED_FILE
+ from c_globals.__main__ import cmd_check, cmd_show, parse_args, main
+
+
+TYPICAL = [
+ (info.Variable.from_parts('src1/spam.c', None, 'var1', 'const char *'),
+ True,
+ ),
+ (info.Variable.from_parts('src1/spam.c', 'ham', 'initialized', 'int'),
+ True,
+ ),
+ (info.Variable.from_parts('src1/spam.c', None, 'var2', 'PyObject *'),
+ False,
+ ),
+ (info.Variable.from_parts('src1/eggs.c', 'tofu', 'ready', 'int'),
+ True,
+ ),
+ (info.Variable.from_parts('src1/spam.c', None, 'freelist', '(PyTupleObject *)[10]'),
+ False,
+ ),
+ (info.Variable.from_parts('src1/sub/ham.c', None, 'var1', 'const char const *'),
+ True,
+ ),
+ (info.Variable.from_parts('src2/jam.c', None, 'var1', 'int'),
+ True,
+ ),
+ (info.Variable.from_parts('src2/jam.c', None, 'var2', 'MyObject *'),
+ False,
+ ),
+ (info.Variable.from_parts('Include/spam.h', None, 'data', 'const int'),
+ True,
+ ),
+ ]
+
+
+class CMDBase(unittest.TestCase):
+
+ maxDiff = None
+
+ _return_find = ()
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+ def _find(self, *args):
+ self.calls.append(('_find', args))
+ return self._return_find
+
+ def _show(self, *args):
+ self.calls.append(('_show', args))
+
+ def _print(self, *args):
+ self.calls.append(('_print', args))
+
+
+class CheckTests(CMDBase):
+
+ def test_defaults(self):
+ self._return_find = []
+
+ cmd_check('check',
+ _find=self._find,
+ _show=self._show,
+ _print=self._print,
+ )
+
+ self.assertEqual(self.calls[0], (
+ '_find', (
+ SOURCE_DIRS,
+ KNOWN_FILE,
+ IGNORED_FILE,
+ ),
+ ))
+
+ def test_all_supported(self):
+ self._return_find = [(v, s) for v, s in TYPICAL if s]
+ dirs = ['src1', 'src2', 'Include']
+
+ cmd_check('check',
+ dirs,
+ ignored='ignored.tsv',
+ known='known.tsv',
+ _find=self._find,
+ _show=self._show,
+ _print=self._print,
+ )
+
+ self.assertEqual(self.calls, [
+ ('_find', (dirs, 'known.tsv', 'ignored.tsv')),
+ #('_print', ('okay',)),
+ ])
+
+ def test_some_unsupported(self):
+ self._return_find = TYPICAL
+ dirs = ['src1', 'src2', 'Include']
+
+ with self.assertRaises(SystemExit) as cm:
+ cmd_check('check',
+ dirs,
+ ignored='ignored.tsv',
+ known='known.tsv',
+ _find=self._find,
+ _show=self._show,
+ _print=self._print,
+ )
+
+ unsupported = [v for v, s in TYPICAL if not s]
+ self.assertEqual(self.calls, [
+ ('_find', (dirs, 'known.tsv', 'ignored.tsv')),
+ ('_print', ('ERROR: found unsupported global variables',)),
+ ('_print', ()),
+ ('_show', (sorted(unsupported),)),
+ ('_print', (' (3 total)',)),
+ ])
+ self.assertEqual(cm.exception.code, 1)
+
+
+class ShowTests(CMDBase):
+
+ def test_defaults(self):
+ self._return_find = []
+
+ cmd_show('show',
+ _find=self._find,
+ _show=self._show,
+ _print=self._print,
+ )
+
+ self.assertEqual(self.calls[0], (
+ '_find', (
+ SOURCE_DIRS,
+ KNOWN_FILE,
+ IGNORED_FILE,
+ ),
+ ))
+
+ def test_typical(self):
+ self._return_find = TYPICAL
+ dirs = ['src1', 'src2', 'Include']
+
+ cmd_show('show',
+ dirs,
+ known='known.tsv',
+ ignored='ignored.tsv',
+ _find=self._find,
+ _show=self._show,
+ _print=self._print,
+ )
+
+ supported = [v for v, s in TYPICAL if s]
+ unsupported = [v for v, s in TYPICAL if not s]
+ self.assertEqual(self.calls, [
+ ('_find', (dirs, 'known.tsv', 'ignored.tsv')),
+ ('_print', ('supported:',)),
+ ('_print', ('----------',)),
+ ('_show', (sorted(supported),)),
+ ('_print', (' (6 total)',)),
+ ('_print', ()),
+ ('_print', ('unsupported:',)),
+ ('_print', ('------------',)),
+ ('_show', (sorted(unsupported),)),
+ ('_print', (' (3 total)',)),
+ ])
+
+
+class ParseArgsTests(unittest.TestCase):
+
+ maxDiff = None
+
+ def test_no_args(self):
+ self.errmsg = None
+ def fail(msg):
+ self.errmsg = msg
+ sys.exit(msg)
+
+ with self.assertRaises(SystemExit):
+ parse_args('cg', [], _fail=fail)
+
+ self.assertEqual(self.errmsg, 'missing command')
+
+ def test_check_no_args(self):
+ cmd, cmdkwargs = parse_args('cg', [
+ 'check',
+ ])
+
+ self.assertEqual(cmd, 'check')
+ self.assertEqual(cmdkwargs, {
+ 'ignored': IGNORED_FILE,
+ 'known': KNOWN_FILE,
+ 'dirs': SOURCE_DIRS,
+ })
+
+ def test_check_full_args(self):
+ cmd, cmdkwargs = parse_args('cg', [
+ 'check',
+ '--ignored', 'spam.tsv',
+ '--known', 'eggs.tsv',
+ 'dir1',
+ 'dir2',
+ 'dir3',
+ ])
+
+ self.assertEqual(cmd, 'check')
+ self.assertEqual(cmdkwargs, {
+ 'ignored': 'spam.tsv',
+ 'known': 'eggs.tsv',
+ 'dirs': ['dir1', 'dir2', 'dir3']
+ })
+
+ def test_show_no_args(self):
+ cmd, cmdkwargs = parse_args('cg', [
+ 'show',
+ ])
+
+ self.assertEqual(cmd, 'show')
+ self.assertEqual(cmdkwargs, {
+ 'ignored': IGNORED_FILE,
+ 'known': KNOWN_FILE,
+ 'dirs': SOURCE_DIRS,
+ 'skip_objects': False,
+ })
+
+ def test_show_full_args(self):
+ cmd, cmdkwargs = parse_args('cg', [
+ 'show',
+ '--ignored', 'spam.tsv',
+ '--known', 'eggs.tsv',
+ 'dir1',
+ 'dir2',
+ 'dir3',
+ ])
+
+ self.assertEqual(cmd, 'show')
+ self.assertEqual(cmdkwargs, {
+ 'ignored': 'spam.tsv',
+ 'known': 'eggs.tsv',
+ 'dirs': ['dir1', 'dir2', 'dir3'],
+ 'skip_objects': False,
+ })
+
+
+def new_stub_commands(*names):
+ calls = []
+ def cmdfunc(cmd, **kwargs):
+ calls.append((cmd, kwargs))
+ commands = {name: cmdfunc for name in names}
+ return commands, calls
+
+
+class MainTests(unittest.TestCase):
+
+ def test_no_command(self):
+ with self.assertRaises(ValueError):
+ main(None, {})
+
+ def test_check(self):
+ commands, calls = new_stub_commands('check', 'show')
+
+ cmdkwargs = {
+ 'ignored': 'spam.tsv',
+ 'known': 'eggs.tsv',
+ 'dirs': ['dir1', 'dir2', 'dir3'],
+ }
+ main('check', cmdkwargs, _COMMANDS=commands)
+
+ self.assertEqual(calls, [
+ ('check', cmdkwargs),
+ ])
+
+ def test_show(self):
+ commands, calls = new_stub_commands('check', 'show')
+
+ cmdkwargs = {
+ 'ignored': 'spam.tsv',
+ 'known': 'eggs.tsv',
+ 'dirs': ['dir1', 'dir2', 'dir3'],
+ }
+ main('show', cmdkwargs, _COMMANDS=commands)
+
+ self.assertEqual(calls, [
+ ('show', cmdkwargs),
+ ])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_find.py b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_find.py
new file mode 100644
index 0000000..b29f966
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_find.py
@@ -0,0 +1,332 @@
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_parser import info
+ from c_globals.find import globals_from_binary, globals
+
+
+class _Base(unittest.TestCase):
+
+ maxDiff = None
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+
+class StaticsFromBinaryTests(_Base):
+
+ _return_iter_symbols = ()
+ _return_resolve_symbols = ()
+ _return_get_symbol_resolver = None
+
+ def setUp(self):
+ super().setUp()
+
+ self.kwargs = dict(
+ _iter_symbols=self._iter_symbols,
+ _resolve=self._resolve_symbols,
+ _get_symbol_resolver=self._get_symbol_resolver,
+ )
+
+ def _iter_symbols(self, binfile, find_local_symbol):
+ self.calls.append(('_iter_symbols', (binfile, find_local_symbol)))
+ return self._return_iter_symbols
+
+ def _resolve_symbols(self, symbols, resolve):
+ self.calls.append(('_resolve_symbols', (symbols, resolve,)))
+ return self._return_resolve_symbols
+
+ def _get_symbol_resolver(self, knownvars, dirnames=None):
+ self.calls.append(('_get_symbol_resolver', (knownvars, dirnames)))
+ return self._return_get_symbol_resolver
+
+ def test_typical(self):
+ symbols = self._return_iter_symbols = ()
+ resolver = self._return_get_symbol_resolver = object()
+ variables = self._return_resolve_symbols = [
+ info.Variable.from_parts('dir1/spam.c', None, 'var1', 'int'),
+ info.Variable.from_parts('dir1/spam.c', None, 'var2', 'static int'),
+ info.Variable.from_parts('dir1/spam.c', None, 'var3', 'char *'),
+ info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', 'const char *'),
+ info.Variable.from_parts('dir1/eggs.c', None, 'var1', 'static int'),
+ info.Variable.from_parts('dir1/eggs.c', 'func1', 'var2', 'static char *'),
+ ]
+ knownvars = object()
+
+ found = list(globals_from_binary('python',
+ knownvars=knownvars,
+ **self.kwargs))
+
+ self.assertEqual(found, [
+ info.Variable.from_parts('dir1/spam.c', None, 'var2', 'static int'),
+ info.Variable.from_parts('dir1/eggs.c', None, 'var1', 'static int'),
+ info.Variable.from_parts('dir1/eggs.c', 'func1', 'var2', 'static char *'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_iter_symbols', ('python', None)),
+ ('_get_symbol_resolver', (knownvars, None)),
+ ('_resolve_symbols', (symbols, resolver)),
+ ])
+
+# self._return_iter_symbols = [
+# s_info.Symbol(('dir1/spam.c', None, 'var1'), 'variable', False),
+# s_info.Symbol(('dir1/spam.c', None, 'var2'), 'variable', False),
+# s_info.Symbol(('dir1/spam.c', None, 'func1'), 'function', False),
+# s_info.Symbol(('dir1/spam.c', None, 'func2'), 'function', True),
+# s_info.Symbol(('dir1/spam.c', None, 'var3'), 'variable', False),
+# s_info.Symbol(('dir1/spam.c', 'func2', 'var4'), 'variable', False),
+# s_info.Symbol(('dir1/ham.c', None, 'var1'), 'variable', True),
+# s_info.Symbol(('dir1/eggs.c', None, 'var1'), 'variable', False),
+# s_info.Symbol(('dir1/eggs.c', None, 'xyz'), 'other', False),
+# s_info.Symbol(('dir1/eggs.c', '???', 'var2'), 'variable', False),
+# s_info.Symbol(('???', None, 'var_x'), 'variable', False),
+# s_info.Symbol(('???', '???', 'var_y'), 'variable', False),
+# s_info.Symbol((None, None, '???'), 'other', False),
+# ]
+# known = object()
+#
+# globals_from_binary('python', knownvars=known, **this.kwargs)
+# found = list(globals_from_symbols(['dir1'], self.iter_symbols))
+#
+# self.assertEqual(found, [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ])
+# self.assertEqual(self.calls, [
+# ('iter_symbols', (['dir1'],)),
+# ])
+#
+# def test_no_symbols(self):
+# self._return_iter_symbols = []
+#
+# found = list(globals_from_symbols(['dir1'], self.iter_symbols))
+#
+# self.assertEqual(found, [])
+# self.assertEqual(self.calls, [
+# ('iter_symbols', (['dir1'],)),
+# ])
+
+ # XXX need functional test
+
+
+#class StaticFromDeclarationsTests(_Base):
+#
+# _return_iter_declarations = ()
+#
+# def iter_declarations(self, dirnames):
+# self.calls.append(('iter_declarations', (dirnames,)))
+# return iter(self._return_iter_declarations)
+#
+# def test_typical(self):
+# self._return_iter_declarations = [
+# None,
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# object(),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# object(),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# object(),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# object(),
+# ]
+#
+# found = list(globals_from_declarations(['dir1'], self.iter_declarations))
+#
+# self.assertEqual(found, [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ])
+# self.assertEqual(self.calls, [
+# ('iter_declarations', (['dir1'],)),
+# ])
+#
+# def test_no_declarations(self):
+# self._return_iter_declarations = []
+#
+# found = list(globals_from_declarations(['dir1'], self.iter_declarations))
+#
+# self.assertEqual(found, [])
+# self.assertEqual(self.calls, [
+# ('iter_declarations', (['dir1'],)),
+# ])
+
+
+#class IterVariablesTests(_Base):
+#
+# _return_from_symbols = ()
+# _return_from_declarations = ()
+#
+# def _from_symbols(self, dirnames, iter_symbols):
+# self.calls.append(('_from_symbols', (dirnames, iter_symbols)))
+# return iter(self._return_from_symbols)
+#
+# def _from_declarations(self, dirnames, iter_declarations):
+# self.calls.append(('_from_declarations', (dirnames, iter_declarations)))
+# return iter(self._return_from_declarations)
+#
+# def test_typical(self):
+# expected = [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ]
+# self._return_from_symbols = expected
+#
+# found = list(iter_variables(['dir1'],
+# _from_symbols=self._from_symbols,
+# _from_declarations=self._from_declarations))
+#
+# self.assertEqual(found, expected)
+# self.assertEqual(self.calls, [
+# ('_from_symbols', (['dir1'], b_symbols.iter_symbols)),
+# ])
+#
+# def test_no_symbols(self):
+# self._return_from_symbols = []
+#
+# found = list(iter_variables(['dir1'],
+# _from_symbols=self._from_symbols,
+# _from_declarations=self._from_declarations))
+#
+# self.assertEqual(found, [])
+# self.assertEqual(self.calls, [
+# ('_from_symbols', (['dir1'], b_symbols.iter_symbols)),
+# ])
+#
+# def test_from_binary(self):
+# expected = [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ]
+# self._return_from_symbols = expected
+#
+# found = list(iter_variables(['dir1'], 'platform',
+# _from_symbols=self._from_symbols,
+# _from_declarations=self._from_declarations))
+#
+# self.assertEqual(found, expected)
+# self.assertEqual(self.calls, [
+# ('_from_symbols', (['dir1'], b_symbols.iter_symbols)),
+# ])
+#
+# def test_from_symbols(self):
+# expected = [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ]
+# self._return_from_symbols = expected
+#
+# found = list(iter_variables(['dir1'], 'symbols',
+# _from_symbols=self._from_symbols,
+# _from_declarations=self._from_declarations))
+#
+# self.assertEqual(found, expected)
+# self.assertEqual(self.calls, [
+# ('_from_symbols', (['dir1'], s_symbols.iter_symbols)),
+# ])
+#
+# def test_from_declarations(self):
+# expected = [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ]
+# self._return_from_declarations = expected
+#
+# found = list(iter_variables(['dir1'], 'declarations',
+# _from_symbols=self._from_symbols,
+# _from_declarations=self._from_declarations))
+#
+# self.assertEqual(found, expected)
+# self.assertEqual(self.calls, [
+# ('_from_declarations', (['dir1'], declarations.iter_all)),
+# ])
+#
+# def test_from_preprocessed(self):
+# expected = [
+# info.Variable.from_parts('dir1/spam.c', None, 'var1', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var2', '???'),
+# info.Variable.from_parts('dir1/spam.c', None, 'var3', '???'),
+# info.Variable.from_parts('dir1/spam.c', 'func2', 'var4', '???'),
+# info.Variable.from_parts('dir1/eggs.c', None, 'var1', '???'),
+# ]
+# self._return_from_declarations = expected
+#
+# found = list(iter_variables(['dir1'], 'preprocessed',
+# _from_symbols=self._from_symbols,
+# _from_declarations=self._from_declarations))
+#
+# self.assertEqual(found, expected)
+# self.assertEqual(self.calls, [
+# ('_from_declarations', (['dir1'], declarations.iter_preprocessed)),
+# ])
+
+
+class StaticsTest(_Base):
+
+ _return_iter_variables = None
+
+ def _iter_variables(self, kind, *, known, dirnames):
+ self.calls.append(
+ ('_iter_variables', (kind, known, dirnames)))
+ return iter(self._return_iter_variables or ())
+
+ def test_typical(self):
+ self._return_iter_variables = [
+ info.Variable.from_parts('src1/spam.c', None, 'var1', 'static const char *'),
+ info.Variable.from_parts('src1/spam.c', None, 'var1b', 'const char *'),
+ info.Variable.from_parts('src1/spam.c', 'ham', 'initialized', 'static int'),
+ info.Variable.from_parts('src1/spam.c', 'ham', 'result', 'int'),
+ info.Variable.from_parts('src1/spam.c', None, 'var2', 'static PyObject *'),
+ info.Variable.from_parts('src1/eggs.c', 'tofu', 'ready', 'static int'),
+ info.Variable.from_parts('src1/spam.c', None, 'freelist', 'static (PyTupleObject *)[10]'),
+ info.Variable.from_parts('src1/sub/ham.c', None, 'var1', 'static const char const *'),
+ info.Variable.from_parts('src2/jam.c', None, 'var1', 'static int'),
+ info.Variable.from_parts('src2/jam.c', None, 'var2', 'static MyObject *'),
+ info.Variable.from_parts('Include/spam.h', None, 'data', 'static const int'),
+ ]
+ dirnames = object()
+ known = object()
+
+ found = list(globals(dirnames, known,
+ kind='platform',
+ _iter_variables=self._iter_variables,
+ ))
+
+ self.assertEqual(found, [
+ info.Variable.from_parts('src1/spam.c', None, 'var1', 'static const char *'),
+ info.Variable.from_parts('src1/spam.c', 'ham', 'initialized', 'static int'),
+ info.Variable.from_parts('src1/spam.c', None, 'var2', 'static PyObject *'),
+ info.Variable.from_parts('src1/eggs.c', 'tofu', 'ready', 'static int'),
+ info.Variable.from_parts('src1/spam.c', None, 'freelist', 'static (PyTupleObject *)[10]'),
+ info.Variable.from_parts('src1/sub/ham.c', None, 'var1', 'static const char const *'),
+ info.Variable.from_parts('src2/jam.c', None, 'var1', 'static int'),
+ info.Variable.from_parts('src2/jam.c', None, 'var2', 'static MyObject *'),
+ info.Variable.from_parts('Include/spam.h', None, 'data', 'static const int'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_iter_variables', ('platform', known, dirnames)),
+ ])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_functional.py b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_functional.py
new file mode 100644
index 0000000..9279790
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_functional.py
@@ -0,0 +1,34 @@
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ pass
+
+
+class SelfCheckTests(unittest.TestCase):
+
+ @unittest.expectedFailure
+ def test_known(self):
+ # Make sure known macros & vartypes aren't hiding unknown local types.
+ # XXX finish!
+ raise NotImplementedError
+
+ @unittest.expectedFailure
+ def test_compare_nm_results(self):
+ # Make sure the "show" results match the statics found by "nm" command.
+ # XXX Skip if "nm" is not available.
+ # XXX finish!
+ raise NotImplementedError
+
+
+class DummySourceTests(unittest.TestCase):
+
+ @unittest.expectedFailure
+ def test_check(self):
+ # XXX finish!
+ raise NotImplementedError
+
+ @unittest.expectedFailure
+ def test_show(self):
+ # XXX finish!
+ raise NotImplementedError
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_show.py b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_show.py
new file mode 100644
index 0000000..ce1dad8
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_show.py
@@ -0,0 +1,52 @@
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_parser import info
+ from c_globals.show import basic
+
+
+TYPICAL = [
+ info.Variable.from_parts('src1/spam.c', None, 'var1', 'static const char *'),
+ info.Variable.from_parts('src1/spam.c', 'ham', 'initialized', 'static int'),
+ info.Variable.from_parts('src1/spam.c', None, 'var2', 'static PyObject *'),
+ info.Variable.from_parts('src1/eggs.c', 'tofu', 'ready', 'static int'),
+ info.Variable.from_parts('src1/spam.c', None, 'freelist', 'static (PyTupleObject *)[10]'),
+ info.Variable.from_parts('src1/sub/ham.c', None, 'var1', 'static const char const *'),
+ info.Variable.from_parts('src2/jam.c', None, 'var1', 'static int'),
+ info.Variable.from_parts('src2/jam.c', None, 'var2', 'static MyObject *'),
+ info.Variable.from_parts('Include/spam.h', None, 'data', 'static const int'),
+ ]
+
+
+class BasicTests(unittest.TestCase):
+
+ maxDiff = None
+
+ def setUp(self):
+ self.lines = []
+
+ def print(self, line):
+ self.lines.append(line)
+
+ def test_typical(self):
+ basic(TYPICAL,
+ _print=self.print)
+
+ self.assertEqual(self.lines, [
+ 'src1/spam.c:var1 static const char *',
+ 'src1/spam.c:ham():initialized static int',
+ 'src1/spam.c:var2 static PyObject *',
+ 'src1/eggs.c:tofu():ready static int',
+ 'src1/spam.c:freelist static (PyTupleObject *)[10]',
+ 'src1/sub/ham.c:var1 static const char const *',
+ 'src2/jam.c:var1 static int',
+ 'src2/jam.c:var2 static MyObject *',
+ 'Include/spam.h:data static const int',
+ ])
+
+ def test_no_rows(self):
+ basic([],
+ _print=self.print)
+
+ self.assertEqual(self.lines, [])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_supported.py b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_supported.py
new file mode 100644
index 0000000..1e7d40e
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_globals/test_supported.py
@@ -0,0 +1,96 @@
+import re
+import textwrap
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_analyzer_common.info import ID
+ from c_parser import info
+ from c_globals.supported import is_supported, ignored_from_file
+
+
+class IsSupportedTests(unittest.TestCase):
+
+ @unittest.expectedFailure
+ def test_supported(self):
+ statics = [
+ info.StaticVar('src1/spam.c', None, 'var1', 'const char *'),
+ info.StaticVar('src1/spam.c', None, 'var1', 'int'),
+ ]
+ for static in statics:
+ with self.subTest(static):
+ result = is_supported(static)
+
+ self.assertTrue(result)
+
+ @unittest.expectedFailure
+ def test_not_supported(self):
+ statics = [
+ info.StaticVar('src1/spam.c', None, 'var1', 'PyObject *'),
+ info.StaticVar('src1/spam.c', None, 'var1', 'PyObject[10]'),
+ ]
+ for static in statics:
+ with self.subTest(static):
+ result = is_supported(static)
+
+ self.assertFalse(result)
+
+
+class IgnoredFromFileTests(unittest.TestCase):
+
+ maxDiff = None
+
+ _return_read_tsv = ()
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+ def _read_tsv(self, *args):
+ self.calls.append(('_read_tsv', args))
+ return self._return_read_tsv
+
+ def test_typical(self):
+ lines = textwrap.dedent('''
+ filename funcname name kind reason
+ file1.c - var1 variable ...
+ file1.c func1 local1 variable |
+ file1.c - var2 variable ???
+ file1.c func2 local2 variable |
+ file2.c - var1 variable reasons
+ ''').strip().splitlines()
+ lines = [re.sub(r'\s{1,8}', '\t', line, 4).replace('|', '')
+ for line in lines]
+ self._return_read_tsv = [tuple(v.strip() for v in line.split('\t'))
+ for line in lines[1:]]
+
+ ignored = ignored_from_file('spam.c', _read_tsv=self._read_tsv)
+
+ self.assertEqual(ignored, {
+ 'variables': {
+ ID('file1.c', '', 'var1'): '...',
+ ID('file1.c', 'func1', 'local1'): '',
+ ID('file1.c', '', 'var2'): '???',
+ ID('file1.c', 'func2', 'local2'): '',
+ ID('file2.c', '', 'var1'): 'reasons',
+ },
+ })
+ self.assertEqual(self.calls, [
+ ('_read_tsv', ('spam.c', 'filename\tfuncname\tname\tkind\treason')),
+ ])
+
+ def test_empty(self):
+ self._return_read_tsv = []
+
+ ignored = ignored_from_file('spam.c', _read_tsv=self._read_tsv)
+
+ self.assertEqual(ignored, {
+ 'variables': {},
+ })
+ self.assertEqual(self.calls, [
+ ('_read_tsv', ('spam.c', 'filename\tfuncname\tname\tkind\treason')),
+ ])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_parser/__init__.py b/Lib/test/test_tools/test_c_analyzer/test_c_parser/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_parser/__init__.py
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_declarations.py b/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_declarations.py
new file mode 100644
index 0000000..b68744e
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_declarations.py
@@ -0,0 +1,795 @@
+import textwrap
+import unittest
+
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_parser.declarations import (
+ iter_global_declarations, iter_local_statements,
+ parse_func, parse_var, parse_compound,
+ iter_variables,
+ )
+
+
+class TestCaseBase(unittest.TestCase):
+
+ maxDiff = None
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+
+class IterGlobalDeclarationsTests(TestCaseBase):
+
+ def test_functions(self):
+ tests = [
+ (textwrap.dedent('''
+ void func1() {
+ return;
+ }
+ '''),
+ textwrap.dedent('''
+ void func1() {
+ return;
+ }
+ ''').strip(),
+ ),
+ (textwrap.dedent('''
+ static unsigned int * _func1(
+ const char *arg1,
+ int *arg2
+ long long arg3
+ )
+ {
+ return _do_something(arg1, arg2, arg3);
+ }
+ '''),
+ textwrap.dedent('''
+ static unsigned int * _func1( const char *arg1, int *arg2 long long arg3 ) {
+ return _do_something(arg1, arg2, arg3);
+ }
+ ''').strip(),
+ ),
+ (textwrap.dedent('''
+ static PyObject *
+ _func1(const char *arg1, PyObject *arg2)
+ {
+ static int initialized = 0;
+ if (!initialized) {
+ initialized = 1;
+ _init(arg1);
+ }
+
+ PyObject *result = _do_something(arg1, arg2);
+ Py_INCREF(result);
+ return result;
+ }
+ '''),
+ textwrap.dedent('''
+ static PyObject * _func1(const char *arg1, PyObject *arg2) {
+ static int initialized = 0;
+ if (!initialized) {
+ initialized = 1;
+ _init(arg1);
+ }
+ PyObject *result = _do_something(arg1, arg2);
+ Py_INCREF(result);
+ return result;
+ }
+ ''').strip(),
+ ),
+ ]
+ for lines, expected in tests:
+ body = textwrap.dedent(
+ expected.partition('{')[2].rpartition('}')[0]
+ ).strip()
+ expected = (expected, body)
+ with self.subTest(lines):
+ lines = lines.splitlines()
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, [expected])
+
+ @unittest.expectedFailure
+ def test_declarations(self):
+ tests = [
+ 'int spam;',
+ 'long long spam;',
+ 'static const int const *spam;',
+ 'int spam;',
+ 'typedef int myint;',
+ 'typedef PyObject * (*unaryfunc)(PyObject *);',
+ # typedef struct
+ # inline struct
+ # enum
+ # inline enum
+ ]
+ for text in tests:
+ expected = (text,
+ ' '.join(l.strip() for l in text.splitlines()))
+ with self.subTest(lines):
+ lines = lines.splitlines()
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, [expected])
+
+ @unittest.expectedFailure
+ def test_declaration_multiple_vars(self):
+ lines = ['static const int const *spam, *ham=NULL, eggs = 3;']
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, [
+ ('static const int const *spam;', None),
+ ('static const int *ham=NULL;', None),
+ ('static const int eggs = 3;', None),
+ ])
+
+ def test_mixed(self):
+ lines = textwrap.dedent('''
+ int spam;
+ static const char const *eggs;
+
+ PyObject * start(void) {
+ static int initialized = 0;
+ if (initialized) {
+ initialized = 1;
+ init();
+ }
+ return _start();
+ }
+
+ char* ham;
+
+ static int stop(char *reason) {
+ ham = reason;
+ return _stop();
+ }
+ ''').splitlines()
+ expected = [
+ (textwrap.dedent('''
+ PyObject * start(void) {
+ static int initialized = 0;
+ if (initialized) {
+ initialized = 1;
+ init();
+ }
+ return _start();
+ }
+ ''').strip(),
+ textwrap.dedent('''
+ static int initialized = 0;
+ if (initialized) {
+ initialized = 1;
+ init();
+ }
+ return _start();
+ ''').strip(),
+ ),
+ (textwrap.dedent('''
+ static int stop(char *reason) {
+ ham = reason;
+ return _stop();
+ }
+ ''').strip(),
+ textwrap.dedent('''
+ ham = reason;
+ return _stop();
+ ''').strip(),
+ ),
+ ]
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, expected)
+ #self.assertEqual([stmt for stmt, _ in stmts],
+ # [stmt for stmt, _ in expected])
+ #self.assertEqual([body for _, body in stmts],
+ # [body for _, body in expected])
+
+ def test_no_statements(self):
+ lines = []
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, [])
+
+ def test_bogus(self):
+ tests = [
+ (textwrap.dedent('''
+ int spam;
+ static const char const *eggs;
+
+ PyObject * start(void) {
+ static int initialized = 0;
+ if (initialized) {
+ initialized = 1;
+ init();
+ }
+ return _start();
+ }
+
+ char* ham;
+
+ static int _stop(void) {
+ // missing closing bracket
+
+ static int stop(char *reason) {
+ ham = reason;
+ return _stop();
+ }
+ '''),
+ [(textwrap.dedent('''
+ PyObject * start(void) {
+ static int initialized = 0;
+ if (initialized) {
+ initialized = 1;
+ init();
+ }
+ return _start();
+ }
+ ''').strip(),
+ textwrap.dedent('''
+ static int initialized = 0;
+ if (initialized) {
+ initialized = 1;
+ init();
+ }
+ return _start();
+ ''').strip(),
+ ),
+ # Neither "stop()" nor "_stop()" are here.
+ ],
+ ),
+ ]
+ for lines, expected in tests:
+ with self.subTest(lines):
+ lines = lines.splitlines()
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, expected)
+ #self.assertEqual([stmt for stmt, _ in stmts],
+ # [stmt for stmt, _ in expected])
+ #self.assertEqual([body for _, body in stmts],
+ # [body for _, body in expected])
+
+ def test_ignore_comments(self):
+ tests = [
+ ('// msg', None),
+ ('// int stmt;', None),
+ (' // ... ', None),
+ ('// /*', None),
+ ('/* int stmt; */', None),
+ ("""
+ /**
+ * ...
+ * int stmt;
+ */
+ """, None),
+ ]
+ for lines, expected in tests:
+ with self.subTest(lines):
+ lines = lines.splitlines()
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, [expected] if expected else [])
+
+
+class IterLocalStatementsTests(TestCaseBase):
+
+ def test_vars(self):
+ tests = [
+ # POTS
+ 'int spam;',
+ 'unsigned int spam;',
+ 'char spam;',
+ 'float spam;',
+
+ # typedefs
+ 'uint spam;',
+ 'MyType spam;',
+
+ # complex
+ 'struct myspam spam;',
+ 'union choice spam;',
+ # inline struct
+ # inline union
+ # enum?
+ ]
+ # pointers
+ tests.extend([
+ # POTS
+ 'int * spam;',
+ 'unsigned int * spam;',
+ 'char *spam;',
+ 'char const *spam = "spamspamspam...";',
+ # typedefs
+ 'MyType *spam;',
+ # complex
+ 'struct myspam *spam;',
+ 'union choice *spam;',
+ # packed with details
+ 'const char const *spam;',
+ # void pointer
+ 'void *data = NULL;',
+ # function pointers
+ 'int (* func)(char *arg1);',
+ 'char * (* func)(void);',
+ ])
+ # storage class
+ tests.extend([
+ 'static int spam;',
+ 'extern int spam;',
+ 'static unsigned int spam;',
+ 'static struct myspam spam;',
+ ])
+ # type qualifier
+ tests.extend([
+ 'const int spam;',
+ 'const unsigned int spam;',
+ 'const struct myspam spam;',
+ ])
+ # combined
+ tests.extend([
+ 'const char *spam = eggs;',
+ 'static const char const *spam = "spamspamspam...";',
+ 'extern const char const *spam;',
+ 'static void *data = NULL;',
+ 'static int (const * func)(char *arg1) = func1;',
+ 'static char * (* func)(void);',
+ ])
+ for line in tests:
+ expected = line
+ with self.subTest(line):
+ stmts = list(iter_local_statements([line]))
+
+ self.assertEqual(stmts, [(expected, None)])
+
+ @unittest.expectedFailure
+ def test_vars_multiline_var(self):
+ lines = textwrap.dedent('''
+ PyObject *
+ spam
+ = NULL;
+ ''').splitlines()
+ expected = 'PyObject * spam = NULL;'
+
+ stmts = list(iter_local_statements(lines))
+
+ self.assertEqual(stmts, [(expected, None)])
+
+ @unittest.expectedFailure
+ def test_declaration_multiple_vars(self):
+ lines = ['static const int const *spam, *ham=NULL, ham2[]={1, 2, 3}, ham3[2]={1, 2}, eggs = 3;']
+
+ stmts = list(iter_global_declarations(lines))
+
+ self.assertEqual(stmts, [
+ ('static const int const *spam;', None),
+ ('static const int *ham=NULL;', None),
+ ('static const int ham[]={1, 2, 3};', None),
+ ('static const int ham[2]={1, 2};', None),
+ ('static const int eggs = 3;', None),
+ ])
+
+ @unittest.expectedFailure
+ def test_other_simple(self):
+ raise NotImplementedError
+
+ @unittest.expectedFailure
+ def test_compound(self):
+ raise NotImplementedError
+
+ @unittest.expectedFailure
+ def test_mixed(self):
+ raise NotImplementedError
+
+ def test_no_statements(self):
+ lines = []
+
+ stmts = list(iter_local_statements(lines))
+
+ self.assertEqual(stmts, [])
+
+ @unittest.expectedFailure
+ def test_bogus(self):
+ raise NotImplementedError
+
+ def test_ignore_comments(self):
+ tests = [
+ ('// msg', None),
+ ('// int stmt;', None),
+ (' // ... ', None),
+ ('// /*', None),
+ ('/* int stmt; */', None),
+ ("""
+ /**
+ * ...
+ * int stmt;
+ */
+ """, None),
+ # mixed with statements
+ ('int stmt; // ...', ('int stmt;', None)),
+ ( 'int stmt; /* ... */', ('int stmt;', None)),
+ ( '/* ... */ int stmt;', ('int stmt;', None)),
+ ]
+ for lines, expected in tests:
+ with self.subTest(lines):
+ lines = lines.splitlines()
+
+ stmts = list(iter_local_statements(lines))
+
+ self.assertEqual(stmts, [expected] if expected else [])
+
+
+class ParseFuncTests(TestCaseBase):
+
+ def test_typical(self):
+ tests = [
+ ('PyObject *\nspam(char *a)\n{\nreturn _spam(a);\n}',
+ 'return _spam(a);',
+ ('spam', 'PyObject * spam(char *a)'),
+ ),
+ ]
+ for stmt, body, expected in tests:
+ with self.subTest(stmt):
+ name, signature = parse_func(stmt, body)
+
+ self.assertEqual((name, signature), expected)
+
+
+class ParseVarTests(TestCaseBase):
+
+ def test_typical(self):
+ tests = [
+ # POTS
+ ('int spam;', ('spam', 'int')),
+ ('unsigned int spam;', ('spam', 'unsigned int')),
+ ('char spam;', ('spam', 'char')),
+ ('float spam;', ('spam', 'float')),
+
+ # typedefs
+ ('uint spam;', ('spam', 'uint')),
+ ('MyType spam;', ('spam', 'MyType')),
+
+ # complex
+ ('struct myspam spam;', ('spam', 'struct myspam')),
+ ('union choice spam;', ('spam', 'union choice')),
+ # inline struct
+ # inline union
+ # enum?
+ ]
+ # pointers
+ tests.extend([
+ # POTS
+ ('int * spam;', ('spam', 'int *')),
+ ('unsigned int * spam;', ('spam', 'unsigned int *')),
+ ('char *spam;', ('spam', 'char *')),
+ ('char const *spam = "spamspamspam...";', ('spam', 'char const *')),
+ # typedefs
+ ('MyType *spam;', ('spam', 'MyType *')),
+ # complex
+ ('struct myspam *spam;', ('spam', 'struct myspam *')),
+ ('union choice *spam;', ('spam', 'union choice *')),
+ # packed with details
+ ('const char const *spam;', ('spam', 'const char const *')),
+ # void pointer
+ ('void *data = NULL;', ('data', 'void *')),
+ # function pointers
+ ('int (* func)(char *);', ('func', 'int (*)(char *)')),
+ ('char * (* func)(void);', ('func', 'char * (*)(void)')),
+ ])
+ # storage class
+ tests.extend([
+ ('static int spam;', ('spam', 'static int')),
+ ('extern int spam;', ('spam', 'extern int')),
+ ('static unsigned int spam;', ('spam', 'static unsigned int')),
+ ('static struct myspam spam;', ('spam', 'static struct myspam')),
+ ])
+ # type qualifier
+ tests.extend([
+ ('const int spam;', ('spam', 'const int')),
+ ('const unsigned int spam;', ('spam', 'const unsigned int')),
+ ('const struct myspam spam;', ('spam', 'const struct myspam')),
+ ])
+ # combined
+ tests.extend([
+ ('const char *spam = eggs;', ('spam', 'const char *')),
+ ('static const char const *spam = "spamspamspam...";',
+ ('spam', 'static const char const *')),
+ ('extern const char const *spam;',
+ ('spam', 'extern const char const *')),
+ ('static void *data = NULL;', ('data', 'static void *')),
+ ('static int (const * func)(char *) = func1;',
+ ('func', 'static int (const *)(char *)')),
+ ('static char * (* func)(void);',
+ ('func', 'static char * (*)(void)')),
+ ])
+ for stmt, expected in tests:
+ with self.subTest(stmt):
+ name, vartype = parse_var(stmt)
+
+ self.assertEqual((name, vartype), expected)
+
+
+@unittest.skip('not finished')
+class ParseCompoundTests(TestCaseBase):
+
+ def test_typical(self):
+ headers, bodies = parse_compound(stmt, blocks)
+ ...
+
+
+class IterVariablesTests(TestCaseBase):
+
+ _return_iter_source_lines = None
+ _return_iter_global = None
+ _return_iter_local = None
+ _return_parse_func = None
+ _return_parse_var = None
+ _return_parse_compound = None
+
+ def _iter_source_lines(self, filename):
+ self.calls.append(
+ ('_iter_source_lines', (filename,)))
+ return self._return_iter_source_lines.splitlines()
+
+ def _iter_global(self, lines):
+ self.calls.append(
+ ('_iter_global', (lines,)))
+ try:
+ return self._return_iter_global.pop(0)
+ except IndexError:
+ return ('???', None)
+
+ def _iter_local(self, lines):
+ self.calls.append(
+ ('_iter_local', (lines,)))
+ try:
+ return self._return_iter_local.pop(0)
+ except IndexError:
+ return ('???', None)
+
+ def _parse_func(self, stmt, body):
+ self.calls.append(
+ ('_parse_func', (stmt, body)))
+ try:
+ return self._return_parse_func.pop(0)
+ except IndexError:
+ return ('???', '???')
+
+ def _parse_var(self, lines):
+ self.calls.append(
+ ('_parse_var', (lines,)))
+ try:
+ return self._return_parse_var.pop(0)
+ except IndexError:
+ return ('???', '???')
+
+ def _parse_compound(self, stmt, blocks):
+ self.calls.append(
+ ('_parse_compound', (stmt, blocks)))
+ try:
+ return self._return_parse_compound.pop(0)
+ except IndexError:
+ return (['???'], ['???'])
+
+ def test_empty_file(self):
+ self._return_iter_source_lines = ''
+ self._return_iter_global = [
+ [],
+ ]
+ self._return_parse_func = None
+ self._return_parse_var = None
+ self._return_parse_compound = None
+
+ srcvars = list(iter_variables('spam.c',
+ _iter_source_lines=self._iter_source_lines,
+ _iter_global=self._iter_global,
+ _iter_local=self._iter_local,
+ _parse_func=self._parse_func,
+ _parse_var=self._parse_var,
+ _parse_compound=self._parse_compound,
+ ))
+
+ self.assertEqual(srcvars, [])
+ self.assertEqual(self.calls, [
+ ('_iter_source_lines', ('spam.c',)),
+ ('_iter_global', ([],)),
+ ])
+
+ def test_no_statements(self):
+ content = textwrap.dedent('''
+ ...
+ ''')
+ self._return_iter_source_lines = content
+ self._return_iter_global = [
+ [],
+ ]
+ self._return_parse_func = None
+ self._return_parse_var = None
+ self._return_parse_compound = None
+
+ srcvars = list(iter_variables('spam.c',
+ _iter_source_lines=self._iter_source_lines,
+ _iter_global=self._iter_global,
+ _iter_local=self._iter_local,
+ _parse_func=self._parse_func,
+ _parse_var=self._parse_var,
+ _parse_compound=self._parse_compound,
+ ))
+
+ self.assertEqual(srcvars, [])
+ self.assertEqual(self.calls, [
+ ('_iter_source_lines', ('spam.c',)),
+ ('_iter_global', (content.splitlines(),)),
+ ])
+
+ def test_typical(self):
+ content = textwrap.dedent('''
+ ...
+ ''')
+ self._return_iter_source_lines = content
+ self._return_iter_global = [
+ [('<lines 1>', None), # var1
+ ('<lines 2>', None), # non-var
+ ('<lines 3>', None), # var2
+ ('<lines 4>', '<body 1>'), # func1
+ ('<lines 9>', None), # var4
+ ],
+ ]
+ self._return_iter_local = [
+ # func1
+ [('<lines 5>', None), # var3
+ ('<lines 6>', [('<header 1>', '<block 1>')]), # if
+ ('<lines 8>', None), # non-var
+ ],
+ # if
+ [('<lines 7>', None), # var2 ("collision" with global var)
+ ],
+ ]
+ self._return_parse_func = [
+ ('func1', '<sig 1>'),
+ ]
+ self._return_parse_var = [
+ ('var1', '<vartype 1>'),
+ (None, None),
+ ('var2', '<vartype 2>'),
+ ('var3', '<vartype 3>'),
+ ('var2', '<vartype 2b>'),
+ ('var4', '<vartype 4>'),
+ (None, None),
+ (None, None),
+ (None, None),
+ ('var5', '<vartype 5>'),
+ ]
+ self._return_parse_compound = [
+ ([[
+ 'if (',
+ '<simple>',
+ ')',
+ ],
+ ],
+ ['<block 1>']),
+ ]
+
+ srcvars = list(iter_variables('spam.c',
+ _iter_source_lines=self._iter_source_lines,
+ _iter_global=self._iter_global,
+ _iter_local=self._iter_local,
+ _parse_func=self._parse_func,
+ _parse_var=self._parse_var,
+ _parse_compound=self._parse_compound,
+ ))
+
+ self.assertEqual(srcvars, [
+ (None, 'var1', '<vartype 1>'),
+ (None, 'var2', '<vartype 2>'),
+ ('func1', 'var3', '<vartype 3>'),
+ ('func1', 'var2', '<vartype 2b>'),
+ ('func1', 'var4', '<vartype 4>'),
+ (None, 'var5', '<vartype 5>'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_iter_source_lines', ('spam.c',)),
+ ('_iter_global', (content.splitlines(),)),
+ ('_parse_var', ('<lines 1>',)),
+ ('_parse_var', ('<lines 2>',)),
+ ('_parse_var', ('<lines 3>',)),
+ ('_parse_func', ('<lines 4>', '<body 1>')),
+ ('_iter_local', (['<body 1>'],)),
+ ('_parse_var', ('<lines 5>',)),
+ ('_parse_compound', ('<lines 6>', [('<header 1>', '<block 1>')])),
+ ('_parse_var', ('if (',)),
+ ('_parse_var', ('<simple>',)),
+ ('_parse_var', (')',)),
+ ('_parse_var', ('<lines 8>',)),
+ ('_iter_local', (['<block 1>'],)),
+ ('_parse_var', ('<lines 7>',)),
+ ('_parse_var', ('<lines 9>',)),
+ ])
+
+ def test_no_locals(self):
+ content = textwrap.dedent('''
+ ...
+ ''')
+ self._return_iter_source_lines = content
+ self._return_iter_global = [
+ [('<lines 1>', None), # var1
+ ('<lines 2>', None), # non-var
+ ('<lines 3>', None), # var2
+ ('<lines 4>', '<body 1>'), # func1
+ ],
+ ]
+ self._return_iter_local = [
+ # func1
+ [('<lines 5>', None), # non-var
+ ('<lines 6>', [('<header 1>', '<block 1>')]), # if
+ ('<lines 8>', None), # non-var
+ ],
+ # if
+ [('<lines 7>', None), # non-var
+ ],
+ ]
+ self._return_parse_func = [
+ ('func1', '<sig 1>'),
+ ]
+ self._return_parse_var = [
+ ('var1', '<vartype 1>'),
+ (None, None),
+ ('var2', '<vartype 2>'),
+ (None, None),
+ (None, None),
+ (None, None),
+ (None, None),
+ (None, None),
+ (None, None),
+ ]
+ self._return_parse_compound = [
+ ([[
+ 'if (',
+ '<simple>',
+ ')',
+ ],
+ ],
+ ['<block 1>']),
+ ]
+
+ srcvars = list(iter_variables('spam.c',
+ _iter_source_lines=self._iter_source_lines,
+ _iter_global=self._iter_global,
+ _iter_local=self._iter_local,
+ _parse_func=self._parse_func,
+ _parse_var=self._parse_var,
+ _parse_compound=self._parse_compound,
+ ))
+
+ self.assertEqual(srcvars, [
+ (None, 'var1', '<vartype 1>'),
+ (None, 'var2', '<vartype 2>'),
+ ])
+ self.assertEqual(self.calls, [
+ ('_iter_source_lines', ('spam.c',)),
+ ('_iter_global', (content.splitlines(),)),
+ ('_parse_var', ('<lines 1>',)),
+ ('_parse_var', ('<lines 2>',)),
+ ('_parse_var', ('<lines 3>',)),
+ ('_parse_func', ('<lines 4>', '<body 1>')),
+ ('_iter_local', (['<body 1>'],)),
+ ('_parse_var', ('<lines 5>',)),
+ ('_parse_compound', ('<lines 6>', [('<header 1>', '<block 1>')])),
+ ('_parse_var', ('if (',)),
+ ('_parse_var', ('<simple>',)),
+ ('_parse_var', (')',)),
+ ('_parse_var', ('<lines 8>',)),
+ ('_iter_local', (['<block 1>'],)),
+ ('_parse_var', ('<lines 7>',)),
+ ])
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_info.py b/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_info.py
new file mode 100644
index 0000000..1dfe5d0
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_info.py
@@ -0,0 +1,208 @@
+import string
+import unittest
+
+from ..util import PseudoStr, StrProxy, Object
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_analyzer_common.info import ID
+ from c_parser.info import (
+ normalize_vartype, Variable,
+ )
+
+
+class NormalizeVartypeTests(unittest.TestCase):
+
+ def test_basic(self):
+ tests = [
+ (None, None),
+ ('', ''),
+ ('int', 'int'),
+ (PseudoStr('int'), 'int'),
+ (StrProxy('int'), 'int'),
+ ]
+ for vartype, expected in tests:
+ with self.subTest(vartype):
+ normalized = normalize_vartype(vartype)
+
+ self.assertEqual(normalized, expected)
+
+
+class VariableTests(unittest.TestCase):
+
+ VALID_ARGS = (
+ ('x/y/z/spam.c', 'func', 'eggs'),
+ 'int',
+ )
+ VALID_KWARGS = dict(zip(Variable._fields, VALID_ARGS))
+ VALID_EXPECTED = VALID_ARGS
+
+ def test_init_typical_global(self):
+ static = Variable(
+ id=ID(
+ filename='x/y/z/spam.c',
+ funcname=None,
+ name='eggs',
+ ),
+ vartype='int',
+ )
+
+ self.assertEqual(static, (
+ ('x/y/z/spam.c', None, 'eggs'),
+ 'int',
+ ))
+
+ def test_init_typical_local(self):
+ static = Variable(
+ id=ID(
+ filename='x/y/z/spam.c',
+ funcname='func',
+ name='eggs',
+ ),
+ vartype='int',
+ )
+
+ self.assertEqual(static, (
+ ('x/y/z/spam.c', 'func', 'eggs'),
+ 'int',
+ ))
+
+ def test_init_all_missing(self):
+ for value in ('', None):
+ with self.subTest(repr(value)):
+ static = Variable(
+ id=value,
+ vartype=value,
+ )
+
+ self.assertEqual(static, (
+ None,
+ None,
+ ))
+
+ def test_init_all_coerced(self):
+ id = ID('x/y/z/spam.c', 'func', 'spam')
+ tests = [
+ ('str subclass',
+ dict(
+ id=(
+ PseudoStr('x/y/z/spam.c'),
+ PseudoStr('func'),
+ PseudoStr('spam'),
+ ),
+ vartype=PseudoStr('int'),
+ ),
+ (id,
+ 'int',
+ )),
+ ('non-str 1',
+ dict(
+ id=id,
+ vartype=Object(),
+ ),
+ (id,
+ '<object>',
+ )),
+ ('non-str 2',
+ dict(
+ id=id,
+ vartype=StrProxy('variable'),
+ ),
+ (id,
+ 'variable',
+ )),
+ ('non-str',
+ dict(
+ id=id,
+ vartype=('a', 'b', 'c'),
+ ),
+ (id,
+ "('a', 'b', 'c')",
+ )),
+ ]
+ for summary, kwargs, expected in tests:
+ with self.subTest(summary):
+ static = Variable(**kwargs)
+
+ for field in Variable._fields:
+ value = getattr(static, field)
+ if field == 'id':
+ self.assertIs(type(value), ID)
+ else:
+ self.assertIs(type(value), str)
+ self.assertEqual(tuple(static), expected)
+
+ def test_iterable(self):
+ static = Variable(**self.VALID_KWARGS)
+
+ id, vartype = static
+
+ values = (id, vartype)
+ for value, expected in zip(values, self.VALID_EXPECTED):
+ self.assertEqual(value, expected)
+
+ def test_fields(self):
+ static = Variable(('a', 'b', 'z'), 'x')
+
+ self.assertEqual(static.id, ('a', 'b', 'z'))
+ self.assertEqual(static.vartype, 'x')
+
+ def test___getattr__(self):
+ static = Variable(('a', 'b', 'z'), 'x')
+
+ self.assertEqual(static.filename, 'a')
+ self.assertEqual(static.funcname, 'b')
+ self.assertEqual(static.name, 'z')
+
+ def test_validate_typical(self):
+ static = Variable(
+ id=ID(
+ filename='x/y/z/spam.c',
+ funcname='func',
+ name='eggs',
+ ),
+ vartype='int',
+ )
+
+ static.validate() # This does not fail.
+
+ def test_validate_missing_field(self):
+ for field in Variable._fields:
+ with self.subTest(field):
+ static = Variable(**self.VALID_KWARGS)
+ static = static._replace(**{field: None})
+
+ with self.assertRaises(TypeError):
+ static.validate()
+
+ def test_validate_bad_field(self):
+ badch = tuple(c for c in string.punctuation + string.digits)
+ notnames = (
+ '1a',
+ 'a.b',
+ 'a-b',
+ '&a',
+ 'a++',
+ ) + badch
+ tests = [
+ ('id', ()), # Any non-empty str is okay.
+ ('vartype', ()), # Any non-empty str is okay.
+ ]
+ seen = set()
+ for field, invalid in tests:
+ for value in invalid:
+ seen.add(value)
+ with self.subTest(f'{field}={value!r}'):
+ static = Variable(**self.VALID_KWARGS)
+ static = static._replace(**{field: value})
+
+ with self.assertRaises(ValueError):
+ static.validate()
+
+ for field, invalid in tests:
+ valid = seen - set(invalid)
+ for value in valid:
+ with self.subTest(f'{field}={value!r}'):
+ static = Variable(**self.VALID_KWARGS)
+ static = static._replace(**{field: value})
+
+ static.validate() # This does not fail.
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_preprocessor.py b/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_preprocessor.py
new file mode 100644
index 0000000..89e1557
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_parser/test_preprocessor.py
@@ -0,0 +1,1562 @@
+import itertools
+import textwrap
+import unittest
+import sys
+
+from ..util import wrapped_arg_combos, StrProxy
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_parser.preprocessor import (
+ iter_lines,
+ # directives
+ parse_directive, PreprocessorDirective,
+ Constant, Macro, IfDirective, Include, OtherDirective,
+ )
+
+
+class TestCaseBase(unittest.TestCase):
+
+ maxDiff = None
+
+ def reset(self):
+ self._calls = []
+ self.errors = None
+
+ @property
+ def calls(self):
+ try:
+ return self._calls
+ except AttributeError:
+ self._calls = []
+ return self._calls
+
+ errors = None
+
+ def try_next_exc(self):
+ if not self.errors:
+ return
+ if exc := self.errors.pop(0):
+ raise exc
+
+ def check_calls(self, *expected):
+ self.assertEqual(self.calls, list(expected))
+ self.assertEqual(self.errors or [], [])
+
+
+class IterLinesTests(TestCaseBase):
+
+ parsed = None
+
+ def check_calls(self, *expected):
+ super().check_calls(*expected)
+ self.assertEqual(self.parsed or [], [])
+
+ def _parse_directive(self, line):
+ self.calls.append(
+ ('_parse_directive', line))
+ self.try_next_exc()
+ return self.parsed.pop(0)
+
+ def test_no_lines(self):
+ lines = []
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [])
+ self.check_calls()
+
+ def test_no_directives(self):
+ lines = textwrap.dedent('''
+
+ // xyz
+ typedef enum {
+ SPAM
+ EGGS
+ } kind;
+
+ struct info {
+ kind kind;
+ int status;
+ };
+
+ typedef struct spam {
+ struct info info;
+ } myspam;
+
+ static int spam = 0;
+
+ /**
+ * ...
+ */
+ static char *
+ get_name(int arg,
+ char *default,
+ )
+ {
+ return default
+ }
+
+ int check(void) {
+ return 0;
+ }
+
+ ''')[1:-1].splitlines()
+ expected = [(lno, line, None, ())
+ for lno, line in enumerate(lines, 1)]
+ expected[1] = (2, ' ', None, ())
+ expected[20] = (21, ' ', None, ())
+ del expected[19]
+ del expected[18]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, expected)
+ self.check_calls()
+
+ def test_single_directives(self):
+ tests = [
+ ('#include <stdio>', Include('<stdio>')),
+ ('#define SPAM 1', Constant('SPAM', '1')),
+ ('#define SPAM() 1', Macro('SPAM', (), '1')),
+ ('#define SPAM(a, b) a = b;', Macro('SPAM', ('a', 'b'), 'a = b;')),
+ ('#if defined(SPAM)', IfDirective('if', 'defined(SPAM)')),
+ ('#ifdef SPAM', IfDirective('ifdef', 'SPAM')),
+ ('#ifndef SPAM', IfDirective('ifndef', 'SPAM')),
+ ('#elseif defined(SPAM)', IfDirective('elseif', 'defined(SPAM)')),
+ ('#else', OtherDirective('else', None)),
+ ('#endif', OtherDirective('endif', None)),
+ ('#error ...', OtherDirective('error', '...')),
+ ('#warning ...', OtherDirective('warning', '...')),
+ ('#__FILE__ ...', OtherDirective('__FILE__', '...')),
+ ('#__LINE__ ...', OtherDirective('__LINE__', '...')),
+ ('#__DATE__ ...', OtherDirective('__DATE__', '...')),
+ ('#__TIME__ ...', OtherDirective('__TIME__', '...')),
+ ('#__TIMESTAMP__ ...', OtherDirective('__TIMESTAMP__', '...')),
+ ]
+ for line, directive in tests:
+ with self.subTest(line):
+ self.reset()
+ self.parsed = [
+ directive,
+ ]
+ text = textwrap.dedent('''
+ static int spam = 0;
+ {}
+ static char buffer[256];
+ ''').strip().format(line)
+ lines = text.strip().splitlines()
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, 'static int spam = 0;', None, ()),
+ (2, line, directive, ()),
+ ((3, 'static char buffer[256];', None, ('defined(SPAM)',))
+ if directive.kind in ('if', 'ifdef', 'elseif')
+ else (3, 'static char buffer[256];', None, ('! defined(SPAM)',))
+ if directive.kind == 'ifndef'
+ else (3, 'static char buffer[256];', None, ())),
+ ])
+ self.check_calls(
+ ('_parse_directive', line),
+ )
+
+ def test_directive_whitespace(self):
+ line = ' # define eggs ( a , b ) { a = b ; } '
+ directive = Macro('eggs', ('a', 'b'), '{ a = b; }')
+ self.parsed = [
+ directive,
+ ]
+ lines = [line]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, line, directive, ()),
+ ])
+ self.check_calls(
+ ('_parse_directive', '#define eggs ( a , b ) { a = b ; }'),
+ )
+
+ @unittest.skipIf(sys.platform == 'win32', 'needs fix under Windows')
+ def test_split_lines(self):
+ directive = Macro('eggs', ('a', 'b'), '{ a = b; }')
+ self.parsed = [
+ directive,
+ ]
+ text = textwrap.dedent(r'''
+ static int spam = 0;
+ #define eggs(a, b) \
+ { \
+ a = b; \
+ }
+ static char buffer[256];
+ ''').strip()
+ lines = [line + '\n' for line in text.splitlines()]
+ lines[-1] = lines[-1][:-1]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, 'static int spam = 0;\n', None, ()),
+ (5, '#define eggs(a, b) { a = b; }\n', directive, ()),
+ (6, 'static char buffer[256];', None, ()),
+ ])
+ self.check_calls(
+ ('_parse_directive', '#define eggs(a, b) { a = b; }'),
+ )
+
+ def test_nested_conditions(self):
+ directives = [
+ IfDirective('ifdef', 'SPAM'),
+ IfDirective('if', 'SPAM == 1'),
+ IfDirective('elseif', 'SPAM == 2'),
+ OtherDirective('else', None),
+ OtherDirective('endif', None),
+ OtherDirective('endif', None),
+ ]
+ self.parsed = list(directives)
+ text = textwrap.dedent(r'''
+ static int spam = 0;
+
+ #ifdef SPAM
+ static int start = 0;
+ # if SPAM == 1
+ static char buffer[10];
+ # elif SPAM == 2
+ static char buffer[100];
+ # else
+ static char buffer[256];
+ # endif
+ static int end = 0;
+ #endif
+
+ static int eggs = 0;
+ ''').strip()
+ lines = [line for line in text.splitlines() if line.strip()]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, 'static int spam = 0;', None, ()),
+ (2, '#ifdef SPAM', directives[0], ()),
+ (3, 'static int start = 0;', None, ('defined(SPAM)',)),
+ (4, '# if SPAM == 1', directives[1], ('defined(SPAM)',)),
+ (5, 'static char buffer[10];', None, ('defined(SPAM)', 'SPAM == 1')),
+ (6, '# elif SPAM == 2', directives[2], ('defined(SPAM)', 'SPAM == 1')),
+ (7, 'static char buffer[100];', None, ('defined(SPAM)', '! (SPAM == 1)', 'SPAM == 2')),
+ (8, '# else', directives[3], ('defined(SPAM)', '! (SPAM == 1)', 'SPAM == 2')),
+ (9, 'static char buffer[256];', None, ('defined(SPAM)', '! (SPAM == 1)', '! (SPAM == 2)')),
+ (10, '# endif', directives[4], ('defined(SPAM)', '! (SPAM == 1)', '! (SPAM == 2)')),
+ (11, 'static int end = 0;', None, ('defined(SPAM)',)),
+ (12, '#endif', directives[5], ('defined(SPAM)',)),
+ (13, 'static int eggs = 0;', None, ()),
+ ])
+ self.check_calls(
+ ('_parse_directive', '#ifdef SPAM'),
+ ('_parse_directive', '#if SPAM == 1'),
+ ('_parse_directive', '#elif SPAM == 2'),
+ ('_parse_directive', '#else'),
+ ('_parse_directive', '#endif'),
+ ('_parse_directive', '#endif'),
+ )
+
+ def test_split_blocks(self):
+ directives = [
+ IfDirective('ifdef', 'SPAM'),
+ OtherDirective('else', None),
+ OtherDirective('endif', None),
+ ]
+ self.parsed = list(directives)
+ text = textwrap.dedent(r'''
+ void str_copy(char *buffer, *orig);
+
+ int init(char *name) {
+ static int initialized = 0;
+ if (initialized) {
+ return 0;
+ }
+ #ifdef SPAM
+ static char buffer[10];
+ str_copy(buffer, char);
+ }
+
+ void copy(char *buffer, *orig) {
+ strncpy(buffer, orig, 9);
+ buffer[9] = 0;
+ }
+
+ #else
+ static char buffer[256];
+ str_copy(buffer, char);
+ }
+
+ void copy(char *buffer, *orig) {
+ strcpy(buffer, orig);
+ }
+
+ #endif
+ ''').strip()
+ lines = [line for line in text.splitlines() if line.strip()]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, 'void str_copy(char *buffer, *orig);', None, ()),
+ (2, 'int init(char *name) {', None, ()),
+ (3, ' static int initialized = 0;', None, ()),
+ (4, ' if (initialized) {', None, ()),
+ (5, ' return 0;', None, ()),
+ (6, ' }', None, ()),
+
+ (7, '#ifdef SPAM', directives[0], ()),
+
+ (8, ' static char buffer[10];', None, ('defined(SPAM)',)),
+ (9, ' str_copy(buffer, char);', None, ('defined(SPAM)',)),
+ (10, '}', None, ('defined(SPAM)',)),
+ (11, 'void copy(char *buffer, *orig) {', None, ('defined(SPAM)',)),
+ (12, ' strncpy(buffer, orig, 9);', None, ('defined(SPAM)',)),
+ (13, ' buffer[9] = 0;', None, ('defined(SPAM)',)),
+ (14, '}', None, ('defined(SPAM)',)),
+
+ (15, '#else', directives[1], ('defined(SPAM)',)),
+
+ (16, ' static char buffer[256];', None, ('! (defined(SPAM))',)),
+ (17, ' str_copy(buffer, char);', None, ('! (defined(SPAM))',)),
+ (18, '}', None, ('! (defined(SPAM))',)),
+ (19, 'void copy(char *buffer, *orig) {', None, ('! (defined(SPAM))',)),
+ (20, ' strcpy(buffer, orig);', None, ('! (defined(SPAM))',)),
+ (21, '}', None, ('! (defined(SPAM))',)),
+
+ (22, '#endif', directives[2], ('! (defined(SPAM))',)),
+ ])
+ self.check_calls(
+ ('_parse_directive', '#ifdef SPAM'),
+ ('_parse_directive', '#else'),
+ ('_parse_directive', '#endif'),
+ )
+
+ @unittest.skipIf(sys.platform == 'win32', 'needs fix under Windows')
+ def test_basic(self):
+ directives = [
+ Include('<stdio.h>'),
+ IfDirective('ifdef', 'SPAM'),
+ IfDirective('if', '! defined(HAM) || !HAM'),
+ Constant('HAM', '0'),
+ IfDirective('elseif', 'HAM < 0'),
+ Constant('HAM', '-1'),
+ OtherDirective('else', None),
+ OtherDirective('endif', None),
+ OtherDirective('endif', None),
+ IfDirective('if', 'defined(HAM) && (HAM < 0 || ! HAM)'),
+ OtherDirective('undef', 'HAM'),
+ OtherDirective('endif', None),
+ IfDirective('ifndef', 'HAM'),
+ OtherDirective('endif', None),
+ ]
+ self.parsed = list(directives)
+ text = textwrap.dedent(r'''
+ #include <stdio.h>
+ print("begin");
+ #ifdef SPAM
+ print("spam");
+ #if ! defined(HAM) || !HAM
+ # DEFINE HAM 0
+ #elseif HAM < 0
+ # DEFINE HAM -1
+ #else
+ print("ham HAM");
+ #endif
+ #endif
+
+ #if defined(HAM) && \
+ (HAM < 0 || ! HAM)
+ print("ham?");
+ #undef HAM
+ # endif
+
+ #ifndef HAM
+ print("no ham");
+ #endif
+ print("end");
+ ''')[1:-1]
+ lines = [line + '\n' for line in text.splitlines()]
+ lines[-1] = lines[-1][:-1]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, '#include <stdio.h>\n', Include('<stdio.h>'), ()),
+ (2, 'print("begin");\n', None, ()),
+ #
+ (3, '#ifdef SPAM\n',
+ IfDirective('ifdef', 'SPAM'),
+ ()),
+ (4, ' print("spam");\n',
+ None,
+ ('defined(SPAM)',)),
+ (5, ' #if ! defined(HAM) || !HAM\n',
+ IfDirective('if', '! defined(HAM) || !HAM'),
+ ('defined(SPAM)',)),
+ (6, '# DEFINE HAM 0\n',
+ Constant('HAM', '0'),
+ ('defined(SPAM)', '! defined(HAM) || !HAM')),
+ (7, ' #elseif HAM < 0\n',
+ IfDirective('elseif', 'HAM < 0'),
+ ('defined(SPAM)', '! defined(HAM) || !HAM')),
+ (8, '# DEFINE HAM -1\n',
+ Constant('HAM', '-1'),
+ ('defined(SPAM)', '! (! defined(HAM) || !HAM)', 'HAM < 0')),
+ (9, ' #else\n',
+ OtherDirective('else', None),
+ ('defined(SPAM)', '! (! defined(HAM) || !HAM)', 'HAM < 0')),
+ (10, ' print("ham HAM");\n',
+ None,
+ ('defined(SPAM)', '! (! defined(HAM) || !HAM)', '! (HAM < 0)')),
+ (11, ' #endif\n',
+ OtherDirective('endif', None),
+ ('defined(SPAM)', '! (! defined(HAM) || !HAM)', '! (HAM < 0)')),
+ (12, '#endif\n',
+ OtherDirective('endif', None),
+ ('defined(SPAM)',)),
+ #
+ (13, '\n', None, ()),
+ #
+ (15, '#if defined(HAM) && (HAM < 0 || ! HAM)\n',
+ IfDirective('if', 'defined(HAM) && (HAM < 0 || ! HAM)'),
+ ()),
+ (16, ' print("ham?");\n',
+ None,
+ ('defined(HAM) && (HAM < 0 || ! HAM)',)),
+ (17, ' #undef HAM\n',
+ OtherDirective('undef', 'HAM'),
+ ('defined(HAM) && (HAM < 0 || ! HAM)',)),
+ (18, '# endif\n',
+ OtherDirective('endif', None),
+ ('defined(HAM) && (HAM < 0 || ! HAM)',)),
+ #
+ (19, '\n', None, ()),
+ #
+ (20, '#ifndef HAM\n',
+ IfDirective('ifndef', 'HAM'),
+ ()),
+ (21, ' print("no ham");\n',
+ None,
+ ('! defined(HAM)',)),
+ (22, '#endif\n',
+ OtherDirective('endif', None),
+ ('! defined(HAM)',)),
+ #
+ (23, 'print("end");', None, ()),
+ ])
+
+ @unittest.skipIf(sys.platform == 'win32', 'needs fix under Windows')
+ def test_typical(self):
+ # We use Include/compile.h from commit 66c4f3f38b86. It has
+ # a good enough mix of code without being too large.
+ directives = [
+ IfDirective('ifndef', 'Py_COMPILE_H'),
+ Constant('Py_COMPILE_H', None),
+
+ IfDirective('ifndef', 'Py_LIMITED_API'),
+
+ Include('"code.h"'),
+
+ IfDirective('ifdef', '__cplusplus'),
+ OtherDirective('endif', None),
+
+ Constant('PyCF_MASK', '(CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | CO_FUTURE_GENERATOR_STOP | CO_FUTURE_ANNOTATIONS)'),
+ Constant('PyCF_MASK_OBSOLETE', '(CO_NESTED)'),
+ Constant('PyCF_SOURCE_IS_UTF8', ' 0x0100'),
+ Constant('PyCF_DONT_IMPLY_DEDENT', '0x0200'),
+ Constant('PyCF_ONLY_AST', '0x0400'),
+ Constant('PyCF_IGNORE_COOKIE', '0x0800'),
+ Constant('PyCF_TYPE_COMMENTS', '0x1000'),
+ Constant('PyCF_ALLOW_TOP_LEVEL_AWAIT', '0x2000'),
+
+ IfDirective('ifndef', 'Py_LIMITED_API'),
+ OtherDirective('endif', None),
+
+ Constant('FUTURE_NESTED_SCOPES', '"nested_scopes"'),
+ Constant('FUTURE_GENERATORS', '"generators"'),
+ Constant('FUTURE_DIVISION', '"division"'),
+ Constant('FUTURE_ABSOLUTE_IMPORT', '"absolute_import"'),
+ Constant('FUTURE_WITH_STATEMENT', '"with_statement"'),
+ Constant('FUTURE_PRINT_FUNCTION', '"print_function"'),
+ Constant('FUTURE_UNICODE_LITERALS', '"unicode_literals"'),
+ Constant('FUTURE_BARRY_AS_BDFL', '"barry_as_FLUFL"'),
+ Constant('FUTURE_GENERATOR_STOP', '"generator_stop"'),
+ Constant('FUTURE_ANNOTATIONS', '"annotations"'),
+
+ Macro('PyAST_Compile', ('mod', 's', 'f', 'ar'), 'PyAST_CompileEx(mod, s, f, -1, ar)'),
+
+ Constant('PY_INVALID_STACK_EFFECT', 'INT_MAX'),
+
+ IfDirective('ifdef', '__cplusplus'),
+ OtherDirective('endif', None),
+
+ OtherDirective('endif', None), # ifndef Py_LIMITED_API
+
+ Constant('Py_single_input', '256'),
+ Constant('Py_file_input', '257'),
+ Constant('Py_eval_input', '258'),
+ Constant('Py_func_type_input', '345'),
+
+ OtherDirective('endif', None), # ifndef Py_COMPILE_H
+ ]
+ self.parsed = list(directives)
+ text = textwrap.dedent(r'''
+ #ifndef Py_COMPILE_H
+ #define Py_COMPILE_H
+
+ #ifndef Py_LIMITED_API
+ #include "code.h"
+
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+
+ /* Public interface */
+ struct _node; /* Declare the existence of this type */
+ PyAPI_FUNC(PyCodeObject *) PyNode_Compile(struct _node *, const char *);
+ /* XXX (ncoghlan): Unprefixed type name in a public API! */
+
+ #define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \
+ CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | \
+ CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | \
+ CO_FUTURE_GENERATOR_STOP | CO_FUTURE_ANNOTATIONS)
+ #define PyCF_MASK_OBSOLETE (CO_NESTED)
+ #define PyCF_SOURCE_IS_UTF8 0x0100
+ #define PyCF_DONT_IMPLY_DEDENT 0x0200
+ #define PyCF_ONLY_AST 0x0400
+ #define PyCF_IGNORE_COOKIE 0x0800
+ #define PyCF_TYPE_COMMENTS 0x1000
+ #define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000
+
+ #ifndef Py_LIMITED_API
+ typedef struct {
+ int cf_flags; /* bitmask of CO_xxx flags relevant to future */
+ int cf_feature_version; /* minor Python version (PyCF_ONLY_AST) */
+ } PyCompilerFlags;
+ #endif
+
+ /* Future feature support */
+
+ typedef struct {
+ int ff_features; /* flags set by future statements */
+ int ff_lineno; /* line number of last future statement */
+ } PyFutureFeatures;
+
+ #define FUTURE_NESTED_SCOPES "nested_scopes"
+ #define FUTURE_GENERATORS "generators"
+ #define FUTURE_DIVISION "division"
+ #define FUTURE_ABSOLUTE_IMPORT "absolute_import"
+ #define FUTURE_WITH_STATEMENT "with_statement"
+ #define FUTURE_PRINT_FUNCTION "print_function"
+ #define FUTURE_UNICODE_LITERALS "unicode_literals"
+ #define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"
+ #define FUTURE_GENERATOR_STOP "generator_stop"
+ #define FUTURE_ANNOTATIONS "annotations"
+
+ struct _mod; /* Declare the existence of this type */
+ #define PyAST_Compile(mod, s, f, ar) PyAST_CompileEx(mod, s, f, -1, ar)
+ PyAPI_FUNC(PyCodeObject *) PyAST_CompileEx(
+ struct _mod *mod,
+ const char *filename, /* decoded from the filesystem encoding */
+ PyCompilerFlags *flags,
+ int optimize,
+ PyArena *arena);
+ PyAPI_FUNC(PyCodeObject *) PyAST_CompileObject(
+ struct _mod *mod,
+ PyObject *filename,
+ PyCompilerFlags *flags,
+ int optimize,
+ PyArena *arena);
+ PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromAST(
+ struct _mod * mod,
+ const char *filename /* decoded from the filesystem encoding */
+ );
+ PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromASTObject(
+ struct _mod * mod,
+ PyObject *filename
+ );
+
+ /* _Py_Mangle is defined in compile.c */
+ PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name);
+
+ #define PY_INVALID_STACK_EFFECT INT_MAX
+ PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
+ PyAPI_FUNC(int) PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump);
+
+ PyAPI_FUNC(int) _PyAST_Optimize(struct _mod *, PyArena *arena, int optimize);
+
+ #ifdef __cplusplus
+ }
+ #endif
+
+ #endif /* !Py_LIMITED_API */
+
+ /* These definitions must match corresponding definitions in graminit.h. */
+ #define Py_single_input 256
+ #define Py_file_input 257
+ #define Py_eval_input 258
+ #define Py_func_type_input 345
+
+ #endif /* !Py_COMPILE_H */
+ ''').strip()
+ lines = [line + '\n' for line in text.splitlines()]
+ lines[-1] = lines[-1][:-1]
+
+ results = list(
+ iter_lines(lines, _parse_directive=self._parse_directive))
+
+ self.assertEqual(results, [
+ (1, '#ifndef Py_COMPILE_H\n',
+ IfDirective('ifndef', 'Py_COMPILE_H'),
+ ()),
+ (2, '#define Py_COMPILE_H\n',
+ Constant('Py_COMPILE_H', None),
+ ('! defined(Py_COMPILE_H)',)),
+ (3, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)',)),
+ (4, '#ifndef Py_LIMITED_API\n',
+ IfDirective('ifndef', 'Py_LIMITED_API'),
+ ('! defined(Py_COMPILE_H)',)),
+ (5, '#include "code.h"\n',
+ Include('"code.h"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (6, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (7, '#ifdef __cplusplus\n',
+ IfDirective('ifdef', '__cplusplus'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (8, 'extern "C" {\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', 'defined(__cplusplus)')),
+ (9, '#endif\n',
+ OtherDirective('endif', None),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', 'defined(__cplusplus)')),
+ (10, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (11, ' \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (12, 'struct _node; \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (13, 'PyAPI_FUNC(PyCodeObject *) PyNode_Compile(struct _node *, const char *);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (14, ' \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (15, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (19, '#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | CO_FUTURE_GENERATOR_STOP | CO_FUTURE_ANNOTATIONS)\n',
+ Constant('PyCF_MASK', '(CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | CO_FUTURE_GENERATOR_STOP | CO_FUTURE_ANNOTATIONS)'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (20, '#define PyCF_MASK_OBSOLETE (CO_NESTED)\n',
+ Constant('PyCF_MASK_OBSOLETE', '(CO_NESTED)'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (21, '#define PyCF_SOURCE_IS_UTF8 0x0100\n',
+ Constant('PyCF_SOURCE_IS_UTF8', ' 0x0100'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (22, '#define PyCF_DONT_IMPLY_DEDENT 0x0200\n',
+ Constant('PyCF_DONT_IMPLY_DEDENT', '0x0200'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (23, '#define PyCF_ONLY_AST 0x0400\n',
+ Constant('PyCF_ONLY_AST', '0x0400'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (24, '#define PyCF_IGNORE_COOKIE 0x0800\n',
+ Constant('PyCF_IGNORE_COOKIE', '0x0800'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (25, '#define PyCF_TYPE_COMMENTS 0x1000\n',
+ Constant('PyCF_TYPE_COMMENTS', '0x1000'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (26, '#define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000\n',
+ Constant('PyCF_ALLOW_TOP_LEVEL_AWAIT', '0x2000'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (27, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (28, '#ifndef Py_LIMITED_API\n',
+ IfDirective('ifndef', 'Py_LIMITED_API'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (29, 'typedef struct {\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', '! defined(Py_LIMITED_API)')),
+ (30, ' int cf_flags; \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', '! defined(Py_LIMITED_API)')),
+ (31, ' int cf_feature_version; \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', '! defined(Py_LIMITED_API)')),
+ (32, '} PyCompilerFlags;\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', '! defined(Py_LIMITED_API)')),
+ (33, '#endif\n',
+ OtherDirective('endif', None),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', '! defined(Py_LIMITED_API)')),
+ (34, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (35, ' \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (36, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (37, 'typedef struct {\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (38, ' int ff_features; \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (39, ' int ff_lineno; \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (40, '} PyFutureFeatures;\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (41, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (42, '#define FUTURE_NESTED_SCOPES "nested_scopes"\n',
+ Constant('FUTURE_NESTED_SCOPES', '"nested_scopes"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (43, '#define FUTURE_GENERATORS "generators"\n',
+ Constant('FUTURE_GENERATORS', '"generators"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (44, '#define FUTURE_DIVISION "division"\n',
+ Constant('FUTURE_DIVISION', '"division"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (45, '#define FUTURE_ABSOLUTE_IMPORT "absolute_import"\n',
+ Constant('FUTURE_ABSOLUTE_IMPORT', '"absolute_import"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (46, '#define FUTURE_WITH_STATEMENT "with_statement"\n',
+ Constant('FUTURE_WITH_STATEMENT', '"with_statement"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (47, '#define FUTURE_PRINT_FUNCTION "print_function"\n',
+ Constant('FUTURE_PRINT_FUNCTION', '"print_function"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (48, '#define FUTURE_UNICODE_LITERALS "unicode_literals"\n',
+ Constant('FUTURE_UNICODE_LITERALS', '"unicode_literals"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (49, '#define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"\n',
+ Constant('FUTURE_BARRY_AS_BDFL', '"barry_as_FLUFL"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (50, '#define FUTURE_GENERATOR_STOP "generator_stop"\n',
+ Constant('FUTURE_GENERATOR_STOP', '"generator_stop"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (51, '#define FUTURE_ANNOTATIONS "annotations"\n',
+ Constant('FUTURE_ANNOTATIONS', '"annotations"'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (52, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (53, 'struct _mod; \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (54, '#define PyAST_Compile(mod, s, f, ar) PyAST_CompileEx(mod, s, f, -1, ar)\n',
+ Macro('PyAST_Compile', ('mod', 's', 'f', 'ar'), 'PyAST_CompileEx(mod, s, f, -1, ar)'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (55, 'PyAPI_FUNC(PyCodeObject *) PyAST_CompileEx(\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (56, ' struct _mod *mod,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (57, ' const char *filename, \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (58, ' PyCompilerFlags *flags,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (59, ' int optimize,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (60, ' PyArena *arena);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (61, 'PyAPI_FUNC(PyCodeObject *) PyAST_CompileObject(\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (62, ' struct _mod *mod,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (63, ' PyObject *filename,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (64, ' PyCompilerFlags *flags,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (65, ' int optimize,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (66, ' PyArena *arena);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (67, 'PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromAST(\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (68, ' struct _mod * mod,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (69, ' const char *filename \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (70, ' );\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (71, 'PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromASTObject(\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (72, ' struct _mod * mod,\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (73, ' PyObject *filename\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (74, ' );\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (75, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (76, ' \n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (77, 'PyAPI_FUNC(PyObject*) _Py_Mangle(PyObject *p, PyObject *name);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (78, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (79, '#define PY_INVALID_STACK_EFFECT INT_MAX\n',
+ Constant('PY_INVALID_STACK_EFFECT', 'INT_MAX'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (80, 'PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (81, 'PyAPI_FUNC(int) PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (82, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (83, 'PyAPI_FUNC(int) _PyAST_Optimize(struct _mod *, PyArena *arena, int optimize);\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (84, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (85, '#ifdef __cplusplus\n',
+ IfDirective('ifdef', '__cplusplus'),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (86, '}\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', 'defined(__cplusplus)')),
+ (87, '#endif\n',
+ OtherDirective('endif', None),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)', 'defined(__cplusplus)')),
+ (88, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (89, '#endif \n',
+ OtherDirective('endif', None),
+ ('! defined(Py_COMPILE_H)', '! defined(Py_LIMITED_API)')),
+ (90, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)',)),
+ (91, ' \n',
+ None,
+ ('! defined(Py_COMPILE_H)',)),
+ (92, '#define Py_single_input 256\n',
+ Constant('Py_single_input', '256'),
+ ('! defined(Py_COMPILE_H)',)),
+ (93, '#define Py_file_input 257\n',
+ Constant('Py_file_input', '257'),
+ ('! defined(Py_COMPILE_H)',)),
+ (94, '#define Py_eval_input 258\n',
+ Constant('Py_eval_input', '258'),
+ ('! defined(Py_COMPILE_H)',)),
+ (95, '#define Py_func_type_input 345\n',
+ Constant('Py_func_type_input', '345'),
+ ('! defined(Py_COMPILE_H)',)),
+ (96, '\n',
+ None,
+ ('! defined(Py_COMPILE_H)',)),
+ (97, '#endif ',
+ OtherDirective('endif', None),
+ ('! defined(Py_COMPILE_H)',)),
+ ])
+ self.check_calls(
+ ('_parse_directive', '#ifndef Py_COMPILE_H'),
+ ('_parse_directive', '#define Py_COMPILE_H'),
+ ('_parse_directive', '#ifndef Py_LIMITED_API'),
+ ('_parse_directive', '#include "code.h"'),
+ ('_parse_directive', '#ifdef __cplusplus'),
+ ('_parse_directive', '#endif'),
+ ('_parse_directive', '#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | CO_FUTURE_GENERATOR_STOP | CO_FUTURE_ANNOTATIONS)'),
+ ('_parse_directive', '#define PyCF_MASK_OBSOLETE (CO_NESTED)'),
+ ('_parse_directive', '#define PyCF_SOURCE_IS_UTF8 0x0100'),
+ ('_parse_directive', '#define PyCF_DONT_IMPLY_DEDENT 0x0200'),
+ ('_parse_directive', '#define PyCF_ONLY_AST 0x0400'),
+ ('_parse_directive', '#define PyCF_IGNORE_COOKIE 0x0800'),
+ ('_parse_directive', '#define PyCF_TYPE_COMMENTS 0x1000'),
+ ('_parse_directive', '#define PyCF_ALLOW_TOP_LEVEL_AWAIT 0x2000'),
+ ('_parse_directive', '#ifndef Py_LIMITED_API'),
+ ('_parse_directive', '#endif'),
+ ('_parse_directive', '#define FUTURE_NESTED_SCOPES "nested_scopes"'),
+ ('_parse_directive', '#define FUTURE_GENERATORS "generators"'),
+ ('_parse_directive', '#define FUTURE_DIVISION "division"'),
+ ('_parse_directive', '#define FUTURE_ABSOLUTE_IMPORT "absolute_import"'),
+ ('_parse_directive', '#define FUTURE_WITH_STATEMENT "with_statement"'),
+ ('_parse_directive', '#define FUTURE_PRINT_FUNCTION "print_function"'),
+ ('_parse_directive', '#define FUTURE_UNICODE_LITERALS "unicode_literals"'),
+ ('_parse_directive', '#define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"'),
+ ('_parse_directive', '#define FUTURE_GENERATOR_STOP "generator_stop"'),
+ ('_parse_directive', '#define FUTURE_ANNOTATIONS "annotations"'),
+ ('_parse_directive', '#define PyAST_Compile(mod, s, f, ar) PyAST_CompileEx(mod, s, f, -1, ar)'),
+ ('_parse_directive', '#define PY_INVALID_STACK_EFFECT INT_MAX'),
+ ('_parse_directive', '#ifdef __cplusplus'),
+ ('_parse_directive', '#endif'),
+ ('_parse_directive', '#endif'),
+ ('_parse_directive', '#define Py_single_input 256'),
+ ('_parse_directive', '#define Py_file_input 257'),
+ ('_parse_directive', '#define Py_eval_input 258'),
+ ('_parse_directive', '#define Py_func_type_input 345'),
+ ('_parse_directive', '#endif'),
+ )
+
+
+class ParseDirectiveTests(unittest.TestCase):
+
+ def test_directives(self):
+ tests = [
+ # includes
+ ('#include "internal/pycore_pystate.h"', Include('"internal/pycore_pystate.h"')),
+ ('#include <stdio>', Include('<stdio>')),
+
+ # defines
+ ('#define SPAM int', Constant('SPAM', 'int')),
+ ('#define SPAM', Constant('SPAM', '')),
+ ('#define SPAM(x, y) run(x, y)', Macro('SPAM', ('x', 'y'), 'run(x, y)')),
+ ('#undef SPAM', None),
+
+ # conditionals
+ ('#if SPAM', IfDirective('if', 'SPAM')),
+ # XXX complex conditionls
+ ('#ifdef SPAM', IfDirective('ifdef', 'SPAM')),
+ ('#ifndef SPAM', IfDirective('ifndef', 'SPAM')),
+ ('#elseif SPAM', IfDirective('elseif', 'SPAM')),
+ # XXX complex conditionls
+ ('#else', OtherDirective('else', '')),
+ ('#endif', OtherDirective('endif', '')),
+
+ # other
+ ('#error oops!', None),
+ ('#warning oops!', None),
+ ('#pragma ...', None),
+ ('#__FILE__ ...', None),
+ ('#__LINE__ ...', None),
+ ('#__DATE__ ...', None),
+ ('#__TIME__ ...', None),
+ ('#__TIMESTAMP__ ...', None),
+
+ # extra whitespace
+ (' # include <stdio> ', Include('<stdio>')),
+ ('#else ', OtherDirective('else', '')),
+ ('#endif ', OtherDirective('endif', '')),
+ ('#define SPAM int ', Constant('SPAM', 'int')),
+ ('#define SPAM ', Constant('SPAM', '')),
+ ]
+ for line, expected in tests:
+ if expected is None:
+ kind, _, text = line[1:].partition(' ')
+ expected = OtherDirective(kind, text)
+ with self.subTest(line):
+ directive = parse_directive(line)
+
+ self.assertEqual(directive, expected)
+
+ def test_bad_directives(self):
+ tests = [
+ # valid directives with bad text
+ '#define 123',
+ '#else spam',
+ '#endif spam',
+ ]
+ for kind in PreprocessorDirective.KINDS:
+ # missing leading "#"
+ tests.append(kind)
+ if kind in ('else', 'endif'):
+ continue
+ # valid directives with missing text
+ tests.append('#' + kind)
+ tests.append('#' + kind + ' ')
+ for line in tests:
+ with self.subTest(line):
+ with self.assertRaises(ValueError):
+ parse_directive(line)
+
+ def test_not_directives(self):
+ tests = [
+ '',
+ ' ',
+ 'directive',
+ 'directive?',
+ '???',
+ ]
+ for line in tests:
+ with self.subTest(line):
+ with self.assertRaises(ValueError):
+ parse_directive(line)
+
+
+class ConstantTests(unittest.TestCase):
+
+ def test_type(self):
+ directive = Constant('SPAM', '123')
+
+ self.assertIs(type(directive), Constant)
+ self.assertIsInstance(directive, PreprocessorDirective)
+
+ def test_attrs(self):
+ d = Constant('SPAM', '123')
+ kind, name, value = d.kind, d.name, d.value
+
+ self.assertEqual(kind, 'define')
+ self.assertEqual(name, 'SPAM')
+ self.assertEqual(value, '123')
+
+ def test_text(self):
+ tests = [
+ (('SPAM', '123'), 'SPAM 123'),
+ (('SPAM',), 'SPAM'),
+ ]
+ for args, expected in tests:
+ with self.subTest(args):
+ d = Constant(*args)
+ text = d.text
+
+ self.assertEqual(text, expected)
+
+ def test_iter(self):
+ kind, name, value = Constant('SPAM', '123')
+
+ self.assertEqual(kind, 'define')
+ self.assertEqual(name, 'SPAM')
+ self.assertEqual(value, '123')
+
+ def test_defaults(self):
+ kind, name, value = Constant('SPAM')
+
+ self.assertEqual(kind, 'define')
+ self.assertEqual(name, 'SPAM')
+ self.assertIs(value, None)
+
+ def test_coerce(self):
+ tests = []
+ # coerced name, value
+ for args in wrapped_arg_combos('SPAM', '123'):
+ tests.append((args, ('SPAM', '123')))
+ # missing name, value
+ for name in ('', ' ', None, StrProxy(' '), ()):
+ for value in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(
+ ((name, value), (None, None)))
+ # whitespace
+ tests.extend([
+ ((' SPAM ', ' 123 '), ('SPAM', '123')),
+ ])
+
+ for args, expected in tests:
+ with self.subTest(args):
+ d = Constant(*args)
+
+ self.assertEqual(d[1:], expected)
+ for i, exp in enumerate(expected, start=1):
+ if exp is not None:
+ self.assertIs(type(d[i]), str)
+
+ def test_valid(self):
+ tests = [
+ ('SPAM', '123'),
+ # unusual name
+ ('_SPAM_', '123'),
+ ('X_1', '123'),
+ # unusual value
+ ('SPAM', None),
+ ]
+ for args in tests:
+ with self.subTest(args):
+ directive = Constant(*args)
+
+ directive.validate()
+
+ def test_invalid(self):
+ tests = [
+ # invalid name
+ ((None, '123'), TypeError),
+ (('_', '123'), ValueError),
+ (('1', '123'), ValueError),
+ (('_1_', '123'), ValueError),
+ # There is no invalid value (including None).
+ ]
+ for args, exctype in tests:
+ with self.subTest(args):
+ directive = Constant(*args)
+
+ with self.assertRaises(exctype):
+ directive.validate()
+
+
+class MacroTests(unittest.TestCase):
+
+ def test_type(self):
+ directive = Macro('SPAM', ('x', 'y'), '123')
+
+ self.assertIs(type(directive), Macro)
+ self.assertIsInstance(directive, PreprocessorDirective)
+
+ def test_attrs(self):
+ d = Macro('SPAM', ('x', 'y'), '123')
+ kind, name, args, body = d.kind, d.name, d.args, d.body
+
+ self.assertEqual(kind, 'define')
+ self.assertEqual(name, 'SPAM')
+ self.assertEqual(args, ('x', 'y'))
+ self.assertEqual(body, '123')
+
+ def test_text(self):
+ tests = [
+ (('SPAM', ('x', 'y'), '123'), 'SPAM(x, y) 123'),
+ (('SPAM', ('x', 'y'),), 'SPAM(x, y)'),
+ ]
+ for args, expected in tests:
+ with self.subTest(args):
+ d = Macro(*args)
+ text = d.text
+
+ self.assertEqual(text, expected)
+
+ def test_iter(self):
+ kind, name, args, body = Macro('SPAM', ('x', 'y'), '123')
+
+ self.assertEqual(kind, 'define')
+ self.assertEqual(name, 'SPAM')
+ self.assertEqual(args, ('x', 'y'))
+ self.assertEqual(body, '123')
+
+ def test_defaults(self):
+ kind, name, args, body = Macro('SPAM', ('x', 'y'))
+
+ self.assertEqual(kind, 'define')
+ self.assertEqual(name, 'SPAM')
+ self.assertEqual(args, ('x', 'y'))
+ self.assertIs(body, None)
+
+ def test_coerce(self):
+ tests = []
+ # coerce name and body
+ for args in wrapped_arg_combos('SPAM', ('x', 'y'), '123'):
+ tests.append(
+ (args, ('SPAM', ('x', 'y'), '123')))
+ # coerce args
+ tests.extend([
+ (('SPAM', 'x', '123'),
+ ('SPAM', ('x',), '123')),
+ (('SPAM', 'x,y', '123'),
+ ('SPAM', ('x', 'y'), '123')),
+ ])
+ # coerce arg names
+ for argnames in wrapped_arg_combos('x', 'y'):
+ tests.append(
+ (('SPAM', argnames, '123'),
+ ('SPAM', ('x', 'y'), '123')))
+ # missing name, body
+ for name in ('', ' ', None, StrProxy(' '), ()):
+ for argnames in (None, ()):
+ for body in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(
+ ((name, argnames, body),
+ (None, (), None)))
+ # missing args
+ tests.extend([
+ (('SPAM', None, '123'),
+ ('SPAM', (), '123')),
+ (('SPAM', (), '123'),
+ ('SPAM', (), '123')),
+ ])
+ # missing arg names
+ for arg in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(
+ (('SPAM', (arg,), '123'),
+ ('SPAM', (None,), '123')))
+ tests.extend([
+ (('SPAM', ('x', '', 'z'), '123'),
+ ('SPAM', ('x', None, 'z'), '123')),
+ ])
+ # whitespace
+ tests.extend([
+ ((' SPAM ', (' x ', ' y '), ' 123 '),
+ ('SPAM', ('x', 'y'), '123')),
+ (('SPAM', 'x, y', '123'),
+ ('SPAM', ('x', 'y'), '123')),
+ ])
+
+ for args, expected in tests:
+ with self.subTest(args):
+ d = Macro(*args)
+
+ self.assertEqual(d[1:], expected)
+ for i, exp in enumerate(expected, start=1):
+ if i == 2:
+ self.assertIs(type(d[i]), tuple)
+ elif exp is not None:
+ self.assertIs(type(d[i]), str)
+
+ def test_init_bad_args(self):
+ tests = [
+ ('SPAM', StrProxy('x'), '123'),
+ ('SPAM', object(), '123'),
+ ]
+ for args in tests:
+ with self.subTest(args):
+ with self.assertRaises(TypeError):
+ Macro(*args)
+
+ def test_valid(self):
+ tests = [
+ # unusual name
+ ('SPAM', ('x', 'y'), 'run(x, y)'),
+ ('_SPAM_', ('x', 'y'), 'run(x, y)'),
+ ('X_1', ('x', 'y'), 'run(x, y)'),
+ # unusual args
+ ('SPAM', (), 'run(x, y)'),
+ ('SPAM', ('_x_', 'y_1'), 'run(x, y)'),
+ ('SPAM', 'x', 'run(x, y)'),
+ ('SPAM', 'x, y', 'run(x, y)'),
+ # unusual body
+ ('SPAM', ('x', 'y'), None),
+ ]
+ for args in tests:
+ with self.subTest(args):
+ directive = Macro(*args)
+
+ directive.validate()
+
+ def test_invalid(self):
+ tests = [
+ # invalid name
+ ((None, ('x', 'y'), '123'), TypeError),
+ (('_', ('x', 'y'), '123'), ValueError),
+ (('1', ('x', 'y'), '123'), ValueError),
+ (('_1', ('x', 'y'), '123'), ValueError),
+ # invalid args
+ (('SPAM', (None, 'y'), '123'), ValueError),
+ (('SPAM', ('x', '_'), '123'), ValueError),
+ (('SPAM', ('x', '1'), '123'), ValueError),
+ (('SPAM', ('x', '_1_'), '123'), ValueError),
+ # There is no invalid body (including None).
+ ]
+ for args, exctype in tests:
+ with self.subTest(args):
+ directive = Macro(*args)
+
+ with self.assertRaises(exctype):
+ directive.validate()
+
+
+class IfDirectiveTests(unittest.TestCase):
+
+ def test_type(self):
+ directive = IfDirective('if', '1')
+
+ self.assertIs(type(directive), IfDirective)
+ self.assertIsInstance(directive, PreprocessorDirective)
+
+ def test_attrs(self):
+ d = IfDirective('if', '1')
+ kind, condition = d.kind, d.condition
+
+ self.assertEqual(kind, 'if')
+ self.assertEqual(condition, '1')
+ #self.assertEqual(condition, (ArithmeticCondition('1'),))
+
+ def test_text(self):
+ tests = [
+ (('if', 'defined(SPAM) && 1 || (EGGS > 3 && defined(HAM))'),
+ 'defined(SPAM) && 1 || (EGGS > 3 && defined(HAM))'),
+ ]
+ for kind in IfDirective.KINDS:
+ tests.append(
+ ((kind, 'SPAM'), 'SPAM'))
+ for args, expected in tests:
+ with self.subTest(args):
+ d = IfDirective(*args)
+ text = d.text
+
+ self.assertEqual(text, expected)
+
+ def test_iter(self):
+ kind, condition = IfDirective('if', '1')
+
+ self.assertEqual(kind, 'if')
+ self.assertEqual(condition, '1')
+ #self.assertEqual(condition, (ArithmeticCondition('1'),))
+
+ #def test_complex_conditions(self):
+ # ...
+
+ def test_coerce(self):
+ tests = []
+ for kind in IfDirective.KINDS:
+ if kind == 'ifdef':
+ cond = 'defined(SPAM)'
+ elif kind == 'ifndef':
+ cond = '! defined(SPAM)'
+ else:
+ cond = 'SPAM'
+ for args in wrapped_arg_combos(kind, 'SPAM'):
+ tests.append((args, (kind, cond)))
+ tests.extend([
+ ((' ' + kind + ' ', ' SPAM '), (kind, cond)),
+ ])
+ for raw in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(((kind, raw), (kind, None)))
+ for kind in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(((kind, 'SPAM'), (None, 'SPAM')))
+ for args, expected in tests:
+ with self.subTest(args):
+ d = IfDirective(*args)
+
+ self.assertEqual(tuple(d), expected)
+ for i, exp in enumerate(expected):
+ if exp is not None:
+ self.assertIs(type(d[i]), str)
+
+ def test_valid(self):
+ tests = []
+ for kind in IfDirective.KINDS:
+ tests.extend([
+ (kind, 'SPAM'),
+ (kind, '_SPAM_'),
+ (kind, 'X_1'),
+ (kind, '()'),
+ (kind, '--'),
+ (kind, '???'),
+ ])
+ for args in tests:
+ with self.subTest(args):
+ directive = IfDirective(*args)
+
+ directive.validate()
+
+ def test_invalid(self):
+ tests = []
+ # kind
+ tests.extend([
+ ((None, 'SPAM'), TypeError),
+ (('_', 'SPAM'), ValueError),
+ (('-', 'SPAM'), ValueError),
+ (('spam', 'SPAM'), ValueError),
+ ])
+ for kind in PreprocessorDirective.KINDS:
+ if kind in IfDirective.KINDS:
+ continue
+ tests.append(
+ ((kind, 'SPAM'), ValueError))
+ # condition
+ for kind in IfDirective.KINDS:
+ tests.extend([
+ ((kind, None), TypeError),
+ # Any other condition is valid.
+ ])
+ for args, exctype in tests:
+ with self.subTest(args):
+ directive = IfDirective(*args)
+
+ with self.assertRaises(exctype):
+ directive.validate()
+
+
+class IncludeTests(unittest.TestCase):
+
+ def test_type(self):
+ directive = Include('<stdio>')
+
+ self.assertIs(type(directive), Include)
+ self.assertIsInstance(directive, PreprocessorDirective)
+
+ def test_attrs(self):
+ d = Include('<stdio>')
+ kind, file, text = d.kind, d.file, d.text
+
+ self.assertEqual(kind, 'include')
+ self.assertEqual(file, '<stdio>')
+ self.assertEqual(text, '<stdio>')
+
+ def test_iter(self):
+ kind, file = Include('<stdio>')
+
+ self.assertEqual(kind, 'include')
+ self.assertEqual(file, '<stdio>')
+
+ def test_coerce(self):
+ tests = []
+ for arg, in wrapped_arg_combos('<stdio>'):
+ tests.append((arg, '<stdio>'))
+ tests.extend([
+ (' <stdio> ', '<stdio>'),
+ ])
+ for arg in ('', ' ', None, StrProxy(' '), ()):
+ tests.append((arg, None ))
+ for arg, expected in tests:
+ with self.subTest(arg):
+ _, file = Include(arg)
+
+ self.assertEqual(file, expected)
+ if expected is not None:
+ self.assertIs(type(file), str)
+
+ def test_valid(self):
+ tests = [
+ '<stdio>',
+ '"spam.h"',
+ '"internal/pycore_pystate.h"',
+ ]
+ for arg in tests:
+ with self.subTest(arg):
+ directive = Include(arg)
+
+ directive.validate()
+
+ def test_invalid(self):
+ tests = [
+ (None, TypeError),
+ # We currently don't check the file.
+ ]
+ for arg, exctype in tests:
+ with self.subTest(arg):
+ directive = Include(arg)
+
+ with self.assertRaises(exctype):
+ directive.validate()
+
+
+class OtherDirectiveTests(unittest.TestCase):
+
+ def test_type(self):
+ directive = OtherDirective('undef', 'SPAM')
+
+ self.assertIs(type(directive), OtherDirective)
+ self.assertIsInstance(directive, PreprocessorDirective)
+
+ def test_attrs(self):
+ d = OtherDirective('undef', 'SPAM')
+ kind, text = d.kind, d.text
+
+ self.assertEqual(kind, 'undef')
+ self.assertEqual(text, 'SPAM')
+
+ def test_iter(self):
+ kind, text = OtherDirective('undef', 'SPAM')
+
+ self.assertEqual(kind, 'undef')
+ self.assertEqual(text, 'SPAM')
+
+ def test_coerce(self):
+ tests = []
+ for kind in OtherDirective.KINDS:
+ if kind in ('else', 'endif'):
+ continue
+ for args in wrapped_arg_combos(kind, '...'):
+ tests.append((args, (kind, '...')))
+ tests.extend([
+ ((' ' + kind + ' ', ' ... '), (kind, '...')),
+ ])
+ for raw in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(((kind, raw), (kind, None)))
+ for kind in ('else', 'endif'):
+ for args in wrapped_arg_combos(kind, None):
+ tests.append((args, (kind, None)))
+ tests.extend([
+ ((' ' + kind + ' ', None), (kind, None)),
+ ])
+ for kind in ('', ' ', None, StrProxy(' '), ()):
+ tests.append(((kind, '...'), (None, '...')))
+ for args, expected in tests:
+ with self.subTest(args):
+ d = OtherDirective(*args)
+
+ self.assertEqual(tuple(d), expected)
+ for i, exp in enumerate(expected):
+ if exp is not None:
+ self.assertIs(type(d[i]), str)
+
+ def test_valid(self):
+ tests = []
+ for kind in OtherDirective.KINDS:
+ if kind in ('else', 'endif'):
+ continue
+ tests.extend([
+ (kind, '...'),
+ (kind, '???'),
+ (kind, 'SPAM'),
+ (kind, '1 + 1'),
+ ])
+ for kind in ('else', 'endif'):
+ tests.append((kind, None))
+ for args in tests:
+ with self.subTest(args):
+ directive = OtherDirective(*args)
+
+ directive.validate()
+
+ def test_invalid(self):
+ tests = []
+ # kind
+ tests.extend([
+ ((None, '...'), TypeError),
+ (('_', '...'), ValueError),
+ (('-', '...'), ValueError),
+ (('spam', '...'), ValueError),
+ ])
+ for kind in PreprocessorDirective.KINDS:
+ if kind in OtherDirective.KINDS:
+ continue
+ tests.append(
+ ((kind, None), ValueError))
+ # text
+ for kind in OtherDirective.KINDS:
+ if kind in ('else', 'endif'):
+ tests.extend([
+ # Any text is invalid.
+ ((kind, 'SPAM'), ValueError),
+ ((kind, '...'), ValueError),
+ ])
+ else:
+ tests.extend([
+ ((kind, None), TypeError),
+ # Any other text is valid.
+ ])
+ for args, exctype in tests:
+ with self.subTest(args):
+ directive = OtherDirective(*args)
+
+ with self.assertRaises(exctype):
+ directive.validate()
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_symbols/__init__.py b/Lib/test/test_tools/test_c_analyzer/test_c_symbols/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_symbols/__init__.py
diff --git a/Lib/test/test_tools/test_c_analyzer/test_c_symbols/test_info.py b/Lib/test/test_tools/test_c_analyzer/test_c_symbols/test_info.py
new file mode 100644
index 0000000..e029dcf
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/test_c_symbols/test_info.py
@@ -0,0 +1,192 @@
+import string
+import unittest
+
+from ..util import PseudoStr, StrProxy, Object
+from .. import tool_imports_for_tests
+with tool_imports_for_tests():
+ from c_analyzer_common.info import ID
+ from c_symbols.info import Symbol
+
+
+class SymbolTests(unittest.TestCase):
+
+ VALID_ARGS = (
+ ID('x/y/z/spam.c', 'func', 'eggs'),
+ Symbol.KIND.VARIABLE,
+ False,
+ )
+ VALID_KWARGS = dict(zip(Symbol._fields, VALID_ARGS))
+ VALID_EXPECTED = VALID_ARGS
+
+ def test_init_typical_binary_local(self):
+ id = ID(None, None, 'spam')
+ symbol = Symbol(
+ id=id,
+ kind=Symbol.KIND.VARIABLE,
+ external=False,
+ )
+
+ self.assertEqual(symbol, (
+ id,
+ Symbol.KIND.VARIABLE,
+ False,
+ ))
+
+ def test_init_typical_binary_global(self):
+ id = ID('Python/ceval.c', None, 'spam')
+ symbol = Symbol(
+ id=id,
+ kind=Symbol.KIND.VARIABLE,
+ external=False,
+ )
+
+ self.assertEqual(symbol, (
+ id,
+ Symbol.KIND.VARIABLE,
+ False,
+ ))
+
+ def test_init_coercion(self):
+ tests = [
+ ('str subclass',
+ dict(
+ id=PseudoStr('eggs'),
+ kind=PseudoStr('variable'),
+ external=0,
+ ),
+ (ID(None, None, 'eggs'),
+ Symbol.KIND.VARIABLE,
+ False,
+ )),
+ ('with filename',
+ dict(
+ id=('x/y/z/spam.c', 'eggs'),
+ kind=PseudoStr('variable'),
+ external=0,
+ ),
+ (ID('x/y/z/spam.c', None, 'eggs'),
+ Symbol.KIND.VARIABLE,
+ False,
+ )),
+ ('non-str 1',
+ dict(
+ id=('a', 'b', 'c'),
+ kind=StrProxy('variable'),
+ external=0,
+ ),
+ (ID('a', 'b', 'c'),
+ Symbol.KIND.VARIABLE,
+ False,
+ )),
+ ('non-str 2',
+ dict(
+ id=('a', 'b', 'c'),
+ kind=Object(),
+ external=0,
+ ),
+ (ID('a', 'b', 'c'),
+ '<object>',
+ False,
+ )),
+ ]
+ for summary, kwargs, expected in tests:
+ with self.subTest(summary):
+ symbol = Symbol(**kwargs)
+
+ for field in Symbol._fields:
+ value = getattr(symbol, field)
+ if field == 'external':
+ self.assertIs(type(value), bool)
+ elif field == 'id':
+ self.assertIs(type(value), ID)
+ else:
+ self.assertIs(type(value), str)
+ self.assertEqual(tuple(symbol), expected)
+
+ def test_init_all_missing(self):
+ id = ID(None, None, 'spam')
+
+ symbol = Symbol(id)
+
+ self.assertEqual(symbol, (
+ id,
+ Symbol.KIND.VARIABLE,
+ None,
+ ))
+
+ def test_fields(self):
+ id = ID('z', 'x', 'a')
+
+ symbol = Symbol(id, 'b', False)
+
+ self.assertEqual(symbol.id, id)
+ self.assertEqual(symbol.kind, 'b')
+ self.assertIs(symbol.external, False)
+
+ def test___getattr__(self):
+ id = ID('z', 'x', 'a')
+ symbol = Symbol(id, 'b', False)
+
+ filename = symbol.filename
+ funcname = symbol.funcname
+ name = symbol.name
+
+ self.assertEqual(filename, 'z')
+ self.assertEqual(funcname, 'x')
+ self.assertEqual(name, 'a')
+
+ def test_validate_typical(self):
+ id = ID('z', 'x', 'a')
+
+ symbol = Symbol(
+ id=id,
+ kind=Symbol.KIND.VARIABLE,
+ external=False,
+ )
+
+ symbol.validate() # This does not fail.
+
+ def test_validate_missing_field(self):
+ for field in Symbol._fields:
+ with self.subTest(field):
+ symbol = Symbol(**self.VALID_KWARGS)
+ symbol = symbol._replace(**{field: None})
+
+ with self.assertRaises(TypeError):
+ symbol.validate()
+
+ def test_validate_bad_field(self):
+ badch = tuple(c for c in string.punctuation + string.digits)
+ notnames = (
+ '1a',
+ 'a.b',
+ 'a-b',
+ '&a',
+ 'a++',
+ ) + badch
+ tests = [
+ ('id', notnames),
+ ('kind', ('bogus',)),
+ ]
+ seen = set()
+ for field, invalid in tests:
+ for value in invalid:
+ if field != 'kind':
+ seen.add(value)
+ with self.subTest(f'{field}={value!r}'):
+ symbol = Symbol(**self.VALID_KWARGS)
+ symbol = symbol._replace(**{field: value})
+
+ with self.assertRaises(ValueError):
+ symbol.validate()
+
+ for field, invalid in tests:
+ if field == 'kind':
+ continue
+ valid = seen - set(invalid)
+ for value in valid:
+ with self.subTest(f'{field}={value!r}'):
+ symbol = Symbol(**self.VALID_KWARGS)
+ symbol = symbol._replace(**{field: value})
+
+ symbol.validate() # This does not fail.
diff --git a/Lib/test/test_tools/test_c_analyzer/util.py b/Lib/test/test_tools/test_c_analyzer/util.py
new file mode 100644
index 0000000..ba73b0a
--- /dev/null
+++ b/Lib/test/test_tools/test_c_analyzer/util.py
@@ -0,0 +1,60 @@
+import itertools
+
+
+class PseudoStr(str):
+ pass
+
+
+class StrProxy:
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return self.value
+ def __bool__(self):
+ return bool(self.value)
+
+
+class Object:
+ def __repr__(self):
+ return '<object>'
+
+
+def wrapped_arg_combos(*args,
+ wrappers=(PseudoStr, StrProxy),
+ skip=(lambda w, i, v: not isinstance(v, str)),
+ ):
+ """Yield every possible combination of wrapped items for the given args.
+
+ Effectively, the wrappers are applied to the args according to the
+ powerset of the args indicies. So the result includes the args
+ completely unwrapped.
+
+ If "skip" is supplied (default is to skip all non-str values) and
+ it returns True for a given arg index/value then that arg will
+ remain unwrapped,
+
+ Only unique results are returned. If an arg was skipped for one
+ of the combinations then it could end up matching one of the other
+ combinations. In that case only one of them will be yielded.
+ """
+ if not args:
+ return
+ indices = list(range(len(args)))
+ # The powerset (from recipe in the itertools docs).
+ combos = itertools.chain.from_iterable(itertools.combinations(indices, r)
+ for r in range(len(indices)+1))
+ seen = set()
+ for combo in combos:
+ for wrap in wrappers:
+ indexes = []
+ applied = list(args)
+ for i in combo:
+ arg = args[i]
+ if skip and skip(wrap, i, arg):
+ continue
+ indexes.append(i)
+ applied[i] = wrap(arg)
+ key = (wrap, tuple(indexes))
+ if key not in seen:
+ yield tuple(applied)
+ seen.add(key)