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