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