Merge branch 'main' into minor-tidyup-of-terminal-py
This commit is contained in:
commit
8a209e16d3
|
@ -165,38 +165,17 @@ jobs:
|
||||||
if: "matrix.use_coverage"
|
if: "matrix.use_coverage"
|
||||||
run: "tox -e ${{ matrix.tox_env }}-coverage"
|
run: "tox -e ${{ matrix.tox_env }}-coverage"
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Generate coverage report
|
||||||
if: matrix.use_coverage && github.repository == 'pytest-dev/pytest'
|
if: "matrix.use_coverage"
|
||||||
env:
|
run: python -m coverage xml
|
||||||
CODECOV_NAME: ${{ matrix.name }}
|
|
||||||
run: bash scripts/upload-coverage.sh -F GHA,${{ runner.os }}
|
|
||||||
|
|
||||||
linting:
|
- name: Upload coverage to Codecov
|
||||||
runs-on: ubuntu-latest
|
if: "matrix.use_coverage"
|
||||||
permissions:
|
uses: codecov/codecov-action@v2
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
fail_ci_if_error: true
|
||||||
|
files: ./coverage.xml
|
||||||
- uses: actions/setup-python@v2
|
verbose: true
|
||||||
|
|
||||||
- name: set PY
|
|
||||||
run: echo "name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/.cache/pre-commit
|
|
||||||
key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }}
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install tox
|
|
||||||
|
|
||||||
- run: tox -e linting
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
|
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
|
||||||
|
|
|
@ -31,7 +31,7 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install packaging requests tabulate[widechars]
|
pip install packaging requests tabulate[widechars] tqdm
|
||||||
|
|
||||||
- name: Update Plugin List
|
- name: Update Plugin List
|
||||||
run: python scripts/update-plugin-list.py
|
run: python scripts/update-plugin-list.py
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.9b0
|
rev: 21.10b0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
|
@ -39,7 +39,7 @@ repos:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py36-plus]
|
args: [--py36-plus]
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v1.18.0
|
rev: v1.19.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
args: [--max-py-version=3.10]
|
args: [--max-py-version=3.10]
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: 3.7
|
|
||||||
install:
|
install:
|
||||||
- requirements: doc/en/requirements.txt
|
- requirements: doc/en/requirements.txt
|
||||||
- method: pip
|
- method: pip
|
||||||
path: .
|
path: .
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
os: ubuntu-20.04
|
||||||
|
tools:
|
||||||
|
python: "3.9"
|
||||||
apt_packages:
|
apt_packages:
|
||||||
- inkscape
|
- inkscape
|
||||||
|
|
||||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -96,6 +96,7 @@ David Vierra
|
||||||
Daw-Ran Liou
|
Daw-Ran Liou
|
||||||
Debi Mishra
|
Debi Mishra
|
||||||
Denis Kirisov
|
Denis Kirisov
|
||||||
|
Denivy Braiam Rück
|
||||||
Dhiren Serai
|
Dhiren Serai
|
||||||
Diego Russo
|
Diego Russo
|
||||||
Dmitry Dygalo
|
Dmitry Dygalo
|
||||||
|
@ -138,6 +139,7 @@ Grigorii Eremeev (budulianin)
|
||||||
Guido Wesdorp
|
Guido Wesdorp
|
||||||
Guoqiang Zhang
|
Guoqiang Zhang
|
||||||
Harald Armin Massa
|
Harald Armin Massa
|
||||||
|
Harshna
|
||||||
Henk-Jaap Wagenaar
|
Henk-Jaap Wagenaar
|
||||||
Holger Kohr
|
Holger Kohr
|
||||||
Hugo van Kemenade
|
Hugo van Kemenade
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
The PDF documentation’s list of plugins doesn’t run off the page anymore.
|
|
@ -0,0 +1,2 @@
|
||||||
|
The test selection options ``pytest -k`` and ``pytest -m`` now support matching
|
||||||
|
names containing forward slash (``/``) characters.
|
|
@ -0,0 +1 @@
|
||||||
|
Fixed internal error when skipping doctests.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix `pytest -vv` crashing with an internal exception `AttributeError: 'str' object has no attribute 'relative_to'` in some cases.
|
|
@ -0,0 +1 @@
|
||||||
|
Add github action to upload coverage report to codecov instead of bash uploader.
|
|
@ -0,0 +1 @@
|
||||||
|
Upgrade readthedocs configuration to use a [newer Ubuntu version](https://blog.readthedocs.com/new-build-specification/) with better unicode support for PDF docs.
|
|
@ -19,6 +19,7 @@ import ast
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
from textwrap import dedent
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -39,9 +40,22 @@ autodoc_member_order = "bysource"
|
||||||
autodoc_typehints = "description"
|
autodoc_typehints = "description"
|
||||||
todo_include_todos = 1
|
todo_include_todos = 1
|
||||||
|
|
||||||
# Use a different latex engine due to possible Unicode characters in the documentation:
|
latex_engine = "lualatex"
|
||||||
# https://docs.readthedocs.io/en/stable/guides/pdf-non-ascii-languages.html
|
|
||||||
latex_engine = "xelatex"
|
latex_elements = {
|
||||||
|
"preamble": dedent(
|
||||||
|
r"""
|
||||||
|
\directlua{
|
||||||
|
luaotfload.add_fallback("fallbacks", {
|
||||||
|
"Noto Serif CJK SC:style=Regular;",
|
||||||
|
"Symbola:Style=Regular;"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
\setmainfont{FreeSerif}[RawFeature={fallback=fallbacks}]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
|
@ -446,3 +460,7 @@ def setup(app: "sphinx.application.Sphinx") -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
|
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
|
||||||
|
|
||||||
|
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
|
||||||
|
# that autodoc can discover references to it.
|
||||||
|
import _pytest.legacypath # noqa: F401
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -638,7 +638,7 @@ tmpdir
|
||||||
|
|
||||||
:ref:`tmpdir and tmpdir_factory`
|
:ref:`tmpdir and tmpdir_factory`
|
||||||
|
|
||||||
.. autofunction:: _pytest.tmpdir.tmpdir()
|
.. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir()
|
||||||
:no-auto-options:
|
:no-auto-options:
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import datetime
|
import datetime
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
from textwrap import dedent
|
||||||
|
from textwrap import indent
|
||||||
|
|
||||||
import packaging.version
|
import packaging.version
|
||||||
import requests
|
import requests
|
||||||
import tabulate
|
import tabulate
|
||||||
|
import wcwidth
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
FILE_HEAD = r"""
|
FILE_HEAD = r"""
|
||||||
.. _plugin-list:
|
.. _plugin-list:
|
||||||
|
@ -14,6 +18,11 @@ Plugin List
|
||||||
|
|
||||||
PyPI projects that match "pytest-\*" are considered plugins and are listed
|
PyPI projects that match "pytest-\*" are considered plugins and are listed
|
||||||
automatically. Packages classified as inactive are excluded.
|
automatically. Packages classified as inactive are excluded.
|
||||||
|
|
||||||
|
.. The following conditional uses a different format for this list when
|
||||||
|
creating a PDF, because otherwise the table gets far too wide for the
|
||||||
|
page.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
DEVELOPMENT_STATUS_CLASSIFIERS = (
|
DEVELOPMENT_STATUS_CLASSIFIERS = (
|
||||||
"Development Status :: 1 - Planning",
|
"Development Status :: 1 - Planning",
|
||||||
|
@ -42,10 +51,15 @@ def escape_rst(text: str) -> str:
|
||||||
def iter_plugins():
|
def iter_plugins():
|
||||||
regex = r">([\d\w-]*)</a>"
|
regex = r">([\d\w-]*)</a>"
|
||||||
response = requests.get("https://pypi.org/simple")
|
response = requests.get("https://pypi.org/simple")
|
||||||
for match in re.finditer(regex, response.text):
|
|
||||||
|
matches = list(
|
||||||
|
match
|
||||||
|
for match in re.finditer(regex, response.text)
|
||||||
|
if match.groups()[0].startswith("pytest-")
|
||||||
|
)
|
||||||
|
|
||||||
|
for match in tqdm(matches, smoothing=0):
|
||||||
name = match.groups()[0]
|
name = match.groups()[0]
|
||||||
if not name.startswith("pytest-"):
|
|
||||||
continue
|
|
||||||
response = requests.get(f"https://pypi.org/pypi/{name}/json")
|
response = requests.get(f"https://pypi.org/pypi/{name}/json")
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
# Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but
|
# Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but
|
||||||
|
@ -79,22 +93,47 @@ def iter_plugins():
|
||||||
summary = escape_rst(info["summary"].replace("\n", ""))
|
summary = escape_rst(info["summary"].replace("\n", ""))
|
||||||
yield {
|
yield {
|
||||||
"name": name,
|
"name": name,
|
||||||
"summary": summary,
|
"summary": summary.strip(),
|
||||||
"last release": last_release,
|
"last release": last_release,
|
||||||
"status": status,
|
"status": status,
|
||||||
"requires": requires,
|
"requires": requires,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def plugin_definitions(plugins):
|
||||||
|
"""Return RST for the plugin list that fits better on a vertical page."""
|
||||||
|
|
||||||
|
for plugin in plugins:
|
||||||
|
yield dedent(
|
||||||
|
f"""
|
||||||
|
{plugin['name']}
|
||||||
|
*last release*: {plugin["last release"]},
|
||||||
|
*status*: {plugin["status"]},
|
||||||
|
*requires*: {plugin["requires"]}
|
||||||
|
|
||||||
|
{plugin["summary"]}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
plugins = list(iter_plugins())
|
plugins = list(iter_plugins())
|
||||||
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
|
|
||||||
plugin_list = pathlib.Path("doc", "en", "reference", "plugin_list.rst")
|
reference_dir = pathlib.Path("doc", "en", "reference")
|
||||||
|
|
||||||
|
plugin_list = reference_dir / "plugin_list.rst"
|
||||||
with plugin_list.open("w") as f:
|
with plugin_list.open("w") as f:
|
||||||
f.write(FILE_HEAD)
|
f.write(FILE_HEAD)
|
||||||
f.write(f"This list contains {len(plugins)} plugins.\n\n")
|
f.write(f"This list contains {len(plugins)} plugins.\n\n")
|
||||||
f.write(plugin_table)
|
f.write(".. only:: not latex\n\n")
|
||||||
f.write("\n")
|
|
||||||
|
wcwidth # reference library that must exist for tabulate to work
|
||||||
|
plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
|
||||||
|
f.write(indent(plugin_table, " "))
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
f.write(".. only:: latex\n\n")
|
||||||
|
f.write(indent("".join(plugin_definitions(plugins)), " "))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
set -x
|
|
||||||
|
|
||||||
# Install coverage.
|
|
||||||
if [[ -z ${TOXENV+x} || -z $TOXENV ]]; then
|
|
||||||
python -m pip install coverage
|
|
||||||
else
|
|
||||||
# Add last TOXENV to $PATH.
|
|
||||||
PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Run coverage.
|
|
||||||
python -m coverage xml
|
|
||||||
|
|
||||||
# Download and verify latest Codecov bash uploader.
|
|
||||||
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
|
|
||||||
curl --silent --show-error --location --connect-timeout 5 --retry 6 -o codecov https://codecov.io/bash
|
|
||||||
VERSION=$(grep --only-matching 'VERSION=\"[0-9\.]*\"' codecov | cut -d'"' -f2)
|
|
||||||
if command -v sha256sum; then
|
|
||||||
sha256sum --check --strict --ignore-missing --quiet <(curl --silent "https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA256SUM")
|
|
||||||
else
|
|
||||||
shasum --algorithm 256 --check --strict --ignore-missing --quiet <(curl --silent "https://raw.githubusercontent.com/codecov/codecov-bash/${VERSION}/SHA256SUM")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload coverage.
|
|
||||||
bash codecov -Z -X fix -f coverage.xml "$@"
|
|
|
@ -20,8 +20,6 @@ from .reports import CollectReport
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest.compat import final
|
from _pytest.compat import final
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
|
@ -142,13 +140,6 @@ class Cache:
|
||||||
res.mkdir(exist_ok=True, parents=True)
|
res.mkdir(exist_ok=True, parents=True)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def makedir(self, name: str) -> LEGACY_PATH:
|
|
||||||
"""Return a directory path object with the given name.
|
|
||||||
|
|
||||||
Same as :func:`mkdir`, but returns a legacy py path instance.
|
|
||||||
"""
|
|
||||||
return legacy_path(self.mkdir(name))
|
|
||||||
|
|
||||||
def _getvaluepath(self, key: str) -> Path:
|
def _getvaluepath(self, key: str) -> Path:
|
||||||
return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key))
|
return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key))
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,6 @@ from _pytest._code import filter_traceback
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest.compat import final
|
from _pytest.compat import final
|
||||||
from _pytest.compat import importlib_metadata
|
from _pytest.compat import importlib_metadata
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import Skipped
|
from _pytest.outcomes import Skipped
|
||||||
from _pytest.pathlib import absolutepath
|
from _pytest.pathlib import absolutepath
|
||||||
|
@ -240,6 +238,7 @@ default_plugins = essential_plugins + (
|
||||||
"unittest",
|
"unittest",
|
||||||
"capture",
|
"capture",
|
||||||
"skipping",
|
"skipping",
|
||||||
|
"legacypath",
|
||||||
"tmpdir",
|
"tmpdir",
|
||||||
"monkeypatch",
|
"monkeypatch",
|
||||||
"recwarn",
|
"recwarn",
|
||||||
|
@ -949,17 +948,6 @@ class Config:
|
||||||
|
|
||||||
self.cache: Optional[Cache] = None
|
self.cache: Optional[Cache] = None
|
||||||
|
|
||||||
@property
|
|
||||||
def invocation_dir(self) -> LEGACY_PATH:
|
|
||||||
"""The directory from which pytest was invoked.
|
|
||||||
|
|
||||||
Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
|
|
||||||
which is a :class:`pathlib.Path`.
|
|
||||||
|
|
||||||
:type: LEGACY_PATH
|
|
||||||
"""
|
|
||||||
return legacy_path(str(self.invocation_params.dir))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rootpath(self) -> Path:
|
def rootpath(self) -> Path:
|
||||||
"""The path to the :ref:`rootdir <rootdir>`.
|
"""The path to the :ref:`rootdir <rootdir>`.
|
||||||
|
@ -970,16 +958,6 @@ class Config:
|
||||||
"""
|
"""
|
||||||
return self._rootpath
|
return self._rootpath
|
||||||
|
|
||||||
@property
|
|
||||||
def rootdir(self) -> LEGACY_PATH:
|
|
||||||
"""The path to the :ref:`rootdir <rootdir>`.
|
|
||||||
|
|
||||||
Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
|
|
||||||
|
|
||||||
:type: LEGACY_PATH
|
|
||||||
"""
|
|
||||||
return legacy_path(str(self.rootpath))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def inipath(self) -> Optional[Path]:
|
def inipath(self) -> Optional[Path]:
|
||||||
"""The path to the :ref:`configfile <configfiles>`.
|
"""The path to the :ref:`configfile <configfiles>`.
|
||||||
|
@ -990,16 +968,6 @@ class Config:
|
||||||
"""
|
"""
|
||||||
return self._inipath
|
return self._inipath
|
||||||
|
|
||||||
@property
|
|
||||||
def inifile(self) -> Optional[LEGACY_PATH]:
|
|
||||||
"""The path to the :ref:`configfile <configfiles>`.
|
|
||||||
|
|
||||||
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
|
|
||||||
|
|
||||||
:type: Optional[LEGACY_PATH]
|
|
||||||
"""
|
|
||||||
return legacy_path(str(self.inipath)) if self.inipath else None
|
|
||||||
|
|
||||||
def add_cleanup(self, func: Callable[[], None]) -> None:
|
def add_cleanup(self, func: Callable[[], None]) -> None:
|
||||||
"""Add a function to be called when the config object gets out of
|
"""Add a function to be called when the config object gets out of
|
||||||
use (usually coninciding with pytest_unconfigure)."""
|
use (usually coninciding with pytest_unconfigure)."""
|
||||||
|
@ -1400,6 +1368,12 @@ class Config:
|
||||||
self._inicache[name] = val = self._getini(name)
|
self._inicache[name] = val = self._getini(name)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
# Meant for easy monkeypatching by legacypath plugin.
|
||||||
|
# Can be inlined back (with no cover removed) once legacypath is gone.
|
||||||
|
def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]):
|
||||||
|
msg = f"unknown configuration type: {type}"
|
||||||
|
raise ValueError(msg, value) # pragma: no cover
|
||||||
|
|
||||||
def _getini(self, name: str):
|
def _getini(self, name: str):
|
||||||
try:
|
try:
|
||||||
description, type, default = self._parser._inidict[name]
|
description, type, default = self._parser._inidict[name]
|
||||||
|
@ -1432,13 +1406,7 @@ class Config:
|
||||||
# a_line_list = ["tests", "acceptance"]
|
# a_line_list = ["tests", "acceptance"]
|
||||||
# in this case, we already have a list ready to use.
|
# in this case, we already have a list ready to use.
|
||||||
#
|
#
|
||||||
if type == "pathlist":
|
if type == "paths":
|
||||||
# TODO: This assert is probably not valid in all cases.
|
|
||||||
assert self.inipath is not None
|
|
||||||
dp = self.inipath.parent
|
|
||||||
input_values = shlex.split(value) if isinstance(value, str) else value
|
|
||||||
return [legacy_path(str(dp / x)) for x in input_values]
|
|
||||||
elif type == "paths":
|
|
||||||
# TODO: This assert is probably not valid in all cases.
|
# TODO: This assert is probably not valid in all cases.
|
||||||
assert self.inipath is not None
|
assert self.inipath is not None
|
||||||
dp = self.inipath.parent
|
dp = self.inipath.parent
|
||||||
|
@ -1453,9 +1421,12 @@ class Config:
|
||||||
return value
|
return value
|
||||||
elif type == "bool":
|
elif type == "bool":
|
||||||
return _strtobool(str(value).strip())
|
return _strtobool(str(value).strip())
|
||||||
else:
|
elif type == "string":
|
||||||
assert type in [None, "string"]
|
|
||||||
return value
|
return value
|
||||||
|
elif type is None:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return self._getini_unknown_type(name, type, value)
|
||||||
|
|
||||||
def _getconftest_pathlist(
|
def _getconftest_pathlist(
|
||||||
self, name: str, path: Path, rootpath: Path
|
self, name: str, path: Path, rootpath: Path
|
||||||
|
|
|
@ -504,12 +504,18 @@ class DoctestModule(pytest.Module):
|
||||||
|
|
||||||
def _find_lineno(self, obj, source_lines):
|
def _find_lineno(self, obj, source_lines):
|
||||||
"""Doctest code does not take into account `@property`, this
|
"""Doctest code does not take into account `@property`, this
|
||||||
is a hackish way to fix it.
|
is a hackish way to fix it. https://bugs.python.org/issue17446
|
||||||
|
|
||||||
https://bugs.python.org/issue17446
|
Wrapped Doctests will need to be unwrapped so the correct
|
||||||
|
line number is returned. This will be reported upstream. #8796
|
||||||
"""
|
"""
|
||||||
if isinstance(obj, property):
|
if isinstance(obj, property):
|
||||||
obj = getattr(obj, "fget", obj)
|
obj = getattr(obj, "fget", obj)
|
||||||
|
|
||||||
|
if hasattr(obj, "__wrapped__"):
|
||||||
|
# Get the main obj in case of it being wrapped
|
||||||
|
obj = inspect.unwrap(obj)
|
||||||
|
|
||||||
# Type ignored because this is a private function.
|
# Type ignored because this is a private function.
|
||||||
return super()._find_lineno( # type:ignore[misc]
|
return super()._find_lineno( # type:ignore[misc]
|
||||||
obj,
|
obj,
|
||||||
|
|
|
@ -46,8 +46,6 @@ from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
from _pytest.compat import getlocation
|
from _pytest.compat import getlocation
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
from _pytest.config import _PluggyPlugin
|
from _pytest.config import _PluggyPlugin
|
||||||
|
@ -528,11 +526,6 @@ class FixtureRequest:
|
||||||
raise AttributeError(f"module not available in {self.scope}-scoped context")
|
raise AttributeError(f"module not available in {self.scope}-scoped context")
|
||||||
return self._pyfuncitem.getparent(_pytest.python.Module).obj
|
return self._pyfuncitem.getparent(_pytest.python.Module).obj
|
||||||
|
|
||||||
@property
|
|
||||||
def fspath(self) -> LEGACY_PATH:
|
|
||||||
"""(deprecated) The file system path of the test module which collected this test."""
|
|
||||||
return legacy_path(self.path)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
if self.scope not in ("function", "class", "module", "package"):
|
if self.scope not in ("function", "class", "module", "package"):
|
||||||
|
|
|
@ -0,0 +1,454 @@
|
||||||
|
"""Add backward compatibility support for the legacy py path type."""
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
import attr
|
||||||
|
from iniconfig import SectionWrapper
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from _pytest.compat import final
|
||||||
|
from _pytest.compat import LEGACY_PATH
|
||||||
|
from _pytest.compat import legacy_path
|
||||||
|
from _pytest.deprecated import check_ispytest
|
||||||
|
from _pytest.nodes import Node
|
||||||
|
from _pytest.terminal import TerminalReporter
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Final
|
||||||
|
|
||||||
|
import pexpect
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class Testdir:
|
||||||
|
"""
|
||||||
|
Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
|
||||||
|
|
||||||
|
All methods just forward to an internal :class:`Pytester` instance, converting results
|
||||||
|
to `legacy_path` objects as necessary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__test__ = False
|
||||||
|
|
||||||
|
CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
|
||||||
|
TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
|
||||||
|
|
||||||
|
def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
|
||||||
|
check_ispytest(_ispytest)
|
||||||
|
self._pytester = pytester
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tmpdir(self) -> LEGACY_PATH:
|
||||||
|
"""Temporary directory where tests are executed."""
|
||||||
|
return legacy_path(self._pytester.path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test_tmproot(self) -> LEGACY_PATH:
|
||||||
|
return legacy_path(self._pytester._test_tmproot)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request(self):
|
||||||
|
return self._pytester._request
|
||||||
|
|
||||||
|
@property
|
||||||
|
def plugins(self):
|
||||||
|
return self._pytester.plugins
|
||||||
|
|
||||||
|
@plugins.setter
|
||||||
|
def plugins(self, plugins):
|
||||||
|
self._pytester.plugins = plugins
|
||||||
|
|
||||||
|
@property
|
||||||
|
def monkeypatch(self) -> pytest.MonkeyPatch:
|
||||||
|
return self._pytester._monkeypatch
|
||||||
|
|
||||||
|
def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
|
||||||
|
"""See :meth:`Pytester.make_hook_recorder`."""
|
||||||
|
return self._pytester.make_hook_recorder(pluginmanager)
|
||||||
|
|
||||||
|
def chdir(self) -> None:
|
||||||
|
"""See :meth:`Pytester.chdir`."""
|
||||||
|
return self._pytester.chdir()
|
||||||
|
|
||||||
|
def finalize(self) -> None:
|
||||||
|
"""See :meth:`Pytester._finalize`."""
|
||||||
|
return self._pytester._finalize()
|
||||||
|
|
||||||
|
def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.makefile`."""
|
||||||
|
if ext and not ext.startswith("."):
|
||||||
|
# pytester.makefile is going to throw a ValueError in a way that
|
||||||
|
# testdir.makefile did not, because
|
||||||
|
# pathlib.Path is stricter suffixes than py.path
|
||||||
|
# This ext arguments is likely user error, but since testdir has
|
||||||
|
# allowed this, we will prepend "." as a workaround to avoid breaking
|
||||||
|
# testdir usage that worked before
|
||||||
|
ext = "." + ext
|
||||||
|
return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
|
||||||
|
|
||||||
|
def makeconftest(self, source) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.makeconftest`."""
|
||||||
|
return legacy_path(self._pytester.makeconftest(source))
|
||||||
|
|
||||||
|
def makeini(self, source) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.makeini`."""
|
||||||
|
return legacy_path(self._pytester.makeini(source))
|
||||||
|
|
||||||
|
def getinicfg(self, source: str) -> SectionWrapper:
|
||||||
|
"""See :meth:`Pytester.getinicfg`."""
|
||||||
|
return self._pytester.getinicfg(source)
|
||||||
|
|
||||||
|
def makepyprojecttoml(self, source) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.makepyprojecttoml`."""
|
||||||
|
return legacy_path(self._pytester.makepyprojecttoml(source))
|
||||||
|
|
||||||
|
def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.makepyfile`."""
|
||||||
|
return legacy_path(self._pytester.makepyfile(*args, **kwargs))
|
||||||
|
|
||||||
|
def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.maketxtfile`."""
|
||||||
|
return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
|
||||||
|
|
||||||
|
def syspathinsert(self, path=None) -> None:
|
||||||
|
"""See :meth:`Pytester.syspathinsert`."""
|
||||||
|
return self._pytester.syspathinsert(path)
|
||||||
|
|
||||||
|
def mkdir(self, name) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.mkdir`."""
|
||||||
|
return legacy_path(self._pytester.mkdir(name))
|
||||||
|
|
||||||
|
def mkpydir(self, name) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.mkpydir`."""
|
||||||
|
return legacy_path(self._pytester.mkpydir(name))
|
||||||
|
|
||||||
|
def copy_example(self, name=None) -> LEGACY_PATH:
|
||||||
|
"""See :meth:`Pytester.copy_example`."""
|
||||||
|
return legacy_path(self._pytester.copy_example(name))
|
||||||
|
|
||||||
|
def getnode(
|
||||||
|
self, config: pytest.Config, arg
|
||||||
|
) -> Optional[Union[pytest.Item, pytest.Collector]]:
|
||||||
|
"""See :meth:`Pytester.getnode`."""
|
||||||
|
return self._pytester.getnode(config, arg)
|
||||||
|
|
||||||
|
def getpathnode(self, path):
|
||||||
|
"""See :meth:`Pytester.getpathnode`."""
|
||||||
|
return self._pytester.getpathnode(path)
|
||||||
|
|
||||||
|
def genitems(
|
||||||
|
self, colitems: List[Union[pytest.Item, pytest.Collector]]
|
||||||
|
) -> List[pytest.Item]:
|
||||||
|
"""See :meth:`Pytester.genitems`."""
|
||||||
|
return self._pytester.genitems(colitems)
|
||||||
|
|
||||||
|
def runitem(self, source):
|
||||||
|
"""See :meth:`Pytester.runitem`."""
|
||||||
|
return self._pytester.runitem(source)
|
||||||
|
|
||||||
|
def inline_runsource(self, source, *cmdlineargs):
|
||||||
|
"""See :meth:`Pytester.inline_runsource`."""
|
||||||
|
return self._pytester.inline_runsource(source, *cmdlineargs)
|
||||||
|
|
||||||
|
def inline_genitems(self, *args):
|
||||||
|
"""See :meth:`Pytester.inline_genitems`."""
|
||||||
|
return self._pytester.inline_genitems(*args)
|
||||||
|
|
||||||
|
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
|
||||||
|
"""See :meth:`Pytester.inline_run`."""
|
||||||
|
return self._pytester.inline_run(
|
||||||
|
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
|
||||||
|
)
|
||||||
|
|
||||||
|
def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
|
||||||
|
"""See :meth:`Pytester.runpytest_inprocess`."""
|
||||||
|
return self._pytester.runpytest_inprocess(*args, **kwargs)
|
||||||
|
|
||||||
|
def runpytest(self, *args, **kwargs) -> pytest.RunResult:
|
||||||
|
"""See :meth:`Pytester.runpytest`."""
|
||||||
|
return self._pytester.runpytest(*args, **kwargs)
|
||||||
|
|
||||||
|
def parseconfig(self, *args) -> pytest.Config:
|
||||||
|
"""See :meth:`Pytester.parseconfig`."""
|
||||||
|
return self._pytester.parseconfig(*args)
|
||||||
|
|
||||||
|
def parseconfigure(self, *args) -> pytest.Config:
|
||||||
|
"""See :meth:`Pytester.parseconfigure`."""
|
||||||
|
return self._pytester.parseconfigure(*args)
|
||||||
|
|
||||||
|
def getitem(self, source, funcname="test_func"):
|
||||||
|
"""See :meth:`Pytester.getitem`."""
|
||||||
|
return self._pytester.getitem(source, funcname)
|
||||||
|
|
||||||
|
def getitems(self, source):
|
||||||
|
"""See :meth:`Pytester.getitems`."""
|
||||||
|
return self._pytester.getitems(source)
|
||||||
|
|
||||||
|
def getmodulecol(self, source, configargs=(), withinit=False):
|
||||||
|
"""See :meth:`Pytester.getmodulecol`."""
|
||||||
|
return self._pytester.getmodulecol(
|
||||||
|
source, configargs=configargs, withinit=withinit
|
||||||
|
)
|
||||||
|
|
||||||
|
def collect_by_name(
|
||||||
|
self, modcol: pytest.Collector, name: str
|
||||||
|
) -> Optional[Union[pytest.Item, pytest.Collector]]:
|
||||||
|
"""See :meth:`Pytester.collect_by_name`."""
|
||||||
|
return self._pytester.collect_by_name(modcol, name)
|
||||||
|
|
||||||
|
def popen(
|
||||||
|
self,
|
||||||
|
cmdargs,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
stdin=CLOSE_STDIN,
|
||||||
|
**kw,
|
||||||
|
):
|
||||||
|
"""See :meth:`Pytester.popen`."""
|
||||||
|
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
|
||||||
|
|
||||||
|
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
|
||||||
|
"""See :meth:`Pytester.run`."""
|
||||||
|
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
|
||||||
|
|
||||||
|
def runpython(self, script) -> pytest.RunResult:
|
||||||
|
"""See :meth:`Pytester.runpython`."""
|
||||||
|
return self._pytester.runpython(script)
|
||||||
|
|
||||||
|
def runpython_c(self, command):
|
||||||
|
"""See :meth:`Pytester.runpython_c`."""
|
||||||
|
return self._pytester.runpython_c(command)
|
||||||
|
|
||||||
|
def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
|
||||||
|
"""See :meth:`Pytester.runpytest_subprocess`."""
|
||||||
|
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
|
||||||
|
|
||||||
|
def spawn_pytest(
|
||||||
|
self, string: str, expect_timeout: float = 10.0
|
||||||
|
) -> "pexpect.spawn":
|
||||||
|
"""See :meth:`Pytester.spawn_pytest`."""
|
||||||
|
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
|
||||||
|
|
||||||
|
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
|
||||||
|
"""See :meth:`Pytester.spawn`."""
|
||||||
|
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<Testdir {self.tmpdir!r}>"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.tmpdir)
|
||||||
|
|
||||||
|
|
||||||
|
pytest.Testdir = Testdir # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyTestdirPlugin:
|
||||||
|
@staticmethod
|
||||||
|
@pytest.fixture
|
||||||
|
def testdir(pytester: pytest.Pytester) -> Testdir:
|
||||||
|
"""
|
||||||
|
Identical to :fixture:`pytester`, and provides an instance whose methods return
|
||||||
|
legacy ``LEGACY_PATH`` objects instead when applicable.
|
||||||
|
|
||||||
|
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
|
||||||
|
"""
|
||||||
|
return Testdir(pytester, _ispytest=True)
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
@attr.s(init=False, auto_attribs=True)
|
||||||
|
class TempdirFactory:
|
||||||
|
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
|
||||||
|
for :class:``TempPathFactory``."""
|
||||||
|
|
||||||
|
_tmppath_factory: pytest.TempPathFactory
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False
|
||||||
|
) -> None:
|
||||||
|
check_ispytest(_ispytest)
|
||||||
|
self._tmppath_factory = tmppath_factory
|
||||||
|
|
||||||
|
def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
|
||||||
|
"""Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object."""
|
||||||
|
return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
|
||||||
|
|
||||||
|
def getbasetemp(self) -> LEGACY_PATH:
|
||||||
|
"""Backward compat wrapper for ``_tmppath_factory.getbasetemp``."""
|
||||||
|
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
|
||||||
|
|
||||||
|
|
||||||
|
pytest.TempdirFactory = TempdirFactory # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
class LegacyTmpdirPlugin:
|
||||||
|
@staticmethod
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory:
|
||||||
|
"""Return a :class:`pytest.TempdirFactory` instance for the test session."""
|
||||||
|
# Set dynamically by pytest_configure().
|
||||||
|
return request.config._tmpdirhandler # type: ignore
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@pytest.fixture
|
||||||
|
def tmpdir(tmp_path: Path) -> LEGACY_PATH:
|
||||||
|
"""Return a temporary directory path object which is unique to each test
|
||||||
|
function invocation, created as a sub directory of the base temporary
|
||||||
|
directory.
|
||||||
|
|
||||||
|
By default, a new base temporary directory is created each test session,
|
||||||
|
and old bases are removed after 3 sessions, to aid in debugging. If
|
||||||
|
``--basetemp`` is used then it is cleared each session. See :ref:`base
|
||||||
|
temporary directory`.
|
||||||
|
|
||||||
|
The returned object is a `legacy_path`_ object.
|
||||||
|
|
||||||
|
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
||||||
|
"""
|
||||||
|
return legacy_path(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
|
||||||
|
"""Return a directory path object with the given name.
|
||||||
|
|
||||||
|
Same as :func:`mkdir`, but returns a legacy py path instance.
|
||||||
|
"""
|
||||||
|
return legacy_path(self.mkdir(name))
|
||||||
|
|
||||||
|
|
||||||
|
def FixtureRequest_fspath(self: pytest.FixtureRequest) -> LEGACY_PATH:
|
||||||
|
"""(deprecated) The file system path of the test module which collected this test."""
|
||||||
|
return legacy_path(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH:
|
||||||
|
"""The directory from which pytest was invoked.
|
||||||
|
|
||||||
|
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
|
||||||
|
|
||||||
|
:type: LEGACY_PATH
|
||||||
|
"""
|
||||||
|
return legacy_path(self.startpath)
|
||||||
|
|
||||||
|
|
||||||
|
def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
|
||||||
|
"""The directory from which pytest was invoked.
|
||||||
|
|
||||||
|
Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
|
||||||
|
which is a :class:`pathlib.Path`.
|
||||||
|
|
||||||
|
:type: LEGACY_PATH
|
||||||
|
"""
|
||||||
|
return legacy_path(str(self.invocation_params.dir))
|
||||||
|
|
||||||
|
|
||||||
|
def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
|
||||||
|
"""The path to the :ref:`rootdir <rootdir>`.
|
||||||
|
|
||||||
|
Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
|
||||||
|
|
||||||
|
:type: LEGACY_PATH
|
||||||
|
"""
|
||||||
|
return legacy_path(str(self.rootpath))
|
||||||
|
|
||||||
|
|
||||||
|
def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
|
||||||
|
"""The path to the :ref:`configfile <configfiles>`.
|
||||||
|
|
||||||
|
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
|
||||||
|
|
||||||
|
:type: Optional[LEGACY_PATH]
|
||||||
|
"""
|
||||||
|
return legacy_path(str(self.inipath)) if self.inipath else None
|
||||||
|
|
||||||
|
|
||||||
|
def Session_stardir(self: pytest.Session) -> LEGACY_PATH:
|
||||||
|
"""The path from which pytest was invoked.
|
||||||
|
|
||||||
|
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
|
||||||
|
|
||||||
|
:type: LEGACY_PATH
|
||||||
|
"""
|
||||||
|
return legacy_path(self.startpath)
|
||||||
|
|
||||||
|
|
||||||
|
def Config__getini_unknown_type(
|
||||||
|
self, name: str, type: str, value: Union[str, List[str]]
|
||||||
|
):
|
||||||
|
if type == "pathlist":
|
||||||
|
# TODO: This assert is probably not valid in all cases.
|
||||||
|
assert self.inipath is not None
|
||||||
|
dp = self.inipath.parent
|
||||||
|
input_values = shlex.split(value) if isinstance(value, str) else value
|
||||||
|
return [legacy_path(str(dp / x)) for x in input_values]
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unknown configuration type: {type}", value)
|
||||||
|
|
||||||
|
|
||||||
|
def Node_fspath(self: Node) -> LEGACY_PATH:
|
||||||
|
"""(deprecated) returns a legacy_path copy of self.path"""
|
||||||
|
return legacy_path(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:
|
||||||
|
self.path = Path(value)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config: pytest.Config) -> None:
|
||||||
|
mp = pytest.MonkeyPatch()
|
||||||
|
config.add_cleanup(mp.undo)
|
||||||
|
|
||||||
|
if config.pluginmanager.has_plugin("pytester"):
|
||||||
|
config.pluginmanager.register(LegacyTestdirPlugin, "legacypath-pytester")
|
||||||
|
|
||||||
|
if config.pluginmanager.has_plugin("tmpdir"):
|
||||||
|
# Create TmpdirFactory and attach it to the config object.
|
||||||
|
#
|
||||||
|
# This is to comply with existing plugins which expect the handler to be
|
||||||
|
# available at pytest_configure time, but ideally should be moved entirely
|
||||||
|
# to the tmpdir_factory session fixture.
|
||||||
|
try:
|
||||||
|
tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined]
|
||||||
|
except AttributeError:
|
||||||
|
# tmpdir plugin is blocked.
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True)
|
||||||
|
mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False)
|
||||||
|
|
||||||
|
config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir")
|
||||||
|
|
||||||
|
# Add Cache.makedir().
|
||||||
|
mp.setattr(pytest.Cache, "makedir", Cache_makedir, raising=False)
|
||||||
|
|
||||||
|
# Add FixtureRequest.fspath property.
|
||||||
|
mp.setattr(
|
||||||
|
pytest.FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add TerminalReporter.startdir property.
|
||||||
|
mp.setattr(
|
||||||
|
TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add Config.{invocation_dir,rootdir,inifile} properties.
|
||||||
|
mp.setattr(
|
||||||
|
pytest.Config, "invocation_dir", property(Config_invocation_dir), raising=False
|
||||||
|
)
|
||||||
|
mp.setattr(pytest.Config, "rootdir", property(Config_rootdir), raising=False)
|
||||||
|
mp.setattr(pytest.Config, "inifile", property(Config_inifile), raising=False)
|
||||||
|
|
||||||
|
# Add Session.startdir property.
|
||||||
|
mp.setattr(pytest.Session, "startdir", property(Session_stardir), raising=False)
|
||||||
|
|
||||||
|
# Add pathlist configuration type.
|
||||||
|
mp.setattr(pytest.Config, "_getini_unknown_type", Config__getini_unknown_type)
|
||||||
|
|
||||||
|
# Add Node.fspath property.
|
||||||
|
mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
|
|
@ -25,8 +25,6 @@ import attr
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
from _pytest.compat import final
|
from _pytest.compat import final
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import directory_arg
|
from _pytest.config import directory_arg
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
|
@ -504,16 +502,6 @@ class Session(nodes.FSCollector):
|
||||||
"""
|
"""
|
||||||
return self.config.invocation_params.dir
|
return self.config.invocation_params.dir
|
||||||
|
|
||||||
@property
|
|
||||||
def stardir(self) -> LEGACY_PATH:
|
|
||||||
"""The path from which pytest was invoked.
|
|
||||||
|
|
||||||
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
|
|
||||||
|
|
||||||
:type: LEGACY_PATH
|
|
||||||
"""
|
|
||||||
return legacy_path(self.startpath)
|
|
||||||
|
|
||||||
def _node_location_to_relpath(self, node_path: Path) -> str:
|
def _node_location_to_relpath(self, node_path: Path) -> str:
|
||||||
# bestrelpath is a quite slow function.
|
# bestrelpath is a quite slow function.
|
||||||
return self._bestrelpathcache[node_path]
|
return self._bestrelpathcache[node_path]
|
||||||
|
|
|
@ -6,7 +6,7 @@ expression: expr? EOF
|
||||||
expr: and_expr ('or' and_expr)*
|
expr: and_expr ('or' and_expr)*
|
||||||
and_expr: not_expr ('and' not_expr)*
|
and_expr: not_expr ('and' not_expr)*
|
||||||
not_expr: 'not' not_expr | '(' expr ')' | ident
|
not_expr: 'not' not_expr | '(' expr ')' | ident
|
||||||
ident: (\w|:|\+|-|\.|\[|\]|\\)+
|
ident: (\w|:|\+|-|\.|\[|\]|\\|/)+
|
||||||
|
|
||||||
The semantics are:
|
The semantics are:
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ class Scanner:
|
||||||
yield Token(TokenType.RPAREN, ")", pos)
|
yield Token(TokenType.RPAREN, ")", pos)
|
||||||
pos += 1
|
pos += 1
|
||||||
else:
|
else:
|
||||||
match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\)+", input[pos:])
|
match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:])
|
||||||
if match:
|
if match:
|
||||||
value = match.group(0)
|
value = match.group(0)
|
||||||
if value == "or":
|
if value == "or":
|
||||||
|
|
|
@ -24,7 +24,6 @@ from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
from _pytest.compat import cached_property
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import LEGACY_PATH
|
from _pytest.compat import LEGACY_PATH
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import ConftestImportFailure
|
from _pytest.config import ConftestImportFailure
|
||||||
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
|
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
|
||||||
|
@ -238,15 +237,6 @@ class Node(metaclass=NodeMeta):
|
||||||
# Deprecated alias. Was never public. Can be removed in a few releases.
|
# Deprecated alias. Was never public. Can be removed in a few releases.
|
||||||
self._store = self.stash
|
self._store = self.stash
|
||||||
|
|
||||||
@property
|
|
||||||
def fspath(self) -> LEGACY_PATH:
|
|
||||||
"""(deprecated) returns a legacy_path copy of self.path"""
|
|
||||||
return legacy_path(self.path)
|
|
||||||
|
|
||||||
@fspath.setter
|
|
||||||
def fspath(self, value: LEGACY_PATH) -> None:
|
|
||||||
self.path = Path(value)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_parent(cls, parent: "Node", **kw):
|
def from_parent(cls, parent: "Node", **kw):
|
||||||
"""Public constructor for Nodes.
|
"""Public constructor for Nodes.
|
||||||
|
|
|
@ -685,6 +685,8 @@ def bestrelpath(directory: Path, dest: Path) -> str:
|
||||||
|
|
||||||
If no such path can be determined, returns dest.
|
If no such path can be determined, returns dest.
|
||||||
"""
|
"""
|
||||||
|
assert isinstance(directory, Path)
|
||||||
|
assert isinstance(dest, Path)
|
||||||
if dest == directory:
|
if dest == directory:
|
||||||
return os.curdir
|
return os.curdir
|
||||||
# Find the longest common directory.
|
# Find the longest common directory.
|
||||||
|
|
|
@ -40,8 +40,6 @@ from _pytest import timing
|
||||||
from _pytest._code import Source
|
from _pytest._code import Source
|
||||||
from _pytest.capture import _get_multicapture
|
from _pytest.capture import _get_multicapture
|
||||||
from _pytest.compat import final
|
from _pytest.compat import final
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import NotSetType
|
from _pytest.compat import NotSetType
|
||||||
from _pytest.config import _PluggyPlugin
|
from _pytest.config import _PluggyPlugin
|
||||||
|
@ -493,17 +491,6 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt
|
||||||
return Pytester(request, tmp_path_factory, _ispytest=True)
|
return Pytester(request, tmp_path_factory, _ispytest=True)
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
def testdir(pytester: "Pytester") -> "Testdir":
|
|
||||||
"""
|
|
||||||
Identical to :fixture:`pytester`, and provides an instance whose methods return
|
|
||||||
legacy ``LEGACY_PATH`` objects instead when applicable.
|
|
||||||
|
|
||||||
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
|
|
||||||
"""
|
|
||||||
return Testdir(pytester, _ispytest=True)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def _sys_snapshot() -> Generator[None, None, None]:
|
def _sys_snapshot() -> Generator[None, None, None]:
|
||||||
snappaths = SysPathsSnapshot()
|
snappaths = SysPathsSnapshot()
|
||||||
|
@ -1531,224 +1518,6 @@ class LineComp:
|
||||||
LineMatcher(lines1).fnmatch_lines(lines2)
|
LineMatcher(lines1).fnmatch_lines(lines2)
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
class Testdir:
|
|
||||||
"""
|
|
||||||
Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
|
|
||||||
|
|
||||||
All methods just forward to an internal :class:`Pytester` instance, converting results
|
|
||||||
to `legacy_path` objects as necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__test__ = False
|
|
||||||
|
|
||||||
CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
|
|
||||||
TimeoutExpired: "Final" = Pytester.TimeoutExpired
|
|
||||||
|
|
||||||
def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
|
|
||||||
check_ispytest(_ispytest)
|
|
||||||
self._pytester = pytester
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tmpdir(self) -> LEGACY_PATH:
|
|
||||||
"""Temporary directory where tests are executed."""
|
|
||||||
return legacy_path(self._pytester.path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def test_tmproot(self) -> LEGACY_PATH:
|
|
||||||
return legacy_path(self._pytester._test_tmproot)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def request(self):
|
|
||||||
return self._pytester._request
|
|
||||||
|
|
||||||
@property
|
|
||||||
def plugins(self):
|
|
||||||
return self._pytester.plugins
|
|
||||||
|
|
||||||
@plugins.setter
|
|
||||||
def plugins(self, plugins):
|
|
||||||
self._pytester.plugins = plugins
|
|
||||||
|
|
||||||
@property
|
|
||||||
def monkeypatch(self) -> MonkeyPatch:
|
|
||||||
return self._pytester._monkeypatch
|
|
||||||
|
|
||||||
def make_hook_recorder(self, pluginmanager) -> HookRecorder:
|
|
||||||
"""See :meth:`Pytester.make_hook_recorder`."""
|
|
||||||
return self._pytester.make_hook_recorder(pluginmanager)
|
|
||||||
|
|
||||||
def chdir(self) -> None:
|
|
||||||
"""See :meth:`Pytester.chdir`."""
|
|
||||||
return self._pytester.chdir()
|
|
||||||
|
|
||||||
def finalize(self) -> None:
|
|
||||||
"""See :meth:`Pytester._finalize`."""
|
|
||||||
return self._pytester._finalize()
|
|
||||||
|
|
||||||
def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.makefile`."""
|
|
||||||
if ext and not ext.startswith("."):
|
|
||||||
# pytester.makefile is going to throw a ValueError in a way that
|
|
||||||
# testdir.makefile did not, because
|
|
||||||
# pathlib.Path is stricter suffixes than py.path
|
|
||||||
# This ext arguments is likely user error, but since testdir has
|
|
||||||
# allowed this, we will prepend "." as a workaround to avoid breaking
|
|
||||||
# testdir usage that worked before
|
|
||||||
ext = "." + ext
|
|
||||||
return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
|
|
||||||
|
|
||||||
def makeconftest(self, source) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.makeconftest`."""
|
|
||||||
return legacy_path(self._pytester.makeconftest(source))
|
|
||||||
|
|
||||||
def makeini(self, source) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.makeini`."""
|
|
||||||
return legacy_path(self._pytester.makeini(source))
|
|
||||||
|
|
||||||
def getinicfg(self, source: str) -> SectionWrapper:
|
|
||||||
"""See :meth:`Pytester.getinicfg`."""
|
|
||||||
return self._pytester.getinicfg(source)
|
|
||||||
|
|
||||||
def makepyprojecttoml(self, source) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.makepyprojecttoml`."""
|
|
||||||
return legacy_path(self._pytester.makepyprojecttoml(source))
|
|
||||||
|
|
||||||
def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.makepyfile`."""
|
|
||||||
return legacy_path(self._pytester.makepyfile(*args, **kwargs))
|
|
||||||
|
|
||||||
def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.maketxtfile`."""
|
|
||||||
return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
|
|
||||||
|
|
||||||
def syspathinsert(self, path=None) -> None:
|
|
||||||
"""See :meth:`Pytester.syspathinsert`."""
|
|
||||||
return self._pytester.syspathinsert(path)
|
|
||||||
|
|
||||||
def mkdir(self, name) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.mkdir`."""
|
|
||||||
return legacy_path(self._pytester.mkdir(name))
|
|
||||||
|
|
||||||
def mkpydir(self, name) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.mkpydir`."""
|
|
||||||
return legacy_path(self._pytester.mkpydir(name))
|
|
||||||
|
|
||||||
def copy_example(self, name=None) -> LEGACY_PATH:
|
|
||||||
"""See :meth:`Pytester.copy_example`."""
|
|
||||||
return legacy_path(self._pytester.copy_example(name))
|
|
||||||
|
|
||||||
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
|
|
||||||
"""See :meth:`Pytester.getnode`."""
|
|
||||||
return self._pytester.getnode(config, arg)
|
|
||||||
|
|
||||||
def getpathnode(self, path):
|
|
||||||
"""See :meth:`Pytester.getpathnode`."""
|
|
||||||
return self._pytester.getpathnode(path)
|
|
||||||
|
|
||||||
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
|
|
||||||
"""See :meth:`Pytester.genitems`."""
|
|
||||||
return self._pytester.genitems(colitems)
|
|
||||||
|
|
||||||
def runitem(self, source):
|
|
||||||
"""See :meth:`Pytester.runitem`."""
|
|
||||||
return self._pytester.runitem(source)
|
|
||||||
|
|
||||||
def inline_runsource(self, source, *cmdlineargs):
|
|
||||||
"""See :meth:`Pytester.inline_runsource`."""
|
|
||||||
return self._pytester.inline_runsource(source, *cmdlineargs)
|
|
||||||
|
|
||||||
def inline_genitems(self, *args):
|
|
||||||
"""See :meth:`Pytester.inline_genitems`."""
|
|
||||||
return self._pytester.inline_genitems(*args)
|
|
||||||
|
|
||||||
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
|
|
||||||
"""See :meth:`Pytester.inline_run`."""
|
|
||||||
return self._pytester.inline_run(
|
|
||||||
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
|
|
||||||
)
|
|
||||||
|
|
||||||
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
|
|
||||||
"""See :meth:`Pytester.runpytest_inprocess`."""
|
|
||||||
return self._pytester.runpytest_inprocess(*args, **kwargs)
|
|
||||||
|
|
||||||
def runpytest(self, *args, **kwargs) -> RunResult:
|
|
||||||
"""See :meth:`Pytester.runpytest`."""
|
|
||||||
return self._pytester.runpytest(*args, **kwargs)
|
|
||||||
|
|
||||||
def parseconfig(self, *args) -> Config:
|
|
||||||
"""See :meth:`Pytester.parseconfig`."""
|
|
||||||
return self._pytester.parseconfig(*args)
|
|
||||||
|
|
||||||
def parseconfigure(self, *args) -> Config:
|
|
||||||
"""See :meth:`Pytester.parseconfigure`."""
|
|
||||||
return self._pytester.parseconfigure(*args)
|
|
||||||
|
|
||||||
def getitem(self, source, funcname="test_func"):
|
|
||||||
"""See :meth:`Pytester.getitem`."""
|
|
||||||
return self._pytester.getitem(source, funcname)
|
|
||||||
|
|
||||||
def getitems(self, source):
|
|
||||||
"""See :meth:`Pytester.getitems`."""
|
|
||||||
return self._pytester.getitems(source)
|
|
||||||
|
|
||||||
def getmodulecol(self, source, configargs=(), withinit=False):
|
|
||||||
"""See :meth:`Pytester.getmodulecol`."""
|
|
||||||
return self._pytester.getmodulecol(
|
|
||||||
source, configargs=configargs, withinit=withinit
|
|
||||||
)
|
|
||||||
|
|
||||||
def collect_by_name(
|
|
||||||
self, modcol: Collector, name: str
|
|
||||||
) -> Optional[Union[Item, Collector]]:
|
|
||||||
"""See :meth:`Pytester.collect_by_name`."""
|
|
||||||
return self._pytester.collect_by_name(modcol, name)
|
|
||||||
|
|
||||||
def popen(
|
|
||||||
self,
|
|
||||||
cmdargs,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
stdin=CLOSE_STDIN,
|
|
||||||
**kw,
|
|
||||||
):
|
|
||||||
"""See :meth:`Pytester.popen`."""
|
|
||||||
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
|
|
||||||
|
|
||||||
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
|
|
||||||
"""See :meth:`Pytester.run`."""
|
|
||||||
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
|
|
||||||
|
|
||||||
def runpython(self, script) -> RunResult:
|
|
||||||
"""See :meth:`Pytester.runpython`."""
|
|
||||||
return self._pytester.runpython(script)
|
|
||||||
|
|
||||||
def runpython_c(self, command):
|
|
||||||
"""See :meth:`Pytester.runpython_c`."""
|
|
||||||
return self._pytester.runpython_c(command)
|
|
||||||
|
|
||||||
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
|
|
||||||
"""See :meth:`Pytester.runpytest_subprocess`."""
|
|
||||||
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
|
|
||||||
|
|
||||||
def spawn_pytest(
|
|
||||||
self, string: str, expect_timeout: float = 10.0
|
|
||||||
) -> "pexpect.spawn":
|
|
||||||
"""See :meth:`Pytester.spawn_pytest`."""
|
|
||||||
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
|
|
||||||
|
|
||||||
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
|
|
||||||
"""See :meth:`Pytester.spawn`."""
|
|
||||||
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"<Testdir {self.tmpdir!r}>"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return str(self.tmpdir)
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
class LineMatcher:
|
class LineMatcher:
|
||||||
"""Flexible matching of text.
|
"""Flexible matching of text.
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,6 @@ from _pytest._code import ExceptionInfo
|
||||||
from _pytest._code.code import ExceptionRepr
|
from _pytest._code.code import ExceptionRepr
|
||||||
from _pytest._io.wcwidth import wcswidth
|
from _pytest._io.wcwidth import wcswidth
|
||||||
from _pytest.compat import final
|
from _pytest.compat import final
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.config import _PluggyPlugin
|
from _pytest.config import _PluggyPlugin
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
|
@ -383,16 +381,6 @@ class TerminalReporter:
|
||||||
def showlongtestinfo(self) -> bool:
|
def showlongtestinfo(self) -> bool:
|
||||||
return self.verbosity > 0
|
return self.verbosity > 0
|
||||||
|
|
||||||
@property
|
|
||||||
def startdir(self) -> LEGACY_PATH:
|
|
||||||
"""The directory from which pytest was invoked.
|
|
||||||
|
|
||||||
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
|
|
||||||
|
|
||||||
:type: LEGACY_PATH
|
|
||||||
"""
|
|
||||||
return legacy_path(self.startpath)
|
|
||||||
|
|
||||||
def hasopt(self, char: str) -> bool:
|
def hasopt(self, char: str) -> bool:
|
||||||
char = {"xfailed": "x", "skipped": "s"}.get(char, char)
|
char = {"xfailed": "x", "skipped": "s"}.get(char, char)
|
||||||
return char in self.reportchars
|
return char in self.reportchars
|
||||||
|
@ -875,7 +863,6 @@ class TerminalReporter:
|
||||||
return line
|
return line
|
||||||
|
|
||||||
# collect_fspath comes from testid which has a "/"-normalized path.
|
# collect_fspath comes from testid which has a "/"-normalized path.
|
||||||
|
|
||||||
if fspath:
|
if fspath:
|
||||||
res = mkrel(nodeid)
|
res = mkrel(nodeid)
|
||||||
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
|
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
|
||||||
|
|
|
@ -13,8 +13,6 @@ from .pathlib import make_numbered_dir
|
||||||
from .pathlib import make_numbered_dir_with_cleanup
|
from .pathlib import make_numbered_dir_with_cleanup
|
||||||
from .pathlib import rm_rf
|
from .pathlib import rm_rf
|
||||||
from _pytest.compat import final
|
from _pytest.compat import final
|
||||||
from _pytest.compat import LEGACY_PATH
|
|
||||||
from _pytest.compat import legacy_path
|
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.deprecated import check_ispytest
|
from _pytest.deprecated import check_ispytest
|
||||||
from _pytest.fixtures import fixture
|
from _pytest.fixtures import fixture
|
||||||
|
@ -157,29 +155,6 @@ class TempPathFactory:
|
||||||
return basetemp
|
return basetemp
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
@attr.s(init=False, auto_attribs=True)
|
|
||||||
class TempdirFactory:
|
|
||||||
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
|
|
||||||
for :class:``TempPathFactory``."""
|
|
||||||
|
|
||||||
_tmppath_factory: TempPathFactory
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
|
|
||||||
) -> None:
|
|
||||||
check_ispytest(_ispytest)
|
|
||||||
self._tmppath_factory = tmppath_factory
|
|
||||||
|
|
||||||
def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
|
|
||||||
"""Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object."""
|
|
||||||
return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
|
|
||||||
|
|
||||||
def getbasetemp(self) -> LEGACY_PATH:
|
|
||||||
"""Backward compat wrapper for ``_tmppath_factory.getbasetemp``."""
|
|
||||||
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
|
|
||||||
|
|
||||||
|
|
||||||
def get_user() -> Optional[str]:
|
def get_user() -> Optional[str]:
|
||||||
"""Return the current user name, or None if getuser() does not work
|
"""Return the current user name, or None if getuser() does not work
|
||||||
in the current environment (see #1010)."""
|
in the current environment (see #1010)."""
|
||||||
|
@ -201,16 +176,7 @@ def pytest_configure(config: Config) -> None:
|
||||||
mp = MonkeyPatch()
|
mp = MonkeyPatch()
|
||||||
config.add_cleanup(mp.undo)
|
config.add_cleanup(mp.undo)
|
||||||
_tmp_path_factory = TempPathFactory.from_config(config, _ispytest=True)
|
_tmp_path_factory = TempPathFactory.from_config(config, _ispytest=True)
|
||||||
_tmpdirhandler = TempdirFactory(_tmp_path_factory, _ispytest=True)
|
|
||||||
mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False)
|
mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False)
|
||||||
mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
|
||||||
def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
|
|
||||||
"""Return a :class:`pytest.TempdirFactory` instance for the test session."""
|
|
||||||
# Set dynamically by pytest_configure() above.
|
|
||||||
return request.config._tmpdirhandler # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
@fixture(scope="session")
|
@fixture(scope="session")
|
||||||
|
@ -228,24 +194,6 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path:
|
||||||
return factory.mktemp(name, numbered=True)
|
return factory.mktemp(name, numbered=True)
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
|
||||||
def tmpdir(tmp_path: Path) -> LEGACY_PATH:
|
|
||||||
"""Return a temporary directory path object which is unique to each test
|
|
||||||
function invocation, created as a sub directory of the base temporary
|
|
||||||
directory.
|
|
||||||
|
|
||||||
By default, a new base temporary directory is created each test session,
|
|
||||||
and old bases are removed after 3 sessions, to aid in debugging. If
|
|
||||||
``--basetemp`` is used then it is cleared each session. See :ref:`base
|
|
||||||
temporary directory`.
|
|
||||||
|
|
||||||
The returned object is a `legacy_path`_ object.
|
|
||||||
|
|
||||||
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
|
||||||
"""
|
|
||||||
return legacy_path(tmp_path)
|
|
||||||
|
|
||||||
|
|
||||||
@fixture
|
@fixture
|
||||||
def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path:
|
def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path:
|
||||||
"""Return a temporary directory path object which is unique to each test
|
"""Return a temporary directory path object which is unique to each test
|
||||||
|
|
|
@ -46,7 +46,6 @@ from _pytest.pytester import LineMatcher
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
from _pytest.pytester import RecordedHookCall
|
from _pytest.pytester import RecordedHookCall
|
||||||
from _pytest.pytester import RunResult
|
from _pytest.pytester import RunResult
|
||||||
from _pytest.pytester import Testdir
|
|
||||||
from _pytest.python import Class
|
from _pytest.python import Class
|
||||||
from _pytest.python import Function
|
from _pytest.python import Function
|
||||||
from _pytest.python import Instance
|
from _pytest.python import Instance
|
||||||
|
@ -61,7 +60,6 @@ from _pytest.recwarn import warns
|
||||||
from _pytest.runner import CallInfo
|
from _pytest.runner import CallInfo
|
||||||
from _pytest.stash import Stash
|
from _pytest.stash import Stash
|
||||||
from _pytest.stash import StashKey
|
from _pytest.stash import StashKey
|
||||||
from _pytest.tmpdir import TempdirFactory
|
|
||||||
from _pytest.tmpdir import TempPathFactory
|
from _pytest.tmpdir import TempPathFactory
|
||||||
from _pytest.warning_types import PytestAssertRewriteWarning
|
from _pytest.warning_types import PytestAssertRewriteWarning
|
||||||
from _pytest.warning_types import PytestCacheWarning
|
from _pytest.warning_types import PytestCacheWarning
|
||||||
|
@ -145,8 +143,6 @@ __all__ = [
|
||||||
"StashKey",
|
"StashKey",
|
||||||
"version_tuple",
|
"version_tuple",
|
||||||
"TempPathFactory",
|
"TempPathFactory",
|
||||||
"Testdir",
|
|
||||||
"TempdirFactory",
|
|
||||||
"UsageError",
|
"UsageError",
|
||||||
"WarningsRecorder",
|
"WarningsRecorder",
|
||||||
"warns",
|
"warns",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
anyio[curio,trio]==3.3.4
|
anyio[curio,trio]==3.3.4
|
||||||
django==3.2.8
|
django==3.2.8
|
||||||
pytest-asyncio==0.16.0
|
pytest-asyncio==0.16.0
|
||||||
pytest-bdd==4.1.0
|
pytest-bdd==5.0.0
|
||||||
pytest-cov==3.0.0
|
pytest-cov==3.0.0
|
||||||
pytest-django==4.4.0
|
pytest-django==4.4.0
|
||||||
pytest-flakes==4.0.4
|
pytest-flakes==4.0.4
|
||||||
|
|
|
@ -967,7 +967,6 @@ class TestRequestBasic:
|
||||||
(item,) = pytester.genitems([modcol])
|
(item,) = pytester.genitems([modcol])
|
||||||
req = fixtures.FixtureRequest(item, _ispytest=True)
|
req = fixtures.FixtureRequest(item, _ispytest=True)
|
||||||
assert req.path == modcol.path
|
assert req.path == modcol.path
|
||||||
assert req.fspath == modcol.fspath
|
|
||||||
|
|
||||||
def test_request_fixturenames(self, pytester: Pytester) -> None:
|
def test_request_fixturenames(self, pytester: Pytester) -> None:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
|
@ -1098,12 +1097,11 @@ class TestRequestSessionScoped:
|
||||||
def session_request(self, request):
|
def session_request(self, request):
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@pytest.mark.parametrize("name", ["path", "fspath", "module"])
|
@pytest.mark.parametrize("name", ["path", "module"])
|
||||||
def test_session_scoped_unavailable_attributes(self, session_request, name):
|
def test_session_scoped_unavailable_attributes(self, session_request, name):
|
||||||
expected = "path" if name == "fspath" else name
|
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
AttributeError,
|
AttributeError,
|
||||||
match=f"{expected} not available in session-scoped context",
|
match=f"{name} not available in session-scoped context",
|
||||||
):
|
):
|
||||||
getattr(session_request, name)
|
getattr(session_request, name)
|
||||||
|
|
||||||
|
|
|
@ -614,7 +614,6 @@ class TestSession:
|
||||||
items2, hookrec = pytester.inline_genitems(item.nodeid)
|
items2, hookrec = pytester.inline_genitems(item.nodeid)
|
||||||
(item2,) = items2
|
(item2,) = items2
|
||||||
assert item2.name == item.name
|
assert item2.name == item.name
|
||||||
assert item2.fspath == item.fspath
|
|
||||||
assert item2.path == item.path
|
assert item2.path == item.path
|
||||||
|
|
||||||
def test_find_byid_without_instance_parents(self, pytester: Pytester) -> None:
|
def test_find_byid_without_instance_parents(self, pytester: Pytester) -> None:
|
||||||
|
|
|
@ -635,14 +635,11 @@ class TestConfigAPI:
|
||||||
pytest.raises(ValueError, config.getini, "other")
|
pytest.raises(ValueError, config.getini, "other")
|
||||||
|
|
||||||
@pytest.mark.parametrize("config_type", ["ini", "pyproject"])
|
@pytest.mark.parametrize("config_type", ["ini", "pyproject"])
|
||||||
@pytest.mark.parametrize("ini_type", ["paths", "pathlist"])
|
def test_addini_paths(self, pytester: Pytester, config_type: str) -> None:
|
||||||
def test_addini_paths(
|
|
||||||
self, pytester: Pytester, config_type: str, ini_type: str
|
|
||||||
) -> None:
|
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
f"""
|
"""
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addini("paths", "my new ini value", type="{ini_type}")
|
parser.addini("paths", "my new ini value", type="paths")
|
||||||
parser.addini("abc", "abc value")
|
parser.addini("abc", "abc value")
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -1521,12 +1518,11 @@ class TestOverrideIniArgs:
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(["custom_option:3.0"])
|
result.stdout.fnmatch_lines(["custom_option:3.0"])
|
||||||
|
|
||||||
@pytest.mark.parametrize("ini_type", ["paths", "pathlist"])
|
def test_override_ini_paths(self, pytester: Pytester) -> None:
|
||||||
def test_override_ini_paths(self, pytester: Pytester, ini_type: str) -> None:
|
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
f"""
|
"""
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
parser.addini("paths", "my new ini value", type="{ini_type}")"""
|
parser.addini("paths", "my new ini value", type="paths")"""
|
||||||
)
|
)
|
||||||
pytester.makeini(
|
pytester.makeini(
|
||||||
"""
|
"""
|
||||||
|
@ -1534,14 +1530,11 @@ class TestOverrideIniArgs:
|
||||||
paths=blah.py"""
|
paths=blah.py"""
|
||||||
)
|
)
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
rf"""
|
r"""
|
||||||
def test_overriden(pytestconfig):
|
def test_overriden(pytestconfig):
|
||||||
config_paths = pytestconfig.getini("paths")
|
config_paths = pytestconfig.getini("paths")
|
||||||
print(config_paths)
|
print(config_paths)
|
||||||
for cpf in config_paths:
|
for cpf in config_paths:
|
||||||
if "{ini_type}" == "pathlist":
|
|
||||||
print('\nuser_path:%s' % cpf.basename)
|
|
||||||
else:
|
|
||||||
print('\nuser_path:%s' % cpf.name)
|
print('\nuser_path:%s' % cpf.name)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
|
@ -1170,6 +1170,41 @@ class TestDoctestSkips:
|
||||||
["*4: UnexpectedException*", "*5: DocTestFailure*", "*8: DocTestFailure*"]
|
["*4: UnexpectedException*", "*5: DocTestFailure*", "*8: DocTestFailure*"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_skipping_wrapped_test(self, pytester):
|
||||||
|
"""
|
||||||
|
Issue 8796: INTERNALERROR raised when skipping a decorated DocTest
|
||||||
|
through pytest_collection_modifyitems.
|
||||||
|
"""
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from _pytest.doctest import DoctestItem
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(config, items):
|
||||||
|
skip_marker = pytest.mark.skip()
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, DoctestItem):
|
||||||
|
item.add_marker(skip_marker)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def my_config_context():
|
||||||
|
'''
|
||||||
|
>>> import os
|
||||||
|
'''
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = pytester.runpytest("--doctest-modules")
|
||||||
|
assert "INTERNALERROR" not in result.stdout.str()
|
||||||
|
result.assert_outcomes(skipped=1)
|
||||||
|
|
||||||
|
|
||||||
class TestDoctestAutoUseFixtures:
|
class TestDoctestAutoUseFixtures:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from _pytest.compat import LEGACY_PATH
|
||||||
|
from _pytest.legacypath import TempdirFactory
|
||||||
|
from _pytest.legacypath import Testdir
|
||||||
|
|
||||||
|
|
||||||
|
def test_item_fspath(pytester: pytest.Pytester) -> None:
|
||||||
|
pytester.makepyfile("def test_func(): pass")
|
||||||
|
items, hookrec = pytester.inline_genitems()
|
||||||
|
assert len(items) == 1
|
||||||
|
(item,) = items
|
||||||
|
items2, hookrec = pytester.inline_genitems(item.nodeid)
|
||||||
|
(item2,) = items2
|
||||||
|
assert item2.name == item.name
|
||||||
|
assert item2.fspath == item.fspath # type: ignore[attr-defined]
|
||||||
|
assert item2.path == item.path
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_testtmproot(testdir: Testdir) -> None:
|
||||||
|
"""Check test_tmproot is a py.path attribute for backward compatibility."""
|
||||||
|
assert testdir.test_tmproot.check(dir=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_makefile_dot_prefixes_extension_silently(
|
||||||
|
testdir: Testdir,
|
||||||
|
) -> None:
|
||||||
|
"""For backwards compat #8192"""
|
||||||
|
p1 = testdir.makefile("foo.bar", "")
|
||||||
|
assert ".foo.bar" in str(p1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_makefile_ext_none_raises_type_error(testdir: Testdir) -> None:
|
||||||
|
"""For backwards compat #8192"""
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
testdir.makefile(None, "")
|
||||||
|
|
||||||
|
|
||||||
|
def test_testdir_makefile_ext_empty_string_makes_file(testdir: Testdir) -> None:
|
||||||
|
"""For backwards compat #8192"""
|
||||||
|
p1 = testdir.makefile("", "")
|
||||||
|
assert "test_testdir_makefile" in str(p1)
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_symlink_to(path: str, to_path: str) -> None:
|
||||||
|
"""Try to make a symlink from "path" to "to_path", skipping in case this platform
|
||||||
|
does not support it or we don't have sufficient privileges (common on Windows)."""
|
||||||
|
try:
|
||||||
|
Path(path).symlink_to(Path(to_path))
|
||||||
|
except OSError:
|
||||||
|
pytest.skip("could not create symbolic link")
|
||||||
|
|
||||||
|
|
||||||
|
def test_tmpdir_factory(
|
||||||
|
tmpdir_factory: TempdirFactory,
|
||||||
|
tmp_path_factory: pytest.TempPathFactory,
|
||||||
|
) -> None:
|
||||||
|
assert str(tmpdir_factory.getbasetemp()) == str(tmp_path_factory.getbasetemp())
|
||||||
|
dir = tmpdir_factory.mktemp("foo")
|
||||||
|
assert dir.exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_tmpdir_equals_tmp_path(tmpdir: LEGACY_PATH, tmp_path: Path) -> None:
|
||||||
|
assert Path(tmpdir) == tmp_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_tmpdir_always_is_realpath(pytester: pytest.Pytester) -> None:
|
||||||
|
# See test_tmp_path_always_is_realpath.
|
||||||
|
realtemp = pytester.mkdir("myrealtemp")
|
||||||
|
linktemp = pytester.path.joinpath("symlinktemp")
|
||||||
|
attempt_symlink_to(str(linktemp), str(realtemp))
|
||||||
|
p = pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_1(tmpdir):
|
||||||
|
import os
|
||||||
|
assert os.path.realpath(str(tmpdir)) == str(tmpdir)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("-s", p, "--basetemp=%s/bt" % linktemp)
|
||||||
|
assert not result.ret
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_makedir(cache: pytest.Cache) -> None:
|
||||||
|
dir = cache.makedir("foo") # type: ignore[attr-defined]
|
||||||
|
assert dir.exists()
|
||||||
|
dir.remove()
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None:
|
||||||
|
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
||||||
|
(item,) = pytester.genitems([modcol])
|
||||||
|
req = pytest.FixtureRequest(item, _ispytest=True)
|
||||||
|
assert req.path == modcol.path
|
||||||
|
assert req.fspath == modcol.fspath # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
class TestFixtureRequestSessionScoped:
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def session_request(self, request):
|
||||||
|
return request
|
||||||
|
|
||||||
|
def test_session_scoped_unavailable_attributes(self, session_request):
|
||||||
|
with pytest.raises(
|
||||||
|
AttributeError,
|
||||||
|
match="path not available in session-scoped context",
|
||||||
|
):
|
||||||
|
session_request.fspath
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("config_type", ["ini", "pyproject"])
|
||||||
|
def test_addini_paths(pytester: pytest.Pytester, config_type: str) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addini("paths", "my new ini value", type="pathlist")
|
||||||
|
parser.addini("abc", "abc value")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
if config_type == "ini":
|
||||||
|
inipath = pytester.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
paths=hello world/sub.py
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
elif config_type == "pyproject":
|
||||||
|
inipath = pytester.makepyprojecttoml(
|
||||||
|
"""
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
paths=["hello", "world/sub.py"]
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
config = pytester.parseconfig()
|
||||||
|
values = config.getini("paths")
|
||||||
|
assert len(values) == 2
|
||||||
|
assert values[0] == inipath.parent.joinpath("hello")
|
||||||
|
assert values[1] == inipath.parent.joinpath("world/sub.py")
|
||||||
|
pytest.raises(ValueError, config.getini, "other")
|
||||||
|
|
||||||
|
|
||||||
|
def test_override_ini_paths(pytester: pytest.Pytester) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addini("paths", "my new ini value", type="pathlist")"""
|
||||||
|
)
|
||||||
|
pytester.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
paths=blah.py"""
|
||||||
|
)
|
||||||
|
pytester.makepyfile(
|
||||||
|
r"""
|
||||||
|
def test_overriden(pytestconfig):
|
||||||
|
config_paths = pytestconfig.getini("paths")
|
||||||
|
print(config_paths)
|
||||||
|
for cpf in config_paths:
|
||||||
|
print('\nuser_path:%s' % cpf.basename)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("--override-ini", "paths=foo/bar1.py foo/bar2.py", "-s")
|
||||||
|
result.stdout.fnmatch_lines(["user_path:bar1.py", "user_path:bar2.py"])
|
|
@ -1111,7 +1111,7 @@ def test_pytest_param_id_allows_none_or_string(s) -> None:
|
||||||
assert pytest.param(id=s)
|
assert pytest.param(id=s)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expr", ("NOT internal_err", "NOT (internal_err)", "bogus/"))
|
@pytest.mark.parametrize("expr", ("NOT internal_err", "NOT (internal_err)", "bogus="))
|
||||||
def test_marker_expr_eval_failure_handling(pytester: Pytester, expr) -> None:
|
def test_marker_expr_eval_failure_handling(pytester: Pytester, expr) -> None:
|
||||||
foo = pytester.makepyfile(
|
foo = pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -144,6 +144,7 @@ def test_syntax_errors(expr: str, column: int, message: str) -> None:
|
||||||
"a:::c",
|
"a:::c",
|
||||||
"a+-b",
|
"a+-b",
|
||||||
r"\nhe\\l\lo\n\t\rbye",
|
r"\nhe\\l\lo\n\t\rbye",
|
||||||
|
"a/b",
|
||||||
"אבגד",
|
"אבגד",
|
||||||
"aaאבגדcc",
|
"aaאבגדcc",
|
||||||
"a[bcd]",
|
"a[bcd]",
|
||||||
|
@ -170,7 +171,6 @@ def test_valid_idents(ident: str) -> None:
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"ident",
|
"ident",
|
||||||
(
|
(
|
||||||
"/",
|
|
||||||
"^",
|
"^",
|
||||||
"*",
|
"*",
|
||||||
"=",
|
"=",
|
||||||
|
|
|
@ -17,7 +17,6 @@ from _pytest.pytester import LineMatcher
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
from _pytest.pytester import SysModulesSnapshot
|
from _pytest.pytester import SysModulesSnapshot
|
||||||
from _pytest.pytester import SysPathsSnapshot
|
from _pytest.pytester import SysPathsSnapshot
|
||||||
from _pytest.pytester import Testdir
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_hook_recorder(pytester: Pytester) -> None:
|
def test_make_hook_recorder(pytester: Pytester) -> None:
|
||||||
|
@ -814,19 +813,6 @@ def test_makefile_joins_absolute_path(pytester: Pytester) -> None:
|
||||||
assert str(p1) == str(pytester.path / "absfile.py")
|
assert str(p1) == str(pytester.path / "absfile.py")
|
||||||
|
|
||||||
|
|
||||||
def test_testtmproot(testdir) -> None:
|
|
||||||
"""Check test_tmproot is a py.path attribute for backward compatibility."""
|
|
||||||
assert testdir.test_tmproot.check(dir=1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_testdir_makefile_dot_prefixes_extension_silently(
|
|
||||||
testdir: Testdir,
|
|
||||||
) -> None:
|
|
||||||
"""For backwards compat #8192"""
|
|
||||||
p1 = testdir.makefile("foo.bar", "")
|
|
||||||
assert ".foo.bar" in str(p1)
|
|
||||||
|
|
||||||
|
|
||||||
def test_pytester_makefile_dot_prefixes_extension_with_warning(
|
def test_pytester_makefile_dot_prefixes_extension_with_warning(
|
||||||
pytester: Pytester,
|
pytester: Pytester,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -837,18 +823,6 @@ def test_pytester_makefile_dot_prefixes_extension_with_warning(
|
||||||
pytester.makefile("foo.bar", "")
|
pytester.makefile("foo.bar", "")
|
||||||
|
|
||||||
|
|
||||||
def test_testdir_makefile_ext_none_raises_type_error(testdir) -> None:
|
|
||||||
"""For backwards compat #8192"""
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
testdir.makefile(None, "")
|
|
||||||
|
|
||||||
|
|
||||||
def test_testdir_makefile_ext_empty_string_makes_file(testdir) -> None:
|
|
||||||
"""For backwards compat #8192"""
|
|
||||||
p1 = testdir.makefile("", "")
|
|
||||||
assert "test_testdir_makefile" in str(p1)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("default")
|
@pytest.mark.filterwarnings("default")
|
||||||
def test_pytester_assert_outcomes_warnings(pytester: Pytester) -> None:
|
def test_pytester_assert_outcomes_warnings(pytester: Pytester) -> None:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
|
|
|
@ -118,8 +118,8 @@ def test_mktemp(pytester: Pytester, basename: str, is_ok: bool) -> None:
|
||||||
result.stdout.fnmatch_lines("*ValueError*")
|
result.stdout.fnmatch_lines("*ValueError*")
|
||||||
|
|
||||||
|
|
||||||
def test_tmpdir_always_is_realpath(pytester: Pytester) -> None:
|
def test_tmp_path_always_is_realpath(pytester: Pytester, monkeypatch) -> None:
|
||||||
# the reason why tmpdir should be a realpath is that
|
# the reason why tmp_path should be a realpath is that
|
||||||
# when you cd to it and do "os.getcwd()" you will anyway
|
# when you cd to it and do "os.getcwd()" you will anyway
|
||||||
# get the realpath. Using the symlinked path can thus
|
# get the realpath. Using the symlinked path can thus
|
||||||
# easily result in path-inequality
|
# easily result in path-inequality
|
||||||
|
@ -128,22 +128,6 @@ def test_tmpdir_always_is_realpath(pytester: Pytester) -> None:
|
||||||
realtemp = pytester.mkdir("myrealtemp")
|
realtemp = pytester.mkdir("myrealtemp")
|
||||||
linktemp = pytester.path.joinpath("symlinktemp")
|
linktemp = pytester.path.joinpath("symlinktemp")
|
||||||
attempt_symlink_to(linktemp, str(realtemp))
|
attempt_symlink_to(linktemp, str(realtemp))
|
||||||
p = pytester.makepyfile(
|
|
||||||
"""
|
|
||||||
def test_1(tmpdir):
|
|
||||||
import os
|
|
||||||
assert os.path.realpath(str(tmpdir)) == str(tmpdir)
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
result = pytester.runpytest("-s", p, "--basetemp=%s/bt" % linktemp)
|
|
||||||
assert not result.ret
|
|
||||||
|
|
||||||
|
|
||||||
def test_tmp_path_always_is_realpath(pytester: Pytester, monkeypatch) -> None:
|
|
||||||
# for reasoning see: test_tmpdir_always_is_realpath test-case
|
|
||||||
realtemp = pytester.mkdir("myrealtemp")
|
|
||||||
linktemp = pytester.path.joinpath("symlinktemp")
|
|
||||||
attempt_symlink_to(linktemp, str(realtemp))
|
|
||||||
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(linktemp))
|
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(linktemp))
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
@ -423,10 +407,6 @@ def attempt_symlink_to(path, to_path):
|
||||||
pytest.skip("could not create symbolic link")
|
pytest.skip("could not create symbolic link")
|
||||||
|
|
||||||
|
|
||||||
def test_tmpdir_equals_tmp_path(tmpdir, tmp_path):
|
|
||||||
assert Path(tmpdir) == tmp_path
|
|
||||||
|
|
||||||
|
|
||||||
def test_basetemp_with_read_only_files(pytester: Pytester) -> None:
|
def test_basetemp_with_read_only_files(pytester: Pytester) -> None:
|
||||||
"""Integration test for #5524"""
|
"""Integration test for #5524"""
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
|
|
Loading…
Reference in New Issue