blob: b7f950f813976370ee8b5dde8637196aeb9d5c25 [file] [log] [blame]
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_analyzer.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()