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