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