1import os 2from clang.cindex import Config 3if 'CLANG_LIBRARY_PATH' in os.environ: 4 Config.set_library_path(os.environ['CLANG_LIBRARY_PATH']) 5 6from contextlib import contextmanager 7import gc 8import os 9import sys 10import tempfile 11import unittest 12 13from clang.cindex import CursorKind 14from clang.cindex import Cursor 15from clang.cindex import File 16from clang.cindex import Index 17from clang.cindex import SourceLocation 18from clang.cindex import SourceRange 19from clang.cindex import TranslationUnitSaveError 20from clang.cindex import TranslationUnitLoadError 21from clang.cindex import TranslationUnit 22from .util import get_cursor 23from .util import get_tu 24from .util import skip_if_no_fspath 25from .util import str_to_path 26 27 28kInputsDir = os.path.join(os.path.dirname(__file__), 'INPUTS') 29 30 31@contextmanager 32def save_tu(tu): 33 """Convenience API to save a TranslationUnit to a file. 34 35 Returns the filename it was saved to. 36 """ 37 with tempfile.NamedTemporaryFile() as t: 38 tu.save(t.name) 39 yield t.name 40 41 42@contextmanager 43def save_tu_pathlike(tu): 44 """Convenience API to save a TranslationUnit to a file. 45 46 Returns the filename it was saved to. 47 """ 48 with tempfile.NamedTemporaryFile() as t: 49 tu.save(str_to_path(t.name)) 50 yield t.name 51 52 53class TestTranslationUnit(unittest.TestCase): 54 def test_spelling(self): 55 path = os.path.join(kInputsDir, 'hello.cpp') 56 tu = TranslationUnit.from_source(path) 57 self.assertEqual(tu.spelling, path) 58 59 def test_cursor(self): 60 path = os.path.join(kInputsDir, 'hello.cpp') 61 tu = get_tu(path) 62 c = tu.cursor 63 self.assertIsInstance(c, Cursor) 64 self.assertIs(c.kind, CursorKind.TRANSLATION_UNIT) 65 66 def test_parse_arguments(self): 67 path = os.path.join(kInputsDir, 'parse_arguments.c') 68 tu = TranslationUnit.from_source(path, ['-DDECL_ONE=hello', '-DDECL_TWO=hi']) 69 spellings = [c.spelling for c in tu.cursor.get_children()] 70 self.assertEqual(spellings[-2], 'hello') 71 self.assertEqual(spellings[-1], 'hi') 72 73 def test_reparse_arguments(self): 74 path = os.path.join(kInputsDir, 'parse_arguments.c') 75 tu = TranslationUnit.from_source(path, ['-DDECL_ONE=hello', '-DDECL_TWO=hi']) 76 tu.reparse() 77 spellings = [c.spelling for c in tu.cursor.get_children()] 78 self.assertEqual(spellings[-2], 'hello') 79 self.assertEqual(spellings[-1], 'hi') 80 81 def test_unsaved_files(self): 82 tu = TranslationUnit.from_source('fake.c', ['-I./'], unsaved_files = [ 83 ('fake.c', """ 84#include "fake.h" 85int x; 86int SOME_DEFINE; 87"""), 88 ('./fake.h', """ 89#define SOME_DEFINE y 90""") 91 ]) 92 spellings = [c.spelling for c in tu.cursor.get_children()] 93 self.assertEqual(spellings[-2], 'x') 94 self.assertEqual(spellings[-1], 'y') 95 96 def test_unsaved_files_2(self): 97 if sys.version_info.major >= 3: 98 from io import StringIO 99 else: 100 from io import BytesIO as StringIO 101 tu = TranslationUnit.from_source('fake.c', unsaved_files = [ 102 ('fake.c', StringIO('int x;'))]) 103 spellings = [c.spelling for c in tu.cursor.get_children()] 104 self.assertEqual(spellings[-1], 'x') 105 106 @skip_if_no_fspath 107 def test_from_source_accepts_pathlike(self): 108 tu = TranslationUnit.from_source(str_to_path('fake.c'), ['-Iincludes'], unsaved_files = [ 109 (str_to_path('fake.c'), """ 110#include "fake.h" 111 int x; 112 int SOME_DEFINE; 113 """), 114 (str_to_path('includes/fake.h'), """ 115#define SOME_DEFINE y 116 """) 117 ]) 118 spellings = [c.spelling for c in tu.cursor.get_children()] 119 self.assertEqual(spellings[-2], 'x') 120 self.assertEqual(spellings[-1], 'y') 121 122 def assert_normpaths_equal(self, path1, path2): 123 """ Compares two paths for equality after normalizing them with 124 os.path.normpath 125 """ 126 self.assertEqual(os.path.normpath(path1), 127 os.path.normpath(path2)) 128 129 def test_includes(self): 130 def eq(expected, actual): 131 if not actual.is_input_file: 132 self.assert_normpaths_equal(expected[0], actual.source.name) 133 self.assert_normpaths_equal(expected[1], actual.include.name) 134 else: 135 self.assert_normpaths_equal(expected[1], actual.include.name) 136 137 src = os.path.join(kInputsDir, 'include.cpp') 138 h1 = os.path.join(kInputsDir, "header1.h") 139 h2 = os.path.join(kInputsDir, "header2.h") 140 h3 = os.path.join(kInputsDir, "header3.h") 141 inc = [(src, h1), (h1, h3), (src, h2), (h2, h3)] 142 143 tu = TranslationUnit.from_source(src) 144 for i in zip(inc, tu.get_includes()): 145 eq(i[0], i[1]) 146 147 def test_inclusion_directive(self): 148 src = os.path.join(kInputsDir, 'include.cpp') 149 h1 = os.path.join(kInputsDir, "header1.h") 150 h2 = os.path.join(kInputsDir, "header2.h") 151 h3 = os.path.join(kInputsDir, "header3.h") 152 inc = [h1, h3, h2, h3, h1] 153 154 tu = TranslationUnit.from_source(src, options=TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD) 155 inclusion_directive_files = [c.get_included_file().name for c in tu.cursor.get_children() if c.kind == CursorKind.INCLUSION_DIRECTIVE] 156 for i in zip(inc, inclusion_directive_files): 157 self.assert_normpaths_equal(i[0], i[1]) 158 159 def test_save(self): 160 """Ensure TranslationUnit.save() works.""" 161 162 tu = get_tu('int foo();') 163 164 with save_tu(tu) as path: 165 self.assertTrue(os.path.exists(path)) 166 self.assertGreater(os.path.getsize(path), 0) 167 168 @skip_if_no_fspath 169 def test_save_pathlike(self): 170 """Ensure TranslationUnit.save() works with PathLike filename.""" 171 172 tu = get_tu('int foo();') 173 174 with save_tu_pathlike(tu) as path: 175 self.assertTrue(os.path.exists(path)) 176 self.assertGreater(os.path.getsize(path), 0) 177 178 def test_save_translation_errors(self): 179 """Ensure that saving to an invalid directory raises.""" 180 181 tu = get_tu('int foo();') 182 183 path = '/does/not/exist/llvm-test.ast' 184 self.assertFalse(os.path.exists(os.path.dirname(path))) 185 186 with self.assertRaises(TranslationUnitSaveError) as cm: 187 tu.save(path) 188 ex = cm.exception 189 expected = TranslationUnitSaveError.ERROR_UNKNOWN 190 self.assertEqual(ex.save_error, expected) 191 192 def test_load(self): 193 """Ensure TranslationUnits can be constructed from saved files.""" 194 195 tu = get_tu('int foo();') 196 self.assertEqual(len(tu.diagnostics), 0) 197 with save_tu(tu) as path: 198 self.assertTrue(os.path.exists(path)) 199 self.assertGreater(os.path.getsize(path), 0) 200 201 tu2 = TranslationUnit.from_ast_file(filename=path) 202 self.assertEqual(len(tu2.diagnostics), 0) 203 204 foo = get_cursor(tu2, 'foo') 205 self.assertIsNotNone(foo) 206 207 # Just in case there is an open file descriptor somewhere. 208 del tu2 209 210 @skip_if_no_fspath 211 def test_load_pathlike(self): 212 """Ensure TranslationUnits can be constructed from saved files - 213 PathLike variant.""" 214 tu = get_tu('int foo();') 215 self.assertEqual(len(tu.diagnostics), 0) 216 with save_tu(tu) as path: 217 tu2 = TranslationUnit.from_ast_file(filename=str_to_path(path)) 218 self.assertEqual(len(tu2.diagnostics), 0) 219 220 foo = get_cursor(tu2, 'foo') 221 self.assertIsNotNone(foo) 222 223 # Just in case there is an open file descriptor somewhere. 224 del tu2 225 226 def test_index_parse(self): 227 path = os.path.join(kInputsDir, 'hello.cpp') 228 index = Index.create() 229 tu = index.parse(path) 230 self.assertIsInstance(tu, TranslationUnit) 231 232 def test_get_file(self): 233 """Ensure tu.get_file() works appropriately.""" 234 235 tu = get_tu('int foo();') 236 237 f = tu.get_file('t.c') 238 self.assertIsInstance(f, File) 239 self.assertEqual(f.name, 't.c') 240 241 with self.assertRaises(Exception): 242 f = tu.get_file('foobar.cpp') 243 244 @skip_if_no_fspath 245 def test_get_file_pathlike(self): 246 """Ensure tu.get_file() works appropriately with PathLike filenames.""" 247 248 tu = get_tu('int foo();') 249 250 f = tu.get_file(str_to_path('t.c')) 251 self.assertIsInstance(f, File) 252 self.assertEqual(f.name, 't.c') 253 254 with self.assertRaises(Exception): 255 f = tu.get_file(str_to_path('foobar.cpp')) 256 257 def test_get_source_location(self): 258 """Ensure tu.get_source_location() works.""" 259 260 tu = get_tu('int foo();') 261 262 location = tu.get_location('t.c', 2) 263 self.assertIsInstance(location, SourceLocation) 264 self.assertEqual(location.offset, 2) 265 self.assertEqual(location.file.name, 't.c') 266 267 location = tu.get_location('t.c', (1, 3)) 268 self.assertIsInstance(location, SourceLocation) 269 self.assertEqual(location.line, 1) 270 self.assertEqual(location.column, 3) 271 self.assertEqual(location.file.name, 't.c') 272 273 def test_get_source_range(self): 274 """Ensure tu.get_source_range() works.""" 275 276 tu = get_tu('int foo();') 277 278 r = tu.get_extent('t.c', (1,4)) 279 self.assertIsInstance(r, SourceRange) 280 self.assertEqual(r.start.offset, 1) 281 self.assertEqual(r.end.offset, 4) 282 self.assertEqual(r.start.file.name, 't.c') 283 self.assertEqual(r.end.file.name, 't.c') 284 285 r = tu.get_extent('t.c', ((1,2), (1,3))) 286 self.assertIsInstance(r, SourceRange) 287 self.assertEqual(r.start.line, 1) 288 self.assertEqual(r.start.column, 2) 289 self.assertEqual(r.end.line, 1) 290 self.assertEqual(r.end.column, 3) 291 self.assertEqual(r.start.file.name, 't.c') 292 self.assertEqual(r.end.file.name, 't.c') 293 294 start = tu.get_location('t.c', 0) 295 end = tu.get_location('t.c', 5) 296 297 r = tu.get_extent('t.c', (start, end)) 298 self.assertIsInstance(r, SourceRange) 299 self.assertEqual(r.start.offset, 0) 300 self.assertEqual(r.end.offset, 5) 301 self.assertEqual(r.start.file.name, 't.c') 302 self.assertEqual(r.end.file.name, 't.c') 303 304 def test_get_tokens_gc(self): 305 """Ensures get_tokens() works properly with garbage collection.""" 306 307 tu = get_tu('int foo();') 308 r = tu.get_extent('t.c', (0, 10)) 309 tokens = list(tu.get_tokens(extent=r)) 310 311 self.assertEqual(tokens[0].spelling, 'int') 312 gc.collect() 313 self.assertEqual(tokens[0].spelling, 'int') 314 315 del tokens[1] 316 gc.collect() 317 self.assertEqual(tokens[0].spelling, 'int') 318 319 # May trigger segfault if we don't do our job properly. 320 del tokens 321 gc.collect() 322 gc.collect() # Just in case. 323 324 def test_fail_from_source(self): 325 path = os.path.join(kInputsDir, 'non-existent.cpp') 326 try: 327 tu = TranslationUnit.from_source(path) 328 except TranslationUnitLoadError: 329 tu = None 330 self.assertEqual(tu, None) 331 332 def test_fail_from_ast_file(self): 333 path = os.path.join(kInputsDir, 'non-existent.ast') 334 try: 335 tu = TranslationUnit.from_ast_file(path) 336 except TranslationUnitLoadError: 337 tu = None 338 self.assertEqual(tu, None) 339