| 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() |