diff options
Diffstat (limited to 'bindings')
22 files changed, 355 insertions, 50 deletions
diff --git a/bindings/python/README.txt b/bindings/python/README.txt index 8a0bf99b30e3..b0f0142a56f1 100644 --- a/bindings/python/README.txt +++ b/bindings/python/README.txt @@ -4,12 +4,12 @@ This directory implements Python bindings for Clang. -You may need to alter LD_LIBRARY_PATH so that the Clang library can be +You may need to set CLANG_LIBRARY_PATH so that the Clang library can be found. The unit tests are designed to be run with any standard test runner. For example: -- $ env PYTHONPATH=$(echo ~/llvm/tools/clang/bindings/python/) \ - LD_LIBRARY_PATH=$(llvm-config --libdir) \ + CLANG_LIBRARY_PATH=$(llvm-config --libdir) \ python -m unittest discover -v tests.cindex.test_index.test_create ... ok ... diff --git a/bindings/python/clang/cindex.py b/bindings/python/clang/cindex.py index 56fcc78763e3..8b23ae90b982 100644 --- a/bindings/python/clang/cindex.py +++ b/bindings/python/clang/cindex.py @@ -44,6 +44,7 @@ The major indexing objects are: Most object information is exposed using properties, when the underlying API call is efficient. """ +from __future__ import absolute_import, division, print_function # TODO # ==== @@ -63,10 +64,10 @@ call is efficient. # o implement additional SourceLocation, SourceRange, and File methods. from ctypes import * -import collections import clang.enumerations +import os import sys if sys.version_info[0] == 3: # Python 3 strings are unicode, translate them to/from utf8 for C-interop. @@ -108,8 +109,6 @@ if sys.version_info[0] == 3: return x return x.encode('utf8') - xrange = range - elif sys.version_info[0] == 2: # Python 2 strings are utf8 byte strings, no translation is needed for # C-interop. @@ -123,6 +122,22 @@ elif sys.version_info[0] == 2: def b(x): return x +# Importing ABC-s directly from collections is deprecated since Python 3.7, +# will stop working in Python 3.8. +# See: https://docs.python.org/dev/whatsnew/3.7.html#id3 +if sys.version_info[:2] >= (3, 7): + from collections import abc as collections_abc +else: + import collections as collections_abc + +# We only support PathLike objects on Python version with os.fspath present +# to be consistent with the Python standard library. On older Python versions +# we only support strings and we have dummy fspath to just pass them through. +try: + fspath = os.fspath +except AttributeError: + def fspath(x): + return x # ctypes doesn't implicitly convert c_void_p to the appropriate wrapper # object. This is a problem, because it means that from_parameter will see an @@ -391,7 +406,7 @@ class Diagnostic(object): @property def ranges(self): - class RangeIterator: + class RangeIterator(object): def __init__(self, diag): self.diag = diag @@ -407,7 +422,7 @@ class Diagnostic(object): @property def fixits(self): - class FixItIterator: + class FixItIterator(object): def __init__(self, diag): self.diag = diag @@ -427,7 +442,7 @@ class Diagnostic(object): @property def children(self): - class ChildDiagnosticsIterator: + class ChildDiagnosticsIterator(object): def __init__(self, diag): self.diag_set = conf.lib.clang_getChildDiagnostics(diag) @@ -547,7 +562,7 @@ class TokenGroup(object): token_group = TokenGroup(tu, tokens_memory, tokens_count) - for i in xrange(0, count): + for i in range(0, count): token = Token() token.int_data = tokens_array[i].int_data token.ptr_data = tokens_array[i].ptr_data @@ -2173,7 +2188,7 @@ class Type(Structure): The returned object is iterable and indexable. Each item in the container is a Type instance. """ - class ArgumentsIterator(collections.Sequence): + class ArgumentsIterator(collections_abc.Sequence): def __init__(self, parent): self.parent = parent self.length = None @@ -2254,6 +2269,12 @@ class Type(Structure): return res + def get_num_template_arguments(self): + return conf.lib.clang_Type_getNumTemplateArguments(self) + + def get_template_argument_type(self, num): + return conf.lib.clang_Type_getTemplateArgumentAsType(self, num) + def get_canonical(self): """ Return the canonical type for a Type. @@ -2460,8 +2481,8 @@ SpellingCache = { # 20: CompletionChunk.Kind("VerticalSpace") } -class CompletionChunk: - class Kind: +class CompletionChunk(object): + class Kind(object): def __init__(self, name): self.name = name @@ -2548,7 +2569,7 @@ completionChunkKindMap = { 20: CompletionChunk.Kind("VerticalSpace")} class CompletionString(ClangObject): - class Availability: + class Availability(object): def __init__(self, name): self.name = name @@ -2641,7 +2662,7 @@ class CodeCompletionResults(ClangObject): @property def diagnostics(self): - class DiagnosticsItr: + class DiagnosticsItr(object): def __init__(self, ccr): self.ccr= ccr @@ -2746,11 +2767,11 @@ class TranslationUnit(ClangObject): etc. e.g. ["-Wall", "-I/path/to/include"]. In-memory file content can be provided via unsaved_files. This is an - iterable of 2-tuples. The first element is the str filename. The - second element defines the content. Content can be provided as str - source code or as file objects (anything with a read() method). If - a file object is being used, content will be read until EOF and the - read cursor will not be reset to its original position. + iterable of 2-tuples. The first element is the filename (str or + PathLike). The second element defines the content. Content can be + provided as str source code or as file objects (anything with a read() + method). If a file object is being used, content will be read until EOF + and the read cursor will not be reset to its original position. options is a bitwise or of TranslationUnit.PARSE_XXX flags which will control parsing behavior. @@ -2795,11 +2816,13 @@ class TranslationUnit(ClangObject): if hasattr(contents, "read"): contents = contents.read() - unsaved_array[i].name = b(name) + unsaved_array[i].name = b(fspath(name)) unsaved_array[i].contents = b(contents) unsaved_array[i].length = len(contents) - ptr = conf.lib.clang_parseTranslationUnit(index, filename, args_array, + ptr = conf.lib.clang_parseTranslationUnit(index, + fspath(filename) if filename is not None else None, + args_array, len(args), unsaved_array, len(unsaved_files), options) @@ -2820,11 +2843,13 @@ class TranslationUnit(ClangObject): index is optional and is the Index instance to use. If not provided, a default Index will be created. + + filename can be str or PathLike. """ if index is None: index = Index.create() - ptr = conf.lib.clang_createTranslationUnit(index, filename) + ptr = conf.lib.clang_createTranslationUnit(index, fspath(filename)) if not ptr: raise TranslationUnitLoadError(filename) @@ -2939,7 +2964,7 @@ class TranslationUnit(ClangObject): """ Return an iterable (and indexable) object containing the diagnostics. """ - class DiagIterator: + class DiagIterator(object): def __init__(self, tu): self.tu = tu @@ -2977,7 +3002,7 @@ class TranslationUnit(ClangObject): print(value) if not isinstance(value, str): raise TypeError('Unexpected unsaved file contents.') - unsaved_files_array[i].name = name + unsaved_files_array[i].name = fspath(name) unsaved_files_array[i].contents = value unsaved_files_array[i].length = len(value) ptr = conf.lib.clang_reparseTranslationUnit(self, len(unsaved_files), @@ -2996,10 +3021,10 @@ class TranslationUnit(ClangObject): case, the reason(s) why should be available via TranslationUnit.diagnostics(). - filename -- The path to save the translation unit to. + filename -- The path to save the translation unit to (str or PathLike). """ options = conf.lib.clang_defaultSaveOptions(self) - result = int(conf.lib.clang_saveTranslationUnit(self, filename, + result = int(conf.lib.clang_saveTranslationUnit(self, fspath(filename), options)) if result != 0: raise TranslationUnitSaveError(result, @@ -3041,10 +3066,10 @@ class TranslationUnit(ClangObject): print(value) if not isinstance(value, str): raise TypeError('Unexpected unsaved file contents.') - unsaved_files_array[i].name = b(name) + unsaved_files_array[i].name = b(fspath(name)) unsaved_files_array[i].contents = b(value) unsaved_files_array[i].length = len(value) - ptr = conf.lib.clang_codeCompleteAt(self, path, line, column, + ptr = conf.lib.clang_codeCompleteAt(self, fspath(path), line, column, unsaved_files_array, len(unsaved_files), options) if ptr: return CodeCompletionResults(ptr) @@ -3072,7 +3097,7 @@ class File(ClangObject): @staticmethod def from_name(translation_unit, file_name): """Retrieve a file handle within the given translation unit.""" - return File(conf.lib.clang_getFile(translation_unit, file_name)) + return File(conf.lib.clang_getFile(translation_unit, fspath(file_name))) @property def name(self): @@ -3171,7 +3196,7 @@ class CompileCommand(object): Invariant : the first argument is the compiler executable """ length = conf.lib.clang_CompileCommand_getNumArgs(self.cmd) - for i in xrange(length): + for i in range(length): yield conf.lib.clang_CompileCommand_getArg(self.cmd, i) class CompileCommands(object): @@ -3223,7 +3248,7 @@ class CompilationDatabase(ClangObject): """Builds a CompilationDatabase from the database found in buildDir""" errorCode = c_uint() try: - cdb = conf.lib.clang_CompilationDatabase_fromDirectory(buildDir, + cdb = conf.lib.clang_CompilationDatabase_fromDirectory(fspath(buildDir), byref(errorCode)) except CompilationDatabaseError as e: raise CompilationDatabaseError(int(errorCode.value), @@ -3236,7 +3261,7 @@ class CompilationDatabase(ClangObject): build filename. Returns None if filename is not found in the database. """ return conf.lib.clang_CompilationDatabase_getCompileCommands(self, - filename) + fspath(filename)) def getAllCompileCommands(self): """ @@ -3999,6 +4024,15 @@ functionList = [ Type, Type.from_result), + ("clang_Type_getNumTemplateArguments", + [Type], + c_int), + + ("clang_Type_getTemplateArgumentAsType", + [Type, c_uint], + Type, + Type.from_result), + ("clang_Type_getOffsetOf", [Type, c_interop_string], c_longlong), @@ -4062,7 +4096,7 @@ def register_functions(lib, ignore_errors): for f in functionList: register(f) -class Config: +class Config(object): library_path = None library_file = None compatibility_check = True @@ -4075,7 +4109,7 @@ class Config: raise Exception("library path must be set before before using " \ "any other functionalities in libclang.") - Config.library_path = path + Config.library_path = fspath(path) @staticmethod def set_library_file(filename): @@ -4084,7 +4118,7 @@ class Config: raise Exception("library file must be set before before using " \ "any other functionalities in libclang.") - Config.library_file = filename + Config.library_file = fspath(filename) @staticmethod def set_compatibility_check(check_status): diff --git a/bindings/python/examples/cindex/cindex-dump.py b/bindings/python/examples/cindex/cindex-dump.py index 5556ad121a3e..acec7e0e0054 100644 --- a/bindings/python/examples/cindex/cindex-dump.py +++ b/bindings/python/examples/cindex/cindex-dump.py @@ -79,7 +79,7 @@ def main(): if not tu: parser.error("unable to load input") - pprint(('diags', map(get_diag_info, tu.diagnostics))) + pprint(('diags', [get_diag_info(d) for d in tu.diagnostics])) pprint(('nodes', get_info(tu.cursor))) if __name__ == '__main__': diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt new file mode 100644 index 000000000000..7af6503f1588 --- /dev/null +++ b/bindings/python/tests/CMakeLists.txt @@ -0,0 +1,46 @@ +# Test target to run Python test suite from main build. + +add_custom_target(check-clang-python + COMMAND ${CMAKE_COMMAND} -E env + CLANG_LIBRARY_PATH=$<TARGET_FILE_DIR:libclang> + ${PYTHON_EXECUTABLE} -m unittest discover + DEPENDS libclang + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..) + +set(RUN_PYTHON_TESTS TRUE) +set_target_properties(check-clang-python PROPERTIES FOLDER "Clang tests") + +# Tests require libclang.so which is only built with LLVM_ENABLE_PIC=ON +if(NOT LLVM_ENABLE_PIC) + set(RUN_PYTHON_TESTS FALSE) +endif() + +# Do not try to run if libclang was built with ASan because +# the sanitizer library will likely be loaded too late to perform +# interception and will then fail. +# We could use LD_PRELOAD/DYLD_INSERT_LIBRARIES but this isn't +# portable so its easier just to not run the tests when building +# with ASan. +list(FIND LLVM_USE_SANITIZER "Address" LLVM_USE_ASAN_INDEX) +if(NOT LLVM_USE_ASAN_INDEX EQUAL -1) + set(RUN_PYTHON_TESTS FALSE) +endif() + +# Tests fail on Windows, and need someone knowledgeable to fix. +# It's not clear whether it's a test or a valid binding problem. +if(WIN32) + set(RUN_PYTHON_TESTS FALSE) +endif() + +# AArch64 and Hexagon have known test failures that need to be +# addressed. +# SystemZ has broken Python/FFI interface: +# https://reviews.llvm.org/D52840#1265716 +if(${LLVM_NATIVE_ARCH} MATCHES "^(AArch64|Hexagon|SystemZ)$") + set(RUN_PYTHON_TESTS FALSE) +endif() + +if(RUN_PYTHON_TESTS) + set_property(GLOBAL APPEND PROPERTY + LLVM_ADDITIONAL_TEST_TARGETS check-clang-python) +endif() diff --git a/bindings/python/tests/cindex/test_access_specifiers.py b/bindings/python/tests/cindex/test_access_specifiers.py index 2f6144be082c..e36424f240aa 100644 --- a/bindings/python/tests/cindex/test_access_specifiers.py +++ b/bindings/python/tests/cindex/test_access_specifiers.py @@ -1,3 +1,7 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) from clang.cindex import AccessSpecifier from clang.cindex import Cursor diff --git a/bindings/python/tests/cindex/test_cdb.py b/bindings/python/tests/cindex/test_cdb.py index 64651af31732..589fc72856bd 100644 --- a/bindings/python/tests/cindex/test_cdb.py +++ b/bindings/python/tests/cindex/test_cdb.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import CompilationDatabase from clang.cindex import CompilationDatabaseError from clang.cindex import CompileCommands @@ -6,6 +11,8 @@ import os import gc import unittest import sys +from .util import skip_if_no_fspath +from .util import str_to_path kInputsDir = os.path.join(os.path.dirname(__file__), 'INPUTS') @@ -26,17 +33,19 @@ class TestCDB(unittest.TestCase): """Check we can load a compilation database""" cdb = CompilationDatabase.fromDirectory(kInputsDir) - def test_lookup_fail(self): - """Check file lookup failure""" - cdb = CompilationDatabase.fromDirectory(kInputsDir) - self.assertIsNone(cdb.getCompileCommands('file_do_not_exist.cpp')) - def test_lookup_succeed(self): """Check we get some results if the file exists in the db""" cdb = CompilationDatabase.fromDirectory(kInputsDir) cmds = cdb.getCompileCommands('/home/john.doe/MyProject/project.cpp') self.assertNotEqual(len(cmds), 0) + @skip_if_no_fspath + def test_lookup_succeed_pathlike(self): + """Same as test_lookup_succeed, but with PathLikes""" + cdb = CompilationDatabase.fromDirectory(str_to_path(kInputsDir)) + cmds = cdb.getCompileCommands(str_to_path('/home/john.doe/MyProject/project.cpp')) + self.assertNotEqual(len(cmds), 0) + def test_all_compilecommand(self): """Check we get all results from the db""" cdb = CompilationDatabase.fromDirectory(kInputsDir) diff --git a/bindings/python/tests/cindex/test_code_completion.py b/bindings/python/tests/cindex/test_code_completion.py index a56bb304cd71..e0b41577aeb3 100644 --- a/bindings/python/tests/cindex/test_code_completion.py +++ b/bindings/python/tests/cindex/test_code_completion.py @@ -1,6 +1,13 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import TranslationUnit import unittest +from .util import skip_if_no_fspath +from .util import str_to_path class TestCodeCompletion(unittest.TestCase): @@ -38,6 +45,32 @@ void f() { ] self.check_completion_results(cr, expected) + @skip_if_no_fspath + def test_code_complete_pathlike(self): + files = [(str_to_path('fake.c'), """ +/// Aaa. +int test1; + +/// Bbb. +void test2(void); + +void f() { + +} +""")] + + tu = TranslationUnit.from_source(str_to_path('fake.c'), ['-std=c99'], unsaved_files=files, + options=TranslationUnit.PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION) + + cr = tu.codeComplete(str_to_path('fake.c'), 9, 1, unsaved_files=files, include_brief_comments=True) + + expected = [ + "{'int', ResultType} | {'test1', TypedText} || Priority: 50 || Availability: Available || Brief comment: Aaa.", + "{'void', ResultType} | {'test2', TypedText} | {'(', LeftParen} | {')', RightParen} || Priority: 50 || Availability: Available || Brief comment: Bbb.", + "{'return', TypedText} || Priority: 40 || Availability: Available || Brief comment: None" + ] + self.check_completion_results(cr, expected) + def test_code_complete_availability(self): files = [('fake.cpp', """ class P { @@ -61,11 +94,11 @@ void f(P x, Q y) { cr = tu.codeComplete('fake.cpp', 12, 5, unsaved_files=files) expected = [ - "{'const', TypedText} || Priority: 40 || Availability: Available || Brief comment: None", - "{'volatile', TypedText} || Priority: 40 || Availability: Available || Brief comment: None", + "{'const', TypedText} || Priority: 50 || Availability: Available || Brief comment: None", + "{'volatile', TypedText} || Priority: 50 || Availability: Available || Brief comment: None", "{'operator', TypedText} || Priority: 40 || Availability: Available || Brief comment: None", - "{'P', TypedText} | {'::', Text} || Priority: 75 || Availability: Available || Brief comment: None", - "{'Q', TypedText} | {'::', Text} || Priority: 75 || Availability: Available || Brief comment: None" + "{'P', TypedText} || Priority: 50 || Availability: Available || Brief comment: None", + "{'Q', TypedText} || Priority: 50 || Availability: Available || Brief comment: None" ] self.check_completion_results(cr, expected) diff --git a/bindings/python/tests/cindex/test_comment.py b/bindings/python/tests/cindex/test_comment.py index d6c6d8e5c5b0..73fb617ae168 100644 --- a/bindings/python/tests/cindex/test_comment.py +++ b/bindings/python/tests/cindex/test_comment.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import TranslationUnit from tests.cindex.util import get_cursor diff --git a/bindings/python/tests/cindex/test_cursor.py b/bindings/python/tests/cindex/test_cursor.py index f5733fd15879..ef875e972474 100644 --- a/bindings/python/tests/cindex/test_cursor.py +++ b/bindings/python/tests/cindex/test_cursor.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + import ctypes import gc import unittest diff --git a/bindings/python/tests/cindex/test_cursor_kind.py b/bindings/python/tests/cindex/test_cursor_kind.py index f1ee753ef8b1..e6b9558b3cc1 100644 --- a/bindings/python/tests/cindex/test_cursor_kind.py +++ b/bindings/python/tests/cindex/test_cursor_kind.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import CursorKind import unittest diff --git a/bindings/python/tests/cindex/test_diagnostics.py b/bindings/python/tests/cindex/test_diagnostics.py index 78b327daa72c..c17d5b28efe9 100644 --- a/bindings/python/tests/cindex/test_diagnostics.py +++ b/bindings/python/tests/cindex/test_diagnostics.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import * from .util import get_tu @@ -46,7 +51,7 @@ class TestDiagnostics(unittest.TestCase): self.assertEqual(tu.diagnostics[0].fixits[0].value, '.f0 = ') def test_diagnostic_range(self): - tu = get_tu('void f() { int i = "a" + 1; }') + tu = get_tu('void f() { int i = "a"; }') self.assertEqual(len(tu.diagnostics), 1) self.assertEqual(tu.diagnostics[0].severity, Diagnostic.Warning) self.assertEqual(tu.diagnostics[0].location.line, 1) @@ -58,7 +63,7 @@ class TestDiagnostics(unittest.TestCase): self.assertEqual(tu.diagnostics[0].ranges[0].start.line, 1) self.assertEqual(tu.diagnostics[0].ranges[0].start.column, 20) self.assertEqual(tu.diagnostics[0].ranges[0].end.line, 1) - self.assertEqual(tu.diagnostics[0].ranges[0].end.column, 27) + self.assertEqual(tu.diagnostics[0].ranges[0].end.column, 23) with self.assertRaises(IndexError): tu.diagnostics[0].ranges[1].start.line diff --git a/bindings/python/tests/cindex/test_exception_specification_kind.py b/bindings/python/tests/cindex/test_exception_specification_kind.py index 80b3639a8ab3..6c13f70fb256 100644 --- a/bindings/python/tests/cindex/test_exception_specification_kind.py +++ b/bindings/python/tests/cindex/test_exception_specification_kind.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + import clang.cindex from clang.cindex import ExceptionSpecificationKind from .util import get_tu diff --git a/bindings/python/tests/cindex/test_file.py b/bindings/python/tests/cindex/test_file.py index 98f6575262cc..a146fe5c9239 100644 --- a/bindings/python/tests/cindex/test_file.py +++ b/bindings/python/tests/cindex/test_file.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import Index, File import unittest diff --git a/bindings/python/tests/cindex/test_index.py b/bindings/python/tests/cindex/test_index.py index cfdf98e628ac..46aafcf222e7 100644 --- a/bindings/python/tests/cindex/test_index.py +++ b/bindings/python/tests/cindex/test_index.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import * import os import unittest diff --git a/bindings/python/tests/cindex/test_linkage.py b/bindings/python/tests/cindex/test_linkage.py index 6b482f8081b3..cdd97fc2df0d 100644 --- a/bindings/python/tests/cindex/test_linkage.py +++ b/bindings/python/tests/cindex/test_linkage.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import LinkageKind from clang.cindex import Cursor from clang.cindex import TranslationUnit diff --git a/bindings/python/tests/cindex/test_location.py b/bindings/python/tests/cindex/test_location.py index cbc32deb4bd5..fbe9770d7ebc 100644 --- a/bindings/python/tests/cindex/test_location.py +++ b/bindings/python/tests/cindex/test_location.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import Cursor from clang.cindex import File from clang.cindex import SourceLocation diff --git a/bindings/python/tests/cindex/test_tls_kind.py b/bindings/python/tests/cindex/test_tls_kind.py index fbc3418a64ef..c828ac83a468 100644 --- a/bindings/python/tests/cindex/test_tls_kind.py +++ b/bindings/python/tests/cindex/test_tls_kind.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import TLSKind from clang.cindex import Cursor from clang.cindex import TranslationUnit diff --git a/bindings/python/tests/cindex/test_token_kind.py b/bindings/python/tests/cindex/test_token_kind.py index 700f95a64624..904e007cafc5 100644 --- a/bindings/python/tests/cindex/test_token_kind.py +++ b/bindings/python/tests/cindex/test_token_kind.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import TokenKind import unittest diff --git a/bindings/python/tests/cindex/test_tokens.py b/bindings/python/tests/cindex/test_tokens.py index c93353dc9da2..dd6d3a3259ed 100644 --- a/bindings/python/tests/cindex/test_tokens.py +++ b/bindings/python/tests/cindex/test_tokens.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from clang.cindex import CursorKind from clang.cindex import Index from clang.cindex import SourceLocation diff --git a/bindings/python/tests/cindex/test_translation_unit.py b/bindings/python/tests/cindex/test_translation_unit.py index d3ee535f4d0d..f3e770a93611 100644 --- a/bindings/python/tests/cindex/test_translation_unit.py +++ b/bindings/python/tests/cindex/test_translation_unit.py @@ -1,6 +1,12 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + from contextlib import contextmanager import gc import os +import sys import tempfile import unittest @@ -15,6 +21,8 @@ from clang.cindex import TranslationUnitLoadError from clang.cindex import TranslationUnit from .util import get_cursor from .util import get_tu +from .util import skip_if_no_fspath +from .util import str_to_path kInputsDir = os.path.join(os.path.dirname(__file__), 'INPUTS') @@ -31,6 +39,17 @@ def save_tu(tu): yield t.name +@contextmanager +def save_tu_pathlike(tu): + """Convenience API to save a TranslationUnit to a file. + + Returns the filename it was saved to. + """ + with tempfile.NamedTemporaryFile() as t: + tu.save(str_to_path(t.name)) + yield t.name + + class TestTranslationUnit(unittest.TestCase): def test_spelling(self): path = os.path.join(kInputsDir, 'hello.cpp') @@ -75,15 +94,31 @@ int SOME_DEFINE; self.assertEqual(spellings[-1], 'y') def test_unsaved_files_2(self): - try: - from StringIO import StringIO - except: + if sys.version_info.major >= 3: from io import StringIO + else: + from io import BytesIO as StringIO tu = TranslationUnit.from_source('fake.c', unsaved_files = [ ('fake.c', StringIO('int x;'))]) spellings = [c.spelling for c in tu.cursor.get_children()] self.assertEqual(spellings[-1], 'x') + @skip_if_no_fspath + def test_from_source_accepts_pathlike(self): + tu = TranslationUnit.from_source(str_to_path('fake.c'), ['-Iincludes'], unsaved_files = [ + (str_to_path('fake.c'), """ +#include "fake.h" + int x; + int SOME_DEFINE; + """), + (str_to_path('includes/fake.h'), """ +#define SOME_DEFINE y + """) + ]) + spellings = [c.spelling for c in tu.cursor.get_children()] + self.assertEqual(spellings[-2], 'x') + self.assertEqual(spellings[-1], 'y') + def assert_normpaths_equal(self, path1, path2): """ Compares two paths for equality after normalizing them with os.path.normpath @@ -130,6 +165,16 @@ int SOME_DEFINE; self.assertTrue(os.path.exists(path)) self.assertGreater(os.path.getsize(path), 0) + @skip_if_no_fspath + def test_save_pathlike(self): + """Ensure TranslationUnit.save() works with PathLike filename.""" + + tu = get_tu('int foo();') + + with save_tu_pathlike(tu) as path: + self.assertTrue(os.path.exists(path)) + self.assertGreater(os.path.getsize(path), 0) + def test_save_translation_errors(self): """Ensure that saving to an invalid directory raises.""" @@ -162,6 +207,22 @@ int SOME_DEFINE; # Just in case there is an open file descriptor somewhere. del tu2 + @skip_if_no_fspath + def test_load_pathlike(self): + """Ensure TranslationUnits can be constructed from saved files - + PathLike variant.""" + tu = get_tu('int foo();') + self.assertEqual(len(tu.diagnostics), 0) + with save_tu(tu) as path: + tu2 = TranslationUnit.from_ast_file(filename=str_to_path(path)) + self.assertEqual(len(tu2.diagnostics), 0) + + foo = get_cursor(tu2, 'foo') + self.assertIsNotNone(foo) + + # Just in case there is an open file descriptor somewhere. + del tu2 + def test_index_parse(self): path = os.path.join(kInputsDir, 'hello.cpp') index = Index.create() @@ -180,6 +241,19 @@ int SOME_DEFINE; with self.assertRaises(Exception): f = tu.get_file('foobar.cpp') + @skip_if_no_fspath + def test_get_file_pathlike(self): + """Ensure tu.get_file() works appropriately with PathLike filenames.""" + + tu = get_tu('int foo();') + + f = tu.get_file(str_to_path('t.c')) + self.assertIsInstance(f, File) + self.assertEqual(f.name, 't.c') + + with self.assertRaises(Exception): + f = tu.get_file(str_to_path('foobar.cpp')) + def test_get_source_location(self): """Ensure tu.get_source_location() works.""" diff --git a/bindings/python/tests/cindex/test_type.py b/bindings/python/tests/cindex/test_type.py index 4dec0583c7b0..bcdbeff9d99c 100644 --- a/bindings/python/tests/cindex/test_type.py +++ b/bindings/python/tests/cindex/test_type.py @@ -1,3 +1,8 @@ +import os +from clang.cindex import Config +if 'CLANG_LIBRARY_PATH' in os.environ: + Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) + import gc import unittest @@ -436,3 +441,28 @@ class TestType(unittest.TestCase): self.assertIsNotNone(testInteger, "Could not find testInteger.") self.assertEqual(testInteger.type.get_address_space(), 2) + + def test_template_arguments(self): + source = """ + class Foo { + }; + template <typename T> + class Template { + }; + Template<Foo> instance; + int bar; + """ + tu = get_tu(source, lang='cpp') + + # Varible with a template argument. + cursor = get_cursor(tu, 'instance') + cursor_type = cursor.type + self.assertEqual(cursor.kind, CursorKind.VAR_DECL) + self.assertEqual(cursor_type.spelling, 'Template<Foo>') + self.assertEqual(cursor_type.get_num_template_arguments(), 1) + template_type = cursor_type.get_template_argument_type(0) + self.assertEqual(template_type.spelling, 'Foo') + + # Variable without a template argument. + cursor = get_cursor(tu, 'bar') + self.assertEqual(cursor.get_num_template_arguments(), -1) diff --git a/bindings/python/tests/cindex/util.py b/bindings/python/tests/cindex/util.py index c53ba7c81bde..57e17941c558 100644 --- a/bindings/python/tests/cindex/util.py +++ b/bindings/python/tests/cindex/util.py @@ -1,5 +1,15 @@ # This file provides common utility functions for the test suite. +import os +HAS_FSPATH = hasattr(os, 'fspath') + +if HAS_FSPATH: + from pathlib import Path as str_to_path +else: + str_to_path = None + +import unittest + from clang.cindex import Cursor from clang.cindex import TranslationUnit @@ -68,8 +78,13 @@ def get_cursors(source, spelling): return cursors +skip_if_no_fspath = unittest.skipUnless(HAS_FSPATH, + "Requires file system path protocol / Python 3.6+") + __all__ = [ 'get_cursor', 'get_cursors', 'get_tu', + 'skip_if_no_fspath', + 'str_to_path', ] |