import textwrap import unittest from .. import tool_imports_for_tests with tool_imports_for_tests(): from c_analyzer.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 = [ [('', None), # var1 ('', None), # non-var ('', None), # var2 ('', ''), # func1 ('', None), # var4 ], ] self._return_iter_local = [ # func1 [('', None), # var3 ('', [('
', '')]), # if ('', None), # non-var ], # if [('', None), # var2 ("collision" with global var) ], ] self._return_parse_func = [ ('func1', ''), ] self._return_parse_var = [ ('var1', ''), (None, None), ('var2', ''), ('var3', ''), ('var2', ''), ('var4', ''), (None, None), (None, None), (None, None), ('var5', ''), ] self._return_parse_compound = [ ([[ 'if (', '', ')', ], ], ['']), ] 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', ''), (None, 'var2', ''), ('func1', 'var3', ''), ('func1', 'var2', ''), ('func1', 'var4', ''), (None, 'var5', ''), ]) self.assertEqual(self.calls, [ ('_iter_source_lines', ('spam.c',)), ('_iter_global', (content.splitlines(),)), ('_parse_var', ('',)), ('_parse_var', ('',)), ('_parse_var', ('',)), ('_parse_func', ('', '')), ('_iter_local', ([''],)), ('_parse_var', ('',)), ('_parse_compound', ('', [('
', '')])), ('_parse_var', ('if (',)), ('_parse_var', ('',)), ('_parse_var', (')',)), ('_parse_var', ('',)), ('_iter_local', ([''],)), ('_parse_var', ('',)), ('_parse_var', ('',)), ]) def test_no_locals(self): content = textwrap.dedent(''' ... ''') self._return_iter_source_lines = content self._return_iter_global = [ [('', None), # var1 ('', None), # non-var ('', None), # var2 ('', ''), # func1 ], ] self._return_iter_local = [ # func1 [('', None), # non-var ('', [('
', '')]), # if ('', None), # non-var ], # if [('', None), # non-var ], ] self._return_parse_func = [ ('func1', ''), ] self._return_parse_var = [ ('var1', ''), (None, None), ('var2', ''), (None, None), (None, None), (None, None), (None, None), (None, None), (None, None), ] self._return_parse_compound = [ ([[ 'if (', '', ')', ], ], ['']), ] 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', ''), (None, 'var2', ''), ]) self.assertEqual(self.calls, [ ('_iter_source_lines', ('spam.c',)), ('_iter_global', (content.splitlines(),)), ('_parse_var', ('',)), ('_parse_var', ('',)), ('_parse_var', ('',)), ('_parse_func', ('', '')), ('_iter_local', ([''],)), ('_parse_var', ('',)), ('_parse_compound', ('', [('
', '')])), ('_parse_var', ('if (',)), ('_parse_var', ('',)), ('_parse_var', (')',)), ('_parse_var', ('',)), ('_iter_local', ([''],)), ('_parse_var', ('',)), ])