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