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