2 from clang
.cindex
import Config
4 if "CLANG_LIBRARY_PATH" in os
.environ
:
5 Config
.set_library_path(os
.environ
["CLANG_LIBRARY_PATH"])
7 from contextlib
import contextmanager
14 from clang
.cindex
import CursorKind
15 from clang
.cindex
import Cursor
16 from clang
.cindex
import File
17 from clang
.cindex
import Index
18 from clang
.cindex
import SourceLocation
19 from clang
.cindex
import SourceRange
20 from clang
.cindex
import TranslationUnitSaveError
21 from clang
.cindex
import TranslationUnitLoadError
22 from clang
.cindex
import TranslationUnit
23 from .util
import get_cursor
24 from .util
import get_tu
25 from .util
import skip_if_no_fspath
26 from .util
import str_to_path
29 kInputsDir
= os
.path
.join(os
.path
.dirname(__file__
), "INPUTS")
34 """Convenience API to save a TranslationUnit to a file.
36 Returns the filename it was saved to.
38 with tempfile
.NamedTemporaryFile() as t
:
44 def save_tu_pathlike(tu
):
45 """Convenience API to save a TranslationUnit to a file.
47 Returns the filename it was saved to.
49 with tempfile
.NamedTemporaryFile() as t
:
50 tu
.save(str_to_path(t
.name
))
54 class TestTranslationUnit(unittest
.TestCase
):
55 def test_spelling(self
):
56 path
= os
.path
.join(kInputsDir
, "hello.cpp")
57 tu
= TranslationUnit
.from_source(path
)
58 self
.assertEqual(tu
.spelling
, path
)
60 def test_cursor(self
):
61 path
= os
.path
.join(kInputsDir
, "hello.cpp")
64 self
.assertIsInstance(c
, Cursor
)
65 self
.assertIs(c
.kind
, CursorKind
.TRANSLATION_UNIT
)
67 def test_parse_arguments(self
):
68 path
= os
.path
.join(kInputsDir
, "parse_arguments.c")
69 tu
= TranslationUnit
.from_source(path
, ["-DDECL_ONE=hello", "-DDECL_TWO=hi"])
70 spellings
= [c
.spelling
for c
in tu
.cursor
.get_children()]
71 self
.assertEqual(spellings
[-2], "hello")
72 self
.assertEqual(spellings
[-1], "hi")
74 def test_reparse_arguments(self
):
75 path
= os
.path
.join(kInputsDir
, "parse_arguments.c")
76 tu
= TranslationUnit
.from_source(path
, ["-DDECL_ONE=hello", "-DDECL_TWO=hi"])
78 spellings
= [c
.spelling
for c
in tu
.cursor
.get_children()]
79 self
.assertEqual(spellings
[-2], "hello")
80 self
.assertEqual(spellings
[-1], "hi")
82 def test_unsaved_files(self
):
83 tu
= TranslationUnit
.from_source(
103 spellings
= [c
.spelling
for c
in tu
.cursor
.get_children()]
104 self
.assertEqual(spellings
[-2], "x")
105 self
.assertEqual(spellings
[-1], "y")
107 def test_unsaved_files_2(self
):
108 if sys
.version_info
.major
>= 3:
109 from io
import StringIO
111 from io
import BytesIO
as StringIO
112 tu
= TranslationUnit
.from_source(
113 "fake.c", unsaved_files
=[("fake.c", StringIO("int x;"))]
115 spellings
= [c
.spelling
for c
in tu
.cursor
.get_children()]
116 self
.assertEqual(spellings
[-1], "x")
119 def test_from_source_accepts_pathlike(self
):
120 tu
= TranslationUnit
.from_source(
121 str_to_path("fake.c"),
125 str_to_path("fake.c"),
133 str_to_path("includes/fake.h"),
135 #define SOME_DEFINE y
140 spellings
= [c
.spelling
for c
in tu
.cursor
.get_children()]
141 self
.assertEqual(spellings
[-2], "x")
142 self
.assertEqual(spellings
[-1], "y")
144 def assert_normpaths_equal(self
, path1
, path2
):
145 """Compares two paths for equality after normalizing them with
148 self
.assertEqual(os
.path
.normpath(path1
), os
.path
.normpath(path2
))
150 def test_includes(self
):
151 def eq(expected
, actual
):
152 if not actual
.is_input_file
:
153 self
.assert_normpaths_equal(expected
[0], actual
.source
.name
)
154 self
.assert_normpaths_equal(expected
[1], actual
.include
.name
)
156 self
.assert_normpaths_equal(expected
[1], actual
.include
.name
)
158 src
= os
.path
.join(kInputsDir
, "include.cpp")
159 h1
= os
.path
.join(kInputsDir
, "header1.h")
160 h2
= os
.path
.join(kInputsDir
, "header2.h")
161 h3
= os
.path
.join(kInputsDir
, "header3.h")
162 inc
= [(src
, h1
), (h1
, h3
), (src
, h2
), (h2
, h3
)]
164 tu
= TranslationUnit
.from_source(src
)
165 for i
in zip(inc
, tu
.get_includes()):
168 def test_inclusion_directive(self
):
169 src
= os
.path
.join(kInputsDir
, "include.cpp")
170 h1
= os
.path
.join(kInputsDir
, "header1.h")
171 h2
= os
.path
.join(kInputsDir
, "header2.h")
172 h3
= os
.path
.join(kInputsDir
, "header3.h")
173 inc
= [h1
, h3
, h2
, h3
, h1
]
175 tu
= TranslationUnit
.from_source(
176 src
, options
=TranslationUnit
.PARSE_DETAILED_PROCESSING_RECORD
178 inclusion_directive_files
= [
179 c
.get_included_file().name
180 for c
in tu
.cursor
.get_children()
181 if c
.kind
== CursorKind
.INCLUSION_DIRECTIVE
183 for i
in zip(inc
, inclusion_directive_files
):
184 self
.assert_normpaths_equal(i
[0], i
[1])
187 """Ensure TranslationUnit.save() works."""
189 tu
= get_tu("int foo();")
191 with
save_tu(tu
) as path
:
192 self
.assertTrue(os
.path
.exists(path
))
193 self
.assertGreater(os
.path
.getsize(path
), 0)
196 def test_save_pathlike(self
):
197 """Ensure TranslationUnit.save() works with PathLike filename."""
199 tu
= get_tu("int foo();")
201 with
save_tu_pathlike(tu
) as path
:
202 self
.assertTrue(os
.path
.exists(path
))
203 self
.assertGreater(os
.path
.getsize(path
), 0)
205 def test_save_translation_errors(self
):
206 """Ensure that saving to an invalid directory raises."""
208 tu
= get_tu("int foo();")
210 path
= "/does/not/exist/llvm-test.ast"
211 self
.assertFalse(os
.path
.exists(os
.path
.dirname(path
)))
213 with self
.assertRaises(TranslationUnitSaveError
) as cm
:
216 expected
= TranslationUnitSaveError
.ERROR_UNKNOWN
217 self
.assertEqual(ex
.save_error
, expected
)
220 """Ensure TranslationUnits can be constructed from saved files."""
222 tu
= get_tu("int foo();")
223 self
.assertEqual(len(tu
.diagnostics
), 0)
224 with
save_tu(tu
) as path
:
225 self
.assertTrue(os
.path
.exists(path
))
226 self
.assertGreater(os
.path
.getsize(path
), 0)
228 tu2
= TranslationUnit
.from_ast_file(filename
=path
)
229 self
.assertEqual(len(tu2
.diagnostics
), 0)
231 foo
= get_cursor(tu2
, "foo")
232 self
.assertIsNotNone(foo
)
234 # Just in case there is an open file descriptor somewhere.
238 def test_load_pathlike(self
):
239 """Ensure TranslationUnits can be constructed from saved files -
241 tu
= get_tu("int foo();")
242 self
.assertEqual(len(tu
.diagnostics
), 0)
243 with
save_tu(tu
) as path
:
244 tu2
= TranslationUnit
.from_ast_file(filename
=str_to_path(path
))
245 self
.assertEqual(len(tu2
.diagnostics
), 0)
247 foo
= get_cursor(tu2
, "foo")
248 self
.assertIsNotNone(foo
)
250 # Just in case there is an open file descriptor somewhere.
253 def test_index_parse(self
):
254 path
= os
.path
.join(kInputsDir
, "hello.cpp")
255 index
= Index
.create()
256 tu
= index
.parse(path
)
257 self
.assertIsInstance(tu
, TranslationUnit
)
259 def test_get_file(self
):
260 """Ensure tu.get_file() works appropriately."""
262 tu
= get_tu("int foo();")
264 f
= tu
.get_file("t.c")
265 self
.assertIsInstance(f
, File
)
266 self
.assertEqual(f
.name
, "t.c")
268 with self
.assertRaises(Exception):
269 f
= tu
.get_file("foobar.cpp")
272 def test_get_file_pathlike(self
):
273 """Ensure tu.get_file() works appropriately with PathLike filenames."""
275 tu
= get_tu("int foo();")
277 f
= tu
.get_file(str_to_path("t.c"))
278 self
.assertIsInstance(f
, File
)
279 self
.assertEqual(f
.name
, "t.c")
281 with self
.assertRaises(Exception):
282 f
= tu
.get_file(str_to_path("foobar.cpp"))
284 def test_get_source_location(self
):
285 """Ensure tu.get_source_location() works."""
287 tu
= get_tu("int foo();")
289 location
= tu
.get_location("t.c", 2)
290 self
.assertIsInstance(location
, SourceLocation
)
291 self
.assertEqual(location
.offset
, 2)
292 self
.assertEqual(location
.file.name
, "t.c")
294 location
= tu
.get_location("t.c", (1, 3))
295 self
.assertIsInstance(location
, SourceLocation
)
296 self
.assertEqual(location
.line
, 1)
297 self
.assertEqual(location
.column
, 3)
298 self
.assertEqual(location
.file.name
, "t.c")
300 def test_get_source_range(self
):
301 """Ensure tu.get_source_range() works."""
303 tu
= get_tu("int foo();")
305 r
= tu
.get_extent("t.c", (1, 4))
306 self
.assertIsInstance(r
, SourceRange
)
307 self
.assertEqual(r
.start
.offset
, 1)
308 self
.assertEqual(r
.end
.offset
, 4)
309 self
.assertEqual(r
.start
.file.name
, "t.c")
310 self
.assertEqual(r
.end
.file.name
, "t.c")
312 r
= tu
.get_extent("t.c", ((1, 2), (1, 3)))
313 self
.assertIsInstance(r
, SourceRange
)
314 self
.assertEqual(r
.start
.line
, 1)
315 self
.assertEqual(r
.start
.column
, 2)
316 self
.assertEqual(r
.end
.line
, 1)
317 self
.assertEqual(r
.end
.column
, 3)
318 self
.assertEqual(r
.start
.file.name
, "t.c")
319 self
.assertEqual(r
.end
.file.name
, "t.c")
321 start
= tu
.get_location("t.c", 0)
322 end
= tu
.get_location("t.c", 5)
324 r
= tu
.get_extent("t.c", (start
, end
))
325 self
.assertIsInstance(r
, SourceRange
)
326 self
.assertEqual(r
.start
.offset
, 0)
327 self
.assertEqual(r
.end
.offset
, 5)
328 self
.assertEqual(r
.start
.file.name
, "t.c")
329 self
.assertEqual(r
.end
.file.name
, "t.c")
331 def test_get_tokens_gc(self
):
332 """Ensures get_tokens() works properly with garbage collection."""
334 tu
= get_tu("int foo();")
335 r
= tu
.get_extent("t.c", (0, 10))
336 tokens
= list(tu
.get_tokens(extent
=r
))
338 self
.assertEqual(tokens
[0].spelling
, "int")
340 self
.assertEqual(tokens
[0].spelling
, "int")
344 self
.assertEqual(tokens
[0].spelling
, "int")
346 # May trigger segfault if we don't do our job properly.
349 gc
.collect() # Just in case.
351 def test_fail_from_source(self
):
352 path
= os
.path
.join(kInputsDir
, "non-existent.cpp")
354 tu
= TranslationUnit
.from_source(path
)
355 except TranslationUnitLoadError
:
357 self
.assertEqual(tu
, None)
359 def test_fail_from_ast_file(self
):
360 path
= os
.path
.join(kInputsDir
, "non-existent.ast")
362 tu
= TranslationUnit
.from_ast_file(path
)
363 except TranslationUnitLoadError
:
365 self
.assertEqual(tu
, None)