| import unittest |
| from pathlib import Path |
| |
| from clang.cindex import ( |
| AccessSpecifier, |
| AvailabilityKind, |
| BinaryOperator, |
| CursorKind, |
| ExceptionSpecificationKind, |
| LanguageKind, |
| LinkageKind, |
| RefQualifierKind, |
| StorageClass, |
| TemplateArgumentKind, |
| TLSKind, |
| TokenKind, |
| TranslationUnit, |
| TypeKind, |
| PrintingPolicyProperty, |
| BaseEnumeration, |
| ) |
| |
| |
| class TestEnums(unittest.TestCase): |
| enums = BaseEnumeration.__subclasses__() |
| |
| def test_from_id(self): |
| """Check that kinds can be constructed from valid IDs""" |
| for enum in self.enums: |
| self.assertEqual(enum.from_id(2), enum(2)) |
| max_value = max([variant.value for variant in enum]) |
| with self.assertRaises(ValueError): |
| enum.from_id(max_value + 1) |
| with self.assertRaises(ValueError): |
| enum.from_id(-1) |
| |
| def test_all_variants(self): |
| """Check that all libclang enum values are also defined in cindex.py""" |
| cenum_to_pythonenum = { |
| "CX_CXXAccessSpecifier": AccessSpecifier, |
| "CX_StorageClass": StorageClass, |
| "CXAvailabilityKind": AvailabilityKind, |
| "CXBinaryOperatorKind": BinaryOperator, |
| "CXCursorKind": CursorKind, |
| "CXCursor_ExceptionSpecificationKind": ExceptionSpecificationKind, |
| "CXLanguageKind": LanguageKind, |
| "CXLinkageKind": LinkageKind, |
| "CXPrintingPolicyProperty": PrintingPolicyProperty, |
| "CXRefQualifierKind": RefQualifierKind, |
| "CXTemplateArgumentKind": TemplateArgumentKind, |
| "CXTLSKind": TLSKind, |
| "CXTokenKind": TokenKind, |
| "CXTypeKind": TypeKind, |
| } |
| |
| indexheader = ( |
| Path(__file__).parent.parent.parent.parent.parent |
| / "include/clang-c/Index.h" |
| ) |
| # FIXME: Index.h is a C file, but we read it as a C++ file because we |
| # don't get ENUM_CONSTANT_DECL cursors otherwise, which we need here |
| # See bug report: https://github.com/llvm/llvm-project/issues/159075 |
| tu = TranslationUnit.from_source(indexheader, ["-x", "c++"]) |
| |
| enum_variant_map = {} |
| # For all enums in self.enums, extract all enum variants defined in Index.h |
| for cursor in tu.cursor.walk_preorder(): |
| if cursor.kind == CursorKind.ENUM_CONSTANT_DECL: |
| python_enum = cenum_to_pythonenum.get(cursor.type.spelling) |
| if python_enum not in enum_variant_map: |
| enum_variant_map[python_enum] = dict() |
| enum_variant_map[python_enum][cursor.enum_value] = cursor.spelling |
| |
| for enum in self.enums: |
| with self.subTest(enum): |
| # This ensures only the custom assert message below is printed |
| self.longMessage = False |
| |
| python_kinds = set([kind.value for kind in enum]) |
| num_to_c_kind = enum_variant_map[enum] |
| c_kinds = set(num_to_c_kind.keys()) |
| # Defined in Index.h but not in cindex.py |
| missing_python_kinds = c_kinds - python_kinds |
| missing_names = set( |
| [num_to_c_kind[kind] for kind in missing_python_kinds] |
| ) |
| self.assertEqual( |
| missing_names, |
| set(), |
| f"{missing_names} variants are missing. " |
| f"Please ensure these are defined in {enum} in cindex.py.", |
| ) |
| # Defined in cindex.py but not in Index.h |
| superfluous_python_kinds = python_kinds - c_kinds |
| missing_names = set( |
| [enum.from_id(kind) for kind in superfluous_python_kinds] |
| ) |
| self.assertEqual( |
| missing_names, |
| set(), |
| f"{missing_names} variants only exist in the Python bindings. " |
| f"Please ensure that all {enum} kinds defined in cindex.py have an equivalent in Index.h", |
| ) |