Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tests] Implementation of the new testing plugin (backward-compatible) [part 2] #12093

Open
wants to merge 69 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
a4ef0a5
patch marker signatures
picnixz Mar 14, 2024
793f45c
update doc
picnixz Mar 14, 2024
4bc677f
revert suppression
picnixz Mar 14, 2024
c69f375
add xdist dependency but disable it for now
picnixz Mar 14, 2024
02e7813
Merge branch 'master' into fix/testing-types
picnixz Mar 14, 2024
42f6044
fixup
picnixz Mar 14, 2024
e17f0b7
Merge branch 'fix/testing-types' into core/test-plugin
picnixz Mar 14, 2024
b5457e8
implement new plugin
picnixz Mar 14, 2024
550a30f
fixup
picnixz Mar 14, 2024
2235e7f
fixup
picnixz Mar 14, 2024
c8ffbd8
try to fix windows
picnixz Mar 14, 2024
2b372cc
fixup
picnixz Mar 14, 2024
866059e
fixup?
picnixz Mar 14, 2024
9ee89b4
fixup
picnixz Mar 14, 2024
100f861
fix a check + test
picnixz Mar 15, 2024
30b82d7
Merge branch 'master' into fix/testing-types
picnixz Mar 15, 2024
875ef51
fixup
picnixz Mar 15, 2024
bc00228
make the plugin backwards compatible
picnixz Mar 15, 2024
4ce0115
Merge branch 'master' into core/test-plugin
picnixz Mar 15, 2024
504bf49
Merge branch 'master' into fix/testing-types
picnixz Mar 15, 2024
6f21442
ensure type safety
picnixz Mar 15, 2024
6789bc3
allow buildername as a keyword argument in the marker
picnixz Mar 15, 2024
333aa8f
allow buildername as a keyword argument in the marker
picnixz Mar 15, 2024
91b035f
allow buildername as a keyword argument in the marker
picnixz Mar 15, 2024
1aaa037
fix deprecation warning
picnixz Mar 15, 2024
42e27f0
revert some changes
picnixz Mar 15, 2024
beb9c1e
revert some changes
picnixz Mar 15, 2024
4d7172a
cleanup and simplify
picnixz Mar 15, 2024
33d5772
cleanup and simplify
picnixz Mar 15, 2024
5be3c74
remove xdist for now
picnixz Mar 15, 2024
b4f43ee
cleanup
picnixz Mar 15, 2024
fe89013
Merge branch 'master' into fix/testing-types
picnixz Mar 15, 2024
a93efe4
revert some changes
picnixz Mar 15, 2024
6c45a41
remove ref
picnixz Mar 15, 2024
f60e97e
revert order
picnixz Mar 15, 2024
ba61814
Merge branch 'fix/testing-types' into core/test-plugin
picnixz Mar 15, 2024
cd94c8f
Merge branch 'master' into core/test-plugin
picnixz Mar 16, 2024
54e394f
Merge branch 'master' into core/test-plugin
picnixz Mar 16, 2024
dce556d
fixup
picnixz Mar 16, 2024
a880fd0
use private naming
picnixz Mar 16, 2024
b5faf40
Merge branch 'master' into core/test-plugin
picnixz Mar 16, 2024
6b43a6a
Refine configuration checksum
picnixz Mar 16, 2024
f7d7b73
Refine configuration checksum
picnixz Mar 16, 2024
d312815
fixup
picnixz Mar 16, 2024
318e075
Merge branch 'master' into core/test-plugin
picnixz Mar 17, 2024
4e06556
fixup
picnixz Mar 17, 2024
33d48f7
Merge branch 'master' into core/test-plugin
picnixz Mar 17, 2024
0da65df
update comment
picnixz Mar 18, 2024
f2e1044
Merge branch 'master' into core/test-plugin
picnixz Mar 23, 2024
31e61b5
fixup isolation deduction
picnixz Mar 23, 2024
86de828
fixup isolation deduction
picnixz Mar 23, 2024
329b98b
cleanup
picnixz Mar 23, 2024
2a5d03a
apply ruff format
picnixz Mar 23, 2024
455b97d
Merge branch 'master' into core/test-plugin
picnixz Mar 23, 2024
e33ba40
fixup
picnixz Mar 23, 2024
847eb7a
Merge branch 'master' into core/test-plugin
picnixz Mar 24, 2024
f787670
Merge branch 'master' into core/test-plugin
picnixz Mar 24, 2024
a257ec2
Merge branch 'master' into core/test-plugin
picnixz Mar 27, 2024
a7e4026
fix lint
picnixz Mar 27, 2024
da6e9c5
Merge branch 'master' into core/test-plugin
picnixz Apr 2, 2024
054cb79
fix lint
picnixz Apr 2, 2024
e859d9f
Merge branch 'master' into core/test-plugin
picnixz Apr 6, 2024
f68197b
fixup
picnixz Apr 6, 2024
d71fdf5
Merge remote-tracking branch 'upstream/master' into core/test-plugin
picnixz Apr 19, 2024
e431ce9
cleanup
picnixz Apr 19, 2024
74d96a3
cleanup
picnixz Apr 19, 2024
9560a65
cleanup
picnixz Apr 19, 2024
a452a03
Merge branch 'master' into core/test-plugin
picnixz Apr 27, 2024
9ca2a3b
Merge branch 'master' into core/test-plugin
picnixz May 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Expand Up @@ -91,6 +91,7 @@ lint = [
]
test = [
"pytest>=6.0",
"pytest-xdist==3.5.0",
"html5lib",
"cython>=3.0",
"setuptools>=67.0", # for Cython compilation
Expand Down Expand Up @@ -289,6 +290,7 @@ ignore_errors = true
minversion = 6.0
addopts = [
"-ra",
"-p no:xdist", # disable xdist for now
"--import-mode=prepend",
# "--pythonwarnings=error",
"--strict-config",
Expand Down
6 changes: 6 additions & 0 deletions sphinx/testing/_internal/__init__.py
@@ -0,0 +1,6 @@
"""This package contains implementation details for the Sphinx testing plugin.

All modules in this package are considered an implementation detail
and any provided functionality can be altered, removed or introduce
breaking changes without prior notice.
"""
155 changes: 155 additions & 0 deletions sphinx/testing/_internal/cache.py
@@ -0,0 +1,155 @@
from __future__ import annotations

__all__ = ()

import dataclasses
import itertools
import shutil
from io import StringIO
from typing import TYPE_CHECKING, TypedDict

if TYPE_CHECKING:
from typing import Any

from sphinx.testing.util import SphinxTestApp, SphinxTestAppWrapperForSkipBuilding


class _CacheEntry(TypedDict):
"""Cached entry in a :class:`ModuleCache`."""

status: str
"""The application's status output."""
warning: str
"""The application's warning output."""


class _CacheFrame(TypedDict):
"""The restored cached value."""

status: StringIO
"""An I/O object initialized to the cached status output."""
warning: StringIO
"""An I/O object initialized to the cached warning output."""


class ModuleCache:
__slots__ = ('_cache',)

def __init__(self) -> None:
self._cache: dict[str, _CacheEntry] = {}

def clear(self) -> None:
"""Clear the cache."""
self._cache.clear()

def store(self, key: str, app: SphinxTestApp) -> None:
"""Cache some attributes from *app* in the cache.

:param key: The cache key (usually a ``shared_result``).
:param app: An application whose attributes are cached.

The application's attributes being cached are:

* The content of :attr:`~sphinx.testing.util.SphinxTestApp.status`.
* The content of :attr:`~sphinx.testing.util.SphinxTestApp.warning`.
"""
if key not in self._cache:
status, warning = app.status.getvalue(), app.warning.getvalue()
self._cache[key] = {'status': status, 'warning': warning}

def restore(self, key: str) -> _CacheFrame | None:
"""Reconstruct the cached attributes for *key*."""
if key not in self._cache:
return None

data = self._cache[key]
return {'status': StringIO(data['status']), 'warning': StringIO(data['warning'])}


@dataclasses.dataclass
class AppInfo:
"""Information to report during the teardown phase of the ``app()`` fixture.

The information is either rendered as a report section (for ``xdist``
integration) or directly printed using a ``print`` statement.
"""

builder: str
"""The builder name."""

testroot_path: str | None
"""The absolute path to the sources directory (if any)."""
shared_result: str | None
"""The user-defined shared result (if any)."""

srcdir: str
"""The absolute path to the application's sources directory."""
outdir: str
"""The absolute path to the application's output directory."""

extras: dict[str, Any] = dataclasses.field(default_factory=dict, init=False)
"""Attributes added by :func:`sphinx.testing.fixtures.app_test_info`."""

# fields below are updated when tearing down :func:`sphinx.testing.fixtures.app`
_messages: str = dataclasses.field(default='', init=False)
"""The application's status messages (updated by the fixture)."""
_warnings: str = dataclasses.field(default='', init=False)
"""The application's warnings messages (updated by the fixture)."""

def update(self, app: SphinxTestApp | SphinxTestAppWrapperForSkipBuilding) -> None:
"""Update the application's status and warning messages."""
self._messages = app.status.getvalue()
self._warnings = app.warning.getvalue()

def render(self, nodeid: str | None = None) -> str:
"""Format the report as a string to print or render.

:param nodeid: Optional node id to include in the report.
:return: The formatted information.
"""
config = [('builder', self.builder)]
if nodeid:
config.insert(0, ('test case', nodeid))

if self.testroot_path:
config.append(('testroot path', self.testroot_path))
config.extend([('srcdir', self.srcdir), ('outdir', self.outdir)])
config.extend((name, repr(value)) for name, value in self.extras.items())

tw, _ = shutil.get_terminal_size()
kw = 8 + max(len(name) for name, _ in config)

lines = itertools.chain(
[f'{" environment ":-^{tw}}'],
(f'{name:{kw}s} {strvalue}' for name, strvalue in config),
[f'{" messages ":-^{tw}}', text] if (text := self._messages) else (),
[f'{" warnings ":-^{tw}}', text] if (text := self._warnings) else (),
['=' * tw],
)
return '\n'.join(lines)


# XXX: RemovedInSphinx90Warning
class LegacyModuleCache: # kept for legacy purposes
cache: dict[str, dict[str, str]] = {}

def store(
self, key: str, app_: SphinxTestApp | SphinxTestAppWrapperForSkipBuilding,
) -> None:
if key in self.cache:
return
data = {
'status': app_.status.getvalue(),
'warning': app_.warning.getvalue(),
}
self.cache[key] = data

def restore(self, key: str) -> dict[str, StringIO]:
if key not in self.cache:
return {}

data = self.cache[key]
return {
'status': StringIO(data['status']),
'warning': StringIO(data['warning']),
}
46 changes: 46 additions & 0 deletions sphinx/testing/_internal/isolation.py
@@ -0,0 +1,46 @@
"""Private module containing isolation-related objects and functionalities.

Use literal strings or booleans to indicate isolation policies instead of
directly using :class:`Isolation` objects, unless it is used internally.
"""

from __future__ import annotations

__all__ = ()

from enum import IntEnum
from enum import auto as _auto
from typing import Literal, Union


class Isolation(IntEnum):
"""Isolation policy for the testing application."""

minimal = _auto()
"""Minimal isolation mode."""
grouped = _auto()
"""Similar to :attr:`always` but for parametrized tests."""
always = _auto()
"""Copy the original testroot to a unique sources and build directory."""


IsolationPolicy = Union[bool, Literal['minimal', 'grouped', 'always']]
"""Allowed values for the isolation policy."""

NormalizableIsolation = Union[IsolationPolicy, Isolation]
"""Normalizable isolation value."""


def normalize_isolation_policy(policy: NormalizableIsolation) -> Isolation:
"""Normalize isolation policy into a :class:`Isolation` object."""
if isinstance(policy, Isolation):
return policy

if isinstance(policy, bool):
return Isolation.always if policy else Isolation.minimal

if isinstance(policy, str) and hasattr(Isolation, policy):
return getattr(Isolation, policy)

msg = f'unknown isolation policy: {policy!r}'
raise TypeError(msg)