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 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
"""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._cli.util.errors import strip_escape_sequences
from sphinx.errors import ExtensionError
from sphinx.testing.util import SphinxTestApp
from sphinx.util import logging
if TYPE_CHECKING:
import os
def test_instantiation(
tmp_path_factory: pytest.TempPathFactory,
rootdir: str | os.PathLike[str] | None,
) -> 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)
saved_path = sys.path.copy()
# When
app_ = SphinxTestApp(
srcdir=src_dir,
status=StringIO(),
warning=StringIO(),
)
sys.path[:] = saved_path
app_.cleanup()
# Then
assert isinstance(app_, sphinx.application.Sphinx)
@pytest.mark.sphinx('html', testroot='root')
def test_events(app: SphinxTestApp) -> None:
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'
@pytest.mark.sphinx('html', testroot='root')
def test_emit_with_nonascii_name_node(app: SphinxTestApp) -> None:
node = nodes.section(names=['\u65e5\u672c\u8a9e'])
app.emit('my_event', node)
@pytest.mark.sphinx('html', testroot='root')
def test_extensions(app: SphinxTestApp) -> None:
app.setup_extension('shutil')
warning = strip_escape_sequences(app.warning.getvalue())
assert "extension 'shutil' has no setup() function" in warning
@pytest.mark.sphinx('html', testroot='root')
def test_extension_in_blacklist(app: SphinxTestApp) -> None:
app.setup_extension('sphinxjp.themecore')
msg = strip_escape_sequences(app.warning.getvalue())
assert msg.startswith("WARNING: the extension 'sphinxjp.themecore' was")
@pytest.mark.sphinx('html', testroot='add_source_parser')
def test_add_source_parser(app: SphinxTestApp) -> None:
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'] == 'restructuredtext'
# .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('html', testroot='extensions')
def test_add_is_parallel_allowed(app: SphinxTestApp) -> None:
logging.setup(app, app.status, app.warning)
assert app.is_parallel_allowed('read') is True
assert app.is_parallel_allowed('write') is True
assert app.warning.getvalue() == ''
app.setup_extension('read_parallel')
assert app.is_parallel_allowed('read') is True
assert app.is_parallel_allowed('write') is True
assert app.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 app.warning.getvalue()
app.extensions.pop('write_parallel')
app.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 app.warning.getvalue()
app.warning.truncate(0) # reset warnings
assert app.is_parallel_allowed('write') is True
assert app.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 app.warning.getvalue()
app.extensions.pop('write_serial')
app.warning.truncate(0) # reset warnings
@pytest.mark.sphinx('dummy', testroot='root')
def test_build_specific(app: SphinxTestApp) -> None:
app.builder.build = Mock() # type: ignore[method-assign,misc]
filenames = [
app.srcdir / 'index.txt', # normal
app.srcdir / 'images', # without suffix
app.srcdir / 'notfound.txt', # not found
app.srcdir / 'img.png', # unknown suffix
Path('/index.txt'), # external file
app.srcdir / 'subdir', # directory
app.srcdir / 'subdir/includes.txt', # file on subdir
app.srcdir / 'subdir/../subdir/excluded.txt', # not normalized
] # fmt: skip
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',
)
|