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