Skip to content

Commit

Permalink
Use read-only test roots (#13285)
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner authored Jan 31, 2025
1 parent d24ffe2 commit 0d4425c
Show file tree
Hide file tree
Showing 13 changed files with 89 additions and 25 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Mount the test roots as read-only
run: |
mkdir -p ./tests/roots-read-only
sudo mount -v --bind --read-only ./tests/roots ./tests/roots-read-only
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
Expand Down
8 changes: 6 additions & 2 deletions sphinx/testing/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,16 @@ def app_params(

test_root = kwargs.pop('testroot', 'root')
kwargs['srcdir'] = srcdir = sphinx_test_tempdir / kwargs.get('srcdir', test_root)
copy_test_root = not {'srcdir', 'copy_test_root'}.isdisjoint(kwargs)

# special support for sphinx/tests
if rootdir is not None:
test_root_path = rootdir / f'test-{test_root}'
if test_root_path.is_dir() and not srcdir.exists():
shutil.copytree(test_root_path, srcdir)
if copy_test_root:
if test_root_path.is_dir():
shutil.copytree(test_root_path, srcdir, dirs_exist_ok=True)
else:
kwargs['srcdir'] = test_root_path

# always write to the temporary directory
kwargs.setdefault('builddir', srcdir / '_build')
Expand Down
6 changes: 5 additions & 1 deletion sphinx/testing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,11 @@ def warning(self) -> StringIO:
def cleanup(self, doctrees: bool = False) -> None:
sys.path[:] = self._saved_path
_clean_up_global_state()
self.docutils_conf_path.unlink(missing_ok=True)
try:
self.docutils_conf_path.unlink(missing_ok=True)
except OSError as exc:
if exc.errno != 30: # Ignore "read-only file system" errors
raise

def __repr__(self) -> str:
return f'<{self.__class__.__name__} buildername={self._builder_name!r}>'
Expand Down
5 changes: 4 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
from collections.abc import Iterator

_TESTS_ROOT = Path(__file__).resolve().parent
_ROOTS_DIR = _TESTS_ROOT / 'roots'
if 'CI' in os.environ and (_TESTS_ROOT / 'roots-read-only').is_dir():
_ROOTS_DIR = _TESTS_ROOT / 'roots-read-only'
else:
_ROOTS_DIR = _TESTS_ROOT / 'roots'


def _init_console(
Expand Down
1 change: 1 addition & 0 deletions tests/test_builders/test_build_linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ def test_too_many_retries(app: SphinxTestApp) -> None:
'linkcheck',
testroot='linkcheck-raw-node',
freshenv=True,
copy_test_root=True,
)
def test_raw_node(app: SphinxTestApp) -> None:
with serve_application(app, OKHandler) as address:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_environment/test_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
)


@pytest.mark.sphinx('dummy', testroot='basic')
@pytest.mark.sphinx('dummy', testroot='basic', copy_test_root=True)
def test_config_status(make_app, app_params):
args, kwargs = app_params

Expand Down
6 changes: 6 additions & 0 deletions tests/test_extensions/test_ext_autodoc_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@ def test_autodoc_typehints_description(app):
'autodoc_typehints': 'description',
'autodoc_typehints_description_target': 'documented',
},
copy_test_root=True,
)
def test_autodoc_typehints_description_no_undoc(app):
# No :type: or :rtype: will be injected for `incr`, which does not have
Expand Down Expand Up @@ -1085,6 +1086,7 @@ def test_autodoc_typehints_description_no_undoc(app):
'autodoc_typehints': 'description',
'autodoc_typehints_description_target': 'documented_params',
},
copy_test_root=True,
)
def test_autodoc_typehints_description_no_undoc_doc_rtype(app):
# No :type: will be injected for `incr`, which does not have a description
Expand Down Expand Up @@ -1154,6 +1156,7 @@ def test_autodoc_typehints_description_no_undoc_doc_rtype(app):
'text',
testroot='ext-autodoc',
confoverrides={'autodoc_typehints': 'description'},
copy_test_root=True,
)
def test_autodoc_typehints_description_with_documented_init(app):
with overwrite_file(
Expand Down Expand Up @@ -1198,6 +1201,7 @@ def test_autodoc_typehints_description_with_documented_init(app):
'autodoc_typehints': 'description',
'autodoc_typehints_description_target': 'documented',
},
copy_test_root=True,
)
def test_autodoc_typehints_description_with_documented_init_no_undoc(app):
with overwrite_file(
Expand Down Expand Up @@ -1232,6 +1236,7 @@ def test_autodoc_typehints_description_with_documented_init_no_undoc(app):
'autodoc_typehints': 'description',
'autodoc_typehints_description_target': 'documented_params',
},
copy_test_root=True,
)
def test_autodoc_typehints_description_with_documented_init_no_undoc_doc_rtype(app):
# see test_autodoc_typehints_description_with_documented_init_no_undoc
Expand Down Expand Up @@ -1276,6 +1281,7 @@ def test_autodoc_typehints_description_for_invalid_node(app):
'text',
testroot='ext-autodoc',
confoverrides={'autodoc_typehints': 'both'},
copy_test_root=True,
)
def test_autodoc_typehints_both(app):
with overwrite_file(
Expand Down
58 changes: 44 additions & 14 deletions tests/test_extensions/test_ext_autosummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def test_extract_summary(capsys):
'dummy',
testroot='ext-autosummary-ext',
confoverrides=defaults.copy(),
copy_test_root=True,
)
def test_get_items_summary(make_app, app_params):
import sphinx.ext.autosummary
Expand Down Expand Up @@ -227,6 +228,7 @@ def str_content(elem: Element) -> str:
'xml',
testroot='ext-autosummary-ext',
confoverrides=defaults.copy(),
copy_test_root=True,
)
def test_escaping(app):
app.build(force_all=True)
Expand All @@ -238,7 +240,7 @@ def test_escaping(app):
assert str_content(title) == 'underscore_module_'


@pytest.mark.sphinx('html', testroot='ext-autosummary')
@pytest.mark.sphinx('html', testroot='ext-autosummary', copy_test_root=True)
def test_autosummary_generate_content_for_module(app):
import autosummary_dummy_module # type: ignore[import-not-found]

Expand Down Expand Up @@ -298,7 +300,7 @@ def test_autosummary_generate_content_for_module(app):
assert context['objtype'] == 'module'


@pytest.mark.sphinx('html', testroot='ext-autosummary')
@pytest.mark.sphinx('html', testroot='ext-autosummary', copy_test_root=True)
def test_autosummary_generate_content_for_module___all__(app):
import autosummary_dummy_module

Expand Down Expand Up @@ -343,7 +345,7 @@ def test_autosummary_generate_content_for_module___all__(app):
assert context['objtype'] == 'module'


@pytest.mark.sphinx('html', testroot='ext-autosummary')
@pytest.mark.sphinx('html', testroot='ext-autosummary', copy_test_root=True)
def test_autosummary_generate_content_for_module_skipped(app):
import autosummary_dummy_module

Expand Down Expand Up @@ -389,7 +391,7 @@ def skip_member(app, what, name, obj, skip, options):
assert context['exceptions'] == []


@pytest.mark.sphinx('html', testroot='ext-autosummary')
@pytest.mark.sphinx('html', testroot='ext-autosummary', copy_test_root=True)
def test_autosummary_generate_content_for_module_imported_members(app):
import autosummary_dummy_module

Expand Down Expand Up @@ -455,7 +457,7 @@ def test_autosummary_generate_content_for_module_imported_members(app):
assert context['objtype'] == 'module'


@pytest.mark.sphinx('html', testroot='ext-autosummary')
@pytest.mark.sphinx('html', testroot='ext-autosummary', copy_test_root=True)
def test_autosummary_generate_content_for_module_imported_members_inherited_module(app):
import autosummary_dummy_inherited_module # type: ignore[import-not-found]

Expand Down Expand Up @@ -501,7 +503,7 @@ def test_autosummary_generate_content_for_module_imported_members_inherited_modu
assert context['objtype'] == 'module'


@pytest.mark.sphinx('dummy', testroot='ext-autosummary')
@pytest.mark.sphinx('dummy', testroot='ext-autosummary', copy_test_root=True)
def test_autosummary_generate(app):
app.build(force_all=True)

Expand Down Expand Up @@ -650,6 +652,7 @@ def test_autosummary_generate(app):
'dummy',
testroot='ext-autosummary',
confoverrides={'autosummary_generate_overwrite': False},
copy_test_root=True,
)
def test_autosummary_generate_overwrite1(app_params, make_app):
args, kwargs = app_params
Expand All @@ -669,6 +672,7 @@ def test_autosummary_generate_overwrite1(app_params, make_app):
'dummy',
testroot='ext-autosummary',
confoverrides={'autosummary_generate_overwrite': True},
copy_test_root=True,
)
def test_autosummary_generate_overwrite2(app_params, make_app):
args, kwargs = app_params
Expand All @@ -684,7 +688,7 @@ def test_autosummary_generate_overwrite2(app_params, make_app):
assert 'autosummary_dummy_module.rst' not in app._warning.getvalue()


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-recursive')
@pytest.mark.sphinx('dummy', testroot='ext-autosummary-recursive', copy_test_root=True)
@pytest.mark.usefixtures('rollback_sysmodules')
def test_autosummary_recursive(app):
sys.modules.pop('package', None) # unload target module to clear the module cache
Expand Down Expand Up @@ -738,7 +742,11 @@ def test_autosummary_recursive_skips_mocked_modules(app):
assert not (app.srcdir / 'generated' / 'package.package.module.rst').exists()


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-filename-map')
@pytest.mark.sphinx(
'dummy',
testroot='ext-autosummary-filename-map',
copy_test_root=True,
)
def test_autosummary_filename_map(app):
app.build()

Expand All @@ -756,6 +764,7 @@ def test_autosummary_filename_map(app):
'latex',
testroot='ext-autosummary-ext',
confoverrides=defaults.copy(),
copy_test_root=True,
)
def test_autosummary_latex_table_colspec(app):
app.build(force_all=True)
Expand Down Expand Up @@ -793,7 +802,11 @@ def test_import_by_name():
assert modname == 'sphinx.ext.autosummary'


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-mock_imports')
@pytest.mark.sphinx(
'dummy',
testroot='ext-autosummary-mock_imports',
copy_test_root=True,
)
def test_autosummary_mock_imports(app):
try:
app.build()
Expand All @@ -805,7 +818,11 @@ def test_autosummary_mock_imports(app):
sys.modules.pop('foo', None) # unload foo module


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-imported_members')
@pytest.mark.sphinx(
'dummy',
testroot='ext-autosummary-imported_members',
copy_test_root=True,
)
def test_autosummary_imported_members(app):
try:
app.build()
Expand All @@ -820,7 +837,11 @@ def test_autosummary_imported_members(app):
sys.modules.pop('autosummary_dummy_package', None)


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-module_all')
@pytest.mark.sphinx(
'dummy',
testroot='ext-autosummary-module_all',
copy_test_root=True,
)
def test_autosummary_module_all(app):
try:
app.build()
Expand All @@ -839,7 +860,11 @@ def test_autosummary_module_all(app):
sys.modules.pop('autosummary_dummy_package_all', None)


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-module_empty_all')
@pytest.mark.sphinx(
'dummy',
testroot='ext-autosummary-module_empty_all',
copy_test_root=True,
)
def test_autosummary_module_empty_all(app):
try:
app.build()
Expand Down Expand Up @@ -867,6 +892,7 @@ def test_autosummary_module_empty_all(app):
'html',
testroot='ext-autodoc',
confoverrides={'extensions': ['sphinx.ext.autosummary']},
copy_test_root=True,
)
def test_generate_autosummary_docs_property(app):
with patch('sphinx.ext.autosummary.generate.find_autosummary_in_files') as mock:
Expand All @@ -886,7 +912,11 @@ def test_generate_autosummary_docs_property(app):
)


@pytest.mark.sphinx('html', testroot='ext-autosummary-skip-member')
@pytest.mark.sphinx(
'html',
testroot='ext-autosummary-skip-member',
copy_test_root=True,
)
def test_autosummary_skip_member(app):
app.build()

Expand All @@ -895,7 +925,7 @@ def test_autosummary_skip_member(app):
assert 'Foo._privatemeth' in content


@pytest.mark.sphinx('html', testroot='ext-autosummary-template')
@pytest.mark.sphinx('html', testroot='ext-autosummary-template', copy_test_root=True)
def test_autosummary_template(app):
app.build()

Expand Down
6 changes: 5 additions & 1 deletion tests/test_extensions/test_ext_autosummary_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ def test_autosummary_import_cycle(app):
assert expected in app.warning.getvalue()


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-module_prefix')
@pytest.mark.sphinx(
'dummy',
testroot='ext-autosummary-module_prefix',
copy_test_root=True,
)
@pytest.mark.usefixtures('rollback_sysmodules')
def test_autosummary_generate_prefixes(app):
app.build()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_extensions/test_ext_intersphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ def log_message(*args, **kwargs):
assert stderr == ''


@pytest.mark.sphinx('html', testroot='ext-intersphinx-role')
@pytest.mark.sphinx('html', testroot='ext-intersphinx-role', copy_test_root=True)
def test_intersphinx_role(app):
inv_file = app.srcdir / 'inventory'
inv_file.write_bytes(INVENTORY_V2)
Expand Down
Loading

0 comments on commit 0d4425c

Please sign in to comment.