diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bbc48adb49a..0416b8cf406 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,14 +31,12 @@ jobs: fail-fast: false matrix: name: [ - "windows-py36", "windows-py37", "windows-py37-pluggy", "windows-py38", "windows-py39", "windows-py310", - "ubuntu-py36", "ubuntu-py37", "ubuntu-py37-pluggy", "ubuntu-py37-freeze", @@ -56,10 +54,6 @@ jobs: ] include: - - name: "windows-py36" - python: "3.6" - os: windows-latest - tox_env: "py36-xdist" - name: "windows-py37" python: "3.7" os: windows-latest @@ -82,10 +76,6 @@ jobs: os: windows-latest tox_env: "py310-xdist" - - name: "ubuntu-py36" - python: "3.6" - os: ubuntu-latest - tox_env: "py36-xdist" - name: "ubuntu-py37" python: "3.7" os: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a740045015..f853528d537 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,12 +32,12 @@ repos: rev: v2.6.0 hooks: - id: reorder-python-imports - args: ['--application-directories=.:src', --py36-plus] + args: ['--application-directories=.:src', --py37-plus] - repo: https://github.com/asottile/pyupgrade rev: v2.29.1 hooks: - id: pyupgrade - args: [--py36-plus] + args: [--py37-plus] - repo: https://github.com/asottile/setup-cfg-fmt rev: v1.20.0 hooks: diff --git a/README.rst b/README.rst index 14733765173..f0fe3563219 100644 --- a/README.rst +++ b/README.rst @@ -100,7 +100,7 @@ Features - Can run `unittest `_ (or trial), `nose `_ test suites out of the box -- Python 3.6+ and PyPy3 +- Python 3.7+ or PyPy3 - Rich plugin architecture, with over 850+ `external plugins `_ and thriving community diff --git a/changelog/9437.breaking.rst b/changelog/9437.breaking.rst new file mode 100644 index 00000000000..60d4337f420 --- /dev/null +++ b/changelog/9437.breaking.rst @@ -0,0 +1 @@ +Dropped support for Python 3.6, which reached `end-of-life `__ at 2021-12-23. diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 6b2ff672c59..47eed9c9354 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -9,7 +9,7 @@ Get Started Install ``pytest`` ---------------------------------------- -``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3. +``pytest`` requires: Python 3.7+ or PyPy3. 1. Run the following command in your command line: diff --git a/doc/en/how-to/skipping.rst b/doc/en/how-to/skipping.rst index 9b74628d59f..9e58dc49e29 100644 --- a/doc/en/how-to/skipping.rst +++ b/doc/en/how-to/skipping.rst @@ -84,14 +84,14 @@ It is also possible to skip the whole module using If you wish to skip something conditionally then you can use ``skipif`` instead. Here is an example of marking a test function to be skipped -when run on an interpreter earlier than Python3.6: +when run on an interpreter earlier than Python3.10: .. code-block:: python import sys - @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher") + @pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher") def test_function(): ... diff --git a/doc/en/index.rst b/doc/en/index.rst index 3d7c2f53709..da0d3bd4220 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -17,7 +17,7 @@ The ``pytest`` framework makes it easy to write small, readable tests, and can scale to support complex functional testing for applications and libraries. -**Pythons**: ``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3. +``pytest`` requires: Python 3.7+ or PyPy3. **PyPI package name**: :pypi:`pytest` @@ -78,7 +78,7 @@ Features - Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box -- Python 3.6+ and PyPy 3 +- Python 3.7+ or PyPy 3 - Rich plugin architecture, with over 800+ :ref:`external plugins ` and thriving community diff --git a/pyproject.toml b/pyproject.toml index 5d32b755c74..70a9406a66e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,8 +28,6 @@ filterwarnings = [ "default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*", # distutils is deprecated in 3.10, scheduled for removal in 3.12 "ignore:The distutils package is deprecated:DeprecationWarning", - # produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8)." - "ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))", # produced by pytest-xdist "ignore:.*type argument to addoption.*:DeprecationWarning", # produced on execnet (pytest-xdist) diff --git a/setup.cfg b/setup.cfg index 26a5d2e63e5..fe6ea4095bc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,6 @@ classifiers = Operating System :: POSIX Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -51,7 +50,7 @@ install_requires = atomicwrites>=1.0;sys_platform=="win32" colorama;sys_platform=="win32" importlib-metadata>=0.12;python_version<"3.8" -python_requires = >=3.6 +python_requires = >=3.7 package_dir = =src setup_requires = diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 50b367e25ef..e4007af2594 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -293,9 +293,8 @@ def _write_pyc_fp( # import. However, there's little reason to deviate. fp.write(importlib.util.MAGIC_NUMBER) # https://www.python.org/dev/peps/pep-0552/ - if sys.version_info >= (3, 7): - flags = b"\x00\x00\x00\x00" - fp.write(flags) + flags = b"\x00\x00\x00\x00" + fp.write(flags) # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) mtime = int(source_stat.st_mtime) & 0xFFFFFFFF size = source_stat.st_size & 0xFFFFFFFF @@ -376,31 +375,29 @@ def _read_pyc( except OSError: return None with fp: - # https://www.python.org/dev/peps/pep-0552/ - has_flags = sys.version_info >= (3, 7) try: stat_result = os.stat(source) mtime = int(stat_result.st_mtime) size = stat_result.st_size - data = fp.read(16 if has_flags else 12) + data = fp.read(16) except OSError as e: trace(f"_read_pyc({source}): OSError {e}") return None # Check for invalid or out of date pyc file. - if len(data) != (16 if has_flags else 12): + if len(data) != (16): trace("_read_pyc(%s): invalid pyc (too short)" % source) return None if data[:4] != importlib.util.MAGIC_NUMBER: trace("_read_pyc(%s): invalid pyc (bad magic number)" % source) return None - if has_flags and data[4:8] != b"\x00\x00\x00\x00": + if data[4:8] != b"\x00\x00\x00\x00": trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source) return None - mtime_data = data[8 if has_flags else 4 : 12 if has_flags else 8] + mtime_data = data[8:12] if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: trace("_read_pyc(%s): out of date" % source) return None - size_data = data[12 if has_flags else 8 : 16 if has_flags else 12] + size_data = data[12:16] if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: trace("_read_pyc(%s): invalid pyc (incorrect size)" % source) return None diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 5af01eb7d95..e4c2a5fdac1 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -4,7 +4,6 @@ import inspect import os import sys -from contextlib import contextmanager from inspect import Parameter from inspect import signature from pathlib import Path @@ -186,16 +185,6 @@ def getfuncargnames( return arg_names -if sys.version_info < (3, 7): - - @contextmanager - def nullcontext(): - yield - -else: - from contextlib import nullcontext as nullcontext # noqa: F401 - - def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: # Note: this code intentionally mirrors the code at the beginning of # getfuncargnames, to get the arguments which were excluded from its result diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 796a1c3f8af..0163554bae4 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -3,8 +3,8 @@ import logging import os import re -import sys from contextlib import contextmanager +from contextlib import nullcontext from io import StringIO from pathlib import Path from typing import AbstractSet @@ -22,7 +22,6 @@ from _pytest._io import TerminalWriter from _pytest.capture import CaptureManager from _pytest.compat import final -from _pytest.compat import nullcontext from _pytest.config import _strtobool from _pytest.config import Config from _pytest.config import create_terminal_writer @@ -628,16 +627,7 @@ def set_log_path(self, fname: str) -> None: # https://github.com/python/mypy/issues/11193 stream: io.TextIOWrapper = fpath.open(mode="w", encoding="UTF-8") # type: ignore[assignment] - if sys.version_info >= (3, 7): - old_stream = self.log_file_handler.setStream(stream) - else: - old_stream = self.log_file_handler.stream - self.log_file_handler.acquire() - try: - self.log_file_handler.flush() - self.log_file_handler.stream = stream - finally: - self.log_file_handler.release() + old_stream = self.log_file_handler.setStream(stream) if old_stream: old_stream.close() diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 42e71ff917e..38eaef2fcc6 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -128,7 +128,7 @@ def get_open_files(self) -> List[Tuple[str, str]]: stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, - universal_newlines=True, + text=True, ).stdout def isopen(line: str) -> bool: diff --git a/testing/test_debugging.py b/testing/test_debugging.py index a822bb57f58..a95b542adec 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -8,14 +8,6 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester -try: - # Type ignored for Python <= 3.6. - breakpoint # type: ignore -except NameError: - SUPPORTS_BREAKPOINT_BUILTIN = False -else: - SUPPORTS_BREAKPOINT_BUILTIN = True - _ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "") @@ -911,14 +903,6 @@ def test_foo(): class TestDebuggingBreakpoints: - def test_supports_breakpoint_module_global(self) -> None: - """Test that supports breakpoint global marks on Python 3.7+.""" - if sys.version_info >= (3, 7): - assert SUPPORTS_BREAKPOINT_BUILTIN is True - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) @pytest.mark.parametrize("arg", ["--pdb", ""]) def test_sys_breakpointhook_configure_and_unconfigure( self, pytester: Pytester, arg: str @@ -952,9 +936,6 @@ def test_nothing(): pass result = pytester.runpytest_subprocess(*args) result.stdout.fnmatch_lines(["*1 passed in *"]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) def test_pdb_custom_cls(self, pytester: Pytester, custom_debugger_hook) -> None: p1 = pytester.makepyfile( """ @@ -969,9 +950,6 @@ def test_nothing(): assert custom_debugger_hook == ["init", "set_trace"] @pytest.mark.parametrize("arg", ["--pdb", ""]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) def test_environ_custom_class( self, pytester: Pytester, custom_debugger_hook, arg: str ) -> None: @@ -1002,9 +980,6 @@ def test_nothing(): pass result = pytester.runpytest_subprocess(*args) result.stdout.fnmatch_lines(["*1 passed in *"]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) @pytest.mark.skipif( not _ENVIRON_PYTHONBREAKPOINT == "", reason="Requires breakpoint() default value", @@ -1025,9 +1000,6 @@ def test_1(): assert "reading from stdin while output" not in rest TestPDB.flush(child) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) def test_pdb_not_altered(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 28529d04378..992f49bc53c 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -295,7 +295,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=True, - universal_newlines=True, + text=True, ).stdout except (OSError, subprocess.CalledProcessError): pytest.skip("bash is not available") diff --git a/tox.ini b/tox.ini index 9d26051ebb7..42e1ee1c777 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ minversion = 3.20.0 distshare = {homedir}/.tox/distshare envlist = linting - py36 py37 py38 py39