2024-01-28 21:12:42 +08:00
|
|
|
# mypy: allow-untyped-defs
|
2019-03-12 23:28:10 +08:00
|
|
|
import os
|
2020-10-03 23:08:14 +08:00
|
|
|
from pathlib import Path
|
2017-12-27 11:47:26 +08:00
|
|
|
import pprint
|
2020-11-07 22:56:00 +08:00
|
|
|
import shutil
|
2017-12-27 11:47:26 +08:00
|
|
|
import sys
|
2024-02-23 18:51:15 +08:00
|
|
|
import tempfile
|
2018-08-24 00:06:17 +08:00
|
|
|
import textwrap
|
2020-11-07 22:56:00 +08:00
|
|
|
from typing import List
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2024-02-23 18:51:15 +08:00
|
|
|
from _pytest.assertion.util import running_on_ci
|
2020-02-11 05:43:30 +08:00
|
|
|
from _pytest.config import ExitCode
|
2020-11-07 22:56:00 +08:00
|
|
|
from _pytest.fixtures import FixtureRequest
|
2018-10-25 15:01:29 +08:00
|
|
|
from _pytest.main import _in_venv
|
|
|
|
from _pytest.main import Session
|
2020-11-07 22:56:00 +08:00
|
|
|
from _pytest.monkeypatch import MonkeyPatch
|
|
|
|
from _pytest.nodes import Item
|
2020-06-08 21:56:40 +08:00
|
|
|
from _pytest.pathlib import symlink_or_skip
|
2020-11-07 22:56:00 +08:00
|
|
|
from _pytest.pytester import HookRecorder
|
2020-10-31 19:55:02 +08:00
|
|
|
from _pytest.pytester import Pytester
|
2018-10-25 15:01:29 +08:00
|
|
|
import pytest
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def ensure_file(file_path: Path) -> Path:
|
|
|
|
"""Ensure that file exists"""
|
|
|
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
file_path.touch(exist_ok=True)
|
|
|
|
return file_path
|
|
|
|
|
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestCollector:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_versus_item(self) -> None:
|
|
|
|
from pytest import Collector
|
|
|
|
from pytest import Item
|
2018-05-23 22:48:46 +08:00
|
|
|
|
2010-11-06 16:58:04 +08:00
|
|
|
assert not issubclass(Collector, Item)
|
|
|
|
assert not issubclass(Item, Collector)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_check_equality(self, pytester: Pytester) -> None:
|
|
|
|
modcol = pytester.getmodulecol(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
def test_pass(): pass
|
|
|
|
def test_fail(): assert 0
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
fn1 = pytester.collect_by_name(modcol, "test_pass")
|
2010-11-13 16:05:11 +08:00
|
|
|
assert isinstance(fn1, pytest.Function)
|
2020-11-07 22:56:00 +08:00
|
|
|
fn2 = pytester.collect_by_name(modcol, "test_pass")
|
2010-11-13 16:05:11 +08:00
|
|
|
assert isinstance(fn2, pytest.Function)
|
2010-11-06 16:58:04 +08:00
|
|
|
|
|
|
|
assert fn1 == fn2
|
|
|
|
assert fn1 != modcol
|
|
|
|
assert hash(fn1) == hash(fn2)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
fn3 = pytester.collect_by_name(modcol, "test_fail")
|
2010-11-13 16:05:11 +08:00
|
|
|
assert isinstance(fn3, pytest.Function)
|
2010-11-06 16:58:04 +08:00
|
|
|
assert not (fn1 == fn3)
|
|
|
|
assert fn1 != fn3
|
|
|
|
|
2017-07-17 07:25:08 +08:00
|
|
|
for fn in fn1, fn2, fn3:
|
2020-01-28 07:41:27 +08:00
|
|
|
assert isinstance(fn, pytest.Function)
|
2020-07-10 14:44:14 +08:00
|
|
|
assert fn != 3 # type: ignore[comparison-overlap]
|
2010-11-06 16:58:04 +08:00
|
|
|
assert fn != modcol
|
2020-07-10 14:44:14 +08:00
|
|
|
assert fn != [1, 2, 3] # type: ignore[comparison-overlap]
|
|
|
|
assert [1, 2, 3] != fn # type: ignore[comparison-overlap]
|
2010-11-06 16:58:04 +08:00
|
|
|
assert modcol != fn
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
assert pytester.collect_by_name(modcol, "doesnotexist") is None
|
2020-01-28 07:41:27 +08:00
|
|
|
|
2022-01-09 18:12:19 +08:00
|
|
|
def test_getparent_and_accessors(self, pytester: Pytester) -> None:
|
2020-11-07 22:56:00 +08:00
|
|
|
modcol = pytester.getmodulecol(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2020-04-09 00:11:04 +08:00
|
|
|
class TestClass:
|
|
|
|
def test_foo(self):
|
2010-11-06 16:58:04 +08:00
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
cls = pytester.collect_by_name(modcol, "TestClass")
|
|
|
|
assert isinstance(cls, pytest.Class)
|
2021-11-13 20:03:44 +08:00
|
|
|
fn = pytester.collect_by_name(cls, "test_foo")
|
2020-11-07 22:56:00 +08:00
|
|
|
assert isinstance(fn, pytest.Function)
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2022-01-09 18:12:19 +08:00
|
|
|
assert fn.getparent(pytest.Module) is modcol
|
|
|
|
assert modcol.module is not None
|
|
|
|
assert modcol.cls is None
|
|
|
|
assert modcol.instance is None
|
|
|
|
|
|
|
|
assert fn.getparent(pytest.Class) is cls
|
|
|
|
assert cls.module is not None
|
|
|
|
assert cls.cls is not None
|
|
|
|
assert cls.instance is None
|
|
|
|
|
|
|
|
assert fn.getparent(pytest.Function) is fn
|
|
|
|
assert fn.module is not None
|
|
|
|
assert fn.cls is not None
|
|
|
|
assert fn.instance is not None
|
|
|
|
assert fn.function is not None
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None:
|
|
|
|
hello = pytester.makefile(".xxx", hello="world")
|
|
|
|
pytester.makepyfile(
|
2010-11-06 16:58:04 +08:00
|
|
|
conftest="""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
|
|
|
class CustomFile(pytest.File):
|
2023-12-07 00:27:17 +08:00
|
|
|
def collect(self):
|
|
|
|
return []
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
if file_path.suffix == ".xxx":
|
|
|
|
return CustomFile.from_parent(path=file_path, parent=parent)
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
node = pytester.getpathnode(hello)
|
2010-11-13 16:05:11 +08:00
|
|
|
assert isinstance(node, pytest.File)
|
2010-11-06 16:58:04 +08:00
|
|
|
assert node.name == "hello.xxx"
|
2010-11-07 17:19:58 +08:00
|
|
|
nodes = node.session.perform_collect([node.nodeid], genitems=False)
|
2010-11-06 16:58:04 +08:00
|
|
|
assert len(nodes) == 1
|
2010-11-13 16:05:11 +08:00
|
|
|
assert isinstance(nodes[0], pytest.File)
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_can_skip_class_with_test_attr(self, pytester: Pytester) -> None:
|
2016-11-30 19:17:51 +08:00
|
|
|
"""Assure test class is skipped when using `__test__=False` (See #2007)."""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2016-11-30 19:17:51 +08:00
|
|
|
"""
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestFoo(object):
|
2016-11-30 19:17:51 +08:00
|
|
|
__test__ = False
|
|
|
|
def __init__(self):
|
|
|
|
pass
|
|
|
|
def test_foo():
|
|
|
|
assert True
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2016-11-30 19:17:51 +08:00
|
|
|
result.stdout.fnmatch_lines(["collected 0 items", "*no tests ran in*"])
|
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestCollectFS:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_ignored_certain_directories(self, pytester: Pytester) -> None:
|
2021-02-21 02:05:43 +08:00
|
|
|
tmp_path = pytester.path
|
|
|
|
ensure_file(tmp_path / "build" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / "dist" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / "_darcs" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / "CVS" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / "{arch}" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / ".whatever" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / ".bzr" / "test_notfound.py")
|
|
|
|
ensure_file(tmp_path / "normal" / "test_found.py")
|
2021-02-25 04:55:35 +08:00
|
|
|
for x in tmp_path.rglob("test_*.py"):
|
2023-06-20 19:55:39 +08:00
|
|
|
x.write_text("def test_hello(): pass", encoding="utf-8")
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("--collect-only")
|
2010-11-06 16:58:04 +08:00
|
|
|
s = result.stdout.str()
|
|
|
|
assert "test_notfound" not in s
|
|
|
|
assert "test_found" in s
|
|
|
|
|
2017-07-12 12:14:38 +08:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"fname",
|
|
|
|
(
|
|
|
|
"activate",
|
|
|
|
"activate.csh",
|
|
|
|
"activate.fish",
|
|
|
|
"Activate",
|
|
|
|
"Activate.bat",
|
|
|
|
"Activate.ps1",
|
2018-05-23 22:48:46 +08:00
|
|
|
),
|
2017-07-12 12:14:38 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None:
|
2017-12-27 11:47:26 +08:00
|
|
|
bindir = "Scripts" if sys.platform.startswith("win") else "bin"
|
2020-11-07 22:56:00 +08:00
|
|
|
ensure_file(pytester.path / "virtual" / bindir / fname)
|
|
|
|
testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py")
|
2023-06-20 19:55:39 +08:00
|
|
|
testfile.write_text("def test_hello(): pass", encoding="utf-8")
|
2017-07-12 12:14:38 +08:00
|
|
|
|
|
|
|
# by default, ignore tests inside a virtualenv
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2019-10-06 01:18:51 +08:00
|
|
|
result.stdout.no_fnmatch_line("*test_invenv*")
|
2017-07-12 12:14:38 +08:00
|
|
|
# allow test collection if user insists
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("--collect-in-virtualenv")
|
2017-07-12 12:14:38 +08:00
|
|
|
assert "test_invenv" in result.stdout.str()
|
|
|
|
# allow test collection if user directly passes in the directory
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("virtual")
|
2017-07-12 12:14:38 +08:00
|
|
|
assert "test_invenv" in result.stdout.str()
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"fname",
|
|
|
|
(
|
|
|
|
"activate",
|
|
|
|
"activate.csh",
|
|
|
|
"activate.fish",
|
|
|
|
"Activate",
|
|
|
|
"Activate.bat",
|
|
|
|
"Activate.ps1",
|
2018-05-23 22:48:46 +08:00
|
|
|
),
|
2017-07-12 12:14:38 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_ignored_virtualenvs_norecursedirs_precedence(
|
|
|
|
self, pytester: Pytester, fname: str
|
|
|
|
) -> None:
|
2017-12-27 11:47:26 +08:00
|
|
|
bindir = "Scripts" if sys.platform.startswith("win") else "bin"
|
2017-07-12 12:14:38 +08:00
|
|
|
# norecursedirs takes priority
|
2020-11-07 22:56:00 +08:00
|
|
|
ensure_file(pytester.path / ".virtual" / bindir / fname)
|
|
|
|
testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py")
|
2023-06-20 19:55:39 +08:00
|
|
|
testfile.write_text("def test_hello(): pass", encoding="utf-8")
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("--collect-in-virtualenv")
|
2019-10-06 01:18:51 +08:00
|
|
|
result.stdout.no_fnmatch_line("*test_invenv*")
|
2017-07-12 12:14:38 +08:00
|
|
|
# ...unless the virtualenv is explicitly given on the CLI
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("--collect-in-virtualenv", ".virtual")
|
2017-07-12 12:14:38 +08:00
|
|
|
assert "test_invenv" in result.stdout.str()
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"fname",
|
|
|
|
(
|
|
|
|
"activate",
|
|
|
|
"activate.csh",
|
|
|
|
"activate.fish",
|
|
|
|
"Activate",
|
|
|
|
"Activate.bat",
|
|
|
|
"Activate.ps1",
|
2018-05-23 22:48:46 +08:00
|
|
|
),
|
2017-07-12 12:14:38 +08:00
|
|
|
)
|
2020-12-16 12:16:05 +08:00
|
|
|
def test__in_venv(self, pytester: Pytester, fname: str) -> None:
|
2017-07-12 12:14:38 +08:00
|
|
|
"""Directly test the virtual env detection function"""
|
2017-12-27 11:47:26 +08:00
|
|
|
bindir = "Scripts" if sys.platform.startswith("win") else "bin"
|
2017-07-12 12:14:38 +08:00
|
|
|
# no bin/activate, not a virtualenv
|
2020-12-16 12:16:05 +08:00
|
|
|
base_path = pytester.mkdir("venv")
|
2020-12-19 20:52:10 +08:00
|
|
|
assert _in_venv(base_path) is False
|
2017-07-12 12:14:38 +08:00
|
|
|
# with bin/activate, totally a virtualenv
|
2020-12-16 12:16:05 +08:00
|
|
|
bin_path = base_path.joinpath(bindir)
|
|
|
|
bin_path.mkdir()
|
|
|
|
bin_path.joinpath(fname).touch()
|
2020-12-19 20:52:10 +08:00
|
|
|
assert _in_venv(base_path) is True
|
2017-07-12 12:14:38 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_custom_norecursedirs(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makeini(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
[pytest]
|
|
|
|
norecursedirs = mydir xyz*
|
|
|
|
"""
|
|
|
|
)
|
2021-02-21 02:05:43 +08:00
|
|
|
tmp_path = pytester.path
|
|
|
|
ensure_file(tmp_path / "mydir" / "test_hello.py").write_text(
|
2023-06-20 19:55:39 +08:00
|
|
|
"def test_1(): pass", encoding="utf-8"
|
|
|
|
)
|
|
|
|
ensure_file(tmp_path / "xyz123" / "test_2.py").write_text(
|
|
|
|
"def test_2(): 0/0", encoding="utf-8"
|
|
|
|
)
|
|
|
|
ensure_file(tmp_path / "xy" / "test_ok.py").write_text(
|
|
|
|
"def test_3(): pass", encoding="utf-8"
|
2021-02-21 02:05:43 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
rec = pytester.inline_run()
|
2010-11-06 16:58:04 +08:00
|
|
|
rec.assertoutcome(passed=1)
|
2020-11-07 22:56:00 +08:00
|
|
|
rec = pytester.inline_run("xyz123/test_2.py")
|
2010-11-06 16:58:04 +08:00
|
|
|
rec.assertoutcome(failed=1)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
|
|
|
|
pytester.makeini(
|
2015-07-09 09:51:18 +08:00
|
|
|
"""
|
|
|
|
[pytest]
|
2022-05-24 16:20:51 +08:00
|
|
|
testpaths = */tests
|
2015-07-09 09:51:18 +08:00
|
|
|
"""
|
|
|
|
)
|
2021-02-21 02:05:43 +08:00
|
|
|
tmp_path = pytester.path
|
2023-06-20 19:55:39 +08:00
|
|
|
ensure_file(tmp_path / "a" / "test_1.py").write_text(
|
|
|
|
"def test_a(): pass", encoding="utf-8"
|
|
|
|
)
|
2022-05-24 16:20:51 +08:00
|
|
|
ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text(
|
2023-06-20 19:55:39 +08:00
|
|
|
"def test_b(): pass", encoding="utf-8"
|
2022-05-24 16:20:51 +08:00
|
|
|
)
|
|
|
|
ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text(
|
2023-06-20 19:55:39 +08:00
|
|
|
"def test_c(): pass", encoding="utf-8"
|
2022-05-24 16:20:51 +08:00
|
|
|
)
|
2015-07-09 09:51:18 +08:00
|
|
|
|
|
|
|
# executing from rootdir only tests from `testpaths` directories
|
|
|
|
# are collected
|
2020-11-07 22:56:00 +08:00
|
|
|
items, reprec = pytester.inline_genitems("-v")
|
2022-05-24 16:20:51 +08:00
|
|
|
assert [x.name for x in items] == ["test_b", "test_c"]
|
2015-07-09 09:51:18 +08:00
|
|
|
|
|
|
|
# check that explicitly passing directories in the command-line
|
|
|
|
# collects the tests
|
2022-05-24 16:20:51 +08:00
|
|
|
for dirname in ("a", "b", "c"):
|
2021-02-21 02:05:43 +08:00
|
|
|
items, reprec = pytester.inline_genitems(tmp_path.joinpath(dirname))
|
2015-07-09 09:51:18 +08:00
|
|
|
assert [x.name for x in items] == ["test_%s" % dirname]
|
|
|
|
|
|
|
|
# changing cwd to each subdirectory and running pytest without
|
|
|
|
# arguments collects the tests in that directory normally
|
2022-05-24 16:20:51 +08:00
|
|
|
for dirname in ("a", "b", "c"):
|
2020-11-07 22:56:00 +08:00
|
|
|
monkeypatch.chdir(pytester.path.joinpath(dirname))
|
|
|
|
items, reprec = pytester.inline_genitems()
|
2015-07-09 09:51:18 +08:00
|
|
|
assert [x.name for x in items] == ["test_%s" % dirname]
|
|
|
|
|
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestCollectPluginHookRelay:
|
2020-12-16 12:16:05 +08:00
|
|
|
def test_pytest_collect_file(self, pytester: Pytester) -> None:
|
2010-11-06 16:58:04 +08:00
|
|
|
wascalled = []
|
2016-11-21 04:59:15 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class Plugin:
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(self, file_path: Path) -> None:
|
|
|
|
if not file_path.name.startswith("."):
|
2016-06-21 04:30:36 +08:00
|
|
|
# Ignore hidden files, e.g. .testmondata.
|
2021-12-03 20:14:09 +08:00
|
|
|
wascalled.append(file_path)
|
2016-11-21 04:59:15 +08:00
|
|
|
|
2020-12-16 12:16:05 +08:00
|
|
|
pytester.makefile(".abc", "xyz")
|
2020-12-19 02:39:33 +08:00
|
|
|
pytest.main(pytester.path, plugins=[Plugin()])
|
2010-11-06 16:58:04 +08:00
|
|
|
assert len(wascalled) == 1
|
2021-03-15 04:20:53 +08:00
|
|
|
assert wascalled[0].suffix == ".abc"
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2016-11-21 04:59:15 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestPrunetraceback:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_custom_repr_failure(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
import not_exists
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makeconftest(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
return MyFile.from_parent(path=file_path, parent=parent)
|
2010-11-06 16:58:04 +08:00
|
|
|
class MyError(Exception):
|
|
|
|
pass
|
2010-11-13 16:05:11 +08:00
|
|
|
class MyFile(pytest.File):
|
2010-11-06 16:58:04 +08:00
|
|
|
def collect(self):
|
|
|
|
raise MyError()
|
|
|
|
def repr_failure(self, excinfo):
|
2021-03-15 04:20:53 +08:00
|
|
|
if isinstance(excinfo.value, MyError):
|
2010-11-06 16:58:04 +08:00
|
|
|
return "hello world"
|
2010-11-13 16:05:11 +08:00
|
|
|
return pytest.File.repr_failure(self, excinfo)
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest(p)
|
2010-11-06 16:58:04 +08:00
|
|
|
result.stdout.fnmatch_lines(["*ERROR collecting*", "*hello world*"])
|
|
|
|
|
2010-11-18 05:12:16 +08:00
|
|
|
@pytest.mark.xfail(reason="other mechanism for adding to reporting needed")
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_report_postprocessing(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
import not_exists
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makeconftest(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
2023-06-13 03:30:06 +08:00
|
|
|
@pytest.hookimpl(wrapper=True)
|
2017-08-31 07:23:55 +08:00
|
|
|
def pytest_make_collect_report():
|
2023-06-13 03:30:06 +08:00
|
|
|
rep = yield
|
2010-11-06 16:58:04 +08:00
|
|
|
rep.headerlines += ["header1"]
|
2023-06-13 03:30:06 +08:00
|
|
|
return rep
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest(p)
|
2010-11-06 16:58:04 +08:00
|
|
|
result.stdout.fnmatch_lines(["*ERROR collecting*", "*header1*"])
|
|
|
|
|
2023-12-31 16:14:23 +08:00
|
|
|
def test_collection_error_traceback_is_clean(self, pytester: Pytester) -> None:
|
|
|
|
"""When a collection error occurs, the report traceback doesn't contain
|
|
|
|
internal pytest stack entries.
|
|
|
|
|
|
|
|
Issue #11710.
|
|
|
|
"""
|
|
|
|
pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
raise Exception("LOUSY")
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
result = pytester.runpytest()
|
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"*ERROR collecting*",
|
|
|
|
"test_*.py:1: in <module>",
|
|
|
|
' raise Exception("LOUSY")',
|
|
|
|
"E Exception: LOUSY",
|
|
|
|
"*= short test summary info =*",
|
|
|
|
],
|
|
|
|
consecutive=True,
|
|
|
|
)
|
|
|
|
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestCustomConftests:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_ignore_collect_path(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makeconftest(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_ignore_collect(collection_path, config):
|
|
|
|
return collection_path.name.startswith("x") or collection_path.name == "test_one.py"
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
sub = pytester.mkdir("xy123")
|
2023-06-20 19:55:39 +08:00
|
|
|
ensure_file(sub / "test_hello.py").write_text("syntax error", encoding="utf-8")
|
|
|
|
sub.joinpath("conftest.py").write_text("syntax error", encoding="utf-8")
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile("def test_hello(): pass")
|
|
|
|
pytester.makepyfile(test_one="syntax error")
|
|
|
|
result = pytester.runpytest("--fulltrace")
|
2010-11-06 16:58:04 +08:00
|
|
|
assert result.ret == 0
|
|
|
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_ignore_collect_not_called_on_argument(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makeconftest(
|
2010-11-07 07:22:16 +08:00
|
|
|
"""
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_ignore_collect(collection_path, config):
|
2010-11-07 07:22:16 +08:00
|
|
|
return True
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
p = pytester.makepyfile("def test_hello(): pass")
|
|
|
|
result = pytester.runpytest(p)
|
2010-11-07 07:22:16 +08:00
|
|
|
assert result.ret == 0
|
2019-03-23 18:36:18 +08:00
|
|
|
result.stdout.fnmatch_lines(["*1 passed*"])
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2019-06-07 18:58:51 +08:00
|
|
|
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
2019-03-23 18:36:18 +08:00
|
|
|
result.stdout.fnmatch_lines(["*collected 0 items*"])
|
2010-11-07 07:22:16 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collectignore_exclude_on_option(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makeconftest(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2020-12-11 19:40:37 +08:00
|
|
|
from pathlib import Path
|
2021-03-15 04:50:27 +08:00
|
|
|
|
|
|
|
class MyPathLike:
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
def __fspath__(self):
|
|
|
|
return "path"
|
|
|
|
|
|
|
|
collect_ignore = [MyPathLike('hello'), 'test_world.py', Path('bye')]
|
|
|
|
|
2010-11-06 16:58:04 +08:00
|
|
|
def pytest_addoption(parser):
|
|
|
|
parser.addoption("--XX", action="store_true", default=False)
|
2021-03-15 04:50:27 +08:00
|
|
|
|
2010-11-06 16:58:04 +08:00
|
|
|
def pytest_configure(config):
|
|
|
|
if config.getvalue("XX"):
|
|
|
|
collect_ignore[:] = []
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.mkdir("hello")
|
|
|
|
pytester.makepyfile(test_world="def test_hello(): pass")
|
|
|
|
result = pytester.runpytest()
|
2019-06-07 18:58:51 +08:00
|
|
|
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
2019-10-06 01:18:51 +08:00
|
|
|
result.stdout.no_fnmatch_line("*passed*")
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("--XX")
|
2010-11-06 16:58:04 +08:00
|
|
|
assert result.ret == 0
|
|
|
|
assert "passed" in result.stdout.str()
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collectignoreglob_exclude_on_option(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makeconftest(
|
2019-02-06 17:50:46 +08:00
|
|
|
"""
|
|
|
|
collect_ignore_glob = ['*w*l[dt]*']
|
|
|
|
def pytest_addoption(parser):
|
|
|
|
parser.addoption("--XX", action="store_true", default=False)
|
|
|
|
def pytest_configure(config):
|
|
|
|
if config.getvalue("XX"):
|
|
|
|
collect_ignore_glob[:] = []
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(test_world="def test_hello(): pass")
|
|
|
|
pytester.makepyfile(test_welt="def test_hallo(): pass")
|
|
|
|
result = pytester.runpytest()
|
2019-06-07 18:58:51 +08:00
|
|
|
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
2019-03-23 18:36:18 +08:00
|
|
|
result.stdout.fnmatch_lines(["*collected 0 items*"])
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("--XX")
|
2019-02-06 17:50:46 +08:00
|
|
|
assert result.ret == 0
|
2019-03-23 18:36:18 +08:00
|
|
|
result.stdout.fnmatch_lines(["*2 passed*"])
|
2019-02-06 17:50:46 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_pytest_fs_collect_hooks_are_seen(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makeconftest(
|
2013-10-12 21:39:22 +08:00
|
|
|
"""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
|
|
|
class MyModule(pytest.Module):
|
2010-11-06 16:58:04 +08:00
|
|
|
pass
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
if file_path.suffix == ".py":
|
|
|
|
return MyModule.from_parent(path=file_path, parent=parent)
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.mkdir("sub")
|
|
|
|
pytester.makepyfile("def test_x(): pass")
|
|
|
|
result = pytester.runpytest("--co")
|
2010-11-06 16:58:04 +08:00
|
|
|
result.stdout.fnmatch_lines(["*MyModule*", "*test_x*"])
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_pytest_collect_file_from_sister_dir(self, pytester: Pytester) -> None:
|
|
|
|
sub1 = pytester.mkpydir("sub1")
|
|
|
|
sub2 = pytester.mkpydir("sub2")
|
|
|
|
conf1 = pytester.makeconftest(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
|
|
|
class MyModule1(pytest.Module):
|
2010-11-06 16:58:04 +08:00
|
|
|
pass
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
if file_path.suffix == ".py":
|
|
|
|
return MyModule1.from_parent(path=file_path, parent=parent)
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
conf1.replace(sub1.joinpath(conf1.name))
|
|
|
|
conf2 = pytester.makeconftest(
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
|
|
|
class MyModule2(pytest.Module):
|
2010-11-06 16:58:04 +08:00
|
|
|
pass
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
if file_path.suffix == ".py":
|
|
|
|
return MyModule2.from_parent(path=file_path, parent=parent)
|
2010-11-06 16:58:04 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
conf2.replace(sub2.joinpath(conf2.name))
|
|
|
|
p = pytester.makepyfile("def test_x(): pass")
|
|
|
|
shutil.copy(p, sub1.joinpath(p.name))
|
|
|
|
shutil.copy(p, sub2.joinpath(p.name))
|
|
|
|
result = pytester.runpytest("--co")
|
2010-11-06 16:58:04 +08:00
|
|
|
result.stdout.fnmatch_lines(["*MyModule1*", "*MyModule2*", "*test_x*"])
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestSession:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_topdir(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile("def test_func(): pass")
|
|
|
|
id = "::".join([p.name, "test_func"])
|
2015-04-28 17:54:53 +08:00
|
|
|
# XXX migrate to collectonly? (see below)
|
2020-11-07 22:56:00 +08:00
|
|
|
config = pytester.parseconfig(id)
|
|
|
|
topdir = pytester.path
|
2019-10-17 03:52:04 +08:00
|
|
|
rcol = Session.from_config(config)
|
2021-01-18 04:20:29 +08:00
|
|
|
assert topdir == rcol.path
|
2017-07-17 07:25:09 +08:00
|
|
|
# rootid = rcol.nodeid
|
|
|
|
# root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0]
|
|
|
|
# assert root2 == rcol, rootid
|
2010-11-06 16:58:04 +08:00
|
|
|
colitems = rcol.perform_collect([rcol.nodeid], genitems=False)
|
|
|
|
assert len(colitems) == 1
|
2023-06-02 21:03:39 +08:00
|
|
|
assert colitems[0].path == topdir
|
2010-11-06 16:58:04 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def get_reported_items(self, hookrec: HookRecorder) -> List[Item]:
|
2017-06-04 05:26:34 +08:00
|
|
|
"""Return pytest.Item instances reported by the pytest_collectreport hook"""
|
|
|
|
calls = hookrec.getcalls("pytest_collectreport")
|
|
|
|
return [
|
|
|
|
x
|
|
|
|
for call in calls
|
|
|
|
for x in call.report.result
|
|
|
|
if isinstance(x, pytest.Item)
|
|
|
|
]
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_protocol_single_function(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile("def test_func(): pass")
|
|
|
|
id = "::".join([p.name, "test_func"])
|
|
|
|
items, hookrec = pytester.inline_genitems(id)
|
2019-11-17 01:53:29 +08:00
|
|
|
(item,) = items
|
2010-09-15 16:30:50 +08:00
|
|
|
assert item.name == "test_func"
|
2010-11-06 16:58:04 +08:00
|
|
|
newid = item.nodeid
|
2010-09-15 16:30:50 +08:00
|
|
|
assert newid == id
|
2017-12-27 11:47:26 +08:00
|
|
|
pprint.pprint(hookrec.calls)
|
2024-02-09 17:14:36 +08:00
|
|
|
topdir = pytester.path # noqa: F841
|
2014-10-06 19:37:57 +08:00
|
|
|
hookrec.assert_contains(
|
|
|
|
[
|
2021-01-18 04:20:29 +08:00
|
|
|
("pytest_collectstart", "collector.path == topdir"),
|
|
|
|
("pytest_make_collect_report", "collector.path == topdir"),
|
|
|
|
("pytest_collectstart", "collector.path == p"),
|
|
|
|
("pytest_make_collect_report", "collector.path == p"),
|
2010-09-15 16:30:50 +08:00
|
|
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
2017-06-04 05:26:34 +08:00
|
|
|
("pytest_collectreport", "report.result[0].name == 'test_func'"),
|
2010-09-15 16:30:50 +08:00
|
|
|
]
|
|
|
|
)
|
2017-06-04 05:26:34 +08:00
|
|
|
# ensure we are reporting the collection of the single test item (#2464)
|
|
|
|
assert [x.name for x in self.get_reported_items(hookrec)] == ["test_func"]
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_protocol_method(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
2010-09-15 16:30:50 +08:00
|
|
|
"""
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestClass(object):
|
2010-09-15 16:30:50 +08:00
|
|
|
def test_method(self):
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
normid = p.name + "::TestClass::test_method"
|
|
|
|
for id in [p.name, p.name + "::TestClass", normid]:
|
|
|
|
items, hookrec = pytester.inline_genitems(id)
|
2010-11-06 16:58:04 +08:00
|
|
|
assert len(items) == 1
|
|
|
|
assert items[0].name == "test_method"
|
|
|
|
newid = items[0].nodeid
|
2010-09-15 16:30:50 +08:00
|
|
|
assert newid == normid
|
2017-06-04 05:26:34 +08:00
|
|
|
# ensure we are reporting the collection of the single test item (#2464)
|
|
|
|
assert [x.name for x in self.get_reported_items(hookrec)] == ["test_method"]
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_custom_nodes_multi_id(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile("def test_func(): pass")
|
|
|
|
pytester.makeconftest(
|
2010-09-15 16:30:50 +08:00
|
|
|
"""
|
2010-11-13 16:05:11 +08:00
|
|
|
import pytest
|
|
|
|
class SpecialItem(pytest.Item):
|
2010-09-15 16:30:50 +08:00
|
|
|
def runtest(self):
|
|
|
|
return # ok
|
2010-11-13 16:05:11 +08:00
|
|
|
class SpecialFile(pytest.File):
|
2010-09-15 16:30:50 +08:00
|
|
|
def collect(self):
|
2020-07-23 08:36:51 +08:00
|
|
|
return [SpecialItem.from_parent(name="check", parent=self)]
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
if file_path.name == %r:
|
|
|
|
return SpecialFile.from_parent(path=file_path, parent=parent)
|
2010-09-15 16:30:50 +08:00
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
% p.name
|
2010-09-15 16:30:50 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
id = p.name
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
items, hookrec = pytester.inline_genitems(id)
|
2017-12-27 11:47:26 +08:00
|
|
|
pprint.pprint(hookrec.calls)
|
2010-09-15 16:30:50 +08:00
|
|
|
assert len(items) == 2
|
2014-10-06 19:37:57 +08:00
|
|
|
hookrec.assert_contains(
|
|
|
|
[
|
2021-01-18 04:20:29 +08:00
|
|
|
("pytest_collectstart", "collector.path == collector.session.path"),
|
2010-09-15 16:30:50 +08:00
|
|
|
(
|
|
|
|
"pytest_collectstart",
|
|
|
|
"collector.__class__.__name__ == 'SpecialFile'",
|
|
|
|
),
|
2024-02-09 17:34:55 +08:00
|
|
|
("pytest_collectstart", "collector.__class__.__name__ == 'Module'"),
|
|
|
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
2020-11-07 22:56:00 +08:00
|
|
|
("pytest_collectreport", "report.nodeid.startswith(p.name)"),
|
2010-09-15 16:30:50 +08:00
|
|
|
]
|
|
|
|
)
|
2017-06-04 05:26:34 +08:00
|
|
|
assert len(self.get_reported_items(hookrec)) == 2
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_subdir_event_ordering(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile("def test_func(): pass")
|
|
|
|
aaa = pytester.mkpydir("aaa")
|
|
|
|
test_aaa = aaa.joinpath("test_aaa.py")
|
|
|
|
p.replace(test_aaa)
|
2011-11-08 02:08:41 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
items, hookrec = pytester.inline_genitems()
|
2010-09-15 16:30:50 +08:00
|
|
|
assert len(items) == 1
|
2017-12-27 11:47:26 +08:00
|
|
|
pprint.pprint(hookrec.calls)
|
2014-10-06 19:37:57 +08:00
|
|
|
hookrec.assert_contains(
|
|
|
|
[
|
2021-01-18 04:20:29 +08:00
|
|
|
("pytest_collectstart", "collector.path == test_aaa"),
|
2010-09-15 16:30:50 +08:00
|
|
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
2017-07-17 07:25:07 +08:00
|
|
|
("pytest_collectreport", "report.nodeid.startswith('aaa/test_aaa.py')"),
|
2010-09-15 16:30:50 +08:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_two_commandline_args(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile("def test_func(): pass")
|
|
|
|
aaa = pytester.mkpydir("aaa")
|
|
|
|
bbb = pytester.mkpydir("bbb")
|
|
|
|
test_aaa = aaa.joinpath("test_aaa.py")
|
|
|
|
shutil.copy(p, test_aaa)
|
|
|
|
test_bbb = bbb.joinpath("test_bbb.py")
|
|
|
|
p.replace(test_bbb)
|
2010-09-26 22:23:43 +08:00
|
|
|
|
2010-09-15 16:30:50 +08:00
|
|
|
id = "."
|
2011-11-08 02:08:41 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
items, hookrec = pytester.inline_genitems(id)
|
2010-09-15 16:30:50 +08:00
|
|
|
assert len(items) == 2
|
2017-12-27 11:47:26 +08:00
|
|
|
pprint.pprint(hookrec.calls)
|
2014-10-06 19:37:57 +08:00
|
|
|
hookrec.assert_contains(
|
|
|
|
[
|
2021-01-18 04:20:29 +08:00
|
|
|
("pytest_collectstart", "collector.path == test_aaa"),
|
2010-09-15 16:30:50 +08:00
|
|
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
2010-11-06 16:58:04 +08:00
|
|
|
("pytest_collectreport", "report.nodeid == 'aaa/test_aaa.py'"),
|
2021-01-18 04:20:29 +08:00
|
|
|
("pytest_collectstart", "collector.path == test_bbb"),
|
2010-09-15 16:30:50 +08:00
|
|
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
2010-11-06 16:58:04 +08:00
|
|
|
("pytest_collectreport", "report.nodeid == 'bbb/test_bbb.py'"),
|
2010-09-15 16:30:50 +08:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_serialization_byid(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makepyfile("def test_func(): pass")
|
|
|
|
items, hookrec = pytester.inline_genitems()
|
2010-09-15 16:30:50 +08:00
|
|
|
assert len(items) == 1
|
2019-11-17 01:53:29 +08:00
|
|
|
(item,) = items
|
2020-11-07 22:56:00 +08:00
|
|
|
items2, hookrec = pytester.inline_genitems(item.nodeid)
|
2019-11-17 01:53:29 +08:00
|
|
|
(item2,) = items2
|
2010-09-15 16:30:50 +08:00
|
|
|
assert item2.name == item.name
|
2021-01-18 04:20:29 +08:00
|
|
|
assert item2.path == item.path
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_find_byid_without_instance_parents(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
2011-02-07 18:09:42 +08:00
|
|
|
"""
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestClass(object):
|
2011-02-07 18:09:42 +08:00
|
|
|
def test_method(self):
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
arg = p.name + "::TestClass::test_method"
|
|
|
|
items, hookrec = pytester.inline_genitems(arg)
|
2011-02-07 18:09:42 +08:00
|
|
|
assert len(items) == 1
|
2019-11-17 01:53:29 +08:00
|
|
|
(item,) = items
|
2018-11-09 18:03:07 +08:00
|
|
|
assert item.nodeid.endswith("TestClass::test_method")
|
2017-06-04 05:26:34 +08:00
|
|
|
# ensure we are reporting the collection of the single test item (#2464)
|
|
|
|
assert [x.name for x in self.get_reported_items(hookrec)] == ["test_method"]
|
2011-02-07 18:09:42 +08:00
|
|
|
|
2024-02-09 17:34:55 +08:00
|
|
|
def test_collect_parametrized_order(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('i', [0, 1, 2])
|
|
|
|
def test_param(i): ...
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
items, hookrec = pytester.inline_genitems(f"{p}::test_param")
|
|
|
|
assert len(items) == 3
|
|
|
|
assert [item.nodeid for item in items] == [
|
|
|
|
"test_collect_parametrized_order.py::test_param[0]",
|
|
|
|
"test_collect_parametrized_order.py::test_param[1]",
|
|
|
|
"test_collect_parametrized_order.py::test_param[2]",
|
|
|
|
]
|
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class Test_getinitialnodes:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_global_file(self, pytester: Pytester) -> None:
|
2021-02-21 02:05:43 +08:00
|
|
|
tmp_path = pytester.path
|
|
|
|
x = ensure_file(tmp_path / "x.py")
|
2021-02-25 04:55:35 +08:00
|
|
|
config = pytester.parseconfigure(x)
|
2020-11-07 22:56:00 +08:00
|
|
|
col = pytester.getnode(config, x)
|
2010-11-13 16:05:11 +08:00
|
|
|
assert isinstance(col, pytest.Module)
|
2010-09-15 16:30:50 +08:00
|
|
|
assert col.name == "x.py"
|
2020-05-01 19:40:17 +08:00
|
|
|
assert col.parent is not None
|
2023-06-02 21:03:39 +08:00
|
|
|
assert col.parent.parent is not None
|
|
|
|
assert col.parent.parent.parent is None
|
2020-11-07 22:56:00 +08:00
|
|
|
for parent in col.listchain():
|
|
|
|
assert parent.config is config
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2022-03-21 23:38:20 +08:00
|
|
|
def test_pkgfile(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
|
2018-08-11 04:57:29 +08:00
|
|
|
"""Verify nesting when a module is within a package.
|
2018-08-11 05:18:07 +08:00
|
|
|
The parent chain should match: Module<x.py> -> Package<subdir> -> Session.
|
|
|
|
Session's parent should always be None.
|
|
|
|
"""
|
2021-02-21 02:05:43 +08:00
|
|
|
tmp_path = pytester.path
|
|
|
|
subdir = tmp_path.joinpath("subdir")
|
2020-11-07 22:56:00 +08:00
|
|
|
x = ensure_file(subdir / "x.py")
|
|
|
|
ensure_file(subdir / "__init__.py")
|
2022-03-21 23:38:20 +08:00
|
|
|
with monkeypatch.context() as mp:
|
|
|
|
mp.chdir(subdir)
|
|
|
|
config = pytester.parseconfigure(x)
|
2020-11-07 22:56:00 +08:00
|
|
|
col = pytester.getnode(config, x)
|
|
|
|
assert col is not None
|
2015-02-27 04:56:44 +08:00
|
|
|
assert col.name == "x.py"
|
2018-08-11 05:18:07 +08:00
|
|
|
assert isinstance(col, pytest.Module)
|
|
|
|
assert isinstance(col.parent, pytest.Package)
|
|
|
|
assert isinstance(col.parent.parent, pytest.Session)
|
|
|
|
# session is batman (has no parents)
|
2018-08-10 09:28:22 +08:00
|
|
|
assert col.parent.parent.parent is None
|
2020-11-07 22:56:00 +08:00
|
|
|
for parent in col.listchain():
|
|
|
|
assert parent.config is config
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class Test_genitems:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_check_collect_hashes(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
2010-09-15 16:30:50 +08:00
|
|
|
"""
|
|
|
|
def test_1():
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_2():
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
shutil.copy(p, p.parent / (p.stem + "2" + ".py"))
|
|
|
|
items, reprec = pytester.inline_genitems(p.parent)
|
2010-09-15 16:30:50 +08:00
|
|
|
assert len(items) == 4
|
|
|
|
for numi, i in enumerate(items):
|
|
|
|
for numj, j in enumerate(items):
|
|
|
|
if numj != numi:
|
|
|
|
assert hash(i) != hash(j)
|
|
|
|
assert i != j
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_example_items1(self, pytester: Pytester) -> None:
|
|
|
|
p = pytester.makepyfile(
|
2018-05-23 22:48:46 +08:00
|
|
|
"""
|
2019-11-18 23:46:18 +08:00
|
|
|
import pytest
|
|
|
|
|
2010-09-15 16:30:50 +08:00
|
|
|
def testone():
|
|
|
|
pass
|
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestX(object):
|
2010-09-15 16:30:50 +08:00
|
|
|
def testmethod_one(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class TestY(TestX):
|
2019-11-18 23:46:18 +08:00
|
|
|
@pytest.mark.parametrize("arg0", [".["])
|
|
|
|
def testmethod_two(self, arg0):
|
|
|
|
pass
|
2018-05-23 22:48:46 +08:00
|
|
|
"""
|
2010-09-15 16:30:50 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
items, reprec = pytester.inline_genitems(p)
|
2019-11-18 23:46:18 +08:00
|
|
|
assert len(items) == 4
|
2010-09-15 16:30:50 +08:00
|
|
|
assert items[0].name == "testone"
|
|
|
|
assert items[1].name == "testmethod_one"
|
|
|
|
assert items[2].name == "testmethod_one"
|
2019-11-18 23:46:18 +08:00
|
|
|
assert items[3].name == "testmethod_two[.[]"
|
2010-09-15 16:30:50 +08:00
|
|
|
|
|
|
|
# let's also test getmodpath here
|
2020-11-07 22:56:00 +08:00
|
|
|
assert items[0].getmodpath() == "testone" # type: ignore[attr-defined]
|
|
|
|
assert items[1].getmodpath() == "TestX.testmethod_one" # type: ignore[attr-defined]
|
|
|
|
assert items[2].getmodpath() == "TestY.testmethod_one" # type: ignore[attr-defined]
|
2019-11-18 23:46:18 +08:00
|
|
|
# PR #6202: Fix incorrect result of getmodpath method. (Resolves issue #6189)
|
2020-11-07 22:56:00 +08:00
|
|
|
assert items[3].getmodpath() == "TestY.testmethod_two[.[]" # type: ignore[attr-defined]
|
2010-09-15 16:30:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
s = items[0].getmodpath(stopatmodule=False) # type: ignore[attr-defined]
|
2010-09-15 16:30:50 +08:00
|
|
|
assert s.endswith("test_example_items1.testone")
|
|
|
|
print(s)
|
2010-11-18 01:24:28 +08:00
|
|
|
|
2022-12-02 23:53:04 +08:00
|
|
|
def test_classmethod_is_discovered(self, pytester: Pytester) -> None:
|
|
|
|
"""Test that classmethods are discovered"""
|
|
|
|
p = pytester.makepyfile(
|
|
|
|
"""
|
|
|
|
class TestCase:
|
|
|
|
@classmethod
|
|
|
|
def test_classmethod(cls) -> None:
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
items, reprec = pytester.inline_genitems(p)
|
|
|
|
ids = [x.getmodpath() for x in items] # type: ignore[attr-defined]
|
|
|
|
assert ids == ["TestCase.test_classmethod"]
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None:
|
2020-07-18 17:35:13 +08:00
|
|
|
"""Test that Python_classes and Python_functions config options work
|
|
|
|
as prefixes and glob-like patterns (#600)."""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makeini(
|
2014-10-17 06:27:10 +08:00
|
|
|
"""
|
|
|
|
[pytest]
|
|
|
|
python_classes = *Suite Test
|
|
|
|
python_functions = *_test test
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
p = pytester.makepyfile(
|
2018-05-23 22:48:46 +08:00
|
|
|
"""
|
2017-02-17 02:41:51 +08:00
|
|
|
class MyTestSuite(object):
|
2014-10-17 06:27:10 +08:00
|
|
|
def x_test(self):
|
|
|
|
pass
|
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class TestCase(object):
|
2014-10-17 06:27:10 +08:00
|
|
|
def test_y(self):
|
|
|
|
pass
|
2018-05-23 22:48:46 +08:00
|
|
|
"""
|
2014-10-17 06:27:10 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
items, reprec = pytester.inline_genitems(p)
|
|
|
|
ids = [x.getmodpath() for x in items] # type: ignore[attr-defined]
|
2014-10-17 06:27:10 +08:00
|
|
|
assert ids == ["MyTestSuite.x_test", "TestCase.test_y"]
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_matchnodes_two_collections_same_file(pytester: Pytester) -> None:
|
|
|
|
pytester.makeconftest(
|
2010-11-18 01:24:28 +08:00
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
def pytest_configure(config):
|
|
|
|
config.pluginmanager.register(Plugin2())
|
|
|
|
|
2017-02-17 02:41:51 +08:00
|
|
|
class Plugin2(object):
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(self, file_path, parent):
|
|
|
|
if file_path.suffix == ".abc":
|
|
|
|
return MyFile2.from_parent(path=file_path, parent=parent)
|
2010-11-18 01:24:28 +08:00
|
|
|
|
2021-12-03 20:14:09 +08:00
|
|
|
def pytest_collect_file(file_path, parent):
|
|
|
|
if file_path.suffix == ".abc":
|
|
|
|
return MyFile1.from_parent(path=file_path, parent=parent)
|
2020-07-23 08:36:51 +08:00
|
|
|
|
|
|
|
class MyFile1(pytest.File):
|
|
|
|
def collect(self):
|
|
|
|
yield Item1.from_parent(name="item1", parent=self)
|
2010-11-18 01:24:28 +08:00
|
|
|
|
|
|
|
class MyFile2(pytest.File):
|
|
|
|
def collect(self):
|
2020-07-23 08:36:51 +08:00
|
|
|
yield Item2.from_parent(name="item2", parent=self)
|
|
|
|
|
|
|
|
class Item1(pytest.Item):
|
|
|
|
def runtest(self):
|
|
|
|
pass
|
2010-11-18 01:24:28 +08:00
|
|
|
|
|
|
|
class Item2(pytest.Item):
|
|
|
|
def runtest(self):
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
p = pytester.makefile(".abc", "")
|
|
|
|
result = pytester.runpytest()
|
2010-11-18 01:24:28 +08:00
|
|
|
assert result.ret == 0
|
|
|
|
result.stdout.fnmatch_lines(["*2 passed*"])
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest("%s::item2" % p.name)
|
2010-11-18 01:24:28 +08:00
|
|
|
res.stdout.fnmatch_lines(["*1 passed*"])
|
2011-11-08 02:08:41 +08:00
|
|
|
|
2010-11-18 01:24:28 +08:00
|
|
|
|
2021-10-06 15:53:34 +08:00
|
|
|
class TestNodeKeywords:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_no_under(self, pytester: Pytester) -> None:
|
|
|
|
modcol = pytester.getmodulecol(
|
2013-10-03 19:53:22 +08:00
|
|
|
"""
|
|
|
|
def test_pass(): pass
|
|
|
|
def test_fail(): assert 0
|
|
|
|
"""
|
|
|
|
)
|
2017-11-04 23:17:20 +08:00
|
|
|
values = list(modcol.keywords)
|
|
|
|
assert modcol.name in values
|
|
|
|
for x in values:
|
2013-10-03 19:53:22 +08:00
|
|
|
assert not x.startswith("_")
|
|
|
|
assert modcol.name in repr(modcol.keywords)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_issue345(self, pytester: Pytester) -> None:
|
|
|
|
pytester.makepyfile(
|
2013-10-03 19:53:22 +08:00
|
|
|
"""
|
|
|
|
def test_should_not_be_selected():
|
|
|
|
assert False, 'I should not have been selected to run'
|
|
|
|
|
|
|
|
def test___repr__():
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
reprec = pytester.inline_run("-k repr")
|
2013-10-03 19:53:22 +08:00
|
|
|
reprec.assertoutcome(passed=1, failed=0)
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_keyword_matching_is_case_insensitive_by_default(
|
|
|
|
self, pytester: Pytester
|
|
|
|
) -> None:
|
2019-12-05 20:56:45 +08:00
|
|
|
"""Check that selection via -k EXPRESSION is case-insensitive.
|
|
|
|
|
|
|
|
Since markers are also added to the node keywords, they too can
|
|
|
|
be matched without having to think about case sensitivity.
|
|
|
|
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2019-12-05 20:56:45 +08:00
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
def test_sPeCiFiCToPiC_1():
|
|
|
|
assert True
|
|
|
|
|
|
|
|
class TestSpecificTopic_2:
|
|
|
|
def test(self):
|
|
|
|
assert True
|
|
|
|
|
|
|
|
@pytest.mark.sPeCiFiCToPic_3
|
|
|
|
def test():
|
|
|
|
assert True
|
|
|
|
|
|
|
|
@pytest.mark.sPeCiFiCToPic_4
|
|
|
|
class Test:
|
|
|
|
def test(self):
|
|
|
|
assert True
|
|
|
|
|
2019-12-06 00:02:18 +08:00
|
|
|
def test_failing_5():
|
|
|
|
assert False, "This should not match"
|
|
|
|
|
2019-12-05 20:56:45 +08:00
|
|
|
"""
|
|
|
|
)
|
2019-12-06 00:02:18 +08:00
|
|
|
num_matching_tests = 4
|
2019-12-05 20:56:45 +08:00
|
|
|
for expression in ("specifictopic", "SPECIFICTOPIC", "SpecificTopic"):
|
2020-11-07 22:56:00 +08:00
|
|
|
reprec = pytester.inline_run("-k " + expression)
|
2019-12-06 00:02:18 +08:00
|
|
|
reprec.assertoutcome(passed=num_matching_tests, failed=0)
|
2019-12-05 20:56:45 +08:00
|
|
|
|
2021-10-06 15:53:34 +08:00
|
|
|
def test_duplicates_handled_correctly(self, pytester: Pytester) -> None:
|
|
|
|
item = pytester.getitem(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.kw
|
|
|
|
class TestClass:
|
|
|
|
pytestmark = pytest.mark.kw
|
|
|
|
def test_method(self): pass
|
|
|
|
test_method.kw = 'method'
|
|
|
|
""",
|
|
|
|
"test_method",
|
|
|
|
)
|
|
|
|
assert item.parent is not None and item.parent.parent is not None
|
|
|
|
item.parent.parent.keywords["kw"] = "class"
|
|
|
|
|
|
|
|
assert item.keywords["kw"] == "method"
|
|
|
|
assert len(item.keywords) == len(set(item.keywords))
|
|
|
|
|
2021-10-07 05:37:19 +08:00
|
|
|
def test_unpacked_marks_added_to_keywords(self, pytester: Pytester) -> None:
|
|
|
|
item = pytester.getitem(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.foo
|
|
|
|
class TestClass:
|
|
|
|
pytestmark = pytest.mark.bar
|
|
|
|
def test_method(self): pass
|
|
|
|
test_method.pytestmark = pytest.mark.baz
|
|
|
|
""",
|
|
|
|
"test_method",
|
|
|
|
)
|
|
|
|
assert isinstance(item, pytest.Function)
|
|
|
|
cls = item.getparent(pytest.Class)
|
|
|
|
assert cls is not None
|
|
|
|
mod = item.getparent(pytest.Module)
|
|
|
|
assert mod is not None
|
|
|
|
|
|
|
|
assert item.keywords["foo"] == pytest.mark.foo.mark
|
|
|
|
assert item.keywords["bar"] == pytest.mark.bar.mark
|
|
|
|
assert item.keywords["baz"] == pytest.mark.baz.mark
|
|
|
|
|
|
|
|
assert cls.keywords["foo"] == pytest.mark.foo.mark
|
|
|
|
assert cls.keywords["bar"] == pytest.mark.bar.mark
|
|
|
|
assert "baz" not in cls.keywords
|
|
|
|
|
|
|
|
assert mod.keywords["foo"] == pytest.mark.foo.mark
|
|
|
|
assert "bar" not in mod.keywords
|
|
|
|
assert "baz" not in mod.keywords
|
|
|
|
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2023-06-02 21:03:39 +08:00
|
|
|
class TestCollectDirectoryHook:
|
|
|
|
def test_custom_directory_example(self, pytester: Pytester) -> None:
|
|
|
|
"""Verify the example from the customdirectory.rst doc."""
|
|
|
|
pytester.copy_example("customdirectory")
|
|
|
|
|
|
|
|
reprec = pytester.inline_run()
|
|
|
|
|
|
|
|
reprec.assertoutcome(passed=2, failed=0)
|
|
|
|
calls = reprec.getcalls("pytest_collect_directory")
|
|
|
|
assert len(calls) == 2
|
|
|
|
assert calls[0].path == pytester.path
|
|
|
|
assert isinstance(calls[0].parent, pytest.Session)
|
|
|
|
assert calls[1].path == pytester.path / "tests"
|
|
|
|
assert isinstance(calls[1].parent, pytest.Dir)
|
|
|
|
|
|
|
|
def test_directory_ignored_if_none(self, pytester: Pytester) -> None:
|
|
|
|
"""If the (entire) hook returns None, it's OK, the directory is ignored."""
|
|
|
|
pytester.makeconftest(
|
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
@pytest.hookimpl(wrapper=True)
|
|
|
|
def pytest_collect_directory():
|
|
|
|
yield
|
|
|
|
return None
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
pytester.makepyfile(
|
|
|
|
**{
|
|
|
|
"tests/test_it.py": """
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
def test_it(): pass
|
|
|
|
""",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
reprec = pytester.inline_run()
|
|
|
|
reprec.assertoutcome(passed=0, failed=0)
|
|
|
|
|
|
|
|
|
2016-06-20 21:05:50 +08:00
|
|
|
COLLECTION_ERROR_PY_FILES = dict(
|
|
|
|
test_01_failure="""
|
|
|
|
def test_1():
|
|
|
|
assert False
|
|
|
|
""",
|
|
|
|
test_02_import_error="""
|
|
|
|
import asdfasdfasdf
|
|
|
|
def test_2():
|
|
|
|
assert True
|
|
|
|
""",
|
|
|
|
test_03_import_error="""
|
|
|
|
import asdfasdfasdf
|
|
|
|
def test_3():
|
|
|
|
assert True
|
|
|
|
""",
|
|
|
|
test_04_success="""
|
|
|
|
def test_4():
|
|
|
|
assert True
|
|
|
|
""",
|
|
|
|
)
|
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_exit_on_collection_error(pytester: Pytester) -> None:
|
2016-06-20 21:05:50 +08:00
|
|
|
"""Verify that all collection errors are collected and no tests executed"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(**COLLECTION_ERROR_PY_FILES)
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest()
|
2016-06-20 21:05:50 +08:00
|
|
|
assert res.ret == 2
|
|
|
|
|
|
|
|
res.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"collected 2 items / 2 errors",
|
|
|
|
"*ERROR collecting test_02_import_error.py*",
|
|
|
|
"*No module named *asdfa*",
|
|
|
|
"*ERROR collecting test_03_import_error.py*",
|
|
|
|
"*No module named *asdfa*",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_exit_on_collection_with_maxfail_smaller_than_n_errors(
|
|
|
|
pytester: Pytester,
|
|
|
|
) -> None:
|
2016-06-20 21:05:50 +08:00
|
|
|
"""
|
|
|
|
Verify collection is aborted once maxfail errors are encountered ignoring
|
|
|
|
further modules which would cause more collection errors.
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(**COLLECTION_ERROR_PY_FILES)
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest("--maxfail=1")
|
2017-10-17 23:53:47 +08:00
|
|
|
assert res.ret == 1
|
2016-06-20 21:05:50 +08:00
|
|
|
res.stdout.fnmatch_lines(
|
2019-11-13 22:55:11 +08:00
|
|
|
[
|
|
|
|
"collected 1 item / 1 error",
|
|
|
|
"*ERROR collecting test_02_import_error.py*",
|
|
|
|
"*No module named *asdfa*",
|
|
|
|
"*! stopping after 1 failures !*",
|
|
|
|
"*= 1 error in *",
|
|
|
|
]
|
2016-06-20 21:05:50 +08:00
|
|
|
)
|
2019-10-06 01:18:51 +08:00
|
|
|
res.stdout.no_fnmatch_line("*test_03*")
|
2016-06-20 21:05:50 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_exit_on_collection_with_maxfail_bigger_than_n_errors(
|
|
|
|
pytester: Pytester,
|
|
|
|
) -> None:
|
2016-06-20 21:05:50 +08:00
|
|
|
"""
|
|
|
|
Verify the test run aborts due to collection errors even if maxfail count of
|
|
|
|
errors was not reached.
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(**COLLECTION_ERROR_PY_FILES)
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest("--maxfail=4")
|
2016-06-20 21:05:50 +08:00
|
|
|
assert res.ret == 2
|
|
|
|
res.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"collected 2 items / 2 errors",
|
|
|
|
"*ERROR collecting test_02_import_error.py*",
|
|
|
|
"*No module named *asdfa*",
|
|
|
|
"*ERROR collecting test_03_import_error.py*",
|
|
|
|
"*No module named *asdfa*",
|
2019-11-13 22:55:11 +08:00
|
|
|
"*! Interrupted: 2 errors during collection !*",
|
|
|
|
"*= 2 errors in *",
|
2016-06-20 21:05:50 +08:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_continue_on_collection_errors(pytester: Pytester) -> None:
|
2016-06-20 21:05:50 +08:00
|
|
|
"""
|
|
|
|
Verify tests are executed even when collection errors occur when the
|
|
|
|
--continue-on-collection-errors flag is set
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(**COLLECTION_ERROR_PY_FILES)
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest("--continue-on-collection-errors")
|
2016-06-20 21:05:50 +08:00
|
|
|
assert res.ret == 1
|
|
|
|
|
|
|
|
res.stdout.fnmatch_lines(
|
2019-10-27 23:02:37 +08:00
|
|
|
["collected 2 items / 2 errors", "*1 failed, 1 passed, 2 errors*"]
|
2016-06-20 21:05:50 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_continue_on_collection_errors_maxfail(pytester: Pytester) -> None:
|
2016-06-20 21:05:50 +08:00
|
|
|
"""
|
|
|
|
Verify tests are executed even when collection errors occur and that maxfail
|
|
|
|
is honoured (including the collection error count).
|
|
|
|
4 tests: 2 collection errors + 1 failure + 1 success
|
|
|
|
test_4 is never executed because the test run is with --maxfail=3 which
|
|
|
|
means it is interrupted after the 2 collection errors + 1 failure.
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(**COLLECTION_ERROR_PY_FILES)
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest("--continue-on-collection-errors", "--maxfail=3")
|
2017-10-17 23:53:47 +08:00
|
|
|
assert res.ret == 1
|
2016-06-20 21:05:50 +08:00
|
|
|
|
2019-10-27 23:02:37 +08:00
|
|
|
res.stdout.fnmatch_lines(["collected 2 items / 2 errors", "*1 failed, 2 errors*"])
|
2017-10-23 19:28:54 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_fixture_scope_sibling_conftests(pytester: Pytester) -> None:
|
2017-10-23 19:28:54 +08:00
|
|
|
"""Regression test case for https://github.com/pytest-dev/pytest/issues/2836"""
|
2020-11-07 22:56:00 +08:00
|
|
|
foo_path = pytester.mkdir("foo")
|
|
|
|
foo_path.joinpath("conftest.py").write_text(
|
2018-08-24 00:06:17 +08:00
|
|
|
textwrap.dedent(
|
|
|
|
"""\
|
|
|
|
import pytest
|
|
|
|
@pytest.fixture
|
|
|
|
def fix():
|
|
|
|
return 1
|
2017-10-23 19:28:54 +08:00
|
|
|
"""
|
2023-06-20 19:55:39 +08:00
|
|
|
),
|
|
|
|
encoding="utf-8",
|
|
|
|
)
|
|
|
|
foo_path.joinpath("test_foo.py").write_text(
|
|
|
|
"def test_foo(fix): assert fix == 1", encoding="utf-8"
|
2018-05-23 22:48:46 +08:00
|
|
|
)
|
2017-10-23 19:28:54 +08:00
|
|
|
|
|
|
|
# Tests in `food/` should not see the conftest fixture from `foo/`
|
2020-11-07 22:56:00 +08:00
|
|
|
food_path = pytester.mkpydir("food")
|
2023-06-20 19:55:39 +08:00
|
|
|
food_path.joinpath("test_food.py").write_text(
|
|
|
|
"def test_food(fix): assert fix == 1", encoding="utf-8"
|
|
|
|
)
|
2017-10-23 19:28:54 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
res = pytester.runpytest()
|
2017-10-23 19:28:54 +08:00
|
|
|
assert res.ret == 1
|
|
|
|
|
|
|
|
res.stdout.fnmatch_lines(
|
|
|
|
[
|
2017-10-23 19:40:32 +08:00
|
|
|
"*ERROR at setup of test_food*",
|
|
|
|
"E*fixture 'fix' not found",
|
2017-10-23 19:28:54 +08:00
|
|
|
"*1 passed, 1 error*",
|
|
|
|
]
|
|
|
|
)
|
2018-08-25 22:09:43 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_init_tests(pytester: Pytester) -> None:
|
2018-08-25 22:09:43 +08:00
|
|
|
"""Check that we collect files from __init__.py files when they patch the 'python_files' (#3773)"""
|
2020-11-07 22:56:00 +08:00
|
|
|
p = pytester.copy_example("collect/collect_init_tests")
|
|
|
|
result = pytester.runpytest(p, "--collect-only")
|
2018-08-25 22:09:43 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
2018-11-07 01:47:19 +08:00
|
|
|
"collected 2 items",
|
2023-06-02 21:03:39 +08:00
|
|
|
"<Dir *>",
|
|
|
|
" <Package tests>",
|
|
|
|
" <Module __init__.py>",
|
|
|
|
" <Function test_init>",
|
|
|
|
" <Module test_foo.py>",
|
|
|
|
" <Function test_foo>",
|
2018-08-25 22:09:43 +08:00
|
|
|
]
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("./tests", "--collect-only")
|
2018-11-01 02:18:12 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
2018-11-07 01:47:19 +08:00
|
|
|
"collected 2 items",
|
2023-06-02 21:03:39 +08:00
|
|
|
"<Dir *>",
|
|
|
|
" <Package tests>",
|
|
|
|
" <Module __init__.py>",
|
|
|
|
" <Function test_init>",
|
|
|
|
" <Module test_foo.py>",
|
|
|
|
" <Function test_foo>",
|
2018-11-07 01:47:19 +08:00
|
|
|
]
|
|
|
|
)
|
|
|
|
# Ignores duplicates with "." and pkginit (#4310).
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("./tests", ".", "--collect-only")
|
2018-11-07 01:47:19 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"collected 2 items",
|
2023-06-02 21:03:39 +08:00
|
|
|
"<Dir *>",
|
|
|
|
" <Package tests>",
|
|
|
|
" <Module __init__.py>",
|
|
|
|
" <Function test_init>",
|
|
|
|
" <Module test_foo.py>",
|
|
|
|
" <Function test_foo>",
|
2018-11-07 18:01:39 +08:00
|
|
|
]
|
|
|
|
)
|
2018-11-08 02:29:55 +08:00
|
|
|
# Same as before, but different order.
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest(".", "tests", "--collect-only")
|
2018-11-07 18:01:39 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"collected 2 items",
|
2023-06-02 21:03:39 +08:00
|
|
|
"<Dir *>",
|
|
|
|
" <Package tests>",
|
|
|
|
" <Module __init__.py>",
|
|
|
|
" <Function test_init>",
|
|
|
|
" <Module test_foo.py>",
|
|
|
|
" <Function test_foo>",
|
2018-11-01 02:18:12 +08:00
|
|
|
]
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("./tests/test_foo.py", "--collect-only")
|
2018-11-08 02:29:55 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
2023-06-02 21:03:39 +08:00
|
|
|
[
|
|
|
|
"<Dir *>",
|
|
|
|
" <Package tests>",
|
|
|
|
" <Module test_foo.py>",
|
|
|
|
" <Function test_foo>",
|
|
|
|
]
|
2018-11-08 02:29:55 +08:00
|
|
|
)
|
2019-10-06 01:18:51 +08:00
|
|
|
result.stdout.no_fnmatch_line("*test_init*")
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("./tests/__init__.py", "--collect-only")
|
2018-11-08 02:29:55 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
2023-06-02 21:03:39 +08:00
|
|
|
[
|
|
|
|
"<Dir *>",
|
|
|
|
" <Package tests>",
|
|
|
|
" <Module __init__.py>",
|
|
|
|
" <Function test_init>",
|
|
|
|
]
|
2018-11-08 02:29:55 +08:00
|
|
|
)
|
2019-10-06 01:18:51 +08:00
|
|
|
result.stdout.no_fnmatch_line("*test_foo*")
|
2018-10-19 07:05:41 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_invalid_signature_message(pytester: Pytester) -> None:
|
2018-10-19 07:05:41 +08:00
|
|
|
"""Check that we issue a proper message when we can't determine the signature of a test
|
|
|
|
function (#4026).
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2018-10-19 07:05:41 +08:00
|
|
|
"""
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
class TestCase:
|
|
|
|
@pytest.fixture
|
|
|
|
def fix():
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2018-10-19 07:05:41 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
["Could not determine arguments of *.fix *: invalid method signature"]
|
|
|
|
)
|
2018-11-01 07:54:48 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_handles_raising_on_dunder_class(pytester: Pytester) -> None:
|
2018-11-01 07:54:48 +08:00
|
|
|
"""Handle proxy classes like Django's LazySettings that might raise on
|
|
|
|
``isinstance`` (#4266).
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2018-11-01 07:54:48 +08:00
|
|
|
"""
|
|
|
|
class ImproperlyConfigured(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class RaisesOnGetAttr(object):
|
|
|
|
def raises(self):
|
|
|
|
raise ImproperlyConfigured
|
|
|
|
|
|
|
|
__class__ = property(raises)
|
|
|
|
|
|
|
|
raises = RaisesOnGetAttr()
|
|
|
|
|
|
|
|
|
|
|
|
def test_1():
|
|
|
|
pass
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2018-11-06 06:17:04 +08:00
|
|
|
result.stdout.fnmatch_lines(["*1 passed in*"])
|
2018-11-01 07:54:48 +08:00
|
|
|
assert result.ret == 0
|
2018-11-06 06:17:04 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
|
|
|
|
subdir = pytester.mkdir("sub")
|
|
|
|
pytester.path.joinpath("conftest.py").write_text(
|
2018-11-06 06:17:04 +08:00
|
|
|
textwrap.dedent(
|
|
|
|
f"""
|
|
|
|
import os
|
|
|
|
os.chdir({str(subdir)!r})
|
|
|
|
"""
|
2023-06-20 19:55:39 +08:00
|
|
|
),
|
|
|
|
encoding="utf-8",
|
2018-11-06 06:17:04 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2018-11-06 06:17:04 +08:00
|
|
|
f"""
|
|
|
|
def test_1():
|
|
|
|
import os
|
|
|
|
assert os.getcwd() == {str(subdir)!r}
|
|
|
|
"""
|
|
|
|
)
|
2022-03-21 12:01:26 +08:00
|
|
|
result = pytester.runpytest()
|
2018-11-01 07:54:48 +08:00
|
|
|
result.stdout.fnmatch_lines(["*1 passed in*"])
|
2018-11-06 06:17:04 +08:00
|
|
|
assert result.ret == 0
|
2018-11-08 07:54:51 +08:00
|
|
|
|
|
|
|
# Handles relative testpaths.
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makeini(
|
2018-11-08 07:54:51 +08:00
|
|
|
"""
|
|
|
|
[pytest]
|
|
|
|
testpaths = .
|
|
|
|
"""
|
|
|
|
)
|
2022-03-21 12:01:26 +08:00
|
|
|
result = pytester.runpytest("--collect-only")
|
2018-11-08 07:54:51 +08:00
|
|
|
result.stdout.fnmatch_lines(["collected 1 item"])
|
2018-11-08 08:24:38 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_pyargs_with_testpaths(
|
|
|
|
pytester: Pytester, monkeypatch: MonkeyPatch
|
|
|
|
) -> None:
|
|
|
|
testmod = pytester.mkdir("testmod")
|
2018-11-17 20:28:10 +08:00
|
|
|
# NOTE: __init__.py is not collected since it does not match python_files.
|
2023-06-20 19:55:39 +08:00
|
|
|
testmod.joinpath("__init__.py").write_text(
|
|
|
|
"def test_func(): pass", encoding="utf-8"
|
|
|
|
)
|
|
|
|
testmod.joinpath("test_file.py").write_text(
|
|
|
|
"def test_func(): pass", encoding="utf-8"
|
|
|
|
)
|
2018-11-17 20:28:10 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
root = pytester.mkdir("root")
|
|
|
|
root.joinpath("pytest.ini").write_text(
|
2018-11-17 20:28:10 +08:00
|
|
|
textwrap.dedent(
|
|
|
|
"""
|
|
|
|
[pytest]
|
|
|
|
addopts = --pyargs
|
|
|
|
testpaths = testmod
|
|
|
|
"""
|
2023-06-20 19:55:39 +08:00
|
|
|
),
|
|
|
|
encoding="utf-8",
|
2018-11-17 20:28:10 +08:00
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep)
|
2022-03-21 23:38:20 +08:00
|
|
|
with monkeypatch.context() as mp:
|
|
|
|
mp.chdir(root)
|
|
|
|
result = pytester.runpytest_subprocess()
|
2018-11-17 20:28:10 +08:00
|
|
|
result.stdout.fnmatch_lines(["*1 passed in*"])
|
|
|
|
|
|
|
|
|
2023-05-11 20:22:17 +08:00
|
|
|
def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
|
|
|
|
"""The testpaths ini option should load conftests in those paths as 'initial' (#10987)."""
|
|
|
|
p = pytester.mkdir("some_path")
|
|
|
|
p.joinpath("conftest.py").write_text(
|
|
|
|
textwrap.dedent(
|
|
|
|
"""
|
|
|
|
def pytest_sessionstart(session):
|
2023-05-12 20:27:24 +08:00
|
|
|
raise Exception("pytest_sessionstart hook successfully run")
|
2023-05-11 20:22:17 +08:00
|
|
|
"""
|
2023-06-20 19:55:40 +08:00
|
|
|
),
|
|
|
|
encoding="utf-8",
|
2023-05-11 20:22:17 +08:00
|
|
|
)
|
|
|
|
pytester.makeini(
|
|
|
|
"""
|
|
|
|
[pytest]
|
|
|
|
testpaths = some_path
|
|
|
|
"""
|
|
|
|
)
|
2023-06-21 01:59:22 +08:00
|
|
|
|
|
|
|
# No command line args - falls back to testpaths.
|
2023-05-11 20:22:17 +08:00
|
|
|
result = pytester.runpytest()
|
2023-06-21 01:59:22 +08:00
|
|
|
assert result.ret == ExitCode.INTERNAL_ERROR
|
2023-05-11 20:22:17 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
2023-05-12 20:27:24 +08:00
|
|
|
"INTERNALERROR* Exception: pytest_sessionstart hook successfully run"
|
2023-05-11 20:22:17 +08:00
|
|
|
)
|
|
|
|
|
2023-06-21 01:59:22 +08:00
|
|
|
# No fallback.
|
|
|
|
result = pytester.runpytest(".")
|
|
|
|
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
|
|
|
|
2023-05-11 20:22:17 +08:00
|
|
|
|
2023-05-12 20:27:24 +08:00
|
|
|
def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None:
|
|
|
|
"""Long option values do not break initial conftests handling (#10169)."""
|
|
|
|
option_value = "x" * 1024 * 1000
|
|
|
|
pytester.makeconftest(
|
|
|
|
"""
|
|
|
|
def pytest_addoption(parser):
|
|
|
|
parser.addoption("--xx", default=None)
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
pytester.makepyfile(
|
|
|
|
f"""
|
|
|
|
def test_foo(request):
|
|
|
|
assert request.config.getoption("xx") == {option_value!r}
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
result = pytester.runpytest(f"--xx={option_value}")
|
|
|
|
assert result.ret == 0
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_symlink_file_arg(pytester: Pytester) -> None:
|
2020-06-08 21:56:40 +08:00
|
|
|
"""Collect a direct symlink works even if it does not match python_files (#4325)."""
|
2020-11-07 22:56:00 +08:00
|
|
|
real = pytester.makepyfile(
|
2018-11-08 08:24:38 +08:00
|
|
|
real="""
|
|
|
|
def test_nodeid(request):
|
2020-06-08 21:56:40 +08:00
|
|
|
assert request.node.nodeid == "symlink.py::test_nodeid"
|
2018-11-08 08:24:38 +08:00
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
symlink = pytester.path.joinpath("symlink.py")
|
2020-06-08 21:56:40 +08:00
|
|
|
symlink_or_skip(real, symlink)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("-v", symlink)
|
2020-06-08 21:56:40 +08:00
|
|
|
result.stdout.fnmatch_lines(["symlink.py::test_nodeid PASSED*", "*1 passed in*"])
|
2018-11-08 08:24:38 +08:00
|
|
|
assert result.ret == 0
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_symlink_out_of_tree(pytester: Pytester) -> None:
|
2018-11-08 08:24:38 +08:00
|
|
|
"""Test collection of symlink via out-of-tree rootdir."""
|
2020-11-07 22:56:00 +08:00
|
|
|
sub = pytester.mkdir("sub")
|
|
|
|
real = sub.joinpath("test_real.py")
|
|
|
|
real.write_text(
|
2018-11-08 08:24:38 +08:00
|
|
|
textwrap.dedent(
|
|
|
|
"""
|
|
|
|
def test_nodeid(request):
|
|
|
|
# Should not contain sub/ prefix.
|
|
|
|
assert request.node.nodeid == "test_real.py::test_nodeid"
|
|
|
|
"""
|
|
|
|
),
|
2023-06-20 19:55:39 +08:00
|
|
|
encoding="utf-8",
|
2018-11-08 08:24:38 +08:00
|
|
|
)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
out_of_tree = pytester.mkdir("out_of_tree")
|
|
|
|
symlink_to_sub = out_of_tree.joinpath("symlink_to_sub")
|
2020-06-08 21:56:40 +08:00
|
|
|
symlink_or_skip(sub, symlink_to_sub)
|
2020-11-07 22:56:00 +08:00
|
|
|
os.chdir(sub)
|
|
|
|
result = pytester.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub)
|
2018-11-08 08:24:38 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
# Should not contain "sub/"!
|
|
|
|
"test_real.py::test_nodeid PASSED"
|
|
|
|
]
|
|
|
|
)
|
2018-11-08 07:54:51 +08:00
|
|
|
assert result.ret == 0
|
2019-02-09 01:03:45 +08:00
|
|
|
|
|
|
|
|
2020-10-31 19:55:02 +08:00
|
|
|
def test_collect_symlink_dir(pytester: Pytester) -> None:
|
|
|
|
"""A symlinked directory is collected."""
|
|
|
|
dir = pytester.mkdir("dir")
|
|
|
|
dir.joinpath("test_it.py").write_text("def test_it(): pass", "utf-8")
|
2021-05-04 20:45:10 +08:00
|
|
|
symlink_or_skip(pytester.path.joinpath("symlink_dir"), dir)
|
2020-10-31 19:55:02 +08:00
|
|
|
result = pytester.runpytest()
|
|
|
|
result.assert_outcomes(passed=2)
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collectignore_via_conftest(pytester: Pytester) -> None:
|
2019-02-09 01:03:45 +08:00
|
|
|
"""collect_ignore in parent conftest skips importing child (issue #4592)."""
|
2020-11-07 22:56:00 +08:00
|
|
|
tests = pytester.mkpydir("tests")
|
2023-06-20 19:55:40 +08:00
|
|
|
tests.joinpath("conftest.py").write_text(
|
|
|
|
"collect_ignore = ['ignore_me']", encoding="utf-8"
|
|
|
|
)
|
2019-02-09 01:03:45 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
ignore_me = tests.joinpath("ignore_me")
|
|
|
|
ignore_me.mkdir()
|
|
|
|
ignore_me.joinpath("__init__.py").touch()
|
2023-06-20 19:55:40 +08:00
|
|
|
ignore_me.joinpath("conftest.py").write_text(
|
|
|
|
"assert 0, 'should_not_be_called'", encoding="utf-8"
|
|
|
|
)
|
2019-02-09 01:03:45 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2019-06-07 18:58:51 +08:00
|
|
|
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
2019-02-09 01:43:51 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
|
|
|
|
subdir = pytester.mkdir("sub")
|
|
|
|
init = subdir.joinpath("__init__.py")
|
2023-06-20 19:55:40 +08:00
|
|
|
init.write_text("def test_init(): pass", encoding="utf-8")
|
2020-11-07 22:56:00 +08:00
|
|
|
p = subdir.joinpath("test_file.py")
|
2023-06-20 19:55:40 +08:00
|
|
|
p.write_text("def test_file(): pass", encoding="utf-8")
|
2019-02-09 01:43:51 +08:00
|
|
|
|
2023-05-27 19:42:19 +08:00
|
|
|
# Just the package directory, the __init__.py module is filtered out.
|
|
|
|
result = pytester.runpytest("-v", subdir)
|
2019-02-09 01:43:51 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"sub/test_file.py::test_file PASSED*",
|
2023-05-27 19:42:19 +08:00
|
|
|
"*1 passed in*",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
# But it's included if specified directly.
|
|
|
|
result = pytester.runpytest("-v", init, p)
|
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"sub/__init__.py::test_init PASSED*",
|
2019-02-09 01:43:51 +08:00
|
|
|
"sub/test_file.py::test_file PASSED*",
|
|
|
|
"*2 passed in*",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2023-05-27 19:42:19 +08:00
|
|
|
# Or if the pattern allows it.
|
|
|
|
result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
|
2019-02-09 01:43:51 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"sub/__init__.py::test_init PASSED*",
|
|
|
|
"sub/test_file.py::test_file PASSED*",
|
|
|
|
"*2 passed in*",
|
|
|
|
]
|
|
|
|
)
|
2019-02-14 00:32:04 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_pkg_init_only(pytester: Pytester) -> None:
|
|
|
|
subdir = pytester.mkdir("sub")
|
|
|
|
init = subdir.joinpath("__init__.py")
|
2023-06-20 19:55:40 +08:00
|
|
|
init.write_text("def test_init(): pass", encoding="utf-8")
|
2019-08-05 23:51:51 +08:00
|
|
|
|
2023-05-27 19:42:19 +08:00
|
|
|
result = pytester.runpytest(subdir)
|
2019-08-05 23:51:51 +08:00
|
|
|
result.stdout.fnmatch_lines(["*no tests ran in*"])
|
|
|
|
|
2023-05-27 19:42:19 +08:00
|
|
|
result = pytester.runpytest("-v", init)
|
|
|
|
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
|
|
|
|
|
|
|
|
result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
|
2019-08-05 23:51:51 +08:00
|
|
|
result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
|
|
|
|
|
|
|
|
|
2019-02-14 00:32:04 +08:00
|
|
|
@pytest.mark.parametrize("use_pkg", (True, False))
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_sub_with_symlinks(use_pkg: bool, pytester: Pytester) -> None:
|
2020-06-08 21:56:40 +08:00
|
|
|
"""Collection works with symlinked files and broken symlinks"""
|
2020-11-07 22:56:00 +08:00
|
|
|
sub = pytester.mkdir("sub")
|
2019-02-14 00:32:04 +08:00
|
|
|
if use_pkg:
|
2020-11-07 22:56:00 +08:00
|
|
|
sub.joinpath("__init__.py").touch()
|
2023-06-20 19:55:40 +08:00
|
|
|
sub.joinpath("test_file.py").write_text("def test_file(): pass", encoding="utf-8")
|
2019-02-14 00:32:04 +08:00
|
|
|
|
|
|
|
# Create a broken symlink.
|
2020-11-07 22:56:00 +08:00
|
|
|
symlink_or_skip("test_doesnotexist.py", sub.joinpath("test_broken.py"))
|
2019-02-14 00:32:04 +08:00
|
|
|
|
|
|
|
# Symlink that gets collected.
|
2020-11-07 22:56:00 +08:00
|
|
|
symlink_or_skip("test_file.py", sub.joinpath("test_symlink.py"))
|
2019-02-14 00:32:04 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("-v", str(sub))
|
2019-02-14 00:32:04 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"sub/test_file.py::test_file PASSED*",
|
|
|
|
"sub/test_symlink.py::test_file PASSED*",
|
|
|
|
"*2 passed in*",
|
|
|
|
]
|
|
|
|
)
|
2019-03-23 22:18:22 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collector_respects_tbstyle(pytester: Pytester) -> None:
|
|
|
|
p1 = pytester.makepyfile("assert 0")
|
|
|
|
result = pytester.runpytest(p1, "--tb=native")
|
2019-06-07 18:58:51 +08:00
|
|
|
assert result.ret == ExitCode.INTERRUPTED
|
2019-03-23 22:18:22 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"*_ ERROR collecting test_collector_respects_tbstyle.py _*",
|
|
|
|
"Traceback (most recent call last):",
|
|
|
|
' File "*/test_collector_respects_tbstyle.py", line 1, in <module>',
|
|
|
|
" assert 0",
|
|
|
|
"AssertionError: assert 0",
|
2019-10-27 23:02:37 +08:00
|
|
|
"*! Interrupted: 1 error during collection !*",
|
2019-03-23 22:18:22 +08:00
|
|
|
"*= 1 error in *",
|
|
|
|
]
|
|
|
|
)
|
2019-11-16 00:31:53 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_does_not_eagerly_collect_packages(pytester: Pytester) -> None:
|
|
|
|
pytester.makepyfile("def test(): pass")
|
|
|
|
pydir = pytester.mkpydir("foopkg")
|
2023-06-20 19:55:40 +08:00
|
|
|
pydir.joinpath("__init__.py").write_text("assert False", encoding="utf-8")
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2019-11-16 00:31:53 +08:00
|
|
|
assert result.ret == ExitCode.OK
|
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_does_not_put_src_on_path(pytester: Pytester) -> None:
|
2019-11-16 00:31:53 +08:00
|
|
|
# `src` is not on sys.path so it should not be importable
|
2020-11-07 22:56:00 +08:00
|
|
|
ensure_file(pytester.path / "src/nope/__init__.py")
|
|
|
|
pytester.makepyfile(
|
2019-11-16 00:31:53 +08:00
|
|
|
"import pytest\n"
|
|
|
|
"def test():\n"
|
|
|
|
" with pytest.raises(ImportError):\n"
|
|
|
|
" import nope\n"
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2019-11-16 00:31:53 +08:00
|
|
|
assert result.ret == ExitCode.OK
|
2020-05-01 21:58:47 +08:00
|
|
|
|
|
|
|
|
2020-12-16 12:16:05 +08:00
|
|
|
def test_fscollector_from_parent(pytester: Pytester, request: FixtureRequest) -> None:
|
2020-05-01 21:58:47 +08:00
|
|
|
"""Ensure File.from_parent can forward custom arguments to the constructor.
|
|
|
|
|
|
|
|
Context: https://github.com/pytest-dev/pytest-cpp/pull/47
|
|
|
|
"""
|
|
|
|
|
|
|
|
class MyCollector(pytest.File):
|
2021-01-18 04:20:29 +08:00
|
|
|
def __init__(self, *k, x, **kw):
|
|
|
|
super().__init__(*k, **kw)
|
2020-05-01 21:58:47 +08:00
|
|
|
self.x = x
|
|
|
|
|
2023-12-07 00:27:17 +08:00
|
|
|
def collect(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2020-05-01 21:58:47 +08:00
|
|
|
collector = MyCollector.from_parent(
|
2021-03-14 03:22:54 +08:00
|
|
|
parent=request.session, path=pytester.path / "foo", x=10
|
2020-05-01 21:58:47 +08:00
|
|
|
)
|
|
|
|
assert collector.x == 10
|
2020-06-13 22:29:01 +08:00
|
|
|
|
|
|
|
|
2024-02-23 15:35:57 +08:00
|
|
|
def test_class_from_parent(request: FixtureRequest) -> None:
|
2021-02-24 00:56:42 +08:00
|
|
|
"""Ensure Class.from_parent can forward custom arguments to the constructor."""
|
|
|
|
|
|
|
|
class MyCollector(pytest.Class):
|
|
|
|
def __init__(self, name, parent, x):
|
|
|
|
super().__init__(name, parent)
|
|
|
|
self.x = x
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_parent(cls, parent, *, name, x):
|
|
|
|
return super().from_parent(parent=parent, name=name, x=x)
|
|
|
|
|
2021-02-24 01:02:45 +08:00
|
|
|
collector = MyCollector.from_parent(parent=request.session, name="foo", x=10)
|
2021-02-24 00:56:42 +08:00
|
|
|
assert collector.x == 10
|
|
|
|
|
|
|
|
|
2020-06-13 22:29:01 +08:00
|
|
|
class TestImportModeImportlib:
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_collect_duplicate_names(self, pytester: Pytester) -> None:
|
2020-06-13 22:29:01 +08:00
|
|
|
"""--import-mode=importlib can import modules with same names that are not in packages."""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2020-06-13 22:29:01 +08:00
|
|
|
**{
|
|
|
|
"tests_a/test_foo.py": "def test_foo1(): pass",
|
|
|
|
"tests_b/test_foo.py": "def test_foo2(): pass",
|
|
|
|
}
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("-v", "--import-mode=importlib")
|
2020-06-13 22:29:01 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"tests_a/test_foo.py::test_foo1 *",
|
|
|
|
"tests_b/test_foo.py::test_foo2 *",
|
|
|
|
"* 2 passed in *",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_conftest(self, pytester: Pytester) -> None:
|
2020-06-13 22:29:01 +08:00
|
|
|
"""Directory containing conftest modules are not put in sys.path as a side-effect of
|
|
|
|
importing them."""
|
2020-11-07 22:56:00 +08:00
|
|
|
tests_dir = pytester.path.joinpath("tests")
|
|
|
|
pytester.makepyfile(
|
2020-06-13 22:29:01 +08:00
|
|
|
**{
|
|
|
|
"tests/conftest.py": "",
|
|
|
|
"tests/test_foo.py": f"""
|
|
|
|
import sys
|
|
|
|
def test_check():
|
|
|
|
assert r"{tests_dir}" not in sys.path
|
|
|
|
""",
|
|
|
|
}
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest("-v", "--import-mode=importlib")
|
2020-06-13 22:29:01 +08:00
|
|
|
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def setup_conftest_and_foo(self, pytester: Pytester) -> None:
|
2020-06-13 22:29:01 +08:00
|
|
|
"""Setup a tests folder to be used to test if modules in that folder can be imported
|
|
|
|
due to side-effects of --import-mode or not."""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
2020-06-13 22:29:01 +08:00
|
|
|
**{
|
|
|
|
"tests/conftest.py": "",
|
|
|
|
"tests/foo.py": """
|
|
|
|
def foo(): return 42
|
|
|
|
""",
|
|
|
|
"tests/test_foo.py": """
|
|
|
|
def test_check():
|
|
|
|
from foo import foo
|
|
|
|
assert foo() == 42
|
|
|
|
""",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_modules_importable_as_side_effect(self, pytester: Pytester) -> None:
|
2020-06-13 22:29:01 +08:00
|
|
|
"""In import-modes `prepend` and `append`, we are able to import modules from folders
|
|
|
|
containing conftest.py files due to the side effect of changing sys.path."""
|
2020-11-07 22:56:00 +08:00
|
|
|
self.setup_conftest_and_foo(pytester)
|
|
|
|
result = pytester.runpytest("-v", "--import-mode=prepend")
|
2020-06-13 22:29:01 +08:00
|
|
|
result.stdout.fnmatch_lines(["* 1 passed in *"])
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_modules_not_importable_as_side_effect(self, pytester: Pytester) -> None:
|
2020-06-13 22:29:01 +08:00
|
|
|
"""In import-mode `importlib`, modules in folders containing conftest.py are not
|
|
|
|
importable, as don't change sys.path or sys.modules as side effect of importing
|
|
|
|
the conftest.py file.
|
|
|
|
"""
|
2020-11-07 22:56:00 +08:00
|
|
|
self.setup_conftest_and_foo(pytester)
|
|
|
|
result = pytester.runpytest("-v", "--import-mode=importlib")
|
2020-06-13 22:29:01 +08:00
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
2020-10-03 03:06:21 +08:00
|
|
|
"*ModuleNotFoundError: No module named 'foo'",
|
|
|
|
"tests?test_foo.py:2: ModuleNotFoundError",
|
2020-06-13 22:29:01 +08:00
|
|
|
"* 1 failed in *",
|
|
|
|
]
|
|
|
|
)
|
assertion/rewrite: fix internal error on collection error due to decorated function
For decorated functions, the lineno of the FunctionDef AST node points
to the `def` line, not to the first decorator line. On the other hand,
in code objects, the `co_firstlineno` points to the first decorator
line.
Assertion rewriting inserts some imports to code it rewrites. The
imports are inserted at the lineno of the first statement in the AST. In
turn, the code object compiled from the rewritten AST uses the lineno of
the first statement (which is the first inserted import).
This means that given a module like this,
```py
@foo
@bar
def baz(): pass
```
the lineno of the code object without assertion rewriting
(`--assertion=plain`) is 1, but with assertion rewriting it is 3.
And *this* causes some issues for the exception repr when e.g. the
decorator line is invalid and raises during collection. The code becomes
confused and crashes with
INTERNALERROR> File "_pytest/_code/code.py", line 638, in get_source
INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip())
INTERNALERROR> IndexError: list index out of range
Fix it by special casing decorators. Maybe there are other cases like
this but off hand I can't think of another Python construct where the
lineno of the item would be after its first line, and this is the only
such issue we have had reported.
2020-09-13 03:28:19 +08:00
|
|
|
|
2022-02-13 20:39:57 +08:00
|
|
|
def test_using_python_path(self, pytester: Pytester) -> None:
|
|
|
|
"""
|
|
|
|
Dummy modules created by insert_missing_modules should not get in
|
|
|
|
the way of modules that could be imported via python path (#9645).
|
|
|
|
"""
|
|
|
|
pytester.makeini(
|
|
|
|
"""
|
|
|
|
[pytest]
|
|
|
|
pythonpath = .
|
|
|
|
addopts = --import-mode importlib
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
pytester.makepyfile(
|
|
|
|
**{
|
|
|
|
"tests/__init__.py": "",
|
|
|
|
"tests/conftest.py": "",
|
|
|
|
"tests/subpath/__init__.py": "",
|
|
|
|
"tests/subpath/helper.py": "",
|
|
|
|
"tests/subpath/test_something.py": """
|
|
|
|
import tests.subpath.helper
|
|
|
|
|
|
|
|
def test_something():
|
|
|
|
assert True
|
|
|
|
""",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
result = pytester.runpytest()
|
|
|
|
result.stdout.fnmatch_lines("*1 passed in*")
|
|
|
|
|
assertion/rewrite: fix internal error on collection error due to decorated function
For decorated functions, the lineno of the FunctionDef AST node points
to the `def` line, not to the first decorator line. On the other hand,
in code objects, the `co_firstlineno` points to the first decorator
line.
Assertion rewriting inserts some imports to code it rewrites. The
imports are inserted at the lineno of the first statement in the AST. In
turn, the code object compiled from the rewritten AST uses the lineno of
the first statement (which is the first inserted import).
This means that given a module like this,
```py
@foo
@bar
def baz(): pass
```
the lineno of the code object without assertion rewriting
(`--assertion=plain`) is 1, but with assertion rewriting it is 3.
And *this* causes some issues for the exception repr when e.g. the
decorator line is invalid and raises during collection. The code becomes
confused and crashes with
INTERNALERROR> File "_pytest/_code/code.py", line 638, in get_source
INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip())
INTERNALERROR> IndexError: list index out of range
Fix it by special casing decorators. Maybe there are other cases like
this but off hand I can't think of another Python construct where the
lineno of the item would be after its first line, and this is the only
such issue we have had reported.
2020-09-13 03:28:19 +08:00
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_does_not_crash_on_error_from_decorated_function(pytester: Pytester) -> None:
|
assertion/rewrite: fix internal error on collection error due to decorated function
For decorated functions, the lineno of the FunctionDef AST node points
to the `def` line, not to the first decorator line. On the other hand,
in code objects, the `co_firstlineno` points to the first decorator
line.
Assertion rewriting inserts some imports to code it rewrites. The
imports are inserted at the lineno of the first statement in the AST. In
turn, the code object compiled from the rewritten AST uses the lineno of
the first statement (which is the first inserted import).
This means that given a module like this,
```py
@foo
@bar
def baz(): pass
```
the lineno of the code object without assertion rewriting
(`--assertion=plain`) is 1, but with assertion rewriting it is 3.
And *this* causes some issues for the exception repr when e.g. the
decorator line is invalid and raises during collection. The code becomes
confused and crashes with
INTERNALERROR> File "_pytest/_code/code.py", line 638, in get_source
INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip())
INTERNALERROR> IndexError: list index out of range
Fix it by special casing decorators. Maybe there are other cases like
this but off hand I can't think of another Python construct where the
lineno of the item would be after its first line, and this is the only
such issue we have had reported.
2020-09-13 03:28:19 +08:00
|
|
|
"""Regression test for an issue around bad exception formatting due to
|
|
|
|
assertion rewriting mangling lineno's (#4984)."""
|
2020-11-07 22:56:00 +08:00
|
|
|
pytester.makepyfile(
|
assertion/rewrite: fix internal error on collection error due to decorated function
For decorated functions, the lineno of the FunctionDef AST node points
to the `def` line, not to the first decorator line. On the other hand,
in code objects, the `co_firstlineno` points to the first decorator
line.
Assertion rewriting inserts some imports to code it rewrites. The
imports are inserted at the lineno of the first statement in the AST. In
turn, the code object compiled from the rewritten AST uses the lineno of
the first statement (which is the first inserted import).
This means that given a module like this,
```py
@foo
@bar
def baz(): pass
```
the lineno of the code object without assertion rewriting
(`--assertion=plain`) is 1, but with assertion rewriting it is 3.
And *this* causes some issues for the exception repr when e.g. the
decorator line is invalid and raises during collection. The code becomes
confused and crashes with
INTERNALERROR> File "_pytest/_code/code.py", line 638, in get_source
INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip())
INTERNALERROR> IndexError: list index out of range
Fix it by special casing decorators. Maybe there are other cases like
this but off hand I can't think of another Python construct where the
lineno of the item would be after its first line, and this is the only
such issue we have had reported.
2020-09-13 03:28:19 +08:00
|
|
|
"""
|
|
|
|
@pytest.fixture
|
|
|
|
def a(): return 4
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
assertion/rewrite: fix internal error on collection error due to decorated function
For decorated functions, the lineno of the FunctionDef AST node points
to the `def` line, not to the first decorator line. On the other hand,
in code objects, the `co_firstlineno` points to the first decorator
line.
Assertion rewriting inserts some imports to code it rewrites. The
imports are inserted at the lineno of the first statement in the AST. In
turn, the code object compiled from the rewritten AST uses the lineno of
the first statement (which is the first inserted import).
This means that given a module like this,
```py
@foo
@bar
def baz(): pass
```
the lineno of the code object without assertion rewriting
(`--assertion=plain`) is 1, but with assertion rewriting it is 3.
And *this* causes some issues for the exception repr when e.g. the
decorator line is invalid and raises during collection. The code becomes
confused and crashes with
INTERNALERROR> File "_pytest/_code/code.py", line 638, in get_source
INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip())
INTERNALERROR> IndexError: list index out of range
Fix it by special casing decorators. Maybe there are other cases like
this but off hand I can't think of another Python construct where the
lineno of the item would be after its first line, and this is the only
such issue we have had reported.
2020-09-13 03:28:19 +08:00
|
|
|
# Not INTERNAL_ERROR
|
|
|
|
assert result.ret == ExitCode.INTERRUPTED
|
2020-10-28 15:27:43 +08:00
|
|
|
|
|
|
|
|
2020-11-07 22:56:00 +08:00
|
|
|
def test_does_not_crash_on_recursive_symlink(pytester: Pytester) -> None:
|
2020-10-28 15:27:43 +08:00
|
|
|
"""Regression test for an issue around recursive symlinks (#7951)."""
|
2020-11-07 22:56:00 +08:00
|
|
|
symlink_or_skip("recursive", pytester.path.joinpath("recursive"))
|
|
|
|
pytester.makepyfile(
|
2020-10-28 15:27:43 +08:00
|
|
|
"""
|
|
|
|
def test_foo(): assert True
|
|
|
|
"""
|
|
|
|
)
|
2020-11-07 22:56:00 +08:00
|
|
|
result = pytester.runpytest()
|
2020-10-28 15:27:43 +08:00
|
|
|
|
|
|
|
assert result.ret == ExitCode.OK
|
|
|
|
assert result.parseoutcomes() == {"passed": 1}
|
2024-02-23 18:51:15 +08:00
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")
|
|
|
|
def test_collect_short_file_windows(pytester: Pytester) -> None:
|
|
|
|
"""Reproducer for #11895: short paths not colleced on Windows."""
|
|
|
|
short_path = tempfile.mkdtemp()
|
|
|
|
if "~" not in short_path: # pragma: no cover
|
|
|
|
if running_on_ci():
|
|
|
|
# On CI, we are expecting that under the current GitHub actions configuration,
|
|
|
|
# tempfile.mkdtemp() is producing short paths, so we want to fail to prevent
|
|
|
|
# this from silently changing without us noticing.
|
|
|
|
pytest.fail(
|
|
|
|
f"tempfile.mkdtemp() failed to produce a short path on CI: {short_path}"
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
# We want to skip failing this test locally in this situation because
|
|
|
|
# depending on the local configuration tempfile.mkdtemp() might not produce a short path:
|
|
|
|
# For example, user might have configured %TEMP% exactly to avoid generating short paths.
|
|
|
|
pytest.skip(
|
|
|
|
f"tempfile.mkdtemp() failed to produce a short path: {short_path}, skipping"
|
|
|
|
)
|
|
|
|
|
|
|
|
test_file = Path(short_path).joinpath("test_collect_short_file_windows.py")
|
|
|
|
test_file.write_text("def test(): pass", encoding="UTF-8")
|
|
|
|
result = pytester.runpytest(short_path)
|
|
|
|
assert result.parseoutcomes() == {"passed": 1}
|
2024-03-01 18:19:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
|
|
|
|
"""When using `--pyargs`, the collection tree of a pyargs collection
|
|
|
|
argument should only include parents in the import path, not up to confcutdir.
|
|
|
|
|
|
|
|
Regression test for #11904.
|
|
|
|
"""
|
|
|
|
site_packages = pytester.path / "venv/lib/site-packages"
|
|
|
|
site_packages.mkdir(parents=True)
|
|
|
|
monkeypatch.syspath_prepend(site_packages)
|
|
|
|
pytester.makepyfile(
|
|
|
|
**{
|
|
|
|
"venv/lib/site-packages/pkg/__init__.py": "",
|
|
|
|
"venv/lib/site-packages/pkg/sub/__init__.py": "",
|
|
|
|
"venv/lib/site-packages/pkg/sub/test_it.py": "def test(): pass",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it")
|
|
|
|
assert result.ret == ExitCode.OK
|
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"<Package venv/lib/site-packages/pkg>",
|
|
|
|
" <Package sub>",
|
|
|
|
" <Module test_it.py>",
|
|
|
|
" <Function test>",
|
|
|
|
],
|
|
|
|
consecutive=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Now with an unrelated rootdir with unrelated files.
|
|
|
|
monkeypatch.chdir(tempfile.gettempdir())
|
|
|
|
|
|
|
|
result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it")
|
|
|
|
assert result.ret == ExitCode.OK
|
|
|
|
result.stdout.fnmatch_lines(
|
|
|
|
[
|
|
|
|
"<Package *pkg>",
|
|
|
|
" <Package sub>",
|
|
|
|
" <Module test_it.py>",
|
|
|
|
" <Function test>",
|
|
|
|
],
|
|
|
|
consecutive=True,
|
|
|
|
)
|