blob: 674fcb1af1c7adf924f646bd8f4dba62abe1713f [file] [log] [blame]
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 = [
[('<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>',)),
])