1"""
2    test_domain_c
3    ~~~~~~~~~~~~~
4
5    Tests the C Domain
6
7    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
8    :license: BSD, see LICENSE for details.
9"""
10
11import zlib
12from xml.etree import ElementTree
13
14import pytest
15
16from sphinx import addnodes
17from sphinx.addnodes import desc
18from sphinx.domains.c import DefinitionError, DefinitionParser, Symbol, _id_prefix, _max_id
19from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
20from sphinx.testing import restructuredtext
21from sphinx.testing.util import assert_node
22
23
24def parse(name, string):
25    class Config:
26        c_id_attributes = ["id_attr", 'LIGHTGBM_C_EXPORT']
27        c_paren_attributes = ["paren_attr"]
28    parser = DefinitionParser(string, location=None, config=Config())
29    parser.allowFallbackExpressionParsing = False
30    ast = parser.parse_declaration(name, name)
31    parser.assert_end()
32    return ast
33
34
35def _check(name, input, idDict, output, key, asTextOutput):
36    if key is None:
37        key = name
38    key += ' '
39    if name in ('function', 'member'):
40        inputActual = input
41        outputAst = output
42        outputAsText = output
43    else:
44        inputActual = input.format(key='')
45        outputAst = output.format(key='')
46        outputAsText = output.format(key=key)
47    if asTextOutput is not None:
48        outputAsText = asTextOutput
49
50    # first a simple check of the AST
51    ast = parse(name, inputActual)
52    res = str(ast)
53    if res != outputAst:
54        print("")
55        print("Input:    ", input)
56        print("Result:   ", res)
57        print("Expected: ", outputAst)
58        raise DefinitionError("")
59    rootSymbol = Symbol(None, None, None, None, None)
60    symbol = rootSymbol.add_declaration(ast, docname="TestDoc", line=42)
61    parentNode = addnodes.desc()
62    signode = addnodes.desc_signature(input, '')
63    parentNode += signode
64    ast.describe_signature(signode, 'lastIsName', symbol, options={})
65    resAsText = parentNode.astext()
66    if resAsText != outputAsText:
67        print("")
68        print("Input:    ", input)
69        print("astext(): ", resAsText)
70        print("Expected: ", outputAsText)
71        raise DefinitionError("")
72
73    idExpected = [None]
74    for i in range(1, _max_id + 1):
75        if i in idDict:
76            idExpected.append(idDict[i])
77        else:
78            idExpected.append(idExpected[i - 1])
79    idActual = [None]
80    for i in range(1, _max_id + 1):
81        # try:
82        id = ast.get_id(version=i)
83        assert id is not None
84        idActual.append(id[len(_id_prefix[i]):])
85        # except NoOldIdError:
86        #     idActual.append(None)
87
88    res = [True]
89    for i in range(1, _max_id + 1):
90        res.append(idExpected[i] == idActual[i])
91
92    if not all(res):
93        print("input:    %s" % input.rjust(20))
94        for i in range(1, _max_id + 1):
95            if res[i]:
96                continue
97            print("Error in id version %d." % i)
98            print("result:   %s" % idActual[i])
99            print("expected: %s" % idExpected[i])
100        # print(rootSymbol.dump(0))
101        raise DefinitionError("")
102
103
104def check(name, input, idDict, output=None, key=None, asTextOutput=None):
105    if output is None:
106        output = input
107    # First, check without semicolon
108    _check(name, input, idDict, output, key, asTextOutput)
109    if name != 'macro':
110        # Second, check with semicolon
111        _check(name, input + ' ;', idDict, output + ';', key,
112               asTextOutput + ';' if asTextOutput is not None else None)
113
114
115def test_expressions():
116    def exprCheck(expr, output=None):
117        class Config:
118            c_id_attributes = ["id_attr"]
119            c_paren_attributes = ["paren_attr"]
120        parser = DefinitionParser(expr, location=None, config=Config())
121        parser.allowFallbackExpressionParsing = False
122        ast = parser.parse_expression()
123        parser.assert_end()
124        # first a simple check of the AST
125        if output is None:
126            output = expr
127        res = str(ast)
128        if res != output:
129            print("")
130            print("Input:    ", input)
131            print("Result:   ", res)
132            print("Expected: ", output)
133            raise DefinitionError("")
134        displayString = ast.get_display_string()
135        if res != displayString:
136            # note: if the expression contains an anon name then this will trigger a falsely
137            print("")
138            print("Input:    ", expr)
139            print("Result:   ", res)
140            print("Display:  ", displayString)
141            raise DefinitionError("")
142
143    # type expressions
144    exprCheck('int*')
145    exprCheck('int *const*')
146    exprCheck('int *volatile*')
147    exprCheck('int *restrict*')
148    exprCheck('int *(*)(double)')
149    exprCheck('const int*')
150    exprCheck('__int64')
151    exprCheck('unsigned __int64')
152
153    # actual expressions
154
155    # primary
156    exprCheck('true')
157    exprCheck('false')
158    ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1']
159    unsignedSuffix = ['', 'u', 'U']
160    longSuffix = ['', 'l', 'L', 'll', 'LL']
161    for i in ints:
162        for u in unsignedSuffix:
163            for l in longSuffix:
164                expr = i + u + l
165                exprCheck(expr)
166                expr = i + l + u
167                exprCheck(expr)
168    for suffix in ['', 'f', 'F', 'l', 'L']:
169        for e in [
170                '5e42', '5e+42', '5e-42',
171                '5.', '5.e42', '5.e+42', '5.e-42',
172                '.5', '.5e42', '.5e+42', '.5e-42',
173                '5.0', '5.0e42', '5.0e+42', '5.0e-42']:
174            expr = e + suffix
175            exprCheck(expr)
176        for e in [
177                'ApF', 'Ap+F', 'Ap-F',
178                'A.', 'A.pF', 'A.p+F', 'A.p-F',
179                '.A', '.ApF', '.Ap+F', '.Ap-F',
180                'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F']:
181            expr = "0x" + e + suffix
182            exprCheck(expr)
183    exprCheck('"abc\\"cba"')  # string
184    # character literals
185    for p in ['', 'u8', 'u', 'U', 'L']:
186        exprCheck(p + "'a'")
187        exprCheck(p + "'\\n'")
188        exprCheck(p + "'\\012'")
189        exprCheck(p + "'\\0'")
190        exprCheck(p + "'\\x0a'")
191        exprCheck(p + "'\\x0A'")
192        exprCheck(p + "'\\u0a42'")
193        exprCheck(p + "'\\u0A42'")
194        exprCheck(p + "'\\U0001f34c'")
195        exprCheck(p + "'\\U0001F34C'")
196
197    exprCheck('(5)')
198    exprCheck('C')
199    # postfix
200    exprCheck('A(2)')
201    exprCheck('A[2]')
202    exprCheck('a.b.c')
203    exprCheck('a->b->c')
204    exprCheck('i++')
205    exprCheck('i--')
206    # unary
207    exprCheck('++5')
208    exprCheck('--5')
209    exprCheck('*5')
210    exprCheck('&5')
211    exprCheck('+5')
212    exprCheck('-5')
213    exprCheck('!5')
214    exprCheck('not 5')
215    exprCheck('~5')
216    exprCheck('compl 5')
217    exprCheck('sizeof(T)')
218    exprCheck('sizeof -42')
219    exprCheck('alignof(T)')
220    # cast
221    exprCheck('(int)2')
222    # binary op
223    exprCheck('5 || 42')
224    exprCheck('5 or 42')
225    exprCheck('5 && 42')
226    exprCheck('5 and 42')
227    exprCheck('5 | 42')
228    exprCheck('5 bitor 42')
229    exprCheck('5 ^ 42')
230    exprCheck('5 xor 42')
231    exprCheck('5 & 42')
232    exprCheck('5 bitand 42')
233    # ['==', '!=']
234    exprCheck('5 == 42')
235    exprCheck('5 != 42')
236    exprCheck('5 not_eq 42')
237    # ['<=', '>=', '<', '>']
238    exprCheck('5 <= 42')
239    exprCheck('5 >= 42')
240    exprCheck('5 < 42')
241    exprCheck('5 > 42')
242    # ['<<', '>>']
243    exprCheck('5 << 42')
244    exprCheck('5 >> 42')
245    # ['+', '-']
246    exprCheck('5 + 42')
247    exprCheck('5 - 42')
248    # ['*', '/', '%']
249    exprCheck('5 * 42')
250    exprCheck('5 / 42')
251    exprCheck('5 % 42')
252    # ['.*', '->*']
253    # conditional
254    # TODO
255    # assignment
256    exprCheck('a = 5')
257    exprCheck('a *= 5')
258    exprCheck('a /= 5')
259    exprCheck('a %= 5')
260    exprCheck('a += 5')
261    exprCheck('a -= 5')
262    exprCheck('a >>= 5')
263    exprCheck('a <<= 5')
264    exprCheck('a &= 5')
265    exprCheck('a and_eq 5')
266    exprCheck('a ^= 5')
267    exprCheck('a xor_eq 5')
268    exprCheck('a |= 5')
269    exprCheck('a or_eq 5')
270
271
272def test_type_definitions():
273    check('type', "{key}T", {1: "T"})
274
275    check('type', "{key}bool *b", {1: 'b'}, key='typedef')
276    check('type', "{key}bool *const b", {1: 'b'}, key='typedef')
277    check('type', "{key}bool *const *b", {1: 'b'}, key='typedef')
278    check('type', "{key}bool *volatile *b", {1: 'b'}, key='typedef')
279    check('type', "{key}bool *restrict *b", {1: 'b'}, key='typedef')
280    check('type', "{key}bool *volatile const b", {1: 'b'}, key='typedef')
281    check('type', "{key}bool *volatile const b", {1: 'b'}, key='typedef')
282    check('type', "{key}bool *volatile const *b", {1: 'b'}, key='typedef')
283    check('type', "{key}bool b[]", {1: 'b'}, key='typedef')
284    check('type', "{key}long long int foo", {1: 'foo'}, key='typedef')
285    # test decl specs on right
286    check('type', "{key}bool const b", {1: 'b'}, key='typedef')
287
288    # from breathe#267 (named function parameters for function pointers
289    check('type', '{key}void (*gpio_callback_t)(struct device *port, uint32_t pin)',
290          {1: 'gpio_callback_t'}, key='typedef')
291
292
293def test_macro_definitions():
294    check('macro', 'M', {1: 'M'})
295    check('macro', 'M()', {1: 'M'})
296    check('macro', 'M(arg)', {1: 'M'})
297    check('macro', 'M(arg1, arg2)', {1: 'M'})
298    check('macro', 'M(arg1, arg2, arg3)', {1: 'M'})
299    check('macro', 'M(...)', {1: 'M'})
300    check('macro', 'M(arg, ...)', {1: 'M'})
301    check('macro', 'M(arg1, arg2, ...)', {1: 'M'})
302    check('macro', 'M(arg1, arg2, arg3, ...)', {1: 'M'})
303    # GNU extension
304    check('macro', 'M(arg1, arg2, arg3...)', {1: 'M'})
305    with pytest.raises(DefinitionError):
306        check('macro', 'M(arg1, arg2..., arg3)', {1: 'M'})
307
308
309def test_member_definitions():
310    check('member', 'void a', {1: 'a'})
311    check('member', '_Bool a', {1: 'a'})
312    check('member', 'bool a', {1: 'a'})
313    check('member', 'char a', {1: 'a'})
314    check('member', 'int a', {1: 'a'})
315    check('member', 'float a', {1: 'a'})
316    check('member', 'double a', {1: 'a'})
317
318    check('member', 'unsigned long a', {1: 'a'})
319    check('member', '__int64 a', {1: 'a'})
320    check('member', 'unsigned __int64 a', {1: 'a'})
321
322    check('member', 'int .a', {1: 'a'})
323
324    check('member', 'int *a', {1: 'a'})
325    check('member', 'int **a', {1: 'a'})
326    check('member', 'const int a', {1: 'a'})
327    check('member', 'volatile int a', {1: 'a'})
328    check('member', 'restrict int a', {1: 'a'})
329    check('member', 'volatile const int a', {1: 'a'})
330    check('member', 'restrict const int a', {1: 'a'})
331    check('member', 'restrict volatile int a', {1: 'a'})
332    check('member', 'restrict volatile const int a', {1: 'a'})
333
334    check('member', 'T t', {1: 't'})
335
336    check('member', 'int a[]', {1: 'a'})
337
338    check('member', 'int (*p)[]', {1: 'p'})
339
340    check('member', 'int a[42]', {1: 'a'})
341    check('member', 'int a = 42', {1: 'a'})
342    check('member', 'T a = {}', {1: 'a'})
343    check('member', 'T a = {1}', {1: 'a'})
344    check('member', 'T a = {1, 2}', {1: 'a'})
345    check('member', 'T a = {1, 2, 3}', {1: 'a'})
346
347    # test from issue #1539
348    check('member', 'CK_UTF8CHAR model[16]', {1: 'model'})
349
350    check('member', 'auto int a', {1: 'a'})
351    check('member', 'register int a', {1: 'a'})
352    check('member', 'extern int a', {1: 'a'})
353    check('member', 'static int a', {1: 'a'})
354
355    check('member', 'thread_local int a', {1: 'a'})
356    check('member', '_Thread_local int a', {1: 'a'})
357    check('member', 'extern thread_local int a', {1: 'a'})
358    check('member', 'thread_local extern int a', {1: 'a'},
359          'extern thread_local int a')
360    check('member', 'static thread_local int a', {1: 'a'})
361    check('member', 'thread_local static int a', {1: 'a'},
362          'static thread_local int a')
363
364    check('member', 'int b : 3', {1: 'b'})
365
366
367def test_function_definitions():
368    check('function', 'void f()', {1: 'f'})
369    check('function', 'void f(int)', {1: 'f'})
370    check('function', 'void f(int i)', {1: 'f'})
371    check('function', 'void f(int i, int j)', {1: 'f'})
372    check('function', 'void f(...)', {1: 'f'})
373    check('function', 'void f(int i, ...)', {1: 'f'})
374    check('function', 'void f(struct T)', {1: 'f'})
375    check('function', 'void f(struct T t)', {1: 'f'})
376    check('function', 'void f(union T)', {1: 'f'})
377    check('function', 'void f(union T t)', {1: 'f'})
378    check('function', 'void f(enum T)', {1: 'f'})
379    check('function', 'void f(enum T t)', {1: 'f'})
380
381    # test from issue #1539
382    check('function', 'void f(A x[])', {1: 'f'})
383
384    # test from issue #2377
385    check('function', 'void (*signal(int sig, void (*func)(int)))(int)', {1: 'signal'})
386
387    check('function', 'extern void f()', {1: 'f'})
388    check('function', 'static void f()', {1: 'f'})
389    check('function', 'inline void f()', {1: 'f'})
390
391    # tests derived from issue #1753 (skip to keep sanity)
392    check('function', "void f(float *q(double))", {1: 'f'})
393    check('function', "void f(float *(*q)(double))", {1: 'f'})
394    check('function', "void f(float (*q)(double))", {1: 'f'})
395    check('function', "int (*f(double d))(float)", {1: 'f'})
396    check('function', "int (*f(bool b))[5]", {1: 'f'})
397    check('function', "void f(int *const p)", {1: 'f'})
398    check('function', "void f(int *volatile const p)", {1: 'f'})
399
400    # from breathe#223
401    check('function', 'void f(struct E e)', {1: 'f'})
402    check('function', 'void f(enum E e)', {1: 'f'})
403    check('function', 'void f(union E e)', {1: 'f'})
404
405    # array declarators
406    check('function', 'void f(int arr[])', {1: 'f'})
407    check('function', 'void f(int arr[*])', {1: 'f'})
408    cvrs = ['', 'const', 'volatile', 'restrict', 'restrict volatile const']
409    for cvr in cvrs:
410        space = ' ' if len(cvr) != 0 else ''
411        check('function', 'void f(int arr[{}*])'.format(cvr), {1: 'f'})
412        check('function', 'void f(int arr[{}])'.format(cvr), {1: 'f'})
413        check('function', 'void f(int arr[{}{}42])'.format(cvr, space), {1: 'f'})
414        check('function', 'void f(int arr[static{}{} 42])'.format(space, cvr), {1: 'f'})
415        check('function', 'void f(int arr[{}{}static 42])'.format(cvr, space), {1: 'f'},
416              output='void f(int arr[static{}{} 42])'.format(space, cvr))
417    check('function', 'void f(int arr[const static volatile 42])', {1: 'f'},
418          output='void f(int arr[static volatile const 42])')
419
420
421def test_nested_name():
422    check('struct', '{key}.A', {1: "A"})
423    check('struct', '{key}.A.B', {1: "A.B"})
424    check('function', 'void f(.A a)', {1: "f"})
425    check('function', 'void f(.A.B a)', {1: "f"})
426
427
428def test_struct_definitions():
429    check('struct', '{key}A', {1: 'A'})
430
431
432def test_union_definitions():
433    check('union', '{key}A', {1: 'A'})
434
435
436def test_enum_definitions():
437    check('enum', '{key}A', {1: 'A'})
438
439    check('enumerator', '{key}A', {1: 'A'})
440    check('enumerator', '{key}A = 42', {1: 'A'})
441
442
443def test_anon_definitions():
444    check('struct', '@a', {1: "@a"}, asTextOutput='struct [anonymous]')
445    check('union', '@a', {1: "@a"}, asTextOutput='union [anonymous]')
446    check('enum', '@a', {1: "@a"}, asTextOutput='enum [anonymous]')
447    check('struct', '@1', {1: "@1"}, asTextOutput='struct [anonymous]')
448    check('struct', '@a.A', {1: "@a.A"}, asTextOutput='struct [anonymous].A')
449
450
451def test_initializers():
452    idsMember = {1: 'v'}
453    idsFunction = {1: 'f'}
454    # no init
455    check('member', 'T v', idsMember)
456    check('function', 'void f(T v)', idsFunction)
457    # with '=', assignment-expression
458    check('member', 'T v = 42', idsMember)
459    check('function', 'void f(T v = 42)', idsFunction)
460    # with '=', braced-init
461    check('member', 'T v = {}', idsMember)
462    check('function', 'void f(T v = {})', idsFunction)
463    check('member', 'T v = {42, 42, 42}', idsMember)
464    check('function', 'void f(T v = {42, 42, 42})', idsFunction)
465    check('member', 'T v = {42, 42, 42,}', idsMember)
466    check('function', 'void f(T v = {42, 42, 42,})', idsFunction)
467    # TODO: designator-list
468
469
470def test_attributes():
471    # style: C++
472    check('member', '[[]] int f', {1: 'f'})
473    check('member', '[ [ ] ] int f', {1: 'f'},
474          # this will fail when the proper grammar is implemented
475          output='[[ ]] int f')
476    check('member', '[[a]] int f', {1: 'f'})
477    # style: GNU
478    check('member', '__attribute__(()) int f', {1: 'f'})
479    check('member', '__attribute__((a)) int f', {1: 'f'})
480    check('member', '__attribute__((a, b)) int f', {1: 'f'})
481    check('member', '__attribute__((optimize(3))) int f', {1: 'f'})
482    check('member', '__attribute__((format(printf, 1, 2))) int f', {1: 'f'})
483    # style: user-defined id
484    check('member', 'id_attr int f', {1: 'f'})
485    # style: user-defined paren
486    check('member', 'paren_attr() int f', {1: 'f'})
487    check('member', 'paren_attr(a) int f', {1: 'f'})
488    check('member', 'paren_attr("") int f', {1: 'f'})
489    check('member', 'paren_attr(()[{}][]{}) int f', {1: 'f'})
490    with pytest.raises(DefinitionError):
491        parse('member', 'paren_attr(() int f')
492    with pytest.raises(DefinitionError):
493        parse('member', 'paren_attr([) int f')
494    with pytest.raises(DefinitionError):
495        parse('member', 'paren_attr({) int f')
496    with pytest.raises(DefinitionError):
497        parse('member', 'paren_attr([)]) int f')
498    with pytest.raises(DefinitionError):
499        parse('member', 'paren_attr((])) int f')
500    with pytest.raises(DefinitionError):
501        parse('member', 'paren_attr({]}) int f')
502
503    # position: decl specs
504    check('function', 'static inline __attribute__(()) void f()', {1: 'f'},
505          output='__attribute__(()) static inline void f()')
506    check('function', '[[attr1]] [[attr2]] void f()', {1: 'f'})
507    # position: declarator
508    check('member', 'int *[[attr]] i', {1: 'i'})
509    check('member', 'int *const [[attr]] volatile i', {1: 'i'},
510          output='int *[[attr]] volatile const i')
511    check('member', 'int *[[attr]] *i', {1: 'i'})
512    # position: parameters
513    check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f'})
514
515    # issue michaeljones/breathe#500
516    check('function', 'LIGHTGBM_C_EXPORT int LGBM_BoosterFree(int handle)',
517          {1: 'LGBM_BoosterFree'})
518
519# def test_print():
520#     # used for getting all the ids out for checking
521#     for a in ids:
522#         print(a)
523#     raise DefinitionError("")
524
525
526def filter_warnings(warning, file):
527    lines = warning.getvalue().split("\n")
528    res = [l for l in lines if "domain-c" in l and "{}.rst".format(file) in l and
529           "WARNING: document isn't included in any toctree" not in l]
530    print("Filtered warnings for file '{}':".format(file))
531    for w in res:
532        print(w)
533    return res
534
535
536def extract_role_links(app, filename):
537    t = (app.outdir / filename).read_text()
538    lis = [l for l in t.split('\n') if l.startswith("<li")]
539    entries = []
540    for l in lis:
541        li = ElementTree.fromstring(l)
542        aList = list(li.iter('a'))
543        assert len(aList) == 1
544        a = aList[0]
545        target = a.attrib['href'].lstrip('#')
546        title = a.attrib['title']
547        assert len(a) == 1
548        code = a[0]
549        assert code.tag == 'code'
550        text = ''.join(code.itertext())
551        entries.append((target, title, text))
552    return entries
553
554
555@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
556def test_build_domain_c(app, status, warning):
557    app.builder.build_all()
558    ws = filter_warnings(warning, "index")
559    assert len(ws) == 0
560
561
562@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
563def test_build_domain_c_namespace(app, status, warning):
564    app.builder.build_all()
565    ws = filter_warnings(warning, "namespace")
566    assert len(ws) == 0
567    t = (app.outdir / "namespace.html").read_text()
568    for id_ in ('NS.NSVar', 'NULLVar', 'ZeroVar', 'NS2.NS3.NS2NS3Var', 'PopVar'):
569        assert 'id="c.{}"'.format(id_) in t
570
571
572@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
573def test_build_domain_c_anon_dup_decl(app, status, warning):
574    app.builder.build_all()
575    ws = filter_warnings(warning, "anon-dup-decl")
576    assert len(ws) == 2
577    assert "WARNING: c:identifier reference target not found: @a" in ws[0]
578    assert "WARNING: c:identifier reference target not found: @b" in ws[1]
579
580
581@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
582def test_build_domain_c_semicolon(app, status, warning):
583    app.builder.build_all()
584    ws = filter_warnings(warning, "semicolon")
585    assert len(ws) == 0
586
587
588@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
589def test_build_function_param_target(app, warning):
590    # the anchor for function parameters should be the function
591    app.builder.build_all()
592    ws = filter_warnings(warning, "function_param_target")
593    assert len(ws) == 0
594    entries = extract_role_links(app, "function_param_target.html")
595    assert entries == [
596        ('c.f', 'i', 'i'),
597        ('c.f', 'f.i', 'f.i'),
598    ]
599
600
601@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
602def test_build_ns_lookup(app, warning):
603    app.builder.build_all()
604    ws = filter_warnings(warning, "ns_lookup")
605    assert len(ws) == 0
606
607
608def _get_obj(app, queryName):
609    domain = app.env.get_domain('c')
610    for name, dispname, objectType, docname, anchor, prio in domain.get_objects():
611        if name == queryName:
612            return (docname, anchor, objectType)
613    return (queryName, "not", "found")
614
615
616def test_cfunction(app):
617    text = (".. c:function:: PyObject* "
618            "PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
619    doctree = restructuredtext.parse(app, text)
620    assert_node(doctree[1], addnodes.desc, desctype="function",
621                domain="c", objtype="function", noindex=False)
622
623    entry = _get_obj(app, 'PyType_GenericAlloc')
624    assert entry == ('index', 'c.PyType_GenericAlloc', 'function')
625
626
627def test_cmember(app):
628    text = ".. c:member:: PyObject* PyTypeObject.tp_bases"
629    doctree = restructuredtext.parse(app, text)
630    assert_node(doctree[1], addnodes.desc, desctype="member",
631                domain="c", objtype="member", noindex=False)
632
633    entry = _get_obj(app, 'PyTypeObject.tp_bases')
634    assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member')
635
636
637def test_cvar(app):
638    text = ".. c:var:: PyObject* PyClass_Type"
639    doctree = restructuredtext.parse(app, text)
640    assert_node(doctree[1], addnodes.desc, desctype="var",
641                domain="c", objtype="var", noindex=False)
642
643    entry = _get_obj(app, 'PyClass_Type')
644    assert entry == ('index', 'c.PyClass_Type', 'member')
645
646
647def test_noindexentry(app):
648    text = (".. c:function:: void f()\n"
649            ".. c:function:: void g()\n"
650            "   :noindexentry:\n")
651    doctree = restructuredtext.parse(app, text)
652    assert_node(doctree, (addnodes.index, desc, addnodes.index, desc))
653    assert_node(doctree[0], addnodes.index, entries=[('single', 'f (C function)', 'c.f', '', None)])
654    assert_node(doctree[2], addnodes.index, entries=[])
655
656
657@pytest.mark.sphinx(testroot='domain-c-intersphinx', confoverrides={'nitpicky': True})
658def test_intersphinx(tempdir, app, status, warning):
659    origSource = """\
660.. c:member:: int _member
661.. c:var:: int _var
662.. c:function:: void _function()
663.. c:macro:: _macro
664.. c:struct:: _struct
665.. c:union:: _union
666.. c:enum:: _enum
667
668    .. c:enumerator:: _enumerator
669
670.. c:type:: _type
671.. c:function:: void _functionParam(int param)
672"""  # noqa
673    inv_file = tempdir / 'inventory'
674    inv_file.write_bytes(b'''\
675# Sphinx inventory version 2
676# Project: C Intersphinx Test
677# Version:
678# The remainder of this file is compressed using zlib.
679''' + zlib.compress(b'''\
680_enum c:enum 1 index.html#c.$ -
681_enum._enumerator c:enumerator 1 index.html#c.$ -
682_enumerator c:enumerator 1 index.html#c._enum.$ -
683_function c:function 1 index.html#c.$ -
684_functionParam c:function 1 index.html#c.$ -
685_functionParam.param c:functionParam 1 index.html#c._functionParam -
686_macro c:macro 1 index.html#c.$ -
687_member c:member 1 index.html#c.$ -
688_struct c:struct 1 index.html#c.$ -
689_type c:type 1 index.html#c.$ -
690_union c:union 1 index.html#c.$ -
691_var c:member 1 index.html#c.$ -
692'''))  # noqa
693    app.config.intersphinx_mapping = {
694        'https://localhost/intersphinx/c/': inv_file,
695    }
696    app.config.intersphinx_cache_limit = 0
697    # load the inventory and check if it's done correctly
698    normalize_intersphinx_mapping(app, app.config)
699    load_mappings(app)
700
701    app.builder.build_all()
702    ws = filter_warnings(warning, "index")
703    assert len(ws) == 0
704