1"""
2    test_ext_viewcode
3    ~~~~~~~~~~~~~~~~~
4
5    Test sphinx.ext.viewcode extension.
6
7    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
8    :license: BSD, see LICENSE for details.
9"""
10
11import re
12
13import pytest
14
15
16@pytest.mark.sphinx(testroot='ext-viewcode')
17def test_viewcode(app, status, warning):
18    app.builder.build_all()
19
20    warnings = re.sub(r'\\+', '/', warning.getvalue())
21    assert re.findall(
22        r"index.rst:\d+: WARNING: Object named 'func1' not found in include " +
23        r"file .*/spam/__init__.py'",
24        warnings
25    )
26
27    result = (app.outdir / 'index.html').read_text()
28    assert result.count('href="_modules/spam/mod1.html#func1"') == 2
29    assert result.count('href="_modules/spam/mod2.html#func2"') == 2
30    assert result.count('href="_modules/spam/mod1.html#Class1"') == 2
31    assert result.count('href="_modules/spam/mod2.html#Class2"') == 2
32    assert result.count('@decorator') == 1
33
34    # test that the class attribute is correctly documented
35    assert result.count('this is Class3') == 2
36    assert 'this is the class attribute class_attr' in result
37    # the next assert fails, until the autodoc bug gets fixed
38    assert result.count('this is the class attribute class_attr') == 2
39
40    result = (app.outdir / '_modules/spam/mod1.html').read_text()
41    result = re.sub('<span class=".*?">', '<span>', result)  # filter pygments classes
42    assert ('<div class="viewcode-block" id="Class1"><a class="viewcode-back" '
43            'href="../../index.html#spam.Class1">[docs]</a>'
44            '<span>@decorator</span>\n'
45            '<span>class</span> <span>Class1</span>'
46            '<span>(</span><span>object</span><span>):</span>\n'
47            '    <span>&quot;&quot;&quot;</span>\n'
48            '<span>    this is Class1</span>\n'
49            '<span>    &quot;&quot;&quot;</span></div>\n') in result
50
51
52@pytest.mark.sphinx('epub', testroot='ext-viewcode')
53def test_viewcode_epub_default(app, status, warning):
54    app.builder.build_all()
55
56    assert not (app.outdir / '_modules/spam/mod1.xhtml').exists()
57
58    result = (app.outdir / 'index.xhtml').read_text()
59    assert result.count('href="_modules/spam/mod1.xhtml#func1"') == 0
60
61
62@pytest.mark.sphinx('epub', testroot='ext-viewcode',
63                    confoverrides={'viewcode_enable_epub': True})
64def test_viewcode_epub_enabled(app, status, warning):
65    app.builder.build_all()
66
67    assert (app.outdir / '_modules/spam/mod1.xhtml').exists()
68
69    result = (app.outdir / 'index.xhtml').read_text()
70    assert result.count('href="_modules/spam/mod1.xhtml#func1"') == 2
71
72
73@pytest.mark.sphinx(testroot='ext-viewcode', tags=['test_linkcode'])
74def test_linkcode(app, status, warning):
75    app.builder.build(['objects'])
76
77    stuff = (app.outdir / 'objects.html').read_text()
78
79    assert 'http://foobar/source/foolib.py' in stuff
80    assert 'http://foobar/js/' in stuff
81    assert 'http://foobar/c/' in stuff
82    assert 'http://foobar/cpp/' in stuff
83
84
85@pytest.mark.sphinx(testroot='ext-viewcode-find')
86def test_local_source_files(app, status, warning):
87    def find_source(app, modname):
88        if modname == 'not_a_package':
89            source = (app.srcdir / 'not_a_package/__init__.py').read_text()
90            tags = {
91                'func1': ('def', 1, 1),
92                'Class1': ('class', 1, 1),
93                'not_a_package.submodule.func1': ('def', 1, 1),
94                'not_a_package.submodule.Class1': ('class', 1, 1),
95            }
96        else:
97            source = (app.srcdir / 'not_a_package/submodule.py').read_text()
98            tags = {
99                'not_a_package.submodule.func1': ('def', 11, 15),
100                'Class1': ('class', 19, 22),
101                'not_a_package.submodule.Class1': ('class', 19, 22),
102                'Class3': ('class', 25, 30),
103                'not_a_package.submodule.Class3.class_attr': ('other', 29, 29),
104            }
105        return (source, tags)
106
107    app.connect('viewcode-find-source', find_source)
108    app.builder.build_all()
109
110    warnings = re.sub(r'\\+', '/', warning.getvalue())
111    assert re.findall(
112        r"index.rst:\d+: WARNING: Object named 'func1' not found in include " +
113        r"file .*/not_a_package/__init__.py'",
114        warnings
115    )
116
117    result = (app.outdir / 'index.html').read_text()
118    assert result.count('href="_modules/not_a_package.html#func1"') == 1
119    assert result.count('href="_modules/not_a_package.html#not_a_package.submodule.func1"') == 1
120    assert result.count('href="_modules/not_a_package/submodule.html#Class1"') == 1
121    assert result.count('href="_modules/not_a_package/submodule.html#Class3"') == 1
122    assert result.count('href="_modules/not_a_package/submodule.html#not_a_package.submodule.Class1"') == 1
123
124    assert result.count('href="_modules/not_a_package/submodule.html#not_a_package.submodule.Class3.class_attr"') == 1
125    assert result.count('This is the class attribute class_attr') == 1
126