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_save(self): 112 """Ensure TranslationUnit.save() works.""" 113 114 tu = get_tu('int foo();') 115 116 path = save_tu(tu) 117 self.assertTrue(os.path.exists(path)) 118 self.assertGreater(os.path.getsize(path), 0) 119 os.unlink(path) 120 121 def test_save_translation_errors(self): 122 """Ensure that saving to an invalid directory raises.""" 123 124 tu = get_tu('int foo();') 125 126 path = '/does/not/exist/llvm-test.ast' 127 self.assertFalse(os.path.exists(os.path.dirname(path))) 128 129 with self.assertRaises(TranslationUnitSaveError) as cm: 130 tu.save(path) 131 ex = cm.exception 132 expected = TranslationUnitSaveError.ERROR_UNKNOWN 133 self.assertEqual(ex.save_error, expected) 134 135 def test_load(self): 136 """Ensure TranslationUnits can be constructed from saved files.""" 137 138 tu = get_tu('int foo();') 139 self.assertEqual(len(tu.diagnostics), 0) 140 path = save_tu(tu) 141 142 self.assertTrue(os.path.exists(path)) 143 self.assertGreater(os.path.getsize(path), 0) 144 145 tu2 = TranslationUnit.from_ast_file(filename=path) 146 self.assertEqual(len(tu2.diagnostics), 0) 147 148 foo = get_cursor(tu2, 'foo') 149 self.assertIsNotNone(foo) 150 151 # Just in case there is an open file descriptor somewhere. 152 del tu2 153 154 os.unlink(path) 155 156 def test_index_parse(self): 157 path = os.path.join(kInputsDir, 'hello.cpp') 158 index = Index.create() 159 tu = index.parse(path) 160 self.assertIsInstance(tu, TranslationUnit) 161 162 def test_get_file(self): 163 """Ensure tu.get_file() works appropriately.""" 164 165 tu = get_tu('int foo();') 166 167 f = tu.get_file('t.c') 168 self.assertIsInstance(f, File) 169 self.assertEqual(f.name, 't.c') 170 171 with self.assertRaises(Exception): 172 f = tu.get_file('foobar.cpp') 173 174 def test_get_source_location(self): 175 """Ensure tu.get_source_location() works.""" 176 177 tu = get_tu('int foo();') 178 179 location = tu.get_location('t.c', 2) 180 self.assertIsInstance(location, SourceLocation) 181 self.assertEqual(location.offset, 2) 182 self.assertEqual(location.file.name, 't.c') 183 184 location = tu.get_location('t.c', (1, 3)) 185 self.assertIsInstance(location, SourceLocation) 186 self.assertEqual(location.line, 1) 187 self.assertEqual(location.column, 3) 188 self.assertEqual(location.file.name, 't.c') 189 190 def test_get_source_range(self): 191 """Ensure tu.get_source_range() works.""" 192 193 tu = get_tu('int foo();') 194 195 r = tu.get_extent('t.c', (1,4)) 196 self.assertIsInstance(r, SourceRange) 197 self.assertEqual(r.start.offset, 1) 198 self.assertEqual(r.end.offset, 4) 199 self.assertEqual(r.start.file.name, 't.c') 200 self.assertEqual(r.end.file.name, 't.c') 201 202 r = tu.get_extent('t.c', ((1,2), (1,3))) 203 self.assertIsInstance(r, SourceRange) 204 self.assertEqual(r.start.line, 1) 205 self.assertEqual(r.start.column, 2) 206 self.assertEqual(r.end.line, 1) 207 self.assertEqual(r.end.column, 3) 208 self.assertEqual(r.start.file.name, 't.c') 209 self.assertEqual(r.end.file.name, 't.c') 210 211 start = tu.get_location('t.c', 0) 212 end = tu.get_location('t.c', 5) 213 214 r = tu.get_extent('t.c', (start, end)) 215 self.assertIsInstance(r, SourceRange) 216 self.assertEqual(r.start.offset, 0) 217 self.assertEqual(r.end.offset, 5) 218 self.assertEqual(r.start.file.name, 't.c') 219 self.assertEqual(r.end.file.name, 't.c') 220 221 def test_get_tokens_gc(self): 222 """Ensures get_tokens() works properly with garbage collection.""" 223 224 tu = get_tu('int foo();') 225 r = tu.get_extent('t.c', (0, 10)) 226 tokens = list(tu.get_tokens(extent=r)) 227 228 self.assertEqual(tokens[0].spelling, 'int') 229 gc.collect() 230 self.assertEqual(tokens[0].spelling, 'int') 231 232 del tokens[1] 233 gc.collect() 234 self.assertEqual(tokens[0].spelling, 'int') 235 236 # May trigger segfault if we don't do our job properly. 237 del tokens 238 gc.collect() 239 gc.collect() # Just in case. 240 241 def test_fail_from_source(self): 242 path = os.path.join(kInputsDir, 'non-existent.cpp') 243 try: 244 tu = TranslationUnit.from_source(path) 245 except TranslationUnitLoadError: 246 tu = None 247 self.assertEqual(tu, None) 248 249 def test_fail_from_ast_file(self): 250 path = os.path.join(kInputsDir, 'non-existent.ast') 251 try: 252 tu = TranslationUnit.from_ast_file(path) 253 except TranslationUnitLoadError: 254 tu = None 255 self.assertEqual(tu, None) 256