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