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