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