"""Test the Sphinx class.""" import shutil import sys from io import StringIO from pathlib import Path 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, strip_escseq from sphinx.util import logging def test_instantiation(tmp_path_factory, rootdir: str, monkeypatch): # 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_escseq(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_escseq(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')