From 8eef8c6004af955f1074905e9656480eeeb3bb67 Mon Sep 17 00:00:00 2001 From: Anton <44246099+antonblr@users.noreply.github.com> Date: Tue, 15 Dec 2020 03:02:32 -0800 Subject: [PATCH] tests: Migrate to pytester - incremental update (#8145) --- testing/code/test_excinfo.py | 57 ++-- testing/examples/test_issue519.py | 9 +- testing/io/test_terminalwriter.py | 5 +- testing/logging/test_fixture.py | 50 +-- testing/logging/test_reporting.py | 275 +++++++-------- testing/test_cacheprovider.py | 533 ++++++++++++++++-------------- testing/test_conftest.py | 21 +- testing/test_monkeypatch.py | 68 ++-- testing/test_pluginmanager.py | 131 +++++--- testing/test_reports.py | 102 +++--- testing/test_runner.py | 253 +++++++------- 11 files changed, 801 insertions(+), 703 deletions(-) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 5b9e3eda5..44d7ab549 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -5,6 +5,7 @@ import os import queue import sys import textwrap +from pathlib import Path from typing import Any from typing import Dict from typing import Tuple @@ -19,7 +20,10 @@ from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo from _pytest._io import TerminalWriter +from _pytest.pathlib import import_path from _pytest.pytester import LineMatcher +from _pytest.pytester import Pytester + if TYPE_CHECKING: from _pytest._code.code import _TracebackStyle @@ -155,10 +159,10 @@ class TestTraceback_f_g_h: newtraceback = traceback.cut(path=path, lineno=firstlineno + 2) assert len(newtraceback) == 1 - def test_traceback_cut_excludepath(self, testdir): - p = testdir.makepyfile("def f(): raise ValueError") + def test_traceback_cut_excludepath(self, pytester: Pytester) -> None: + p = pytester.makepyfile("def f(): raise ValueError") with pytest.raises(ValueError) as excinfo: - p.pyimport().f() + import_path(p).f() # type: ignore[attr-defined] basedir = py.path.local(pytest.__file__).dirpath() newtraceback = excinfo.traceback.cut(excludepath=basedir) for x in newtraceback: @@ -406,8 +410,8 @@ def test_match_succeeds(): excinfo.match(r".*zero.*") -def test_match_raises_error(testdir): - testdir.makepyfile( +def test_match_raises_error(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def test_division_zero(): @@ -416,14 +420,14 @@ def test_match_raises_error(testdir): excinfo.match(r'[123]+') """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret != 0 exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"]) result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") - result = testdir.runpytest("--fulltrace") + result = pytester.runpytest("--fulltrace") assert result.ret != 0 result.stdout.fnmatch_lines( ["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"] @@ -432,15 +436,14 @@ def test_match_raises_error(testdir): class TestFormattedExcinfo: @pytest.fixture - def importasmod(self, request, _sys_snapshot): + def importasmod(self, tmp_path: Path, _sys_snapshot): def importasmod(source): source = textwrap.dedent(source) - tmpdir = request.getfixturevalue("tmpdir") - modpath = tmpdir.join("mod.py") - tmpdir.ensure("__init__.py") - modpath.write(source) + modpath = tmp_path.joinpath("mod.py") + tmp_path.joinpath("__init__.py").touch() + modpath.write_text(source) importlib.invalidate_caches() - return modpath.pyimport() + return import_path(modpath) return importasmod @@ -682,7 +685,7 @@ raise ValueError() p = FormattedExcinfo(style="short") reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) lines = reprtb.lines - basename = py.path.local(mod.__file__).basename + basename = Path(mod.__file__).name assert lines[0] == " func1()" assert reprtb.reprfileloc is not None assert basename in str(reprtb.reprfileloc.path) @@ -948,7 +951,9 @@ raise ValueError() assert line.endswith("mod.py") assert tw_mock.lines[12] == ":3: ValueError" - def test_toterminal_long_missing_source(self, importasmod, tmpdir, tw_mock): + def test_toterminal_long_missing_source( + self, importasmod, tmp_path: Path, tw_mock + ) -> None: mod = importasmod( """ def g(x): @@ -958,7 +963,7 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.f) - tmpdir.join("mod.py").remove() + tmp_path.joinpath("mod.py").unlink() excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() repr.toterminal(tw_mock) @@ -978,7 +983,9 @@ raise ValueError() assert line.endswith("mod.py") assert tw_mock.lines[10] == ":3: ValueError" - def test_toterminal_long_incomplete_source(self, importasmod, tmpdir, tw_mock): + def test_toterminal_long_incomplete_source( + self, importasmod, tmp_path: Path, tw_mock + ) -> None: mod = importasmod( """ def g(x): @@ -988,7 +995,7 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.f) - tmpdir.join("mod.py").write("asdf") + tmp_path.joinpath("mod.py").write_text("asdf") excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() repr.toterminal(tw_mock) @@ -1374,16 +1381,18 @@ def test_repr_traceback_with_unicode(style, encoding): assert repr_traceback is not None -def test_cwd_deleted(testdir): - testdir.makepyfile( +def test_cwd_deleted(pytester: Pytester) -> None: + pytester.makepyfile( """ - def test(tmpdir): - tmpdir.chdir() - tmpdir.remove() + import os + + def test(tmp_path): + os.chdir(tmp_path) + tmp_path.unlink() assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["* 1 failed in *"]) result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stderr.no_fnmatch_line("*INTERNALERROR*") diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py index e83f18fdc..85ba545e6 100644 --- a/testing/examples/test_issue519.py +++ b/testing/examples/test_issue519.py @@ -1,3 +1,6 @@ -def test_510(testdir): - testdir.copy_example("issue_519.py") - testdir.runpytest("issue_519.py") +from _pytest.pytester import Pytester + + +def test_510(pytester: Pytester) -> None: + pytester.copy_example("issue_519.py") + pytester.runpytest("issue_519.py") diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index db0ccf06a..fac7593ea 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -3,6 +3,7 @@ import os import re import shutil import sys +from pathlib import Path from typing import Generator from unittest import mock @@ -64,10 +65,10 @@ win32 = int(sys.platform == "win32") class TestTerminalWriter: @pytest.fixture(params=["path", "stringio"]) def tw( - self, request, tmpdir + self, request, tmp_path: Path ) -> Generator[terminalwriter.TerminalWriter, None, None]: if request.param == "path": - p = tmpdir.join("tmpfile") + p = tmp_path.joinpath("tmpfile") f = open(str(p), "w+", encoding="utf8") tw = terminalwriter.TerminalWriter(f) diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index ffd51bcad..f82df1971 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -2,14 +2,14 @@ import logging import pytest from _pytest.logging import caplog_records_key -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester logger = logging.getLogger(__name__) sublogger = logging.getLogger(__name__ + ".baz") -def test_fixture_help(testdir): - result = testdir.runpytest("--fixtures") +def test_fixture_help(pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines(["*caplog*"]) @@ -28,12 +28,12 @@ def test_change_level(caplog): assert "CRITICAL" in caplog.text -def test_change_level_undo(testdir: Testdir) -> None: +def test_change_level_undo(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test. Tests the logging output themselves (affacted both by logger and handler levels). """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -49,17 +49,17 @@ def test_change_level_undo(testdir: Testdir) -> None: assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"]) result.stdout.no_fnmatch_line("*log from test2*") -def test_change_level_undos_handler_level(testdir: Testdir) -> None: +def test_change_level_undos_handler_level(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test (handler). Issue #7569. Tests the handler level specifically. """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -78,7 +78,7 @@ def test_change_level_undos_handler_level(testdir: Testdir) -> None: assert caplog.handler.level == 43 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=3) @@ -172,8 +172,8 @@ def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardow assert set(caplog._item._store[caplog_records_key]) == {"setup", "call"} -def test_ini_controls_global_log_level(testdir): - testdir.makepyfile( +def test_ini_controls_global_log_level(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import logging @@ -187,20 +187,20 @@ def test_ini_controls_global_log_level(testdir): assert 'ERROR' in caplog.text """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=ERROR """ ) - result = testdir.runpytest() + result = pytester.runpytest() # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 -def test_caplog_can_override_global_log_level(testdir): - testdir.makepyfile( +def test_caplog_can_override_global_log_level(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import logging @@ -227,19 +227,19 @@ def test_caplog_can_override_global_log_level(testdir): assert "message won't be shown" not in caplog.text """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=WARNING """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 -def test_caplog_captures_despite_exception(testdir): - testdir.makepyfile( +def test_caplog_captures_despite_exception(pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest import logging @@ -255,26 +255,28 @@ def test_caplog_captures_despite_exception(testdir): raise Exception() """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=WARNING """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*ERROR message will be shown*"]) result.stdout.no_fnmatch_line("*DEBUG message won't be shown*") assert result.ret == 1 -def test_log_report_captures_according_to_config_option_upon_failure(testdir): +def test_log_report_captures_according_to_config_option_upon_failure( + pytester: Pytester, +) -> None: """Test that upon failure: (1) `caplog` succeeded to capture the DEBUG message and assert on it => No `Exception` is raised. (2) The `DEBUG` message does NOT appear in the `Captured log call` report. (3) The stdout, `INFO`, and `WARNING` messages DO appear in the test reports due to `--log-level=INFO`. """ - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -299,7 +301,7 @@ def test_log_report_captures_according_to_config_option_upon_failure(testdir): """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") result.stdout.no_fnmatch_line("*Exception: caplog failed to capture DEBUG*") result.stdout.no_fnmatch_line("*DEBUG log message*") result.stdout.fnmatch_lines( diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index fc9f10823..a5ab8b98b 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -6,12 +6,13 @@ from typing import cast import pytest from _pytest.capture import CaptureManager from _pytest.config import ExitCode -from _pytest.pytester import Testdir +from _pytest.fixtures import FixtureRequest +from _pytest.pytester import Pytester from _pytest.terminal import TerminalReporter -def test_nothing_logged(testdir): - testdir.makepyfile( +def test_nothing_logged(pytester: Pytester) -> None: + pytester.makepyfile( """ import sys @@ -21,7 +22,7 @@ def test_nothing_logged(testdir): assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 1 result.stdout.fnmatch_lines(["*- Captured stdout call -*", "text going to stdout"]) result.stdout.fnmatch_lines(["*- Captured stderr call -*", "text going to stderr"]) @@ -29,8 +30,8 @@ def test_nothing_logged(testdir): result.stdout.fnmatch_lines(["*- Captured *log call -*"]) -def test_messages_logged(testdir): - testdir.makepyfile( +def test_messages_logged(pytester: Pytester) -> None: + pytester.makepyfile( """ import sys import logging @@ -44,15 +45,15 @@ def test_messages_logged(testdir): assert False """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") assert result.ret == 1 result.stdout.fnmatch_lines(["*- Captured *log call -*", "*text going to logger*"]) result.stdout.fnmatch_lines(["*- Captured stdout call -*", "text going to stdout"]) result.stdout.fnmatch_lines(["*- Captured stderr call -*", "text going to stderr"]) -def test_root_logger_affected(testdir): - testdir.makepyfile( +def test_root_logger_affected(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging logger = logging.getLogger() @@ -65,8 +66,8 @@ def test_root_logger_affected(testdir): assert 0 """ ) - log_file = testdir.tmpdir.join("pytest.log").strpath - result = testdir.runpytest("--log-level=ERROR", "--log-file=pytest.log") + log_file = str(pytester.path.joinpath("pytest.log")) + result = pytester.runpytest("--log-level=ERROR", "--log-file=pytest.log") assert result.ret == 1 # The capture log calls in the stdout section only contain the @@ -87,8 +88,8 @@ def test_root_logger_affected(testdir): assert "error text going to logger" in contents -def test_log_cli_level_log_level_interaction(testdir): - testdir.makepyfile( +def test_log_cli_level_log_level_interaction(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging logger = logging.getLogger() @@ -102,7 +103,7 @@ def test_log_cli_level_log_level_interaction(testdir): """ ) - result = testdir.runpytest("--log-cli-level=INFO", "--log-level=ERROR") + result = pytester.runpytest("--log-cli-level=INFO", "--log-level=ERROR") assert result.ret == 1 result.stdout.fnmatch_lines( @@ -117,8 +118,8 @@ def test_log_cli_level_log_level_interaction(testdir): result.stdout.no_re_match_line("DEBUG") -def test_setup_logging(testdir): - testdir.makepyfile( +def test_setup_logging(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging @@ -132,7 +133,7 @@ def test_setup_logging(testdir): assert False """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -144,8 +145,8 @@ def test_setup_logging(testdir): ) -def test_teardown_logging(testdir): - testdir.makepyfile( +def test_teardown_logging(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging @@ -159,7 +160,7 @@ def test_teardown_logging(testdir): assert False """ ) - result = testdir.runpytest("--log-level=INFO") + result = pytester.runpytest("--log-level=INFO") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -172,9 +173,9 @@ def test_teardown_logging(testdir): @pytest.mark.parametrize("enabled", [True, False]) -def test_log_cli_enabled_disabled(testdir, enabled): +def test_log_cli_enabled_disabled(pytester: Pytester, enabled: bool) -> None: msg = "critical message logged by test" - testdir.makepyfile( + pytester.makepyfile( """ import logging def test_log_cli(): @@ -184,13 +185,13 @@ def test_log_cli_enabled_disabled(testdir, enabled): ) ) if enabled: - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() if enabled: result.stdout.fnmatch_lines( [ @@ -204,9 +205,9 @@ def test_log_cli_enabled_disabled(testdir, enabled): assert msg not in result.stdout.str() -def test_log_cli_default_level(testdir): +def test_log_cli_default_level(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -217,14 +218,14 @@ def test_log_cli_default_level(testdir): logging.getLogger('catchlog').warning("WARNING message will be shown") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -238,10 +239,12 @@ def test_log_cli_default_level(testdir): assert result.ret == 0 -def test_log_cli_default_level_multiple_tests(testdir, request): +def test_log_cli_default_level_multiple_tests( + pytester: Pytester, request: FixtureRequest +) -> None: """Ensure we reset the first newline added by the live logger between tests""" filename = request.node.name + ".py" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -252,14 +255,14 @@ def test_log_cli_default_level_multiple_tests(testdir, request): logging.warning("log message from test_log_2") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ f"{filename}::test_log_1 ", @@ -273,11 +276,13 @@ def test_log_cli_default_level_multiple_tests(testdir, request): ) -def test_log_cli_default_level_sections(testdir, request): +def test_log_cli_default_level_sections( + pytester: Pytester, request: FixtureRequest +) -> None: """Check that with live logging enable we are printing the correct headers during start/setup/call/teardown/finish.""" filename = request.node.name + ".py" - testdir.makeconftest( + pytester.makeconftest( """ import pytest import logging @@ -290,7 +295,7 @@ def test_log_cli_default_level_sections(testdir, request): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -308,14 +313,14 @@ def test_log_cli_default_level_sections(testdir, request): logging.warning("log message from test_log_2") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ f"{filename}::test_log_1 ", @@ -347,11 +352,13 @@ def test_log_cli_default_level_sections(testdir, request): ) -def test_live_logs_unknown_sections(testdir, request): +def test_live_logs_unknown_sections( + pytester: Pytester, request: FixtureRequest +) -> None: """Check that with live logging enable we are printing the correct headers during start/setup/call/teardown/finish.""" filename = request.node.name + ".py" - testdir.makeconftest( + pytester.makeconftest( """ import pytest import logging @@ -367,7 +374,7 @@ def test_live_logs_unknown_sections(testdir, request): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -383,14 +390,14 @@ def test_live_logs_unknown_sections(testdir, request): """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ "*WARNING*Unknown Section*", @@ -409,11 +416,13 @@ def test_live_logs_unknown_sections(testdir, request): ) -def test_sections_single_new_line_after_test_outcome(testdir, request): +def test_sections_single_new_line_after_test_outcome( + pytester: Pytester, request: FixtureRequest +) -> None: """Check that only a single new line is written between log messages during teardown/finish.""" filename = request.node.name + ".py" - testdir.makeconftest( + pytester.makeconftest( """ import pytest import logging @@ -427,7 +436,7 @@ def test_sections_single_new_line_after_test_outcome(testdir, request): """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -443,14 +452,14 @@ def test_sections_single_new_line_after_test_outcome(testdir, request): logging.warning("log message from test_log_1") """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( [ f"{filename}::test_log_1 ", @@ -487,9 +496,9 @@ def test_sections_single_new_line_after_test_outcome(testdir, request): ) -def test_log_cli_level(testdir): +def test_log_cli_level(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -501,14 +510,14 @@ def test_log_cli_level(testdir): print('PASSED') """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_cli=true """ ) - result = testdir.runpytest("-s", "--log-cli-level=INFO") + result = pytester.runpytest("-s", "--log-cli-level=INFO") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -522,7 +531,7 @@ def test_log_cli_level(testdir): # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 - result = testdir.runpytest("-s", "--log-level=INFO") + result = pytester.runpytest("-s", "--log-level=INFO") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -537,15 +546,15 @@ def test_log_cli_level(testdir): assert result.ret == 0 -def test_log_cli_ini_level(testdir): - testdir.makeini( +def test_log_cli_ini_level(pytester: Pytester) -> None: + pytester.makeini( """ [pytest] log_cli=true log_cli_level = INFO """ ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -558,7 +567,7 @@ def test_log_cli_ini_level(testdir): """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines( @@ -577,11 +586,11 @@ def test_log_cli_ini_level(testdir): "cli_args", ["", "--log-level=WARNING", "--log-file-level=WARNING", "--log-cli-level=WARNING"], ) -def test_log_cli_auto_enable(testdir, cli_args): +def test_log_cli_auto_enable(pytester: Pytester, cli_args: str) -> None: """Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI. It should not be auto enabled if the same configs are set on the INI file. """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -591,7 +600,7 @@ def test_log_cli_auto_enable(testdir, cli_args): """ ) - testdir.makeini( + pytester.makeini( """ [pytest] log_level=INFO @@ -599,7 +608,7 @@ def test_log_cli_auto_enable(testdir, cli_args): """ ) - result = testdir.runpytest(cli_args) + result = pytester.runpytest(cli_args) stdout = result.stdout.str() if cli_args == "--log-cli-level=WARNING": result.stdout.fnmatch_lines( @@ -620,9 +629,9 @@ def test_log_cli_auto_enable(testdir, cli_args): assert "WARNING" not in stdout -def test_log_file_cli(testdir): +def test_log_file_cli(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -635,9 +644,9 @@ def test_log_file_cli(testdir): """ ) - log_file = testdir.tmpdir.join("pytest.log").strpath + log_file = str(pytester.path.joinpath("pytest.log")) - result = testdir.runpytest( + result = pytester.runpytest( "-s", f"--log-file={log_file}", "--log-file-level=WARNING" ) @@ -653,9 +662,9 @@ def test_log_file_cli(testdir): assert "This log message won't be shown" not in contents -def test_log_file_cli_level(testdir): +def test_log_file_cli_level(pytester: Pytester) -> None: # Default log file level - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -668,9 +677,9 @@ def test_log_file_cli_level(testdir): """ ) - log_file = testdir.tmpdir.join("pytest.log").strpath + log_file = str(pytester.path.joinpath("pytest.log")) - result = testdir.runpytest("-s", f"--log-file={log_file}", "--log-file-level=INFO") + result = pytester.runpytest("-s", f"--log-file={log_file}", "--log-file-level=INFO") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines(["test_log_file_cli_level.py PASSED"]) @@ -684,22 +693,22 @@ def test_log_file_cli_level(testdir): assert "This log message won't be shown" not in contents -def test_log_level_not_changed_by_default(testdir): - testdir.makepyfile( +def test_log_level_not_changed_by_default(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging def test_log_file(): assert logging.getLogger().level == logging.WARNING """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") result.stdout.fnmatch_lines(["* 1 passed in *"]) -def test_log_file_ini(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_file_ini(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -708,7 +717,7 @@ def test_log_file_ini(testdir): log_file ) ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -721,7 +730,7 @@ def test_log_file_ini(testdir): """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines(["test_log_file_ini.py PASSED"]) @@ -735,10 +744,10 @@ def test_log_file_ini(testdir): assert "This log message won't be shown" not in contents -def test_log_file_ini_level(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_file_ini_level(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -747,7 +756,7 @@ def test_log_file_ini_level(testdir): log_file ) ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import logging @@ -760,7 +769,7 @@ def test_log_file_ini_level(testdir): """ ) - result = testdir.runpytest("-s") + result = pytester.runpytest("-s") # fnmatch_lines does an assertion internally result.stdout.fnmatch_lines(["test_log_file_ini_level.py PASSED"]) @@ -774,10 +783,10 @@ def test_log_file_ini_level(testdir): assert "This log message won't be shown" not in contents -def test_log_file_unicode(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_file_unicode(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -786,7 +795,7 @@ def test_log_file_unicode(testdir): log_file ) ) - testdir.makepyfile( + pytester.makepyfile( """\ import logging @@ -797,7 +806,7 @@ def test_log_file_unicode(testdir): """ ) - result = testdir.runpytest() + result = pytester.runpytest() # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 @@ -810,11 +819,13 @@ def test_log_file_unicode(testdir): @pytest.mark.parametrize("has_capture_manager", [True, False]) -def test_live_logging_suspends_capture(has_capture_manager: bool, request) -> None: +def test_live_logging_suspends_capture( + has_capture_manager: bool, request: FixtureRequest +) -> None: """Test that capture manager is suspended when we emitting messages for live logging. This tests the implementation calls instead of behavior because it is difficult/impossible to do it using - ``testdir`` facilities because they do their own capturing. + ``pytester`` facilities because they do their own capturing. We parametrize the test to also make sure _LiveLoggingStreamHandler works correctly if no capture manager plugin is installed. @@ -856,8 +867,8 @@ def test_live_logging_suspends_capture(has_capture_manager: bool, request) -> No assert cast(io.StringIO, out_file).getvalue() == "\nsome message\n" -def test_collection_live_logging(testdir): - testdir.makepyfile( +def test_collection_live_logging(pytester: Pytester) -> None: + pytester.makepyfile( """ import logging @@ -865,22 +876,22 @@ def test_collection_live_logging(testdir): """ ) - result = testdir.runpytest("--log-cli-level=INFO") + result = pytester.runpytest("--log-cli-level=INFO") result.stdout.fnmatch_lines( ["*--- live log collection ---*", "*Normal message*", "collected 0 items"] ) @pytest.mark.parametrize("verbose", ["", "-q", "-qq"]) -def test_collection_collect_only_live_logging(testdir, verbose): - testdir.makepyfile( +def test_collection_collect_only_live_logging(pytester: Pytester, verbose: str) -> None: + pytester.makepyfile( """ def test_simple(): pass """ ) - result = testdir.runpytest("--collect-only", "--log-cli-level=INFO", verbose) + result = pytester.runpytest("--collect-only", "--log-cli-level=INFO", verbose) expected_lines = [] @@ -907,10 +918,10 @@ def test_collection_collect_only_live_logging(testdir, verbose): result.stdout.fnmatch_lines(expected_lines) -def test_collection_logging_to_file(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_collection_logging_to_file(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -920,7 +931,7 @@ def test_collection_logging_to_file(testdir): ) ) - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -932,7 +943,7 @@ def test_collection_logging_to_file(testdir): """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*--- live log collection ---*") @@ -945,10 +956,10 @@ def test_collection_logging_to_file(testdir): assert "info message in test_simple" in contents -def test_log_in_hooks(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_in_hooks(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -958,7 +969,7 @@ def test_log_in_hooks(testdir): log_file ) ) - testdir.makeconftest( + pytester.makeconftest( """ import logging @@ -972,7 +983,7 @@ def test_log_in_hooks(testdir): logging.info('sessionfinish') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"]) with open(log_file) as rfh: contents = rfh.read() @@ -981,10 +992,10 @@ def test_log_in_hooks(testdir): assert "sessionfinish" in contents -def test_log_in_runtest_logreport(testdir): - log_file = testdir.tmpdir.join("pytest.log").strpath +def test_log_in_runtest_logreport(pytester: Pytester) -> None: + log_file = str(pytester.path.joinpath("pytest.log")) - testdir.makeini( + pytester.makeini( """ [pytest] log_file={} @@ -994,7 +1005,7 @@ def test_log_in_runtest_logreport(testdir): log_file ) ) - testdir.makeconftest( + pytester.makeconftest( """ import logging logger = logging.getLogger(__name__) @@ -1003,29 +1014,29 @@ def test_log_in_runtest_logreport(testdir): logger.info("logreport") """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_first(): assert True """ ) - testdir.runpytest() + pytester.runpytest() with open(log_file) as rfh: contents = rfh.read() assert contents.count("logreport") == 3 -def test_log_set_path(testdir): - report_dir_base = testdir.tmpdir.strpath +def test_log_set_path(pytester: Pytester) -> None: + report_dir_base = str(pytester.path) - testdir.makeini( + pytester.makeini( """ [pytest] log_file_level = DEBUG log_cli=true """ ) - testdir.makeconftest( + pytester.makeconftest( """ import os import pytest @@ -1040,7 +1051,7 @@ def test_log_set_path(testdir): repr(report_dir_base) ) ) - testdir.makepyfile( + pytester.makepyfile( """ import logging logger = logging.getLogger("testcase-logger") @@ -1053,7 +1064,7 @@ def test_log_set_path(testdir): assert True """ ) - testdir.runpytest() + pytester.runpytest() with open(os.path.join(report_dir_base, "test_first")) as rfh: content = rfh.read() assert "message from test 1" in content @@ -1063,10 +1074,10 @@ def test_log_set_path(testdir): assert "message from test 2" in content -def test_colored_captured_log(testdir): +def test_colored_captured_log(pytester: Pytester) -> None: """Test that the level names of captured log messages of a failing test are colored.""" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1077,7 +1088,7 @@ def test_colored_captured_log(testdir): assert False """ ) - result = testdir.runpytest("--log-level=INFO", "--color=yes") + result = pytester.runpytest("--log-level=INFO", "--color=yes") assert result.ret == 1 result.stdout.fnmatch_lines( [ @@ -1087,9 +1098,9 @@ def test_colored_captured_log(testdir): ) -def test_colored_ansi_esc_caplogtext(testdir): +def test_colored_ansi_esc_caplogtext(pytester: Pytester) -> None: """Make sure that caplog.text does not contain ANSI escape sequences.""" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1100,11 +1111,11 @@ def test_colored_ansi_esc_caplogtext(testdir): assert '\x1b' not in caplog.text """ ) - result = testdir.runpytest("--log-level=INFO", "--color=yes") + result = pytester.runpytest("--log-level=INFO", "--color=yes") assert result.ret == 0 -def test_logging_emit_error(testdir: Testdir) -> None: +def test_logging_emit_error(pytester: Pytester) -> None: """An exception raised during emit() should fail the test. The default behavior of logging is to print "Logging error" @@ -1112,7 +1123,7 @@ def test_logging_emit_error(testdir: Testdir) -> None: pytest overrides this behavior to propagate the exception. """ - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1120,7 +1131,7 @@ def test_logging_emit_error(testdir: Testdir) -> None: logging.warning('oops', 'first', 2) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(failed=1) result.stdout.fnmatch_lines( [ @@ -1130,10 +1141,10 @@ def test_logging_emit_error(testdir: Testdir) -> None: ) -def test_logging_emit_error_supressed(testdir: Testdir) -> None: +def test_logging_emit_error_supressed(pytester: Pytester) -> None: """If logging is configured to silently ignore errors, pytest doesn't propagate errors either.""" - testdir.makepyfile( + pytester.makepyfile( """ import logging @@ -1142,13 +1153,15 @@ def test_logging_emit_error_supressed(testdir: Testdir) -> None: logging.warning('oops', 'first', 2) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.assert_outcomes(passed=1) -def test_log_file_cli_subdirectories_are_successfully_created(testdir): - path = testdir.makepyfile(""" def test_logger(): pass """) +def test_log_file_cli_subdirectories_are_successfully_created( + pytester: Pytester, +) -> None: + path = pytester.makepyfile(""" def test_logger(): pass """) expected = os.path.join(os.path.dirname(str(path)), "foo", "bar") - result = testdir.runpytest("--log-file=foo/bar/logf.log") + result = pytester.runpytest("--log-file=foo/bar/logf.log") assert "logf.log" in os.listdir(expected) assert result.ret == ExitCode.OK diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index ccc7304b0..7f0827bd4 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1,31 +1,32 @@ import os import shutil -import stat import sys - -import py +from pathlib import Path +from typing import List import pytest from _pytest.config import ExitCode +from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester -from _pytest.pytester import Testdir pytest_plugins = ("pytester",) class TestNewAPI: - def test_config_cache_makedir(self, testdir): - testdir.makeini("[pytest]") - config = testdir.parseconfigure() + def test_config_cache_makedir(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + config = pytester.parseconfigure() + assert config.cache is not None with pytest.raises(ValueError): config.cache.makedir("key/name") p = config.cache.makedir("name") assert p.check() - def test_config_cache_dataerror(self, testdir): - testdir.makeini("[pytest]") - config = testdir.parseconfigure() + def test_config_cache_dataerror(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + config = pytester.parseconfigure() + assert config.cache is not None cache = config.cache pytest.raises(TypeError, lambda: cache.set("key/name", cache)) config.cache.set("key/name", 0) @@ -34,39 +35,45 @@ class TestNewAPI: assert val == -2 @pytest.mark.filterwarnings("ignore:could not create cache path") - def test_cache_writefail_cachfile_silent(self, testdir): - testdir.makeini("[pytest]") - testdir.tmpdir.join(".pytest_cache").write("gone wrong") - config = testdir.parseconfigure() + def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + pytester.path.joinpath(".pytest_cache").write_text("gone wrong") + config = pytester.parseconfigure() cache = config.cache + assert cache is not None cache.set("test/broken", []) @pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows") @pytest.mark.filterwarnings( "ignore:could not create cache path:pytest.PytestWarning" ) - def test_cache_writefail_permissions(self, testdir): - testdir.makeini("[pytest]") - cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache")) - mode = os.stat(cache_dir)[stat.ST_MODE] - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0) + def test_cache_writefail_permissions(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]") + cache_dir = pytester.path.joinpath(".pytest_cache") + cache_dir.mkdir() + mode = cache_dir.stat().st_mode + cache_dir.chmod(0) try: - config = testdir.parseconfigure() + config = pytester.parseconfigure() cache = config.cache + assert cache is not None cache.set("test/broken", []) finally: - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode) + cache_dir.chmod(mode) @pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows") @pytest.mark.filterwarnings("default") - def test_cache_failure_warns(self, testdir, monkeypatch): + def test_cache_failure_warns( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") - cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache")) - mode = os.stat(cache_dir)[stat.ST_MODE] - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0) + cache_dir = pytester.path.joinpath(".pytest_cache") + cache_dir.mkdir() + mode = cache_dir.stat().st_mode + cache_dir.chmod(0) try: - testdir.makepyfile("def test_error(): raise Exception") - result = testdir.runpytest() + pytester.makepyfile("def test_error(): raise Exception") + result = pytester.runpytest() assert result.ret == 1 # warnings from nodeids, lastfailed, and stepwise result.stdout.fnmatch_lines( @@ -81,28 +88,28 @@ class TestNewAPI: ] ) finally: - testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode) + cache_dir.chmod(mode) - def test_config_cache(self, testdir): - testdir.makeconftest( + def test_config_cache(self, pytester: Pytester) -> None: + pytester.makeconftest( """ def pytest_configure(config): # see that we get cache information early on assert hasattr(config, "cache") """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_session(pytestconfig): assert hasattr(pytestconfig, "cache") """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def test_cachefuncarg(self, testdir): - testdir.makepyfile( + def test_cachefuncarg(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest def test_cachefuncarg(cache): @@ -114,13 +121,13 @@ class TestNewAPI: assert val == [1] """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def test_custom_rel_cache_dir(self, testdir): + def test_custom_rel_cache_dir(self, pytester: Pytester) -> None: rel_cache_dir = os.path.join("custom_cache_dir", "subdir") - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = {cache_dir} @@ -128,14 +135,14 @@ class TestNewAPI: cache_dir=rel_cache_dir ) ) - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest() - assert testdir.tmpdir.join(rel_cache_dir).isdir() + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest() + assert pytester.path.joinpath(rel_cache_dir).is_dir() - def test_custom_abs_cache_dir(self, testdir, tmpdir_factory): + def test_custom_abs_cache_dir(self, pytester: Pytester, tmpdir_factory) -> None: tmp = str(tmpdir_factory.mktemp("tmp")) abs_cache_dir = os.path.join(tmp, "custom_cache_dir") - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = {cache_dir} @@ -143,13 +150,15 @@ class TestNewAPI: cache_dir=abs_cache_dir ) ) - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest() - assert py.path.local(abs_cache_dir).isdir() + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest() + assert Path(abs_cache_dir).is_dir() - def test_custom_cache_dir_with_env_var(self, testdir, monkeypatch): + def test_custom_cache_dir_with_env_var( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setenv("env_var", "custom_cache_dir") - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = {cache_dir} @@ -157,31 +166,33 @@ class TestNewAPI: cache_dir="$env_var" ) ) - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest() - assert testdir.tmpdir.join("custom_cache_dir").isdir() + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest() + assert pytester.path.joinpath("custom_cache_dir").is_dir() @pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "/tox_env_dir"))) -def test_cache_reportheader(env, testdir, monkeypatch): - testdir.makepyfile("""def test_foo(): pass""") +def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + pytester.makepyfile("""def test_foo(): pass""") if env: monkeypatch.setenv(*env) expected = os.path.join(env[1], ".pytest_cache") else: monkeypatch.delenv("TOX_ENV_DIR", raising=False) expected = ".pytest_cache" - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines(["cachedir: %s" % expected]) -def test_cache_reportheader_external_abspath(testdir, tmpdir_factory): +def test_cache_reportheader_external_abspath( + pytester: Pytester, tmpdir_factory +) -> None: external_cache = tmpdir_factory.mktemp( "test_cache_reportheader_external_abspath_abs" ) - testdir.makepyfile("def test_hello(): pass") - testdir.makeini( + pytester.makepyfile("def test_hello(): pass") + pytester.makeini( """ [pytest] cache_dir = {abscache} @@ -189,15 +200,15 @@ def test_cache_reportheader_external_abspath(testdir, tmpdir_factory): abscache=external_cache ) ) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines([f"cachedir: {external_cache}"]) -def test_cache_show(testdir): - result = testdir.runpytest("--cache-show") +def test_cache_show(pytester: Pytester) -> None: + result = pytester.runpytest("--cache-show") assert result.ret == 0 result.stdout.fnmatch_lines(["*cache is empty*"]) - testdir.makeconftest( + pytester.makeconftest( """ def pytest_configure(config): config.cache.set("my/name", [1,2,3]) @@ -208,10 +219,10 @@ def test_cache_show(testdir): dp.ensure("world") """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 5 # no tests executed - result = testdir.runpytest("--cache-show") + result = pytester.runpytest("--cache-show") result.stdout.fnmatch_lines( [ "*cachedir:*", @@ -228,7 +239,7 @@ def test_cache_show(testdir): ) assert result.ret == 0 - result = testdir.runpytest("--cache-show", "*/hello") + result = pytester.runpytest("--cache-show", "*/hello") result.stdout.fnmatch_lines( [ "*cachedir:*", @@ -246,25 +257,27 @@ def test_cache_show(testdir): class TestLastFailed: - def test_lastfailed_usecase(self, testdir, monkeypatch): + def test_lastfailed_usecase( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setattr("sys.dont_write_bytecode", True) - p = testdir.makepyfile( + p = pytester.makepyfile( """ def test_1(): assert 0 def test_2(): assert 0 def test_3(): assert 1 """ ) - result = testdir.runpytest(str(p)) + result = pytester.runpytest(str(p)) result.stdout.fnmatch_lines(["*2 failed*"]) - p = testdir.makepyfile( + p = pytester.makepyfile( """ def test_1(): assert 1 def test_2(): assert 1 def test_3(): assert 0 """ ) - result = testdir.runpytest(str(p), "--lf") + result = pytester.runpytest(str(p), "--lf") result.stdout.fnmatch_lines( [ "collected 3 items / 1 deselected / 2 selected", @@ -272,7 +285,7 @@ class TestLastFailed: "*= 2 passed, 1 deselected in *", ] ) - result = testdir.runpytest(str(p), "--lf") + result = pytester.runpytest(str(p), "--lf") result.stdout.fnmatch_lines( [ "collected 3 items", @@ -280,27 +293,27 @@ class TestLastFailed: "*1 failed*2 passed*", ] ) - testdir.tmpdir.join(".pytest_cache").mkdir(".git") - result = testdir.runpytest(str(p), "--lf", "--cache-clear") + pytester.path.joinpath(".pytest_cache", ".git").mkdir(parents=True) + result = pytester.runpytest(str(p), "--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) - assert testdir.tmpdir.join(".pytest_cache", "README.md").isfile() - assert testdir.tmpdir.join(".pytest_cache", ".git").isdir() + assert pytester.path.joinpath(".pytest_cache", "README.md").is_file() + assert pytester.path.joinpath(".pytest_cache", ".git").is_dir() # Run this again to make sure clear-cache is robust if os.path.isdir(".pytest_cache"): shutil.rmtree(".pytest_cache") - result = testdir.runpytest("--lf", "--cache-clear") + result = pytester.runpytest("--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) - def test_failedfirst_order(self, testdir): - testdir.makepyfile( + def test_failedfirst_order(self, pytester: Pytester) -> None: + pytester.makepyfile( test_a="def test_always_passes(): pass", test_b="def test_always_fails(): assert 0", ) - result = testdir.runpytest() + result = pytester.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines(["test_a.py*", "test_b.py*"]) - result = testdir.runpytest("--ff") + result = pytester.runpytest("--ff") # Test order will be failing tests first result.stdout.fnmatch_lines( [ @@ -311,40 +324,42 @@ class TestLastFailed: ] ) - def test_lastfailed_failedfirst_order(self, testdir): - testdir.makepyfile( + def test_lastfailed_failedfirst_order(self, pytester: Pytester) -> None: + pytester.makepyfile( test_a="def test_always_passes(): assert 1", test_b="def test_always_fails(): assert 0", ) - result = testdir.runpytest() + result = pytester.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines(["test_a.py*", "test_b.py*"]) - result = testdir.runpytest("--lf", "--ff") + result = pytester.runpytest("--lf", "--ff") # Test order will be failing tests first result.stdout.fnmatch_lines(["test_b.py*"]) result.stdout.no_fnmatch_line("*test_a.py*") - def test_lastfailed_difference_invocations(self, testdir, monkeypatch): + def test_lastfailed_difference_invocations( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setattr("sys.dont_write_bytecode", True) - testdir.makepyfile( + pytester.makepyfile( test_a=""" def test_a1(): assert 0 def test_a2(): assert 1 """, test_b="def test_b1(): assert 0", ) - p = testdir.tmpdir.join("test_a.py") - p2 = testdir.tmpdir.join("test_b.py") + p = pytester.path.joinpath("test_a.py") + p2 = pytester.path.joinpath("test_b.py") - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) - result = testdir.runpytest("--lf", p2) + result = pytester.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 failed*"]) - testdir.makepyfile(test_b="def test_b1(): assert 1") - result = testdir.runpytest("--lf", p2) + pytester.makepyfile(test_b="def test_b1(): assert 1") + result = pytester.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest("--lf", p) + result = pytester.runpytest("--lf", p) result.stdout.fnmatch_lines( [ "collected 2 items / 1 deselected / 1 selected", @@ -353,21 +368,23 @@ class TestLastFailed: ] ) - def test_lastfailed_usecase_splice(self, testdir, monkeypatch): + def test_lastfailed_usecase_splice( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: monkeypatch.setattr("sys.dont_write_bytecode", True) - testdir.makepyfile( + pytester.makepyfile( "def test_1(): assert 0", test_something="def test_2(): assert 0" ) - p2 = testdir.tmpdir.join("test_something.py") - result = testdir.runpytest() + p2 = pytester.path.joinpath("test_something.py") + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) - result = testdir.runpytest("--lf", p2) + result = pytester.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 failed*"]) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines(["*2 failed*"]) - def test_lastfailed_xpass(self, testdir): - testdir.inline_runsource( + def test_lastfailed_xpass(self, pytester: Pytester) -> None: + pytester.inline_runsource( """ import pytest @pytest.mark.xfail @@ -375,15 +392,16 @@ class TestLastFailed: assert 1 """ ) - config = testdir.parseconfigure() + config = pytester.parseconfigure() + assert config.cache is not None lastfailed = config.cache.get("cache/lastfailed", -1) assert lastfailed == -1 - def test_non_serializable_parametrize(self, testdir): + def test_non_serializable_parametrize(self, pytester: Pytester) -> None: """Test that failed parametrized tests with unmarshable parameters don't break pytest-cache. """ - testdir.makepyfile( + pytester.makepyfile( r""" import pytest @@ -394,26 +412,26 @@ class TestLastFailed: assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) - def test_terminal_report_lastfailed(self, testdir): - test_a = testdir.makepyfile( + def test_terminal_report_lastfailed(self, pytester: Pytester) -> None: + test_a = pytester.makepyfile( test_a=""" def test_a1(): pass def test_a2(): pass """ ) - test_b = testdir.makepyfile( + test_b = pytester.makepyfile( test_b=""" def test_b1(): assert 0 def test_b2(): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 4 items", "*2 failed, 2 passed in*"]) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -422,7 +440,7 @@ class TestLastFailed: ] ) - result = testdir.runpytest(test_a, "--lf") + result = pytester.runpytest(test_a, "--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -431,7 +449,7 @@ class TestLastFailed: ] ) - result = testdir.runpytest(test_b, "--lf") + result = pytester.runpytest(test_b, "--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -440,7 +458,7 @@ class TestLastFailed: ] ) - result = testdir.runpytest("test_b.py::test_b1", "--lf") + result = pytester.runpytest("test_b.py::test_b1", "--lf") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -449,17 +467,17 @@ class TestLastFailed: ] ) - def test_terminal_report_failedfirst(self, testdir): - testdir.makepyfile( + def test_terminal_report_failedfirst(self, pytester: Pytester) -> None: + pytester.makepyfile( test_a=""" def test_a1(): assert 0 def test_a2(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "*1 failed, 1 passed in*"]) - result = testdir.runpytest("--ff") + result = pytester.runpytest("--ff") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -468,9 +486,11 @@ class TestLastFailed: ] ) - def test_lastfailed_collectfailure(self, testdir, monkeypatch): + def test_lastfailed_collectfailure( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: - testdir.makepyfile( + pytester.makepyfile( test_maybe=""" import os env = os.environ @@ -485,8 +505,9 @@ class TestLastFailed: monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) - testdir.runpytest("-q") - config = testdir.parseconfigure() + pytester.runpytest("-q") + config = pytester.parseconfigure() + assert config.cache is not None lastfailed = config.cache.get("cache/lastfailed", -1) return lastfailed @@ -499,8 +520,10 @@ class TestLastFailed: lastfailed = rlf(fail_import=0, fail_run=1) assert list(lastfailed) == ["test_maybe.py::test_hello"] - def test_lastfailed_failure_subset(self, testdir, monkeypatch): - testdir.makepyfile( + def test_lastfailed_failure_subset( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile( test_maybe=""" import os env = os.environ @@ -511,7 +534,7 @@ class TestLastFailed: """ ) - testdir.makepyfile( + pytester.makepyfile( test_maybe2=""" import os env = os.environ @@ -530,8 +553,9 @@ class TestLastFailed: monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) - result = testdir.runpytest("-q", "--lf", *args) - config = testdir.parseconfigure() + result = pytester.runpytest("-q", "--lf", *args) + config = pytester.parseconfigure() + assert config.cache is not None lastfailed = config.cache.get("cache/lastfailed", -1) return result, lastfailed @@ -552,61 +576,63 @@ class TestLastFailed: assert list(lastfailed) == ["test_maybe.py"] result.stdout.fnmatch_lines(["*2 passed*"]) - def test_lastfailed_creates_cache_when_needed(self, testdir): + def test_lastfailed_creates_cache_when_needed(self, pytester: Pytester) -> None: # Issue #1342 - testdir.makepyfile(test_empty="") - testdir.runpytest("-q", "--lf") + pytester.makepyfile(test_empty="") + pytester.runpytest("-q", "--lf") assert not os.path.exists(".pytest_cache/v/cache/lastfailed") - testdir.makepyfile(test_successful="def test_success():\n assert True") - testdir.runpytest("-q", "--lf") + pytester.makepyfile(test_successful="def test_success():\n assert True") + pytester.runpytest("-q", "--lf") assert not os.path.exists(".pytest_cache/v/cache/lastfailed") - testdir.makepyfile(test_errored="def test_error():\n assert False") - testdir.runpytest("-q", "--lf") + pytester.makepyfile(test_errored="def test_error():\n assert False") + pytester.runpytest("-q", "--lf") assert os.path.exists(".pytest_cache/v/cache/lastfailed") - def test_xfail_not_considered_failure(self, testdir): - testdir.makepyfile( + def test_xfail_not_considered_failure(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail def test(): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 xfailed*"]) - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] - def test_xfail_strict_considered_failure(self, testdir): - testdir.makepyfile( + def test_xfail_strict_considered_failure(self, pytester: Pytester) -> None: + pytester.makepyfile( """ import pytest @pytest.mark.xfail(strict=True) def test(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed*"]) - assert self.get_cached_last_failed(testdir) == [ + assert self.get_cached_last_failed(pytester) == [ "test_xfail_strict_considered_failure.py::test" ] @pytest.mark.parametrize("mark", ["mark.xfail", "mark.skip"]) - def test_failed_changed_to_xfail_or_skip(self, testdir, mark): - testdir.makepyfile( + def test_failed_changed_to_xfail_or_skip( + self, pytester: Pytester, mark: str + ) -> None: + pytester.makepyfile( """ import pytest def test(): assert 0 """ ) - result = testdir.runpytest() - assert self.get_cached_last_failed(testdir) == [ + result = pytester.runpytest() + assert self.get_cached_last_failed(pytester) == [ "test_failed_changed_to_xfail_or_skip.py::test" ] assert result.ret == 1 - testdir.makepyfile( + pytester.makepyfile( """ import pytest @pytest.{mark} @@ -615,66 +641,69 @@ class TestLastFailed: mark=mark ) ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 0 - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] assert result.ret == 0 @pytest.mark.parametrize("quiet", [True, False]) @pytest.mark.parametrize("opt", ["--ff", "--lf"]) - def test_lf_and_ff_prints_no_needless_message(self, quiet, opt, testdir): + def test_lf_and_ff_prints_no_needless_message( + self, quiet: bool, opt: str, pytester: Pytester + ) -> None: # Issue 3853 - testdir.makepyfile("def test(): assert 0") + pytester.makepyfile("def test(): assert 0") args = [opt] if quiet: args.append("-q") - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) result.stdout.no_fnmatch_line("*run all*") - result = testdir.runpytest(*args) + result = pytester.runpytest(*args) if quiet: result.stdout.no_fnmatch_line("*run all*") else: assert "rerun previous" in result.stdout.str() - def get_cached_last_failed(self, testdir): - config = testdir.parseconfigure() + def get_cached_last_failed(self, pytester: Pytester) -> List[str]: + config = pytester.parseconfigure() + assert config.cache is not None return sorted(config.cache.get("cache/lastfailed", {})) - def test_cache_cumulative(self, testdir): + def test_cache_cumulative(self, pytester: Pytester) -> None: """Test workflow where user fixes errors gradually file by file using --lf.""" # 1. initial run - test_bar = testdir.makepyfile( + test_bar = pytester.makepyfile( test_bar=""" def test_bar_1(): pass def test_bar_2(): assert 0 """ ) - test_foo = testdir.makepyfile( + test_foo = pytester.makepyfile( test_foo=""" def test_foo_3(): pass def test_foo_4(): assert 0 """ ) - testdir.runpytest() - assert self.get_cached_last_failed(testdir) == [ + pytester.runpytest() + assert self.get_cached_last_failed(pytester) == [ "test_bar.py::test_bar_2", "test_foo.py::test_foo_4", ] # 2. fix test_bar_2, run only test_bar.py - testdir.makepyfile( + pytester.makepyfile( test_bar=""" def test_bar_1(): pass def test_bar_2(): pass """ ) - result = testdir.runpytest(test_bar) + result = pytester.runpytest(test_bar) result.stdout.fnmatch_lines(["*2 passed*"]) # ensure cache does not forget that test_foo_4 failed once before - assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] + assert self.get_cached_last_failed(pytester) == ["test_foo.py::test_foo_4"] - result = testdir.runpytest("--last-failed") + result = pytester.runpytest("--last-failed") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -682,16 +711,16 @@ class TestLastFailed: "*= 1 failed in *", ] ) - assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] + assert self.get_cached_last_failed(pytester) == ["test_foo.py::test_foo_4"] # 3. fix test_foo_4, run only test_foo.py - test_foo = testdir.makepyfile( + test_foo = pytester.makepyfile( test_foo=""" def test_foo_3(): pass def test_foo_4(): pass """ ) - result = testdir.runpytest(test_foo, "--last-failed") + result = pytester.runpytest(test_foo, "--last-failed") result.stdout.fnmatch_lines( [ "collected 2 items / 1 deselected / 1 selected", @@ -699,29 +728,31 @@ class TestLastFailed: "*= 1 passed, 1 deselected in *", ] ) - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] - result = testdir.runpytest("--last-failed") + result = pytester.runpytest("--last-failed") result.stdout.fnmatch_lines(["*4 passed*"]) - assert self.get_cached_last_failed(testdir) == [] + assert self.get_cached_last_failed(pytester) == [] - def test_lastfailed_no_failures_behavior_all_passed(self, testdir): - testdir.makepyfile( + def test_lastfailed_no_failures_behavior_all_passed( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ def test_1(): pass def test_2(): pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 passed*"]) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines(["*2 passed*"]) - result = testdir.runpytest("--lf", "--lfnf", "all") + result = pytester.runpytest("--lf", "--lfnf", "all") result.stdout.fnmatch_lines(["*2 passed*"]) # Ensure the list passed to pytest_deselected is a copy, # and not a reference which is cleared right after. - testdir.makeconftest( + pytester.makeconftest( """ deselected = [] @@ -734,7 +765,7 @@ class TestLastFailed: """ ) - result = testdir.runpytest("--lf", "--lfnf", "none") + result = pytester.runpytest("--lf", "--lfnf", "none") result.stdout.fnmatch_lines( [ "collected 2 items / 2 deselected", @@ -745,26 +776,28 @@ class TestLastFailed: ) assert result.ret == ExitCode.NO_TESTS_COLLECTED - def test_lastfailed_no_failures_behavior_empty_cache(self, testdir): - testdir.makepyfile( + def test_lastfailed_no_failures_behavior_empty_cache( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( """ def test_1(): pass def test_2(): assert 0 """ ) - result = testdir.runpytest("--lf", "--cache-clear") + result = pytester.runpytest("--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*1 passed*"]) - result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "all") + result = pytester.runpytest("--lf", "--cache-clear", "--lfnf", "all") result.stdout.fnmatch_lines(["*1 failed*1 passed*"]) - result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none") + result = pytester.runpytest("--lf", "--cache-clear", "--lfnf", "none") result.stdout.fnmatch_lines(["*2 desel*"]) - def test_lastfailed_skip_collection(self, testdir): + def test_lastfailed_skip_collection(self, pytester: Pytester) -> None: """ Test --lf behavior regarding skipping collection of files that are not marked as failed in the cache (#5172). """ - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ import pytest @@ -782,10 +815,10 @@ class TestLastFailed: } ) # first run: collects 8 items (test_1: 3, test_2: 5) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 8 items", "*2 failed*6 passed*"]) # second run: collects only 5 items from test_2, because all tests from test_1 have passed - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -795,14 +828,14 @@ class TestLastFailed: ) # add another file and check if message is correct when skipping more than 1 file - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_3.py": """ def test_3(): pass """ } ) - result = testdir.runpytest("--lf") + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -811,18 +844,20 @@ class TestLastFailed: ] ) - def test_lastfailed_with_known_failures_not_being_selected(self, testdir): - testdir.makepyfile( + def test_lastfailed_with_known_failures_not_being_selected( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( **{ "pkg1/test_1.py": """def test_1(): assert 0""", "pkg1/test_2.py": """def test_2(): pass""", } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) - py.path.local("pkg1/test_1.py").remove() - result = testdir.runpytest("--lf") + Path("pkg1/test_1.py").unlink() + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -832,8 +867,8 @@ class TestLastFailed: ) # Recreate file with known failure. - testdir.makepyfile(**{"pkg1/test_1.py": """def test_1(): assert 0"""}) - result = testdir.runpytest("--lf") + pytester.makepyfile(**{"pkg1/test_1.py": """def test_1(): assert 0"""}) + result = pytester.runpytest("--lf") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -843,8 +878,8 @@ class TestLastFailed: ) # Remove/rename test: collects the file again. - testdir.makepyfile(**{"pkg1/test_1.py": """def test_renamed(): assert 0"""}) - result = testdir.runpytest("--lf", "-rf") + pytester.makepyfile(**{"pkg1/test_1.py": """def test_renamed(): assert 0"""}) + result = pytester.runpytest("--lf", "-rf") result.stdout.fnmatch_lines( [ "collected 2 items", @@ -856,7 +891,7 @@ class TestLastFailed: ] ) - result = testdir.runpytest("--lf", "--co") + result = pytester.runpytest("--lf", "--co") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -867,13 +902,13 @@ class TestLastFailed: ] ) - def test_lastfailed_args_with_deselected(self, testdir: Testdir) -> None: + def test_lastfailed_args_with_deselected(self, pytester: Pytester) -> None: """Test regression with --lf running into NoMatch error. This was caused by it not collecting (non-failed) nodes given as arguments. """ - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ def test_pass(): pass @@ -881,11 +916,11 @@ class TestLastFailed: """, } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) assert result.ret == 1 - result = testdir.runpytest("pkg1/test_1.py::test_pass", "--lf", "--co") + result = pytester.runpytest("pkg1/test_1.py::test_pass", "--lf", "--co") assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -898,7 +933,7 @@ class TestLastFailed: consecutive=True, ) - result = testdir.runpytest( + result = pytester.runpytest( "pkg1/test_1.py::test_pass", "pkg1/test_1.py::test_fail", "--lf", "--co" ) assert result.ret == 0 @@ -913,9 +948,9 @@ class TestLastFailed: ], ) - def test_lastfailed_with_class_items(self, testdir: Testdir) -> None: + def test_lastfailed_with_class_items(self, pytester: Pytester) -> None: """Test regression with --lf deselecting whole classes.""" - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ class TestFoo: @@ -926,11 +961,11 @@ class TestLastFailed: """, } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 3 items", "* 2 failed, 1 passed in *"]) assert result.ret == 1 - result = testdir.runpytest("--lf", "--co") + result = pytester.runpytest("--lf", "--co") assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -947,8 +982,8 @@ class TestLastFailed: consecutive=True, ) - def test_lastfailed_with_all_filtered(self, testdir: Testdir) -> None: - testdir.makepyfile( + def test_lastfailed_with_all_filtered(self, pytester: Pytester) -> None: + pytester.makepyfile( **{ "pkg1/test_1.py": """ def test_fail(): assert 0 @@ -956,19 +991,19 @@ class TestLastFailed: """, } ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) assert result.ret == 1 # Remove known failure. - testdir.makepyfile( + pytester.makepyfile( **{ "pkg1/test_1.py": """ def test_pass(): pass """, } ) - result = testdir.runpytest("--lf", "--co") + result = pytester.runpytest("--lf", "--co") result.stdout.fnmatch_lines( [ "collected 1 item", @@ -1015,8 +1050,8 @@ class TestLastFailed: class TestNewFirst: - def test_newfirst_usecase(self, testdir): - testdir.makepyfile( + def test_newfirst_usecase(self, pytester: Pytester, testdir) -> None: + pytester.makepyfile( **{ "test_1/test_1.py": """ def test_1(): assert 1 @@ -1026,24 +1061,24 @@ class TestNewFirst: """, } ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) - result = testdir.runpytest("-v") + p1 = pytester.path.joinpath("test_1/test_1.py") + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) + + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( ["*test_1/test_1.py::test_1 PASSED*", "*test_2/test_2.py::test_1 PASSED*"] ) - result = testdir.runpytest("-v", "--nf") + result = pytester.runpytest("-v", "--nf") result.stdout.fnmatch_lines( ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"] ) - testdir.tmpdir.join("test_1/test_1.py").write( - "def test_1(): assert 1\n" "def test_2(): assert 1\n" - ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) + p1.write_text("def test_1(): assert 1\n" "def test_2(): assert 1\n") + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) - result = testdir.runpytest("--nf", "--collect-only", "-q") + result = pytester.runpytest("--nf", "--collect-only", "-q") result.stdout.fnmatch_lines( [ "test_1/test_1.py::test_2", @@ -1053,15 +1088,15 @@ class TestNewFirst: ) # Newest first with (plugin) pytest_collection_modifyitems hook. - testdir.makepyfile( + pytester.makepyfile( myplugin=""" def pytest_collection_modifyitems(items): items[:] = sorted(items, key=lambda item: item.nodeid) print("new_items:", [x.nodeid for x in items]) """ ) - testdir.syspathinsert() - result = testdir.runpytest("--nf", "-p", "myplugin", "--collect-only", "-q") + pytester.syspathinsert() + result = pytester.runpytest("--nf", "-p", "myplugin", "--collect-only", "-q") result.stdout.fnmatch_lines( [ "new_items: *test_1.py*test_1.py*test_2.py*", @@ -1071,8 +1106,8 @@ class TestNewFirst: ] ) - def test_newfirst_parametrize(self, testdir): - testdir.makepyfile( + def test_newfirst_parametrize(self, pytester: Pytester) -> None: + pytester.makepyfile( **{ "test_1/test_1.py": """ import pytest @@ -1087,9 +1122,10 @@ class TestNewFirst: } ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) + p1 = pytester.path.joinpath("test_1/test_1.py") + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) - result = testdir.runpytest("-v") + result = pytester.runpytest("-v") result.stdout.fnmatch_lines( [ "*test_1/test_1.py::test_1[1*", @@ -1099,7 +1135,7 @@ class TestNewFirst: ] ) - result = testdir.runpytest("-v", "--nf") + result = pytester.runpytest("-v", "--nf") result.stdout.fnmatch_lines( [ "*test_2/test_2.py::test_1[1*", @@ -1109,20 +1145,20 @@ class TestNewFirst: ] ) - testdir.tmpdir.join("test_1/test_1.py").write( + p1.write_text( "import pytest\n" "@pytest.mark.parametrize('num', [1, 2, 3])\n" "def test_1(num): assert num\n" ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) + os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) # Running only a subset does not forget about existing ones. - result = testdir.runpytest("-v", "--nf", "test_2/test_2.py") + result = pytester.runpytest("-v", "--nf", "test_2/test_2.py") result.stdout.fnmatch_lines( ["*test_2/test_2.py::test_1[1*", "*test_2/test_2.py::test_1[2*"] ) - result = testdir.runpytest("-v", "--nf") + result = pytester.runpytest("-v", "--nf") result.stdout.fnmatch_lines( [ "*test_1/test_1.py::test_1[3*", @@ -1135,27 +1171,28 @@ class TestNewFirst: class TestReadme: - def check_readme(self, testdir): - config = testdir.parseconfigure() + def check_readme(self, pytester: Pytester) -> bool: + config = pytester.parseconfigure() + assert config.cache is not None readme = config.cache._cachedir.joinpath("README.md") return readme.is_file() - def test_readme_passed(self, testdir): - testdir.makepyfile("def test_always_passes(): pass") - testdir.runpytest() - assert self.check_readme(testdir) is True + def test_readme_passed(self, pytester: Pytester) -> None: + pytester.makepyfile("def test_always_passes(): pass") + pytester.runpytest() + assert self.check_readme(pytester) is True - def test_readme_failed(self, testdir): - testdir.makepyfile("def test_always_fails(): assert 0") - testdir.runpytest() - assert self.check_readme(testdir) is True + def test_readme_failed(self, pytester: Pytester) -> None: + pytester.makepyfile("def test_always_fails(): assert 0") + pytester.runpytest() + assert self.check_readme(pytester) is True -def test_gitignore(testdir): +def test_gitignore(pytester: Pytester) -> None: """Ensure we automatically create .gitignore file in the pytest_cache directory (#3286).""" from _pytest.cacheprovider import Cache - config = testdir.parseconfig() + config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) cache.set("foo", "bar") msg = "# Created by pytest automatically.\n*\n" @@ -1168,16 +1205,16 @@ def test_gitignore(testdir): assert gitignore_path.read_text(encoding="UTF-8") == "custom" -def test_does_not_create_boilerplate_in_existing_dirs(testdir): +def test_does_not_create_boilerplate_in_existing_dirs(pytester: Pytester) -> None: from _pytest.cacheprovider import Cache - testdir.makeini( + pytester.makeini( """ [pytest] cache_dir = . """ ) - config = testdir.parseconfig() + config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) cache.set("foo", "bar") @@ -1186,12 +1223,12 @@ def test_does_not_create_boilerplate_in_existing_dirs(testdir): assert not os.path.exists("README.md") -def test_cachedir_tag(testdir): +def test_cachedir_tag(pytester: Pytester) -> None: """Ensure we automatically create CACHEDIR.TAG file in the pytest_cache directory (#4278).""" from _pytest.cacheprovider import Cache from _pytest.cacheprovider import CACHEDIR_TAG_CONTENT - config = testdir.parseconfig() + config = pytester.parseconfig() cache = Cache.for_config(config, _ispytest=True) cache.set("foo", "bar") cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG") diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 36e83191b..80f2a6d0b 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -8,8 +8,6 @@ from typing import Generator from typing import List from typing import Optional -import py - import pytest from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -93,9 +91,9 @@ class TestConftestValueAccessGlobal: conftest = ConftestWithSetinitial(startdir) mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend") assert value == 1.5 - path = py.path.local(mod.__file__) - assert path.dirpath() == basedir / "adir" / "b" - assert path.purebasename.startswith("conftest") + path = Path(mod.__file__) + assert path.parent == basedir / "adir" / "b" + assert path.stem == "conftest" def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None: @@ -361,11 +359,10 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> def test_fixture_dependency(pytester: Pytester) -> None: - ct1 = pytester.makeconftest("") - ct1 = pytester.makepyfile("__init__.py") - ct1.write_text("") + pytester.makeconftest("") + pytester.path.joinpath("__init__.py").touch() sub = pytester.mkdir("sub") - sub.joinpath("__init__.py").write_text("") + sub.joinpath("__init__.py").touch() sub.joinpath("conftest.py").write_text( textwrap.dedent( """\ @@ -387,7 +384,7 @@ def test_fixture_dependency(pytester: Pytester) -> None: ) subsub = sub.joinpath("subsub") subsub.mkdir() - subsub.joinpath("__init__.py").write_text("") + subsub.joinpath("__init__.py").touch() subsub.joinpath("test_bar.py").write_text( textwrap.dedent( """\ @@ -525,8 +522,8 @@ class TestConftestVisibility: """#616""" dirs = self._setup_tree(pytester) print("pytest run in cwd: %s" % (dirs[chdir].relative_to(pytester.path))) - print("pytestarg : %s" % (testarg)) - print("expected pass : %s" % (expect_ntests_passed)) + print("pytestarg : %s" % testarg) + print("expected pass : %s" % expect_ntests_passed) os.chdir(dirs[chdir]) reprec = pytester.inline_run(testarg, "-q", "--traceconfig") reprec.assertoutcome(passed=expect_ntests_passed) diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index c20ff7480..0b97a0e5a 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -2,15 +2,14 @@ import os import re import sys import textwrap +from pathlib import Path from typing import Dict from typing import Generator from typing import Type -import py - import pytest from _pytest.monkeypatch import MonkeyPatch -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester @pytest.fixture @@ -233,8 +232,8 @@ def test_setenv_prepend() -> None: assert "XYZ123" not in os.environ -def test_monkeypatch_plugin(testdir: Testdir) -> None: - reprec = testdir.inline_runsource( +def test_monkeypatch_plugin(pytester: Pytester) -> None: + reprec = pytester.inline_runsource( """ def test_method(monkeypatch): assert monkeypatch.__class__.__name__ == "MonkeyPatch" @@ -268,33 +267,33 @@ def test_syspath_prepend_double_undo(mp: MonkeyPatch) -> None: sys.path[:] = old_syspath -def test_chdir_with_path_local(mp: MonkeyPatch, tmpdir: py.path.local) -> None: - mp.chdir(tmpdir) - assert os.getcwd() == tmpdir.strpath +def test_chdir_with_path_local(mp: MonkeyPatch, tmp_path: Path) -> None: + mp.chdir(tmp_path) + assert os.getcwd() == str(tmp_path) -def test_chdir_with_str(mp: MonkeyPatch, tmpdir: py.path.local) -> None: - mp.chdir(tmpdir.strpath) - assert os.getcwd() == tmpdir.strpath +def test_chdir_with_str(mp: MonkeyPatch, tmp_path: Path) -> None: + mp.chdir(str(tmp_path)) + assert os.getcwd() == str(tmp_path) -def test_chdir_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None: +def test_chdir_undo(mp: MonkeyPatch, tmp_path: Path) -> None: cwd = os.getcwd() - mp.chdir(tmpdir) + mp.chdir(tmp_path) mp.undo() assert os.getcwd() == cwd -def test_chdir_double_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None: - mp.chdir(tmpdir.strpath) +def test_chdir_double_undo(mp: MonkeyPatch, tmp_path: Path) -> None: + mp.chdir(str(tmp_path)) mp.undo() - tmpdir.chdir() + os.chdir(tmp_path) mp.undo() - assert os.getcwd() == tmpdir.strpath + assert os.getcwd() == str(tmp_path) -def test_issue185_time_breaks(testdir: Testdir) -> None: - testdir.makepyfile( +def test_issue185_time_breaks(pytester: Pytester) -> None: + pytester.makepyfile( """ import time def test_m(monkeypatch): @@ -303,7 +302,7 @@ def test_issue185_time_breaks(testdir: Testdir) -> None: monkeypatch.setattr(time, "time", f) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *1 passed* @@ -311,9 +310,9 @@ def test_issue185_time_breaks(testdir: Testdir) -> None: ) -def test_importerror(testdir: Testdir) -> None: - p = testdir.mkpydir("package") - p.join("a.py").write( +def test_importerror(pytester: Pytester) -> None: + p = pytester.mkpydir("package") + p.joinpath("a.py").write_text( textwrap.dedent( """\ import doesnotexist @@ -322,7 +321,7 @@ def test_importerror(testdir: Testdir) -> None: """ ) ) - testdir.tmpdir.join("test_importerror.py").write( + pytester.path.joinpath("test_importerror.py").write_text( textwrap.dedent( """\ def test_importerror(monkeypatch): @@ -330,7 +329,7 @@ def test_importerror(testdir: Testdir) -> None: """ ) ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines( """ *import error in package.a: No module named 'doesnotexist'* @@ -420,16 +419,18 @@ def test_context_classmethod() -> None: def test_syspath_prepend_with_namespace_packages( - testdir: Testdir, monkeypatch: MonkeyPatch + pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: for dirname in "hello", "world": - d = testdir.mkdir(dirname) - ns = d.mkdir("ns_pkg") - ns.join("__init__.py").write( + d = pytester.mkdir(dirname) + ns = d.joinpath("ns_pkg") + ns.mkdir() + ns.joinpath("__init__.py").write_text( "__import__('pkg_resources').declare_namespace(__name__)" ) - lib = ns.mkdir(dirname) - lib.join("__init__.py").write("def check(): return %r" % dirname) + lib = ns.joinpath(dirname) + lib.mkdir() + lib.joinpath("__init__.py").write_text("def check(): return %r" % dirname) monkeypatch.syspath_prepend("hello") import ns_pkg.hello @@ -446,8 +447,7 @@ def test_syspath_prepend_with_namespace_packages( assert ns_pkg.world.check() == "world" # Should invalidate caches via importlib.invalidate_caches. - tmpdir = testdir.tmpdir - modules_tmpdir = tmpdir.mkdir("modules_tmpdir") + modules_tmpdir = pytester.mkdir("modules_tmpdir") monkeypatch.syspath_prepend(str(modules_tmpdir)) - modules_tmpdir.join("main_app.py").write("app = True") + modules_tmpdir.joinpath("main_app.py").write_text("app = True") from main_app import app # noqa: F401 diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 89f10a7db..a5282a507 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,13 +1,17 @@ import os +import shutil import sys import types from typing import List import pytest +from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PytestPluginManager from _pytest.config.exceptions import UsageError from _pytest.main import Session +from _pytest.monkeypatch import MonkeyPatch +from _pytest.pathlib import import_path from _pytest.pytester import Pytester @@ -18,7 +22,7 @@ def pytestpm() -> PytestPluginManager: class TestPytestPluginInteractions: def test_addhooks_conftestplugin( - self, pytester: Pytester, _config_for_test + self, pytester: Pytester, _config_for_test: Config ) -> None: pytester.makepyfile( newhooks=""" @@ -45,15 +49,15 @@ class TestPytestPluginInteractions: res = config.hook.pytest_myhook(xyz=10) assert res == [11] - def test_addhooks_nohooks(self, testdir): - testdir.makeconftest( + def test_addhooks_nohooks(self, pytester: Pytester) -> None: + pytester.makeconftest( """ import sys def pytest_addhooks(pluginmanager): pluginmanager.add_hookspecs(sys) """ ) - res = testdir.runpytest() + res = pytester.runpytest() assert res.ret != 0 res.stderr.fnmatch_lines(["*did not find*sys*"]) @@ -70,8 +74,8 @@ class TestPytestPluginInteractions: config.pluginmanager._importconftest(p, importmode="prepend") assert config.option.test123 - def test_configure(self, testdir): - config = testdir.parseconfig() + def test_configure(self, pytester: Pytester) -> None: + config = pytester.parseconfig() values = [] class A: @@ -90,7 +94,7 @@ class TestPytestPluginInteractions: config.pluginmanager.register(A()) assert len(values) == 2 - def test_hook_tracing(self, _config_for_test) -> None: + def test_hook_tracing(self, _config_for_test: Config) -> None: pytestpm = _config_for_test.pluginmanager # fully initialized with plugins saveindent = [] @@ -139,9 +143,9 @@ class TestPytestPluginInteractions: ihook_b = session.gethookproxy(pytester.path / "tests") assert ihook_a is not ihook_b - def test_hook_with_addoption(self, testdir): + def test_hook_with_addoption(self, pytester: Pytester) -> None: """Test that hooks can be used in a call to pytest_addoption""" - testdir.makepyfile( + pytester.makepyfile( newhooks=""" import pytest @pytest.hookspec(firstresult=True) @@ -149,7 +153,7 @@ class TestPytestPluginInteractions: pass """ ) - testdir.makepyfile( + pytester.makepyfile( myplugin=""" import newhooks def pytest_addhooks(pluginmanager): @@ -159,30 +163,32 @@ class TestPytestPluginInteractions: parser.addoption("--config", help="Config, defaults to %(default)s", default=default_value) """ ) - testdir.makeconftest( + pytester.makeconftest( """ pytest_plugins=("myplugin",) def pytest_default_value(): return "default_value" """ ) - res = testdir.runpytest("--help") + res = pytester.runpytest("--help") res.stdout.fnmatch_lines(["*--config=CONFIG*default_value*"]) -def test_default_markers(testdir): - result = testdir.runpytest("--markers") +def test_default_markers(pytester: Pytester) -> None: + result = pytester.runpytest("--markers") result.stdout.fnmatch_lines(["*tryfirst*first*", "*trylast*last*"]) -def test_importplugin_error_message(testdir, pytestpm): +def test_importplugin_error_message( + pytester: Pytester, pytestpm: PytestPluginManager +) -> None: """Don't hide import errors when importing plugins and provide an easy to debug message. See #375 and #1998. """ - testdir.syspathinsert(testdir.tmpdir) - testdir.makepyfile( + pytester.syspathinsert(pytester.path) + pytester.makepyfile( qwe="""\ def test_traceback(): raise ImportError('Not possible to import: ☺') @@ -199,7 +205,7 @@ def test_importplugin_error_message(testdir, pytestpm): class TestPytestPluginManager: - def test_register_imported_modules(self): + def test_register_imported_modules(self) -> None: pm = PytestPluginManager() mod = types.ModuleType("x.y.pytest_hello") pm.register(mod) @@ -219,23 +225,27 @@ class TestPytestPluginManager: assert pm.get_plugin("pytest_xyz") == mod assert pm.is_registered(mod) - def test_consider_module(self, testdir, pytestpm: PytestPluginManager) -> None: - testdir.syspathinsert() - testdir.makepyfile(pytest_p1="#") - testdir.makepyfile(pytest_p2="#") + def test_consider_module( + self, pytester: Pytester, pytestpm: PytestPluginManager + ) -> None: + pytester.syspathinsert() + pytester.makepyfile(pytest_p1="#") + pytester.makepyfile(pytest_p2="#") mod = types.ModuleType("temp") mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"] pytestpm.consider_module(mod) assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1" assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2" - def test_consider_module_import_module(self, testdir, _config_for_test) -> None: + def test_consider_module_import_module( + self, pytester: Pytester, _config_for_test: Config + ) -> None: pytestpm = _config_for_test.pluginmanager mod = types.ModuleType("x") mod.__dict__["pytest_plugins"] = "pytest_a" - aplugin = testdir.makepyfile(pytest_a="#") - reprec = testdir.make_hook_recorder(pytestpm) - testdir.syspathinsert(aplugin.dirpath()) + aplugin = pytester.makepyfile(pytest_a="#") + reprec = pytester.make_hook_recorder(pytestpm) + pytester.syspathinsert(aplugin.parent) pytestpm.consider_module(mod) call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name) assert call.plugin.__name__ == "pytest_a" @@ -245,30 +255,37 @@ class TestPytestPluginManager: values = reprec.getcalls("pytest_plugin_registered") assert len(values) == 1 - def test_consider_env_fails_to_import(self, monkeypatch, pytestpm): + def test_consider_env_fails_to_import( + self, monkeypatch: MonkeyPatch, pytestpm: PytestPluginManager + ) -> None: monkeypatch.setenv("PYTEST_PLUGINS", "nonexisting", prepend=",") with pytest.raises(ImportError): pytestpm.consider_env() @pytest.mark.filterwarnings("always") - def test_plugin_skip(self, testdir, monkeypatch): - p = testdir.makepyfile( + def test_plugin_skip(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + p = pytester.makepyfile( skipping1=""" import pytest pytest.skip("hello", allow_module_level=True) """ ) - p.copy(p.dirpath("skipping2.py")) + shutil.copy(p, p.with_name("skipping2.py")) monkeypatch.setenv("PYTEST_PLUGINS", "skipping2") - result = testdir.runpytest("-p", "skipping1", syspathinsert=True) + result = pytester.runpytest("-p", "skipping1", syspathinsert=True) assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines( ["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"] ) - def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm): - testdir.syspathinsert() - testdir.makepyfile(xy123="#") + def test_consider_env_plugin_instantiation( + self, + pytester: Pytester, + monkeypatch: MonkeyPatch, + pytestpm: PytestPluginManager, + ) -> None: + pytester.syspathinsert() + pytester.makepyfile(xy123="#") monkeypatch.setitem(os.environ, "PYTEST_PLUGINS", "xy123") l1 = len(pytestpm.get_plugins()) pytestpm.consider_env() @@ -279,9 +296,11 @@ class TestPytestPluginManager: l3 = len(pytestpm.get_plugins()) assert l2 == l3 - def test_pluginmanager_ENV_startup(self, testdir, monkeypatch): - testdir.makepyfile(pytest_x500="#") - p = testdir.makepyfile( + def test_pluginmanager_ENV_startup( + self, pytester: Pytester, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile(pytest_x500="#") + p = pytester.makepyfile( """ import pytest def test_hello(pytestconfig): @@ -290,17 +309,19 @@ class TestPytestPluginManager: """ ) monkeypatch.setenv("PYTEST_PLUGINS", "pytest_x500", prepend=",") - result = testdir.runpytest(p, syspathinsert=True) + result = pytester.runpytest(p, syspathinsert=True) assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def test_import_plugin_importname(self, testdir, pytestpm): + def test_import_plugin_importname( + self, pytester: Pytester, pytestpm: PytestPluginManager + ) -> None: pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y") pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y") - testdir.syspathinsert() + pytester.syspathinsert() pluginname = "pytest_hello" - testdir.makepyfile(**{pluginname: ""}) + pytester.makepyfile(**{pluginname: ""}) pytestpm.import_plugin("pytest_hello") len1 = len(pytestpm.get_plugins()) pytestpm.import_plugin("pytest_hello") @@ -311,25 +332,29 @@ class TestPytestPluginManager: plugin2 = pytestpm.get_plugin("pytest_hello") assert plugin2 is plugin1 - def test_import_plugin_dotted_name(self, testdir, pytestpm): + def test_import_plugin_dotted_name( + self, pytester: Pytester, pytestpm: PytestPluginManager + ) -> None: pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y") pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y") - testdir.syspathinsert() - testdir.mkpydir("pkg").join("plug.py").write("x=3") + pytester.syspathinsert() + pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3") pluginname = "pkg.plug" pytestpm.import_plugin(pluginname) mod = pytestpm.get_plugin("pkg.plug") assert mod.x == 3 - def test_consider_conftest_deps(self, testdir, pytestpm): - mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() + def test_consider_conftest_deps( + self, pytester: Pytester, pytestpm: PytestPluginManager, + ) -> None: + mod = import_path(pytester.makepyfile("pytest_plugins='xyz'")) with pytest.raises(ImportError): pytestpm.consider_conftest(mod) class TestPytestPluginManagerBootstrapming: - def test_preparse_args(self, pytestpm): + def test_preparse_args(self, pytestpm: PytestPluginManager) -> None: pytest.raises( ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) ) @@ -346,7 +371,7 @@ class TestPytestPluginManagerBootstrapming: with pytest.raises(UsageError, match="^plugin main cannot be disabled$"): pytestpm.consider_preparse(["-p", "no:main"]) - def test_plugin_prevent_register(self, pytestpm): + def test_plugin_prevent_register(self, pytestpm: PytestPluginManager) -> None: pytestpm.consider_preparse(["xyz", "-p", "no:abc"]) l1 = pytestpm.get_plugins() pytestpm.register(42, name="abc") @@ -354,7 +379,9 @@ class TestPytestPluginManagerBootstrapming: assert len(l2) == len(l1) assert 42 not in l2 - def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm): + def test_plugin_prevent_register_unregistered_alredy_registered( + self, pytestpm: PytestPluginManager + ) -> None: pytestpm.register(42, name="abc") l1 = pytestpm.get_plugins() assert 42 in l1 @@ -363,8 +390,8 @@ class TestPytestPluginManagerBootstrapming: assert 42 not in l2 def test_plugin_prevent_register_stepwise_on_cacheprovider_unregister( - self, pytestpm - ): + self, pytestpm: PytestPluginManager + ) -> None: """From PR #4304: The only way to unregister a module is documented at the end of https://docs.pytest.org/en/stable/plugins.html. @@ -380,7 +407,7 @@ class TestPytestPluginManagerBootstrapming: assert 42 not in l2 assert 43 not in l2 - def test_blocked_plugin_can_be_used(self, pytestpm): + def test_blocked_plugin_can_be_used(self, pytestpm: PytestPluginManager) -> None: pytestpm.consider_preparse(["xyz", "-p", "no:abc", "-p", "abc"]) assert pytestpm.has_plugin("abc") diff --git a/testing/test_reports.py b/testing/test_reports.py index b97b1fc29..b376f6198 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,29 +1,30 @@ -from pathlib import Path from typing import Sequence from typing import Union +import py.path + import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr from _pytest.config import Config -from _pytest.pytester import Testdir +from _pytest.pytester import Pytester from _pytest.reports import CollectReport from _pytest.reports import TestReport class TestReportSerialization: - def test_xdist_longrepr_to_str_issue_241(self, testdir: Testdir) -> None: + def test_xdist_longrepr_to_str_issue_241(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#241. This test came originally from test_remote.py in xdist (ca03269). """ - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): assert False def test_b(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 6 test_a_call = reports[1] @@ -35,12 +36,12 @@ class TestReportSerialization: assert test_b_call.outcome == "passed" assert test_b_call._to_json()["longrepr"] is None - def test_xdist_report_longrepr_reprcrash_130(self, testdir: Testdir) -> None: + def test_xdist_report_longrepr_reprcrash_130(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#130 This test came originally from test_remote.py in xdist (ca03269). """ - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ def test_fail(): assert False, 'Expected Message' @@ -74,14 +75,14 @@ class TestReportSerialization: # Missing section attribute PR171 assert added_section in a.longrepr.sections - def test_reprentries_serialization_170(self, testdir: Testdir) -> None: + def test_reprentries_serialization_170(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#170 This test came originally from test_remote.py in xdist (ca03269). """ from _pytest._code.code import ReprEntry - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ def test_repr_entry(): x = 0 @@ -120,14 +121,14 @@ class TestReportSerialization: assert rep_entry.reprlocals.lines == a_entry.reprlocals.lines assert rep_entry.style == a_entry.style - def test_reprentries_serialization_196(self, testdir: Testdir) -> None: + def test_reprentries_serialization_196(self, pytester: Pytester) -> None: """Regarding issue pytest-xdist#196 This test came originally from test_remote.py in xdist (ca03269). """ from _pytest._code.code import ReprEntryNative - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ def test_repr_entry_native(): x = 0 @@ -149,9 +150,9 @@ class TestReportSerialization: assert isinstance(rep_entries[i], ReprEntryNative) assert rep_entries[i].lines == a_entries[i].lines - def test_itemreport_outcomes(self, testdir: Testdir) -> None: + def test_itemreport_outcomes(self, pytester: Pytester) -> None: # This test came originally from test_remote.py in xdist (ca03269). - reprec = testdir.inline_runsource( + reprec = pytester.inline_runsource( """ import pytest def test_pass(): pass @@ -183,9 +184,9 @@ class TestReportSerialization: if rep.failed: assert newrep.longreprtext == rep.longreprtext - def test_collectreport_passed(self, testdir: Testdir) -> None: + def test_collectreport_passed(self, pytester: Pytester) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" - reprec = testdir.inline_runsource("def test_func(): pass") + reprec = pytester.inline_runsource("def test_func(): pass") reports = reprec.getreports("pytest_collectreport") for rep in reports: d = rep._to_json() @@ -194,9 +195,9 @@ class TestReportSerialization: assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped - def test_collectreport_fail(self, testdir: Testdir) -> None: + def test_collectreport_fail(self, pytester: Pytester) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" - reprec = testdir.inline_runsource("qwe abc") + reprec = pytester.inline_runsource("qwe abc") reports = reprec.getreports("pytest_collectreport") assert reports for rep in reports: @@ -208,9 +209,9 @@ class TestReportSerialization: if rep.failed: assert newrep.longrepr == str(rep.longrepr) - def test_extended_report_deserialization(self, testdir: Testdir) -> None: + def test_extended_report_deserialization(self, pytester: Pytester) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" - reprec = testdir.inline_runsource("qwe abc") + reprec = pytester.inline_runsource("qwe abc") reports = reprec.getreports("pytest_collectreport") assert reports for rep in reports: @@ -224,33 +225,33 @@ class TestReportSerialization: if rep.failed: assert newrep.longrepr == str(rep.longrepr) - def test_paths_support(self, testdir: Testdir) -> None: + def test_paths_support(self, pytester: Pytester) -> None: """Report attributes which are py.path or pathlib objects should become strings.""" - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): assert False """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 test_a_call = reports[1] - test_a_call.path1 = testdir.tmpdir # type: ignore[attr-defined] - test_a_call.path2 = Path(testdir.tmpdir) # type: ignore[attr-defined] + test_a_call.path1 = py.path.local(pytester.path) # type: ignore[attr-defined] + test_a_call.path2 = pytester.path # type: ignore[attr-defined] data = test_a_call._to_json() - assert data["path1"] == str(testdir.tmpdir) - assert data["path2"] == str(testdir.tmpdir) + assert data["path1"] == str(pytester.path) + assert data["path2"] == str(pytester.path) - def test_deserialization_failure(self, testdir: Testdir) -> None: + def test_deserialization_failure(self, pytester: Pytester) -> None: """Check handling of failure during deserialization of report types.""" - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): assert False """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 test_a_call = reports[1] @@ -265,9 +266,11 @@ class TestReportSerialization: TestReport._from_json(data) @pytest.mark.parametrize("report_class", [TestReport, CollectReport]) - def test_chained_exceptions(self, testdir: Testdir, tw_mock, report_class) -> None: + def test_chained_exceptions( + self, pytester: Pytester, tw_mock, report_class + ) -> None: """Check serialization/deserialization of report objects containing chained exceptions (#5786)""" - testdir.makepyfile( + pytester.makepyfile( """ def foo(): raise ValueError('value error') @@ -283,7 +286,7 @@ class TestReportSerialization: ) ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() if report_class is TestReport: reports: Union[ Sequence[TestReport], Sequence[CollectReport] @@ -338,14 +341,14 @@ class TestReportSerialization: # elsewhere and we do check the contents of the longrepr object after loading it. loaded_report.longrepr.toterminal(tw_mock) - def test_chained_exceptions_no_reprcrash(self, testdir: Testdir, tw_mock) -> None: + def test_chained_exceptions_no_reprcrash(self, pytester: Pytester, tw_mock) -> None: """Regression test for tracebacks without a reprcrash (#5971) This happens notably on exceptions raised by multiprocess.pool: the exception transfer from subprocess to main process creates an artificial exception, which ExceptionInfo can't obtain the ReprFileLocation from. """ - testdir.makepyfile( + pytester.makepyfile( """ from concurrent.futures import ProcessPoolExecutor @@ -358,8 +361,8 @@ class TestReportSerialization: """ ) - testdir.syspathinsert() - reprec = testdir.inline_run() + pytester.syspathinsert() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") @@ -396,12 +399,13 @@ class TestReportSerialization: loaded_report.longrepr.toterminal(tw_mock) def test_report_prevent_ConftestImportFailure_hiding_exception( - self, testdir: Testdir + self, pytester: Pytester ) -> None: - sub_dir = testdir.tmpdir.join("ns").ensure_dir() - sub_dir.join("conftest").new(ext=".py").write("import unknown") + sub_dir = pytester.path.joinpath("ns") + sub_dir.mkdir() + sub_dir.joinpath("conftest.py").write_text("import unknown") - result = testdir.runpytest_subprocess(".") + result = pytester.runpytest_subprocess(".") result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") @@ -409,14 +413,14 @@ class TestReportSerialization: class TestHooks: """Test that the hooks are working correctly for plugins""" - def test_test_report(self, testdir: Testdir, pytestconfig: Config) -> None: - testdir.makepyfile( + def test_test_report(self, pytester: Pytester, pytestconfig: Config) -> None: + pytester.makepyfile( """ def test_a(): assert False def test_b(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 6 for rep in reports: @@ -431,14 +435,14 @@ class TestHooks: assert new_rep.when == rep.when assert new_rep.outcome == rep.outcome - def test_collect_report(self, testdir: Testdir, pytestconfig: Config) -> None: - testdir.makepyfile( + def test_collect_report(self, pytester: Pytester, pytestconfig: Config) -> None: + pytester.makepyfile( """ def test_a(): assert False def test_b(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports("pytest_collectreport") assert len(reports) == 2 for rep in reports: @@ -457,14 +461,14 @@ class TestHooks: "hook_name", ["pytest_runtest_logreport", "pytest_collectreport"] ) def test_invalid_report_types( - self, testdir: Testdir, pytestconfig: Config, hook_name: str + self, pytester: Pytester, pytestconfig: Config, hook_name: str ) -> None: - testdir.makepyfile( + pytester.makepyfile( """ def test_a(): pass """ ) - reprec = testdir.inline_run() + reprec = pytester.inline_run() reports = reprec.getreports(hook_name) assert reports rep = reports[0] diff --git a/testing/test_runner.py b/testing/test_runner.py index a1f1db48d..8ce0f6735 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -2,26 +2,28 @@ import inspect import os import sys import types +from pathlib import Path from typing import Dict from typing import List from typing import Tuple from typing import Type -import py - -import _pytest._code import pytest from _pytest import outcomes from _pytest import reports from _pytest import runner +from _pytest._code import ExceptionInfo +from _pytest._code.code import ExceptionChainRepr from _pytest.config import ExitCode +from _pytest.monkeypatch import MonkeyPatch from _pytest.outcomes import OutcomeException +from _pytest.pytester import Pytester class TestSetupState: - def test_setup(self, testdir) -> None: + def test_setup(self, pytester: Pytester) -> None: ss = runner.SetupState() - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") values = [1] ss.prepare(item) ss.addfinalizer(values.pop, colitem=item) @@ -29,15 +31,15 @@ class TestSetupState: ss._pop_and_teardown() assert not values - def test_teardown_exact_stack_empty(self, testdir) -> None: - item = testdir.getitem("def test_func(): pass") + def test_teardown_exact_stack_empty(self, pytester: Pytester) -> None: + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.teardown_exact(item, None) ss.teardown_exact(item, None) ss.teardown_exact(item, None) - def test_setup_fails_and_failure_is_cached(self, testdir) -> None: - item = testdir.getitem( + def test_setup_fails_and_failure_is_cached(self, pytester: Pytester) -> None: + item = pytester.getitem( """ def setup_module(mod): raise ValueError(42) @@ -48,7 +50,7 @@ class TestSetupState: pytest.raises(ValueError, lambda: ss.prepare(item)) pytest.raises(ValueError, lambda: ss.prepare(item)) - def test_teardown_multiple_one_fails(self, testdir) -> None: + def test_teardown_multiple_one_fails(self, pytester: Pytester) -> None: r = [] def fin1(): @@ -60,7 +62,7 @@ class TestSetupState: def fin3(): r.append("fin3") - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) @@ -70,7 +72,7 @@ class TestSetupState: assert err.value.args == ("oops",) assert r == ["fin3", "fin1"] - def test_teardown_multiple_fail(self, testdir) -> None: + def test_teardown_multiple_fail(self, pytester: Pytester) -> None: # Ensure the first exception is the one which is re-raised. # Ideally both would be reported however. def fin1(): @@ -79,7 +81,7 @@ class TestSetupState: def fin2(): raise Exception("oops2") - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) ss.addfinalizer(fin2, item) @@ -87,7 +89,7 @@ class TestSetupState: ss._callfinalizers(item) assert err.value.args == ("oops2",) - def test_teardown_multiple_scopes_one_fails(self, testdir) -> None: + def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None: module_teardown = [] def fin_func(): @@ -96,7 +98,7 @@ class TestSetupState: def fin_module(): module_teardown.append("fin_module") - item = testdir.getitem("def test_func(): pass") + item = pytester.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin_module, item.listchain()[-2]) ss.addfinalizer(fin_func, item) @@ -107,8 +109,8 @@ class TestSetupState: class BaseFunctionalTests: - def test_passfunction(self, testdir) -> None: - reports = testdir.runitem( + def test_passfunction(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): pass @@ -120,8 +122,8 @@ class BaseFunctionalTests: assert rep.outcome == "passed" assert not rep.longrepr - def test_failfunction(self, testdir) -> None: - reports = testdir.runitem( + def test_failfunction(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): assert 0 @@ -135,8 +137,8 @@ class BaseFunctionalTests: assert rep.outcome == "failed" # assert isinstance(rep.longrepr, ReprExceptionInfo) - def test_skipfunction(self, testdir) -> None: - reports = testdir.runitem( + def test_skipfunction(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def test_func(): @@ -155,8 +157,8 @@ class BaseFunctionalTests: # assert rep.skipped.location.path # assert not rep.skipped.failurerepr - def test_skip_in_setup_function(self, testdir) -> None: - reports = testdir.runitem( + def test_skip_in_setup_function(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def setup_function(func): @@ -176,8 +178,8 @@ class BaseFunctionalTests: assert len(reports) == 2 assert reports[1].passed # teardown - def test_failure_in_setup_function(self, testdir) -> None: - reports = testdir.runitem( + def test_failure_in_setup_function(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def setup_function(func): @@ -193,8 +195,8 @@ class BaseFunctionalTests: assert rep.when == "setup" assert len(reports) == 2 - def test_failure_in_teardown_function(self, testdir) -> None: - reports = testdir.runitem( + def test_failure_in_teardown_function(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def teardown_function(func): @@ -213,8 +215,8 @@ class BaseFunctionalTests: # assert rep.longrepr.reprcrash.lineno == 3 # assert rep.longrepr.reprtraceback.reprentries - def test_custom_failure_repr(self, testdir) -> None: - testdir.makepyfile( + def test_custom_failure_repr(self, pytester: Pytester) -> None: + pytester.makepyfile( conftest=""" import pytest class Function(pytest.Function): @@ -222,7 +224,7 @@ class BaseFunctionalTests: return "hello" """ ) - reports = testdir.runitem( + reports = pytester.runitem( """ import pytest def test_func(): @@ -238,8 +240,8 @@ class BaseFunctionalTests: # assert rep.failed.where.path.basename == "test_func.py" # assert rep.failed.failurerepr == "hello" - def test_teardown_final_returncode(self, testdir) -> None: - rec = testdir.inline_runsource( + def test_teardown_final_returncode(self, pytester: Pytester) -> None: + rec = pytester.inline_runsource( """ def test_func(): pass @@ -249,8 +251,8 @@ class BaseFunctionalTests: ) assert rec.ret == 1 - def test_logstart_logfinish_hooks(self, testdir) -> None: - rec = testdir.inline_runsource( + def test_logstart_logfinish_hooks(self, pytester: Pytester) -> None: + rec = pytester.inline_runsource( """ import pytest def test_func(): @@ -266,8 +268,8 @@ class BaseFunctionalTests: assert rep.nodeid == "test_logstart_logfinish_hooks.py::test_func" assert rep.location == ("test_logstart_logfinish_hooks.py", 1, "test_func") - def test_exact_teardown_issue90(self, testdir) -> None: - rec = testdir.inline_runsource( + def test_exact_teardown_issue90(self, pytester: Pytester) -> None: + rec = pytester.inline_runsource( """ import pytest @@ -306,9 +308,9 @@ class BaseFunctionalTests: assert reps[5].nodeid.endswith("test_func") assert reps[5].failed - def test_exact_teardown_issue1206(self, testdir) -> None: + def test_exact_teardown_issue1206(self, pytester: Pytester) -> None: """Issue shadowing error with wrong number of arguments on teardown_method.""" - rec = testdir.inline_runsource( + rec = pytester.inline_runsource( """ import pytest @@ -335,14 +337,19 @@ class BaseFunctionalTests: assert reps[2].nodeid.endswith("test_method") assert reps[2].failed assert reps[2].when == "teardown" - assert reps[2].longrepr.reprcrash.message in ( + longrepr = reps[2].longrepr + assert isinstance(longrepr, ExceptionChainRepr) + assert longrepr.reprcrash + assert longrepr.reprcrash.message in ( "TypeError: teardown_method() missing 2 required positional arguments: 'y' and 'z'", # Python >= 3.10 "TypeError: TestClass.teardown_method() missing 2 required positional arguments: 'y' and 'z'", ) - def test_failure_in_setup_function_ignores_custom_repr(self, testdir) -> None: - testdir.makepyfile( + def test_failure_in_setup_function_ignores_custom_repr( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( conftest=""" import pytest class Function(pytest.Function): @@ -350,7 +357,7 @@ class BaseFunctionalTests: assert 0 """ ) - reports = testdir.runitem( + reports = pytester.runitem( """ def setup_function(func): raise ValueError(42) @@ -369,9 +376,9 @@ class BaseFunctionalTests: # assert rep.outcome.where.path.basename == "test_func.py" # assert instanace(rep.failed.failurerepr, PythonFailureRepr) - def test_systemexit_does_not_bail_out(self, testdir) -> None: + def test_systemexit_does_not_bail_out(self, pytester: Pytester) -> None: try: - reports = testdir.runitem( + reports = pytester.runitem( """ def test_func(): raise SystemExit(42) @@ -383,9 +390,9 @@ class BaseFunctionalTests: assert rep.failed assert rep.when == "call" - def test_exit_propagates(self, testdir) -> None: + def test_exit_propagates(self, pytester: Pytester) -> None: try: - testdir.runitem( + pytester.runitem( """ import pytest def test_func(): @@ -405,9 +412,9 @@ class TestExecutionNonForked(BaseFunctionalTests): return f - def test_keyboardinterrupt_propagates(self, testdir) -> None: + def test_keyboardinterrupt_propagates(self, pytester: Pytester) -> None: try: - testdir.runitem( + pytester.runitem( """ def test_func(): raise KeyboardInterrupt("fake") @@ -420,8 +427,8 @@ class TestExecutionNonForked(BaseFunctionalTests): class TestSessionReports: - def test_collect_result(self, testdir) -> None: - col = testdir.getmodulecol( + def test_collect_result(self, pytester: Pytester) -> None: + col = pytester.getmodulecol( """ def test_func1(): pass @@ -489,8 +496,8 @@ def test_callinfo() -> None: @pytest.mark.xfail -def test_runtest_in_module_ordering(testdir) -> None: - p1 = testdir.makepyfile( +def test_runtest_in_module_ordering(pytester: Pytester) -> None: + p1 = pytester.makepyfile( """ import pytest def pytest_runtest_setup(item): # runs after class-level! @@ -517,7 +524,7 @@ def test_runtest_in_module_ordering(testdir) -> None: del item.function.mylist """ ) - result = testdir.runpytest(p1) + result = pytester.runpytest(p1) result.stdout.fnmatch_lines(["*2 passed*"]) @@ -547,8 +554,8 @@ def test_pytest_fail() -> None: assert s.startswith("Failed") -def test_pytest_exit_msg(testdir) -> None: - testdir.makeconftest( +def test_pytest_exit_msg(pytester: Pytester) -> None: + pytester.makeconftest( """ import pytest @@ -556,7 +563,7 @@ def test_pytest_exit_msg(testdir) -> None: pytest.exit('oh noes') """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stderr.fnmatch_lines(["Exit: oh noes"]) @@ -570,22 +577,22 @@ def _strip_resource_warnings(lines): ] -def test_pytest_exit_returncode(testdir) -> None: - testdir.makepyfile( +def test_pytest_exit_returncode(pytester: Pytester) -> None: + pytester.makepyfile( """\ import pytest def test_foo(): pytest.exit("some exit msg", 99) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*! *Exit: some exit msg !*"]) assert _strip_resource_warnings(result.stderr.lines) == [] assert result.ret == 99 # It prints to stderr also in case of exit during pytest_sessionstart. - testdir.makeconftest( + pytester.makeconftest( """\ import pytest @@ -593,7 +600,7 @@ def test_pytest_exit_returncode(testdir) -> None: pytest.exit("during_sessionstart", 98) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*! *Exit: during_sessionstart !*"]) assert _strip_resource_warnings(result.stderr.lines) == [ "Exit: during_sessionstart" @@ -601,9 +608,9 @@ def test_pytest_exit_returncode(testdir) -> None: assert result.ret == 98 -def test_pytest_fail_notrace_runtest(testdir) -> None: +def test_pytest_fail_notrace_runtest(pytester: Pytester) -> None: """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest def test_hello(): @@ -612,14 +619,14 @@ def test_pytest_fail_notrace_runtest(testdir) -> None: pytest.fail("world", pytrace=False) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["world", "hello"]) result.stdout.no_fnmatch_line("*def teardown_function*") -def test_pytest_fail_notrace_collection(testdir) -> None: +def test_pytest_fail_notrace_collection(pytester: Pytester) -> None: """Test pytest.fail(..., pytrace=False) does not show tracebacks during collection.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest def some_internal_function(): @@ -627,17 +634,17 @@ def test_pytest_fail_notrace_collection(testdir) -> None: some_internal_function() """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["hello"]) result.stdout.no_fnmatch_line("*def some_internal_function()*") -def test_pytest_fail_notrace_non_ascii(testdir) -> None: +def test_pytest_fail_notrace_non_ascii(pytester: Pytester) -> None: """Fix pytest.fail with pytrace=False with non-ascii characters (#1178). This tests with native and unicode strings containing non-ascii chars. """ - testdir.makepyfile( + pytester.makepyfile( """\ import pytest @@ -645,28 +652,28 @@ def test_pytest_fail_notrace_non_ascii(testdir) -> None: pytest.fail('oh oh: ☺', pytrace=False) """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*test_hello*", "oh oh: ☺"]) result.stdout.no_fnmatch_line("*def test_hello*") -def test_pytest_no_tests_collected_exit_status(testdir) -> None: - result = testdir.runpytest() +def test_pytest_no_tests_collected_exit_status(pytester: Pytester) -> None: + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 0 items*"]) assert result.ret == ExitCode.NO_TESTS_COLLECTED - testdir.makepyfile( + pytester.makepyfile( test_foo=""" def test_foo(): assert 1 """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*1 passed*"]) assert result.ret == ExitCode.OK - result = testdir.runpytest("-k nonmatch") + result = pytester.runpytest("-k nonmatch") result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*1 deselected*"]) assert result.ret == ExitCode.NO_TESTS_COLLECTED @@ -677,7 +684,7 @@ def test_exception_printing_skip() -> None: try: pytest.skip("hello") except pytest.skip.Exception: - excinfo = _pytest._code.ExceptionInfo.from_current() + excinfo = ExceptionInfo.from_current() s = excinfo.exconly(tryshort=True) assert s.startswith("Skipped") @@ -698,10 +705,10 @@ def test_importorskip(monkeypatch) -> None: excrepr = excinfo.getrepr() assert excrepr is not None assert excrepr.reprcrash is not None - path = py.path.local(excrepr.reprcrash.path) + path = Path(excrepr.reprcrash.path) # check that importorskip reports the actual call # in this test the test_runner.py file - assert path.purebasename == "test_runner" + assert path.stem == "test_runner" pytest.raises(SyntaxError, pytest.importorskip, "x y z") pytest.raises(SyntaxError, pytest.importorskip, "x=y") mod = types.ModuleType("hello123") @@ -712,9 +719,7 @@ def test_importorskip(monkeypatch) -> None: mod2 = pytest.importorskip("hello123", minversion="1.3") assert mod2 == mod except pytest.skip.Exception: # pragma: no cover - assert False, "spurious skip: {}".format( - _pytest._code.ExceptionInfo.from_current() - ) + assert False, f"spurious skip: {ExceptionInfo.from_current()}" def test_importorskip_imports_last_module_part() -> None: @@ -732,14 +737,12 @@ def test_importorskip_dev_module(monkeypatch) -> None: with pytest.raises(pytest.skip.Exception): pytest.importorskip("mockmodule1", minversion="0.14.0") except pytest.skip.Exception: # pragma: no cover - assert False, "spurious skip: {}".format( - _pytest._code.ExceptionInfo.from_current() - ) + assert False, f"spurious skip: {ExceptionInfo.from_current()}" -def test_importorskip_module_level(testdir) -> None: +def test_importorskip_module_level(pytester: Pytester) -> None: """`importorskip` must be able to skip entire modules when used at module level.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest foobarbaz = pytest.importorskip("foobarbaz") @@ -748,13 +751,13 @@ def test_importorskip_module_level(testdir) -> None: pass """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) -def test_importorskip_custom_reason(testdir) -> None: +def test_importorskip_custom_reason(pytester: Pytester) -> None: """Make sure custom reasons are used.""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest foobarbaz = pytest.importorskip("foobarbaz2", reason="just because") @@ -763,13 +766,13 @@ def test_importorskip_custom_reason(testdir) -> None: pass """ ) - result = testdir.runpytest("-ra") + result = pytester.runpytest("-ra") result.stdout.fnmatch_lines(["*just because*"]) result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) -def test_pytest_cmdline_main(testdir) -> None: - p = testdir.makepyfile( +def test_pytest_cmdline_main(pytester: Pytester) -> None: + p = pytester.makepyfile( """ import pytest def test_hello(): @@ -786,8 +789,8 @@ def test_pytest_cmdline_main(testdir) -> None: assert ret == 0 -def test_unicode_in_longrepr(testdir) -> None: - testdir.makeconftest( +def test_unicode_in_longrepr(pytester: Pytester) -> None: + pytester.makeconftest( """\ import pytest @pytest.hookimpl(hookwrapper=True) @@ -798,19 +801,19 @@ def test_unicode_in_longrepr(testdir) -> None: rep.longrepr = 'ä' """ ) - testdir.makepyfile( + pytester.makepyfile( """ def test_out(): assert 0 """ ) - result = testdir.runpytest() + result = pytester.runpytest() assert result.ret == 1 assert "UnicodeEncodeError" not in result.stderr.str() -def test_failure_in_setup(testdir) -> None: - testdir.makepyfile( +def test_failure_in_setup(pytester: Pytester) -> None: + pytester.makepyfile( """ def setup_module(): 0/0 @@ -818,24 +821,26 @@ def test_failure_in_setup(testdir) -> None: pass """ ) - result = testdir.runpytest("--tb=line") + result = pytester.runpytest("--tb=line") result.stdout.no_fnmatch_line("*def setup_module*") -def test_makereport_getsource(testdir) -> None: - testdir.makepyfile( +def test_makereport_getsource(pytester: Pytester) -> None: + pytester.makepyfile( """ def test_foo(): if False: pass else: assert False """ ) - result = testdir.runpytest() + result = pytester.runpytest() result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stdout.fnmatch_lines(["*else: assert False*"]) -def test_makereport_getsource_dynamic_code(testdir, monkeypatch) -> None: +def test_makereport_getsource_dynamic_code( + pytester: Pytester, monkeypatch: MonkeyPatch +) -> None: """Test that exception in dynamically generated code doesn't break getting the source line.""" import inspect @@ -849,7 +854,7 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch) -> None: monkeypatch.setattr(inspect, "findsource", findsource) - testdir.makepyfile( + pytester.makepyfile( """ import pytest @@ -861,7 +866,7 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch) -> None: assert False """ ) - result = testdir.runpytest("-vv") + result = pytester.runpytest("-vv") result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"]) @@ -896,12 +901,12 @@ def test_store_except_info_on_error() -> None: assert not hasattr(sys, "last_traceback") -def test_current_test_env_var(testdir, monkeypatch) -> None: +def test_current_test_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: pytest_current_test_vars: List[Tuple[str, str]] = [] monkeypatch.setattr( sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False ) - testdir.makepyfile( + pytester.makepyfile( """ import pytest import sys @@ -917,7 +922,7 @@ def test_current_test_env_var(testdir, monkeypatch) -> None: sys.pytest_current_test_vars.append(('call', os.environ['PYTEST_CURRENT_TEST'])) """ ) - result = testdir.runpytest_inprocess() + result = pytester.runpytest_inprocess() assert result.ret == 0 test_id = "test_current_test_env_var.py::test" assert pytest_current_test_vars == [ @@ -934,8 +939,8 @@ class TestReportContents: def getrunner(self): return lambda item: runner.runtestprotocol(item, log=False) - def test_longreprtext_pass(self, testdir) -> None: - reports = testdir.runitem( + def test_longreprtext_pass(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): pass @@ -944,9 +949,9 @@ class TestReportContents: rep = reports[1] assert rep.longreprtext == "" - def test_longreprtext_skip(self, testdir) -> None: + def test_longreprtext_skip(self, pytester: Pytester) -> None: """TestReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)""" - reports = testdir.runitem( + reports = pytester.runitem( """ import pytest def test_func(): @@ -957,22 +962,22 @@ class TestReportContents: assert isinstance(call_rep.longrepr, tuple) assert "Skipped" in call_rep.longreprtext - def test_longreprtext_collect_skip(self, testdir) -> None: + def test_longreprtext_collect_skip(self, pytester: Pytester) -> None: """CollectReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)""" - testdir.makepyfile( + pytester.makepyfile( """ import pytest pytest.skip(allow_module_level=True) """ ) - rec = testdir.inline_run() + rec = pytester.inline_run() calls = rec.getcalls("pytest_collectreport") _, call = calls assert isinstance(call.report.longrepr, tuple) assert "Skipped" in call.report.longreprtext - def test_longreprtext_failure(self, testdir) -> None: - reports = testdir.runitem( + def test_longreprtext_failure(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): x = 1 @@ -982,8 +987,8 @@ class TestReportContents: rep = reports[1] assert "assert 1 == 4" in rep.longreprtext - def test_captured_text(self, testdir) -> None: - reports = testdir.runitem( + def test_captured_text(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest import sys @@ -1012,8 +1017,8 @@ class TestReportContents: assert call.capstderr == "setup: stderr\ncall: stderr\n" assert teardown.capstderr == "setup: stderr\ncall: stderr\nteardown: stderr\n" - def test_no_captured_text(self, testdir) -> None: - reports = testdir.runitem( + def test_no_captured_text(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ def test_func(): pass @@ -1023,8 +1028,8 @@ class TestReportContents: assert rep.capstdout == "" assert rep.capstderr == "" - def test_longrepr_type(self, testdir) -> None: - reports = testdir.runitem( + def test_longrepr_type(self, pytester: Pytester) -> None: + reports = pytester.runitem( """ import pytest def test_func(): @@ -1032,7 +1037,7 @@ class TestReportContents: """ ) rep = reports[1] - assert isinstance(rep.longrepr, _pytest._code.code.ExceptionRepr) + assert isinstance(rep.longrepr, ExceptionChainRepr) def test_outcome_exception_bad_msg() -> None: