summaryrefslogtreecommitdiffstats
path: root/tests/test_application.py
blob: 1fc49d61042f61f196c7986fcb8f3fa3805fdd33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""Test the Sphinx class."""
from __future__ import annotations

import shutil
import sys
from io import StringIO
from pathlib import Path
from typing import TYPE_CHECKING
from unittest.mock import Mock

import pytest
from docutils import nodes

import sphinx.application
from sphinx.errors import ExtensionError
from sphinx.testing.util import SphinxTestApp
from sphinx.util import logging
from sphinx.util.console import strip_colors

if TYPE_CHECKING:
    import os


def test_instantiation(
    tmp_path_factory: pytest.TempPathFactory,
    rootdir: str | os.PathLike[str] | None,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    # Given
    src_dir = tmp_path_factory.getbasetemp() / 'root'

    # special support for sphinx/tests
    if rootdir and not src_dir.exists():
        shutil.copytree(Path(str(rootdir)) / 'test-root', src_dir)

    syspath = sys.path[:]

    # When
    app_ = SphinxTestApp(
        srcdir=src_dir,
        status=StringIO(),
        warning=StringIO(),
    )
    sys.path[:] = syspath
    app_.cleanup()

    # Then
    assert isinstance(app_, sphinx.application.Sphinx)


def test_events(app, status, warning):
    def empty():
        pass
    with pytest.raises(ExtensionError) as excinfo:
        app.connect("invalid", empty)
    assert "Unknown event name: invalid" in str(excinfo.value)

    app.add_event("my_event")
    with pytest.raises(ExtensionError) as excinfo:
        app.add_event("my_event")
    assert "Event 'my_event' already present" in str(excinfo.value)

    def mock_callback(a_app, *args):
        assert a_app is app
        assert emit_args == args
        return "ret"
    emit_args = (1, 3, "string")
    listener_id = app.connect("my_event", mock_callback)
    assert app.emit("my_event", *emit_args) == ["ret"], "Callback not called"

    app.disconnect(listener_id)
    assert app.emit("my_event", *emit_args) == [], \
        "Callback called when disconnected"


def test_emit_with_nonascii_name_node(app, status, warning):
    node = nodes.section(names=['\u65e5\u672c\u8a9e'])
    app.emit('my_event', node)


def test_extensions(app, status, warning):
    app.setup_extension('shutil')
    warning = strip_colors(warning.getvalue())
    assert "extension 'shutil' has no setup() function" in warning


def test_extension_in_blacklist(app, status, warning):
    app.setup_extension('sphinxjp.themecore')
    msg = strip_colors(warning.getvalue())
    assert msg.startswith("WARNING: the extension 'sphinxjp.themecore' was")


@pytest.mark.sphinx(testroot='add_source_parser')
def test_add_source_parser(app, status, warning):
    assert set(app.config.source_suffix) == {'.rst', '.test'}

    # .rst; only in :confval:`source_suffix`
    assert '.rst' not in app.registry.get_source_parsers()
    assert app.registry.source_suffix['.rst'] is None

    # .test; configured by API
    assert app.registry.source_suffix['.test'] == 'test'
    assert 'test' in app.registry.get_source_parsers()
    assert app.registry.get_source_parsers()['test'].__name__ == 'TestSourceParser'


@pytest.mark.sphinx(testroot='extensions')
def test_add_is_parallel_allowed(app, status, warning):
    logging.setup(app, status, warning)

    assert app.is_parallel_allowed('read') is True
    assert app.is_parallel_allowed('write') is True
    assert warning.getvalue() == ''

    app.setup_extension('read_parallel')
    assert app.is_parallel_allowed('read') is True
    assert app.is_parallel_allowed('write') is True
    assert warning.getvalue() == ''
    app.extensions.pop('read_parallel')

    app.setup_extension('write_parallel')
    assert app.is_parallel_allowed('read') is False
    assert app.is_parallel_allowed('write') is True
    assert ("the write_parallel extension does not declare if it is safe "
            "for parallel reading, assuming it isn't - please ") in warning.getvalue()
    app.extensions.pop('write_parallel')
    warning.truncate(0)  # reset warnings

    app.setup_extension('read_serial')
    assert app.is_parallel_allowed('read') is False
    assert "the read_serial extension is not safe for parallel reading" in warning.getvalue()
    warning.truncate(0)  # reset warnings
    assert app.is_parallel_allowed('write') is True
    assert warning.getvalue() == ''
    app.extensions.pop('read_serial')

    app.setup_extension('write_serial')
    assert app.is_parallel_allowed('read') is False
    assert app.is_parallel_allowed('write') is False
    assert ("the write_serial extension does not declare if it is safe "
            "for parallel reading, assuming it isn't - please ") in warning.getvalue()
    app.extensions.pop('write_serial')
    warning.truncate(0)  # reset warnings


@pytest.mark.sphinx('dummy', testroot='root')
def test_build_specific(app):
    app.builder.build = Mock()
    filenames = [app.srcdir / 'index.txt',                      # normal
                 app.srcdir / 'images',                         # without suffix
                 app.srcdir / 'notfound.txt',                   # not found
                 app.srcdir / 'img.png',                        # unknown suffix
                 '/index.txt',                                  # external file
                 app.srcdir / 'subdir',                         # directory
                 app.srcdir / 'subdir/includes.txt',            # file on subdir
                 app.srcdir / 'subdir/../subdir/excluded.txt']  # not normalized
    app.build(False, filenames)

    expected = ['index', 'subdir/includes', 'subdir/excluded']
    app.builder.build.assert_called_with(expected,
                                         method='specific',
                                         summary='3 source files given on command line')