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 ', Include('')), ('#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(''), 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 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 \n', Include(''), ()), (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 ', Include('')), # 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 ', Include('')), ('#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('') self.assertIs(type(directive), Include) self.assertIsInstance(directive, PreprocessorDirective) def test_attrs(self): d = Include('') kind, file, text = d.kind, d.file, d.text self.assertEqual(kind, 'include') self.assertEqual(file, '') self.assertEqual(text, '') def test_iter(self): kind, file = Include('') self.assertEqual(kind, 'include') self.assertEqual(file, '') def test_coerce(self): tests = [] for arg, in wrapped_arg_combos(''): tests.append((arg, '')) tests.extend([ (' ', ''), ]) 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 = [ '', '"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()