diff --git a/.travis.yml b/.travis.yml index 9914800f8..659af7273 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,18 +19,13 @@ install: jobs: include: # OSX tests - first (in test stage), since they are the slower ones. - - &test-macos - os: osx + - os: osx + # NOTE: (tests with) pexpect appear to be buggy on Travis, + # at least with coverage. + # Log: https://travis-ci.org/pytest-dev/pytest/jobs/500358864 osx_image: xcode10.1 language: generic - # Coverage for: - # - py2 with symlink in test_cmdline_python_package_symlink. - env: TOXENV=py27-xdist PYTEST_COVERAGE=1 - before_install: - - python -V - - test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 27 - - <<: *test-macos - env: TOXENV=py37-pexpect,py37-xdist PYTEST_COVERAGE=1 + env: TOXENV=py37-xdist PYTEST_COVERAGE=1 before_install: - which python3 - python3 -V @@ -38,20 +33,14 @@ jobs: - python -V - test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37 - # Full run of latest (major) supported versions, without xdist. - - env: TOXENV=py27 - python: '2.7' + # Full run of latest supported version, without xdist. - env: TOXENV=py37 python: '3.7' # Coverage tracking is slow with pypy, skip it. - - env: TOXENV=pypy-xdist - python: 'pypy' - env: TOXENV=pypy3-xdist python: 'pypy3' - - env: TOXENV=py34-xdist - python: '3.4' - env: TOXENV=py35-xdist python: '3.5' @@ -62,12 +51,6 @@ jobs: # Empty PYTEST_ADDOPTS to run this non-verbose. - env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS= - # Specialized factors for py27. - - env: TOXENV=py27-nobyte-numpy-xdist - python: '2.7' - - env: TOXENV=py27-pluggymaster-xdist - python: '2.7' - # Specialized factors for py37. # Coverage for: # - test_sys_breakpoint_interception (via pexpect). @@ -81,12 +64,7 @@ jobs: if: type = cron - stage: baseline - # Coverage for: - # - _pytest.unittest._handle_skip (via pexpect). - env: TOXENV=py27-pexpect,py27-twisted PYTEST_COVERAGE=1 - python: '2.7' - # Use py36 here for faster baseline. - - env: TOXENV=py36-xdist + env: TOXENV=py36-xdist python: '3.6' - env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1 cache: diff --git a/README.rst b/README.rst index 44fa8ac72..9739a1bda 100644 --- a/README.rst +++ b/README.rst @@ -85,7 +85,7 @@ Features - Can run `unittest `_ (or trial), `nose `_ test suites out of the box; -- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); +- Python 3.5+ and PyPy3; - Rich plugin architecture, with over 315+ `external plugins `_ and thriving community; diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8e50486de..72e4af732 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,7 +4,6 @@ trigger: variables: PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv" - python.needs_vc: False COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage" COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc" PYTEST_COVERAGE: '0' @@ -16,44 +15,10 @@ jobs: vmImage: "vs2017-win2016" strategy: matrix: - py27: - python.version: '2.7' - tox.env: 'py27' - py27-nobyte-lsof-numpy: - python.version: '2.7' - tox.env: 'py27-lsof-nobyte-numpy' - # Coverage for: - # - test_supports_breakpoint_module_global - # - test_terminal_reporter_writer_attr (without xdist) - # - "if write" branch in _pytest.assertion.rewrite - # - numpy - # - pytester's LsofFdLeakChecker (being skipped) - PYTEST_COVERAGE: '1' - py27-twisted: - python.version: '2.7' - tox.env: 'py27-twisted' - python.needs_vc: True - py27-pluggymaster-xdist: - python.version: '2.7' - tox.env: 'py27-pluggymaster-xdist' - # Coverage for: - # - except-IOError in _attempt_to_close_capture_file for py2. - # Also seen with py27-nobyte (using xdist), and py27-xdist. - # But no exception with py27-pexpect,py27-twisted,py27-numpy. - PYTEST_COVERAGE: '1' - # -- pypy2 and pypy3 are disabled for now: #5279 -- - # pypy: - # python.version: 'pypy2' - # tox.env: 'pypy' + # -- pypy3 disabled for now: #5279 -- # pypy3: # python.version: 'pypy3' # tox.env: 'pypy3' - py34-xdist: - python.version: '3.4' - tox.env: 'py34-xdist' - # Coverage for: - # - _pytest.compat._bytes_to_ascii - PYTEST_COVERAGE: '1' py35-xdist: python.version: '3.5' tox.env: 'py35-xdist' @@ -87,10 +52,6 @@ jobs: versionSpec: '$(python.version)' architecture: 'x64' - - script: choco install vcpython27 - condition: eq(variables['python.needs_vc'], True) - displayName: 'Install VC for py27' - - script: python -m pip install --upgrade pip && python -m pip install tox displayName: 'Install tox' diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html index 0e088d67e..f4d06a99f 100644 --- a/doc/en/_templates/globaltoc.html +++ b/doc/en/_templates/globaltoc.html @@ -10,6 +10,7 @@
  • Changelog
  • Contributing
  • Backwards Compatibility
  • +
  • Python 2.7 and 3.4 Support
  • License
  • Contact Channels
  • diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index b8d8bef0c..c3381600f 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -288,8 +288,7 @@ its test methods: This is equivalent to directly applying the decorator to the two test functions. -To remain backward-compatible with Python 2.4 you can also set a -``pytestmark`` attribute on a TestClass like this: +Due to legacy reasons, it is possible to set the ``pytestmark`` attribute on a TestClass like this: .. code-block:: python diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index d6c62cbe8..5a7b79ec3 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -1,9 +1,9 @@ Installation and Getting Started =================================== -**Pythons**: Python 2.7, 3.4, 3.5, 3.6, 3.7, Jython, PyPy-2.3 +**Pythons**: Python 3.5, 3.6, 3.7, PyPy3 -**Platforms**: Unix/Posix and Windows +**Platforms**: Linux and Windows **PyPI package name**: `pytest `_ diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index f08bd5c40..2314c0066 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -7,8 +7,7 @@ Good Integration Practices Install package with pip ------------------------------------------------- -For development, we recommend you use venv_ for virtual environments -(or virtualenv_ for Python 2.7) and +For development, we recommend you use venv_ for virtual environments and pip_ for installing your application and any dependencies, as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from your system Python installation. diff --git a/doc/en/index.rst b/doc/en/index.rst index 3ace95eff..5439770fb 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -61,7 +61,7 @@ Features - Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box; -- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); +- Python Python 3.5+ and PyPy 3; - Rich plugin architecture, with over 315+ `external plugins `_ and thriving community; diff --git a/setup.cfg b/setup.cfg index 9d0aa332e..2d6e5bee1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,4 @@ [metadata] - name = pytest description = pytest: simple powerful testing with Python long_description = file: README.rst @@ -23,13 +22,11 @@ classifiers = Topic :: Software Development :: Testing Topic :: Software Development :: Libraries Topic :: Utilities - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 platforms = unix, linux, osx, cygwin, win32 [options] @@ -43,8 +40,7 @@ packages = _pytest.mark py_modules = pytest -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* - +python_requires = >=3.5 [options.entry_points] console_scripts = @@ -59,13 +55,9 @@ all_files = 1 [upload_sphinx] upload-dir = doc/en/build/html -[bdist_wheel] -universal = 1 - [check-manifest] ignore = _pytest/_version.py - [devpi:upload] formats = sdist.tgz,bdist_wheel diff --git a/setup.py b/setup.py index 18d32201c..910fc839d 100644 --- a/setup.py +++ b/setup.py @@ -8,10 +8,8 @@ INSTALL_REQUIRES = [ "six>=1.10.0", "packaging", "attrs>=17.4.0", - 'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"', - 'more-itertools>=4.0.0;python_version>"2.7"', + "more-itertools>=4.0.0", "atomicwrites>=1.0", - 'funcsigs>=1.0;python_version<"3.0"', 'pathlib2>=2.2.0;python_version<"3.6"', 'colorama;sys_platform=="win32"', "pluggy>=0.12,<1.0", @@ -30,9 +28,9 @@ def main(): "testing": [ "argcomplete", "hypothesis>=3.56", + "mock", "nose", "requests", - "mock;python_version=='2.7'", ], }, # fmt: on diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 8c73ccc6a..8fd20f82d 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -9,25 +9,16 @@ import sys import traceback from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS +from traceback import format_exception_only from weakref import ref import attr import pluggy import py -from six import text_type import _pytest from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr -from _pytest.compat import _PY2 -from _pytest.compat import _PY3 -from _pytest.compat import PY35 -from _pytest.compat import safe_str - -if _PY3: - from traceback import format_exception_only -else: - from ._py2traceback import format_exception_only class Code(object): @@ -208,8 +199,7 @@ class TracebackEntry(object): locals = property(getlocals, None, None, "locals of underlaying frame") def getfirstlinesource(self): - # on Jython this firstlineno can be -1 apparently - return max(self.frame.code.firstlineno, 0) + return self.frame.code.firstlineno def getsource(self, astcache=None): """ return failing source code. """ @@ -391,9 +381,7 @@ class ExceptionInfo(object): help for navigating the traceback. """ - _assert_start_repr = ( - "AssertionError(u'assert " if _PY2 else "AssertionError('assert " - ) + _assert_start_repr = "AssertionError('assert " _excinfo = attr.ib() _striptext = attr.ib(default="") @@ -558,11 +546,6 @@ class ExceptionInfo(object): loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) return str(loc) - def __unicode__(self): - entry = self.traceback[-1] - loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) - return text_type(loc) - def match(self, regexp): """ Check whether the regular expression 'regexp' is found in the string @@ -692,8 +675,7 @@ class FormattedExcinfo(object): source = _pytest._code.Source("???") line_index = 0 else: - # entry.getfirstlinesource() can be -1, should be 0 on jython - line_index = entry.lineno - max(entry.getfirstlinesource(), 0) + line_index = entry.lineno - entry.getfirstlinesource() lines = [] style = entry._repr_style @@ -733,7 +715,7 @@ class FormattedExcinfo(object): if self.tbfilter: traceback = traceback.filter() - if is_recursion_error(excinfo): + if excinfo.errisinstance(RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) else: extraline = None @@ -769,7 +751,7 @@ class FormattedExcinfo(object): " Displaying first and last {max_frames} stack frames out of {total}." ).format( exc_type=type(e).__name__, - exc_msg=safe_str(e), + exc_msg=str(e), max_frames=max_frames, total=len(traceback), ) @@ -784,64 +766,51 @@ class FormattedExcinfo(object): return traceback, extraline def repr_excinfo(self, excinfo): - if _PY2: - reprtraceback = self.repr_traceback(excinfo) - reprcrash = excinfo._getreprcrash() - return ReprExceptionInfo(reprtraceback, reprcrash) - else: - repr_chain = [] - e = excinfo.value - descr = None - seen = set() - while e is not None and id(e) not in seen: - seen.add(id(e)) - if excinfo: - reprtraceback = self.repr_traceback(excinfo) - reprcrash = excinfo._getreprcrash() - else: - # fallback to native repr if the exception doesn't have a traceback: - # ExceptionInfo objects require a full traceback to work - reprtraceback = ReprTracebackNative( - traceback.format_exception(type(e), e, None) - ) - reprcrash = None + repr_chain = [] + e = excinfo.value + descr = None + seen = set() + while e is not None and id(e) not in seen: + seen.add(id(e)) + if excinfo: + reprtraceback = self.repr_traceback(excinfo) + reprcrash = excinfo._getreprcrash() + else: + # fallback to native repr if the exception doesn't have a traceback: + # ExceptionInfo objects require a full traceback to work + reprtraceback = ReprTracebackNative( + traceback.format_exception(type(e), e, None) + ) + reprcrash = None - repr_chain += [(reprtraceback, reprcrash, descr)] - if e.__cause__ is not None and self.chain: - e = e.__cause__ - excinfo = ( - ExceptionInfo((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) - descr = "The above exception was the direct cause of the following exception:" - elif ( - e.__context__ is not None - and not e.__suppress_context__ - and self.chain - ): - e = e.__context__ - excinfo = ( - ExceptionInfo((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) - descr = "During handling of the above exception, another exception occurred:" - else: - e = None - repr_chain.reverse() - return ExceptionChainRepr(repr_chain) + repr_chain += [(reprtraceback, reprcrash, descr)] + if e.__cause__ is not None and self.chain: + e = e.__cause__ + excinfo = ( + ExceptionInfo((type(e), e, e.__traceback__)) + if e.__traceback__ + else None + ) + descr = "The above exception was the direct cause of the following exception:" + elif ( + e.__context__ is not None and not e.__suppress_context__ and self.chain + ): + e = e.__context__ + excinfo = ( + ExceptionInfo((type(e), e, e.__traceback__)) + if e.__traceback__ + else None + ) + descr = "During handling of the above exception, another exception occurred:" + else: + e = None + repr_chain.reverse() + return ExceptionChainRepr(repr_chain) class TerminalRepr(object): def __str__(self): - s = self.__unicode__() - if _PY2: - s = s.encode("utf-8") - return s - - def __unicode__(self): # FYI this is called from pytest-xdist's serialization of exception # information. io = py.io.TextIO() @@ -1006,7 +975,7 @@ class ReprFuncArgs(TerminalRepr): if self.args: linesofar = "" for name, value in self.args: - ns = "%s = %s" % (safe_str(name), safe_str(value)) + ns = "%s = %s" % (name, value) if len(ns) + len(linesofar) + 2 > tw.fullwidth: if linesofar: tw.line(linesofar) @@ -1038,23 +1007,6 @@ def getrawcode(obj, trycall=True): return obj -if PY35: # RecursionError introduced in 3.5 - - def is_recursion_error(excinfo): - return excinfo.errisinstance(RecursionError) # noqa - - -else: - - def is_recursion_error(excinfo): - if not excinfo.errisinstance(RuntimeError): - return False - try: - return "maximum recursion depth exceeded" in str(excinfo.value) - except UnicodeError: - return False - - # relative paths that we use to filter traceback entries from appearing to the user; # see filter_traceback # note: if we need to add more paths than what we have now we should probably use a list diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 6b6abb863..f959de29c 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -74,10 +74,6 @@ class AssertionState(object): def install_importhook(config): """Try to install the rewrite hook, raise SystemError if it fails.""" - # Jython has an AST bug that make the assertion rewriting hook malfunction. - if sys.platform.startswith("java"): - raise SystemError("rewrite not supported") - config._assertstate = AssertionState(config, "rewrite") config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config) sys.meta_path.insert(0, hook) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index b9142147b..785a7b12f 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -15,6 +15,7 @@ import string import struct import sys import types +from importlib.util import spec_from_file_location import atomicwrites import py @@ -25,7 +26,6 @@ from _pytest.assertion import util from _pytest.assertion.util import ( # noqa: F401 format_explanation as _format_explanation, ) -from _pytest.compat import spec_from_file_location from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import PurePath @@ -35,8 +35,6 @@ if hasattr(imp, "get_tag"): else: if hasattr(sys, "pypy_version_info"): impl = "pypy" - elif sys.platform == "java": - impl = "jython" else: impl = "cpython" ver = sys.version_info @@ -46,15 +44,6 @@ else: PYC_EXT = ".py" + (__debug__ and "c" or "o") PYC_TAIL = "." + PYTEST_TAG + PYC_EXT -ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3 - -if sys.version_info >= (3, 5): - ast_Call = ast.Call -else: - - def ast_Call(a, b, c): - return ast.Call(a, b, c, None, None) - class AssertionRewritingHook(object): """PEP302 Import hook which rewrites asserts.""" @@ -364,37 +353,6 @@ def _rewrite_test(config, fn): source = fn.read("rb") except EnvironmentError: return None, None - if ASCII_IS_DEFAULT_ENCODING: - # ASCII is the default encoding in Python 2. Without a coding - # declaration, Python 2 will complain about any bytes in the file - # outside the ASCII range. Sadly, this behavior does not extend to - # compile() or ast.parse(), which prefer to interpret the bytes as - # latin-1. (At least they properly handle explicit coding cookies.) To - # preserve this error behavior, we could force ast.parse() to use ASCII - # as the encoding by inserting a coding cookie. Unfortunately, that - # messes up line numbers. Thus, we have to check ourselves if anything - # is outside the ASCII range in the case no encoding is explicitly - # declared. For more context, see issue #269. Yay for Python 3 which - # gets this right. - end1 = source.find("\n") - end2 = source.find("\n", end1 + 1) - if ( - not source.startswith(BOM_UTF8) - and cookie_re.match(source[0:end1]) is None - and cookie_re.match(source[end1 + 1 : end2]) is None - ): - if hasattr(state, "_indecode"): - # encodings imported us again, so don't rewrite. - return None, None - state._indecode = True - try: - try: - source.decode("ascii") - except UnicodeDecodeError: - # Let it fail in real import. - return None, None - finally: - del state._indecode try: tree = ast.parse(source, filename=fn.strpath) except SyntaxError: @@ -737,7 +695,7 @@ class AssertionRewriter(ast.NodeVisitor): """Call a helper in this module.""" py_name = ast.Name("@pytest_ar", ast.Load()) attr = ast.Attribute(py_name, name, ast.Load()) - return ast_Call(attr, list(args), []) + return ast.Call(attr, list(args), []) def builtin(self, name): """Return the builtin called *name*.""" @@ -847,11 +805,9 @@ class AssertionRewriter(ast.NodeVisitor): msg = self.pop_format_context(template) fmt = self.helper("_format_explanation", msg) err_name = ast.Name("AssertionError", ast.Load()) - exc = ast_Call(err_name, [fmt], []) - if sys.version_info[0] >= 3: - raise_ = ast.Raise(exc, None) - else: - raise_ = ast.Raise(exc, None, None) + exc = ast.Call(err_name, [fmt], []) + raise_ = ast.Raise(exc, None) + body.append(raise_) # Clear temporary variables by setting them to None. if self.variables: @@ -893,7 +849,7 @@ warn_explicit( def visit_Name(self, name): # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. - locs = ast_Call(self.builtin("locals"), [], []) + locs = ast.Call(self.builtin("locals"), [], []) inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) @@ -920,7 +876,7 @@ warn_explicit( res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) expl_format = self.pop_format_context(ast.Str(expl)) - call = ast_Call(app, [expl_format], []) + call = ast.Call(app, [expl_format], []) self.on_failure.append(ast.Expr(call)) if i < levels: cond = res @@ -959,9 +915,9 @@ warn_explicit( and isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp)) ) - def visit_Call_35(self, call): + def visit_Call(self, call): """ - visit `ast.Call` nodes on Python3.5 and after + visit `ast.Call` nodes """ if self._is_any_call_with_generator_or_list_comprehension(call): return self._visit_all(call) @@ -1013,46 +969,6 @@ warn_explicit( new_starred = ast.Starred(res, starred.ctx) return new_starred, "*" + expl - def visit_Call_legacy(self, call): - """ - visit `ast.Call nodes on 3.4 and below` - """ - if self._is_any_call_with_generator_or_list_comprehension(call): - return self._visit_all(call) - new_func, func_expl = self.visit(call.func) - arg_expls = [] - new_args = [] - new_kwargs = [] - new_star = new_kwarg = None - for arg in call.args: - res, expl = self.visit(arg) - new_args.append(res) - arg_expls.append(expl) - for keyword in call.keywords: - res, expl = self.visit(keyword.value) - new_kwargs.append(ast.keyword(keyword.arg, res)) - arg_expls.append(keyword.arg + "=" + expl) - if call.starargs: - new_star, expl = self.visit(call.starargs) - arg_expls.append("*" + expl) - if call.kwargs: - new_kwarg, expl = self.visit(call.kwargs) - arg_expls.append("**" + expl) - expl = "%s(%s)" % (func_expl, ", ".join(arg_expls)) - new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg) - res = self.assign(new_call) - res_expl = self.explanation_param(self.display(res)) - outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) - return res, outer_expl - - # ast.Call signature changed on 3.5, - # conditionally change which methods is named - # visit_Call depending on Python version - if sys.version_info >= (3, 5): - visit_Call = visit_Call_35 - else: - visit_Call = visit_Call_legacy - def visit_Attribute(self, attr): if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 1fee64ce0..7bcb70946 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -5,11 +5,11 @@ from __future__ import division from __future__ import print_function import pprint +from collections.abc import Sequence import six import _pytest._code -from ..compat import Sequence from _pytest import outcomes from _pytest._io.saferepr import saferepr diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 045248cb7..1eb9acc13 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -18,7 +18,6 @@ import py import six import pytest -from .compat import _PY2 as PY2 from .pathlib import Path from .pathlib import resolve_from_str from .pathlib import rmtree @@ -129,7 +128,7 @@ class Cache(object): if not cache_dir_exists_already: self._ensure_supporting_files() try: - f = path.open("wb" if PY2 else "w") + f = path.open("w") except (IOError, OSError): self.warn("cache could not write path {path}", path=path) else: diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 68c17772f..3587018b8 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -18,7 +18,6 @@ from tempfile import TemporaryFile import six import pytest -from _pytest.compat import _PY3 from _pytest.compat import CaptureIO patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} @@ -283,10 +282,6 @@ def capsysbinary(request): ``out`` and ``err`` will be ``bytes`` objects. """ _ensure_only_one_capture_fixture(request, "capsysbinary") - # Currently, the implementation uses the python3 specific `.buffer` - # property of CaptureIO. - if sys.version_info < (3,): - raise request.raiseerror("capsysbinary is only supported on Python 3") with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture: yield fixture @@ -434,7 +429,7 @@ class EncodedFile(object): def write(self, obj): if isinstance(obj, six.text_type): obj = obj.encode(self.encoding, "replace") - elif _PY3: + else: raise TypeError( "write() argument must be str, not {}".format(type(obj).__name__) ) @@ -608,7 +603,7 @@ class FDCaptureBinary(object): os.dup2(targetfd_save, self.targetfd) os.close(targetfd_save) self.syscapture.done() - _attempt_to_close_capture_file(self.tmpfile) + self.tmpfile.close() self._state = "done" def suspend(self): @@ -681,7 +676,7 @@ class SysCapture(object): def done(self): setattr(sys, self.name, self._old) del self._old - _attempt_to_close_capture_file(self.tmpfile) + self.tmpfile.close() self._state = "done" def suspend(self): @@ -738,10 +733,7 @@ class DontReadFromInput(six.Iterator): @property def buffer(self): - if sys.version_info >= (3, 0): - return self - else: - raise AttributeError("redirected stdin has no attribute buffer") + return self def _colorama_workaround(): @@ -837,14 +829,3 @@ def _py36_windowsconsoleio_workaround(stream): sys.stdin = _reopen_stdio(sys.stdin, "rb") sys.stdout = _reopen_stdio(sys.stdout, "wb") sys.stderr = _reopen_stdio(sys.stderr, "wb") - - -def _attempt_to_close_capture_file(f): - """Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)""" - if six.PY2: - try: - f.close() - except IOError: - pass - else: - f.close() diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 7668c3a94..9d38b97cb 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -6,59 +6,28 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import codecs import functools import inspect +import io import re import sys from contextlib import contextmanager +from inspect import Parameter +from inspect import signature import py -import six -from six import text_type import _pytest from _pytest._io.saferepr import saferepr from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME -try: - import enum -except ImportError: # pragma: no cover - # Only available in Python 3.4+ or as a backport - enum = None - -_PY3 = sys.version_info > (3, 0) -_PY2 = not _PY3 - - -if _PY3: - from inspect import signature, Parameter as Parameter -else: - from funcsigs import signature, Parameter as Parameter NOTSET = object() -PY35 = sys.version_info[:2] >= (3, 5) -PY36 = sys.version_info[:2] >= (3, 6) -MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError" - - -if _PY3: - from collections.abc import MutableMapping as MappingMixin - from collections.abc import Iterable, Mapping, Sequence, Sized -else: - # those raise DeprecationWarnings in Python >=3.7 - from collections import MutableMapping as MappingMixin # noqa - from collections import Iterable, Mapping, Sequence, Sized # noqa - - -if sys.version_info >= (3, 4): - from importlib.util import spec_from_file_location -else: - - def spec_from_file_location(*_, **__): - return None +MODULE_NOT_FOUND_ERROR = ( + "ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError" +) def _format_args(func): @@ -195,72 +164,36 @@ def _translate_non_printable(s): return s.translate(_non_printable_ascii_translate_table) -if _PY3: - STRING_TYPES = bytes, str - UNICODE_TYPES = six.text_type +STRING_TYPES = bytes, str - if PY35: - def _bytes_to_ascii(val): - return val.decode("ascii", "backslashreplace") +def _bytes_to_ascii(val): + return val.decode("ascii", "backslashreplace") + +def ascii_escaped(val): + """If val is pure ascii, returns it as a str(). Otherwise, escapes + bytes objects into a sequence of escaped bytes: + + b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' + + and escapes unicode objects into a sequence of escaped unicode + ids, e.g.: + + '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944' + + note: + the obvious "v.decode('unicode-escape')" will return + valid utf-8 unicode if it finds them in bytes, but we + want to return escaped bytes for any byte, even if they match + a utf-8 string. + + """ + if isinstance(val, bytes): + ret = _bytes_to_ascii(val) else: - - def _bytes_to_ascii(val): - if val: - # source: http://goo.gl/bGsnwC - encoded_bytes, _ = codecs.escape_encode(val) - return encoded_bytes.decode("ascii") - else: - # empty bytes crashes codecs.escape_encode (#1087) - return "" - - def ascii_escaped(val): - """If val is pure ascii, returns it as a str(). Otherwise, escapes - bytes objects into a sequence of escaped bytes: - - b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' - - and escapes unicode objects into a sequence of escaped unicode - ids, e.g.: - - '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944' - - note: - the obvious "v.decode('unicode-escape')" will return - valid utf-8 unicode if it finds them in bytes, but we - want to return escaped bytes for any byte, even if they match - a utf-8 string. - - """ - if isinstance(val, bytes): - ret = _bytes_to_ascii(val) - else: - ret = val.encode("unicode_escape").decode("ascii") - return _translate_non_printable(ret) - - -else: - STRING_TYPES = six.string_types - UNICODE_TYPES = six.text_type - - def ascii_escaped(val): - """In py2 bytes and str are the same type, so return if it's a bytes - object, return it unchanged if it is a full ascii string, - otherwise escape it into its binary form. - - If it's a unicode string, change the unicode characters into - unicode escapes. - - """ - if isinstance(val, bytes): - try: - ret = val.decode("ascii") - except UnicodeDecodeError: - ret = val.encode("string-escape").decode("ascii") - else: - ret = val.encode("unicode-escape").decode("ascii") - return _translate_non_printable(ret) + ret = val.encode("unicode_escape").decode("ascii") + return _translate_non_printable(ret) class _PytestWrapper(object): @@ -357,36 +290,6 @@ def safe_isclass(obj): return False -def _is_unittest_unexpected_success_a_failure(): - """Return if the test suite should fail if an @expectedFailure unittest test PASSES. - - From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful: - Changed in version 3.4: Returns False if there were any - unexpectedSuccesses from tests marked with the expectedFailure() decorator. - """ - return sys.version_info >= (3, 4) - - -if _PY3: - - def safe_str(v): - """returns v as string""" - return str(v) - - -else: - - def safe_str(v): - """returns v as string, converting to ascii if necessary""" - try: - return str(v) - except UnicodeError: - if not isinstance(v, text_type): - v = text_type(v) - errors = "replace" - return v.encode("utf-8", errors) - - COLLECT_FAKEMODULE_ATTRIBUTES = ( "Collector", "Module", @@ -410,27 +313,14 @@ def _setup_collect_fakemodule(): setattr(pytest.collect, attr, getattr(pytest, attr)) -if _PY2: - # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO. - from py.io import TextIO +class CaptureIO(io.TextIOWrapper): + def __init__(self): + super(CaptureIO, self).__init__( + io.BytesIO(), encoding="UTF-8", newline="", write_through=True + ) - class CaptureIO(TextIO): - @property - def encoding(self): - return getattr(self, "_encoding", "UTF-8") - - -else: - import io - - class CaptureIO(io.TextIOWrapper): - def __init__(self): - super(CaptureIO, self).__init__( - io.BytesIO(), encoding="UTF-8", newline="", write_through=True - ) - - def getvalue(self): - return self.buffer.getvalue().decode("UTF-8") + def getvalue(self): + return self.buffer.getvalue().decode("UTF-8") class FuncargnamesCompatAttr(object): @@ -442,16 +332,3 @@ class FuncargnamesCompatAttr(object): def funcargnames(self): """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" return self.fixturenames - - -if six.PY2: - - def lru_cache(*_, **__): - def dec(fn): - return fn - - return dec - - -else: - from functools import lru_cache # noqa: F401 diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 7a5deb13f..ee588be5b 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -12,6 +12,7 @@ import shlex import sys import types import warnings +from functools import lru_cache import importlib_metadata import py @@ -31,8 +32,6 @@ from .findpaths import exists from _pytest import deprecated from _pytest._code import ExceptionInfo from _pytest._code import filter_traceback -from _pytest.compat import lru_cache -from _pytest.compat import safe_str from _pytest.outcomes import fail from _pytest.outcomes import Skipped from _pytest.warning_types import PytestConfigWarning @@ -73,7 +72,7 @@ def main(args=None, plugins=None): if exc_info.traceback else exc_info.exconly() ) - formatted_tb = safe_str(exc_repr) + formatted_tb = str(exc_repr) for line in formatted_tb.splitlines(): tw.line(line.rstrip(), red=True) return 4 @@ -403,12 +402,6 @@ class PytestPluginManager(PluginManager): else: directory = path - if six.PY2: # py2 is not using lru_cache. - try: - return self._dirpath2confmods[directory] - except KeyError: - pass - # XXX these days we may rather want to use config.rootdir # and allow users to opt into looking into the rootdir parent # directories instead of requiring to specify confcutdir @@ -566,7 +559,7 @@ class PytestPluginManager(PluginManager): except ImportError as e: new_exc_message = 'Error importing plugin "%s": %s' % ( modname, - safe_str(e.args[0]), + str(e.args[0]), ) new_exc = ImportError(new_exc_message) tb = sys.exc_info()[2] diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 95a80bb4f..c63554703 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -333,7 +333,6 @@ class DoctestTextfile(pytest.Module): checker=_get_checker(), continue_on_failure=_get_continue_on_failure(self.config), ) - _fix_spoof_python2(runner, encoding) parser = doctest.DocTestParser() test = parser.get_doctest(text, globs, name, filename, 0) @@ -539,32 +538,6 @@ def _get_report_choice(key): }[key] -def _fix_spoof_python2(runner, encoding): - """ - Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This - should patch only doctests for text files because they don't have a way to declare their - encoding. Doctests in docstrings from Python modules don't have the same problem given that - Python already decoded the strings. - - This fixes the problem related in issue #2434. - """ - from _pytest.compat import _PY2 - - if not _PY2: - return - - from doctest import _SpoofOut - - class UnicodeSpoof(_SpoofOut): - def getvalue(self): - result = _SpoofOut.getvalue(self) - if encoding and isinstance(result, bytes): - result = result.decode(encoding) - return result - - runner._fakeout = UnicodeSpoof() - - @pytest.fixture(scope="session") def doctest_namespace(): """ diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 280a48608..53c916ebe 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1303,11 +1303,7 @@ class FixtureManager(object): # during fixture definition we wrap the original fixture function # to issue a warning if called directly, so here we unwrap it in order to not emit the warning # when pytest itself calls the fixture function - if six.PY2 and unittest: - # hack on Python 2 because of the unbound methods - obj = get_real_func(obj) - else: - obj = get_real_method(obj, holderobj) + obj = get_real_method(obj, holderobj) fixture_def = FixtureDef( self, diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 3972113cb..9a169a2bb 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -26,10 +26,6 @@ import pytest from _pytest import nodes from _pytest.config import filename_arg -# Python 2.X and 3.X compatibility -if sys.version_info[0] < 3: - from codecs import open - class Junit(py.xml.Namespace): pass diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 577a5407b..a5f41173a 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -43,10 +43,7 @@ class ColoredLevelFormatter(logging.Formatter): def __init__(self, terminalwriter, *args, **kwargs): super(ColoredLevelFormatter, self).__init__(*args, **kwargs) - if six.PY2: - self._original_fmt = self._fmt - else: - self._original_fmt = self._style._fmt + self._original_fmt = self._style._fmt self._level_to_fmt_mapping = {} levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) @@ -70,10 +67,7 @@ class ColoredLevelFormatter(logging.Formatter): def format(self, record): fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt) - if six.PY2: - self._fmt = fmt - else: - self._style._fmt = fmt + self._style._fmt = fmt return super(ColoredLevelFormatter, self).format(record) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index fa4d8d3d5..4978f8b53 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -4,7 +4,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import contextlib import fnmatch import functools import os @@ -342,46 +341,6 @@ def pytest_collection_modifyitems(items, config): items[:] = remaining -@contextlib.contextmanager -def _patched_find_module(): - """Patch bug in pkgutil.ImpImporter.find_module - - When using pkgutil.find_loader on python<3.4 it removes symlinks - from the path due to a call to os.path.realpath. This is not consistent - with actually doing the import (in these versions, pkgutil and __import__ - did not share the same underlying code). This can break conftest - discovery for pytest where symlinks are involved. - - The only supported python<3.4 by pytest is python 2.7. - """ - if six.PY2: # python 3.4+ uses importlib instead - - def find_module_patched(self, fullname, path=None): - # Note: we ignore 'path' argument since it is only used via meta_path - subname = fullname.split(".")[-1] - if subname != fullname and self.path is None: - return None - if self.path is None: - path = None - else: - # original: path = [os.path.realpath(self.path)] - path = [self.path] - try: - file, filename, etc = pkgutil.imp.find_module(subname, path) - except ImportError: - return None - return pkgutil.ImpLoader(fullname, file, filename, etc) - - old_find_module = pkgutil.ImpImporter.find_module - pkgutil.ImpImporter.find_module = find_module_patched - try: - yield - finally: - pkgutil.ImpImporter.find_module = old_find_module - else: - yield - - class FSHookProxy(object): def __init__(self, fspath, pm, remove_mods): self.fspath = fspath @@ -662,23 +621,14 @@ class Session(nodes.FSCollector): ihook.pytest_collect_directory(path=dirpath, parent=self) return True - if six.PY2: - - @staticmethod - def _visit_filter(f): - return f.check(file=1) and not f.strpath.endswith("*.pyc") - - else: - - @staticmethod - def _visit_filter(f): - return f.check(file=1) + @staticmethod + def _visit_filter(f): + return f.check(file=1) def _tryconvertpyarg(self, x): """Convert a dotted module name to path.""" try: - with _patched_find_module(): - loader = pkgutil.find_loader(x) + loader = pkgutil.find_loader(x) except ImportError: return x if loader is None: @@ -686,8 +636,7 @@ class Session(nodes.FSCollector): # This method is sometimes invoked when AssertionRewritingHook, which # does not define a get_filename method, is already in place: try: - with _patched_find_module(): - path = loader.get_filename(x) + path = loader.get_filename(x) except AttributeError: # Retrieve path from AssertionRewritingHook: path = loader.modules[x][0].co_filename diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 9602e8acf..59faaea29 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -2,6 +2,7 @@ import inspect import warnings from collections import namedtuple +from collections.abc import MutableMapping from operator import attrgetter import attr @@ -9,7 +10,6 @@ import six from ..compat import ascii_escaped from ..compat import getfslineno -from ..compat import MappingMixin from ..compat import NOTSET from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS from _pytest.outcomes import fail @@ -343,7 +343,7 @@ class MarkGenerator(object): MARK_GEN = MarkGenerator() -class NodeKeywords(MappingMixin): +class NodeKeywords(MutableMapping): def __init__(self, node): self.node = node self.parent = node.parent diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index e8671b0c7..5a017aa28 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -222,15 +222,6 @@ class MonkeyPatch(object): self._setitem.append((dic, name, dic.get(name, notset))) del dic[name] - def _warn_if_env_name_is_not_str(self, name): - """On Python 2, warn if the given environment variable name is not a native str (#4056)""" - if six.PY2 and not isinstance(name, str): - warnings.warn( - pytest.PytestWarning( - "Environment variable name {!r} should be str".format(name) - ) - ) - def setenv(self, name, value, prepend=None): """ Set environment variable ``name`` to ``value``. If ``prepend`` is a character, read the current environment variable value @@ -248,7 +239,6 @@ class MonkeyPatch(object): value = str(value) if prepend and name in os.environ: value = value + prepend + os.environ[name] - self._warn_if_env_name_is_not_str(name) self.setitem(os.environ, name, value) def delenv(self, name, raising=True): @@ -258,7 +248,6 @@ class MonkeyPatch(object): If ``raising`` is set to False, no exception will be raised if the environment variable is missing. """ - self._warn_if_env_name_is_not_str(name) self.delitem(os.environ, name, raising=raising) def syspath_prepend(self, path): @@ -279,10 +268,9 @@ class MonkeyPatch(object): # since then the mtime based FileFinder cache (that gets created in # this case already) gets not invalidated when writing the new files # quickly afterwards. - if sys.version_info >= (3, 3): - from importlib import invalidate_caches + from importlib import invalidate_caches - invalidate_caches() + invalidate_caches() def chdir(self, path): """ Change the current working directory to the specified path. diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 3f4171207..78c8b587a 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -4,7 +4,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import sys import tempfile import six @@ -70,18 +69,10 @@ def create_new_paste(contents): :returns: url to the pasted contents """ import re + from urllib.request import urlopen + from urllib.parse import urlencode - if sys.version_info < (3, 0): - from urllib import urlopen, urlencode - else: - from urllib.request import urlopen - from urllib.parse import urlencode - - params = { - "code": contents, - "lexer": "python3" if sys.version_info[0] == 3 else "python", - "expiry": "1week", - } + params = {"code": contents, "lexer": "python3", "expiry": "1week"} url = "https://bpaste.net" response = urlopen(url, data=urlencode(params).encode("ascii")).read() m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8")) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 729c41797..2cd931b32 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -8,7 +8,6 @@ import os import shutil import sys import uuid -from functools import reduce from os.path import expanduser from os.path import expandvars from os.path import isabs @@ -18,9 +17,8 @@ from posixpath import sep as posix_sep import six from six.moves import map -from .compat import PY36 -if PY36: +if sys.version_info[:2] >= (3, 6): from pathlib import Path, PurePath else: from pathlib2 import Path, PurePath @@ -84,17 +82,6 @@ def parse_num(maybe_num): return -1 -if six.PY2: - - def _max(iterable, default): - """needed due to python2.7 lacking the default argument for max""" - return reduce(max, iterable, default) - - -else: - _max = max - - def _force_symlink(root, target, link_to): """helper to create the current symlink @@ -119,7 +106,7 @@ def make_numbered_dir(root, prefix): """create a directory with an increased number as suffix for the given prefix""" for i in range(10): # try up to 10 times to create the folder - max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1) + max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) new_number = max_existing + 1 new_path = root.joinpath("{}{}".format(prefix, new_number)) try: @@ -230,7 +217,7 @@ def try_cleanup(path, consider_lock_dead_if_created_before): def cleanup_candidates(root, prefix, keep): """lists candidates for numbered directories to be removed - follows py.path""" - max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1) + max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) max_delete = max_existing - keep paths = find_prefixed(root, prefix) paths, paths2 = itertools.tee(paths) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 605451630..d3611b338 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -13,6 +13,7 @@ import subprocess import sys import time import traceback +from collections.abc import Sequence from fnmatch import fnmatch from weakref import WeakKeyDictionary @@ -25,8 +26,6 @@ from _pytest._io.saferepr import saferepr from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.capture import MultiCapture from _pytest.capture import SysCapture -from _pytest.compat import safe_str -from _pytest.compat import Sequence from _pytest.main import EXIT_INTERRUPTED from _pytest.main import EXIT_OK from _pytest.main import Session @@ -911,7 +910,7 @@ class Testdir(object): def _ensure_basetemp(self, args): args = list(args) for x in args: - if safe_str(x).startswith("--basetemp"): + if str(x).startswith("--basetemp"): break else: args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp")) @@ -1124,25 +1123,11 @@ class Testdir(object): if timeout is None: ret = popen.wait() - elif six.PY3: + else: try: ret = popen.wait(timeout) except subprocess.TimeoutExpired: handle_timeout() - else: - end = time.time() + timeout - - resolution = min(0.1, timeout / 10) - - while True: - ret = popen.poll() - if ret is not None: - break - - if time.time() > end: - handle_timeout() - - time.sleep(resolution) finally: f1.close() f2.close() diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 5f1e6885b..e420f6924 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -5,6 +5,7 @@ from __future__ import division from __future__ import print_function import collections +import enum import fnmatch import inspect import os @@ -22,7 +23,6 @@ from _pytest import fixtures from _pytest import nodes from _pytest._code import filter_traceback from _pytest.compat import ascii_escaped -from _pytest.compat import enum from _pytest.compat import get_default_arg_names from _pytest.compat import get_real_func from _pytest.compat import getfslineno @@ -35,7 +35,6 @@ from _pytest.compat import NOTSET from _pytest.compat import REGEX_TYPE from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass -from _pytest.compat import safe_str from _pytest.compat import STRING_TYPES from _pytest.config import hookimpl from _pytest.main import FSHookProxy @@ -531,7 +530,7 @@ class Module(nodes.File, PyCollector): if exc_info.traceback else exc_info.exconly() ) - formatted_tb = safe_str(exc_repr) + formatted_tb = str(exc_repr) raise self.CollectError( "ImportError while importing test module '{fspath}'.\n" "Hint: make sure your test modules/packages have valid Python names.\n" diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index df09aa32d..d5ee3f160 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -5,6 +5,9 @@ import math import pprint import sys import warnings +from collections.abc import Iterable +from collections.abc import Mapping +from collections.abc import Sized from decimal import Decimal from numbers import Number @@ -15,9 +18,6 @@ from six.moves import zip import _pytest._code from _pytest import deprecated from _pytest.compat import isclass -from _pytest.compat import Iterable -from _pytest.compat import Mapping -from _pytest.compat import Sized from _pytest.compat import STRING_TYPES from _pytest.outcomes import fail @@ -81,9 +81,6 @@ class ApproxBase(object): def __ne__(self, actual): return not (actual == self) - if sys.version_info[0] == 2: - __cmp__ = _cmp_raises_type_error - def _approx_scalar(self, x): return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) @@ -122,9 +119,6 @@ class ApproxNumpy(ApproxBase): list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist()) return "approx({!r})".format(list_scalars) - if sys.version_info[0] == 2: - __cmp__ = _cmp_raises_type_error - def __eq__(self, actual): import numpy as np @@ -251,10 +245,7 @@ class ApproxScalar(ApproxBase): except ValueError: vetted_tolerance = "???" - if sys.version_info[0] == 2: - return "{} +- {}".format(self.expected, vetted_tolerance) - else: - return u"{} \u00b1 {}".format(self.expected, vetted_tolerance) + return "{} \u00b1 {}".format(self.expected, vetted_tolerance) def __eq__(self, actual): """ @@ -736,8 +727,6 @@ class RaisesContext(object): fail(self.message) self.excinfo.__init__(tp) suppress_exception = issubclass(self.excinfo.type, self.expected_exception) - if sys.version_info[0] == 2 and suppress_exception: - sys.exc_clear() if self.match_expr is not None and suppress_exception: self.excinfo.match(self.match_expr) return suppress_exception diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 574c6a1cc..409825f48 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -9,8 +9,6 @@ import re import sys import warnings -import six - import _pytest._code from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS from _pytest.deprecated import WARNS_EXEC @@ -156,44 +154,13 @@ class WarningsRecorder(warnings.catch_warnings): raise RuntimeError("Cannot enter %r twice" % self) self._list = super(WarningsRecorder, self).__enter__() warnings.simplefilter("always") - # python3 keeps track of a "filter version", when the filters are - # updated previously seen warnings can be re-warned. python2 has no - # concept of this so we must reset the warnings registry manually. - # trivial patching of `warnings.warn` seems to be enough somehow? - if six.PY2: - - def warn(message, category=None, stacklevel=1): - # duplicate the stdlib logic due to - # bad handing in the c version of warnings - if isinstance(message, Warning): - category = message.__class__ - # Check category argument - if category is None: - category = UserWarning - assert issubclass(category, Warning) - - # emulate resetting the warn registry - f_globals = sys._getframe(stacklevel).f_globals - if "__warningregistry__" in f_globals: - orig = f_globals["__warningregistry__"] - f_globals["__warningregistry__"] = None - try: - return self._saved_warn(message, category, stacklevel + 1) - finally: - f_globals["__warningregistry__"] = orig - else: - return self._saved_warn(message, category, stacklevel + 1) - - warnings.warn, self._saved_warn = warn, warnings.warn return self def __exit__(self, *exc_info): if not self._entered: __tracebackhide__ = True raise RuntimeError("Cannot exit %r without entering first" % self) - # see above where `self._saved_warn` is assigned - if six.PY2: - warnings.warn = self._saved_warn + super(WarningsRecorder, self).__exit__(*exc_info) # Built-in catch_warnings does not reset entered state so we do it diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index bc8b88e71..94227c39c 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -129,17 +129,13 @@ def pytest_runtest_makereport(item, call): evalxfail = getattr(item, "_evalxfail", None) # unitttest special case, see setting of _unexpectedsuccess if hasattr(item, "_unexpectedsuccess") and rep.when == "call": - from _pytest.compat import _is_unittest_unexpected_success_a_failure if item._unexpectedsuccess: rep.longrepr = "Unexpected success: {}".format(item._unexpectedsuccess) else: rep.longrepr = "Unexpected success" - if _is_unittest_unexpected_success_a_failure(): - rep.outcome = "failed" - else: - rep.outcome = "passed" - rep.wasxfail = rep.longrepr + rep.outcome = "failed" + elif item.config.option.runxfail: pass # don't interefere elif call.excinfo and call.excinfo.errisinstance(xfail.Exception): diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index eb1970d51..e5ede1cad 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -992,21 +992,6 @@ def _get_line_with_reprcrash_message(config, rep, termwidth): msg = msg[:max_len_msg] while wcswidth(msg) > max_len_msg: msg = msg[:-1] - if six.PY2: - # on python 2 systems with narrow unicode compilation, trying to - # get a single character out of a multi-byte unicode character such as - # u'😄' will result in a High Surrogate (U+D83D) character, which is - # rendered as u'�'; in this case we just strip that character out as it - # serves no purpose being rendered - try: - surrogate = six.unichr(0xD83D) - msg = msg.rstrip(surrogate) - except ValueError: # pragma: no cover - # Jython cannot represent this lone surrogate at all (#5256): - # ValueError: unichr() arg is a lone surrogate in range - # (0xD800, 0xDFFF) (Jython UTF-16 encoding) - # ignore this case as it shouldn't appear in the string anyway - pass msg += ellipsis line += sep + msg return line diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index a8a703771..4a3686247 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -11,7 +11,6 @@ import warnings import attr import py -import six import pytest from .pathlib import ensure_reset_dir @@ -32,9 +31,7 @@ class TempPathFactory(object): # using os.path.abspath() to get absolute path instead of resolve() as it # does not work the same in all platforms (see #4427) # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012) - converter=attr.converters.optional( - lambda p: Path(os.path.abspath(six.text_type(p))) - ) + converter=attr.converters.optional(lambda p: Path(os.path.abspath(str(p)))) ) _trace = attr.ib() _basetemp = attr.ib(default=None) diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index e2e7efdc5..4380f1295 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -113,23 +113,9 @@ class TestCaseFunction(Function): def setup(self): self._testcase = self.parent.obj(self.name) - self._fix_unittest_skip_decorator() if hasattr(self, "_request"): self._request._fillfixtures() - def _fix_unittest_skip_decorator(self): - """ - The @unittest.skip decorator calls functools.wraps(self._testcase) - The call to functools.wraps() fails unless self._testcase - has a __name__ attribute. This is usually automatically supplied - if the test is a function or method, but we need to add manually - here. - - See issue #1169 - """ - if sys.version_info[0] == 2: - setattr(self._testcase, "__name__", self.name) - def teardown(self): self._testcase = None @@ -208,12 +194,7 @@ class TestCaseFunction(Function): skip_why = getattr( self._testcase.__class__, "__unittest_skip_why__", "" ) or getattr(testMethod, "__unittest_skip_why__", "") - try: # PY3, unittest2 on PY2 - self._testcase._addSkip(self, self._testcase, skip_why) - except TypeError: # PY2 - if sys.version_info[0] != 2: - raise - self._testcase._addSkip(self, skip_why) + self._testcase._addSkip(self, self._testcase, skip_why) return True return False diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index a3debae46..a2579bf75 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -8,7 +8,6 @@ import warnings from contextlib import contextmanager import pytest -from _pytest import compat SHOW_PYTEST_WARNINGS_ARG = "-Walways::pytest.RemovedInPytest4Warning" @@ -104,22 +103,8 @@ def catch_warnings_for_item(config, ihook, when, item): def warning_record_to_str(warning_message): - """Convert a warnings.WarningMessage to a string. - - This takes lot of unicode shenaningans into account for Python 2. - When Python 2 support is dropped this function can be greatly simplified. - """ + """Convert a warnings.WarningMessage to a string.""" warn_msg = warning_message.message - unicode_warning = False - if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args): - new_args = [] - for m in warn_msg.args: - new_args.append( - compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m - ) - unicode_warning = list(warn_msg.args) != new_args - warn_msg.args = new_args - msg = warnings.formatwarning( warn_msg, warning_message.category, @@ -127,12 +112,6 @@ def warning_record_to_str(warning_message): warning_message.lineno, warning_message.line, ) - if unicode_warning: - warnings.warn( - "Warning is using unicode non convertible to ascii, " - "converting to a safe representation:\n {!r}".format(compat.safe_str(msg)), - UnicodeWarning, - ) return msg diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 7016cf13b..5ed04d638 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -176,7 +176,6 @@ class TestGeneralUsage(object): result = testdir.runpytest(p) result.stdout.fnmatch_lines( [ - # XXX on jython this fails: "> import import_fails", "ImportError while importing test module*", "*No module named *does_not_work*", ] @@ -222,9 +221,7 @@ class TestGeneralUsage(object): " foo()", "conftest.py:2: in foo", " import qwerty", - "E {}: No module named {q}qwerty{q}".format( - exc_name, q="'" if six.PY3 else "" - ), + "E {}: No module named 'qwerty'".format(exc_name), ] ) @@ -540,7 +537,6 @@ class TestInvocationVariants(object): result = testdir.runpython(p) assert result.ret == 0 - @pytest.mark.xfail("sys.platform.startswith('java')") def test_pydoc(self, testdir): for name in ("py.test", "pytest"): result = testdir.runpython_c("import {};help({})".format(name, name)) @@ -783,10 +779,7 @@ class TestInvocationVariants(object): d_local = testdir.mkdir("local") symlink_location = os.path.join(str(d_local), "lib") - if six.PY2: - os.symlink(str(d), symlink_location) - else: - os.symlink(str(d), symlink_location, target_is_directory=True) + os.symlink(str(d), symlink_location, target_is_directory=True) # The structure of the test directory is now: # . @@ -1185,9 +1178,6 @@ def test_usage_error_code(testdir): assert result.ret == EXIT_USAGEERROR -@pytest.mark.skipif( - sys.version_info[:2] < (3, 5), reason="async def syntax python 3.5+ only" -) @pytest.mark.filterwarnings("default") def test_warn_on_async_function(testdir): testdir.makepyfile( diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 0b63fcf4d..fcde20b41 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -4,6 +4,7 @@ from __future__ import division from __future__ import print_function import sys +from unittest import mock from six import text_type from test_excinfo import TWMock @@ -11,11 +12,6 @@ from test_excinfo import TWMock import _pytest._code import pytest -try: - import mock -except ImportError: - import unittest.mock as mock - def test_ne(): code1 = _pytest._code.Code(compile('foo = "bar"', "", "exec")) @@ -92,21 +88,6 @@ def test_unicode_handling(): excinfo = pytest.raises(Exception, f) text_type(excinfo) - if sys.version_info < (3,): - bytes(excinfo) - - -@pytest.mark.skipif(sys.version_info[0] >= 3, reason="python 2 only issue") -def test_unicode_handling_syntax_error(): - value = u"ąć".encode("UTF-8") - - def f(): - raise SyntaxError("invalid syntax", (None, 1, 3, value)) - - excinfo = pytest.raises(Exception, f) - str(excinfo) - if sys.version_info[0] < 3: - text_type(excinfo) def test_code_getargs(): @@ -202,10 +183,8 @@ class TestReprFuncArgs(object): r = ReprFuncArgs(args) r.toterminal(tw) - if sys.version_info[0] >= 3: - assert ( - tw.lines[0] - == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" - ) - else: - assert tw.lines[0] == "unicode_string = São Paulo, utf8_string = São Paulo" + + assert ( + tw.lines[0] + == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" + ) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index a76797301..33cd7169b 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -17,7 +17,7 @@ import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo -from _pytest._code.code import ReprExceptionInfo + try: import importlib @@ -26,8 +26,6 @@ except ImportError: else: invalidate_import_caches = getattr(importlib, "invalidate_caches", None) -failsonjython = pytest.mark.xfail("sys.platform.startswith('java')") - pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3])) @@ -146,7 +144,6 @@ class TestTraceback_f_g_h(object): assert s.startswith("def f():") assert s.endswith("raise ValueError") - @failsonjython def test_traceback_entry_getsource_in_construct(self): source = _pytest._code.Source( """\ @@ -500,8 +497,7 @@ class TestFormattedExcinfo(object): excinfo = _pytest._code.ExceptionInfo.from_current() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" def test_repr_many_line_source_not_existing(self): pr = FormattedExcinfo() @@ -519,8 +515,7 @@ raise ValueError() excinfo = _pytest._code.ExceptionInfo.from_current() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" def test_repr_source_failing_fullsource(self): pr = FormattedExcinfo() @@ -577,14 +572,12 @@ raise ValueError() fail = IOError() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" fail = py.error.ENOENT # noqa repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" def test_repr_local(self): p = FormattedExcinfo(showlocals=True) @@ -828,9 +821,9 @@ raise ValueError() repr = p.repr_excinfo(excinfo) assert repr.reprtraceback assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries) - if sys.version_info[0] >= 3: - assert repr.chain[0][0] - assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) + + assert repr.chain[0][0] + assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.message == "ValueError: 0" @@ -916,13 +909,11 @@ raise ValueError() for style in ("short", "long", "no"): for showlocals in (True, False): repr = excinfo.getrepr(style=style, showlocals=showlocals) - if sys.version_info[0] < 3: - assert isinstance(repr, ReprExceptionInfo) assert repr.reprtraceback.style == style - if sys.version_info[0] >= 3: - assert isinstance(repr, ExceptionChainRepr) - for repr in repr.chain: - assert repr[0].style == style + + assert isinstance(repr, ExceptionChainRepr) + for repr in repr.chain: + assert repr[0].style == style def test_reprexcinfo_unicode(self): from _pytest._code.code import TerminalRepr @@ -1133,7 +1124,6 @@ raise ValueError() msg.endswith("mod.py") assert tw.lines[20] == ":9: ValueError" - @pytest.mark.skipif("sys.version_info[0] < 3") def test_exc_chain_repr(self, importasmod): mod = importasmod( """ @@ -1219,7 +1209,6 @@ raise ValueError() assert line.endswith("mod.py") assert tw.lines[47] == ":15: AttributeError" - @pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"]) def test_exc_repr_chain_suppression(self, importasmod, mode): """Check that exc repr does not show chained exceptions in Python 3. @@ -1261,7 +1250,6 @@ raise ValueError() assert tw.lines[9] == ":6: AttributeError" assert len(tw.lines) == 10 - @pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.parametrize( "reason, description", [ @@ -1321,7 +1309,6 @@ raise ValueError() ] ) - @pytest.mark.skipif("sys.version_info[0] < 3") def test_exc_chain_repr_cycle(self, importasmod): mod = importasmod( """ diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 0a2a98c02..b86d18d0c 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -16,8 +16,6 @@ import _pytest._code import pytest from _pytest._code import Source -failsonjython = pytest.mark.xfail("sys.platform.startswith('java')") - def test_source_str_function(): x = Source("3") @@ -122,7 +120,7 @@ def test_source_strip_multiline(): def test_syntaxerror_rerepresentation(): ex = pytest.raises(SyntaxError, _pytest._code.compile, "xyz xyz") assert ex.value.lineno == 1 - assert ex.value.offset in (4, 5, 7) # XXX pypy/jython versus cpython? + assert ex.value.offset == 7 assert ex.value.text.strip(), "x x" diff --git a/testing/example_scripts/acceptance/fixture_mock_integration.py b/testing/example_scripts/acceptance/fixture_mock_integration.py index c8ccdf1b4..58afe730e 100644 --- a/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,10 +1,6 @@ # -*- coding: utf-8 -*- """Reproduces issue #3774""" - -try: - import mock -except ImportError: - import unittest.mock as mock +from unittest import mock import pytest diff --git a/testing/python/approx.py b/testing/python/approx.py index cd8df5d27..18508f1fe 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import doctest import operator -import sys from decimal import Decimal from fractions import Fraction from operator import eq @@ -28,7 +27,7 @@ class MyDocTestRunner(doctest.DocTestRunner): class TestApprox(object): @pytest.fixture def plus_minus(self): - return u"\u00b1" if sys.version_info[0] > 2 else u"+-" + return u"\u00b1" def test_repr_string(self, plus_minus): tol1, tol2, infr = "1.0e-06", "2.0e-06", "inf" diff --git a/testing/python/collect.py b/testing/python/collect.py index 501b30a49..4a1aa724f 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -679,8 +679,6 @@ class TestSorting(object): assert fn1 == fn2 assert fn1 != modcol - if sys.version_info < (3, 0): - assert cmp(fn1, fn2) == 0 # NOQA assert hash(fn1) == hash(fn2) fn3 = testdir.collect_by_name(modcol, "test_fail") diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 945ab8627..953431126 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -13,8 +13,6 @@ from _pytest import fixtures from _pytest import python from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG -PY3 = sys.version_info >= (3, 0) - class TestMetafunc(object): def Metafunc(self, func, config=None): diff --git a/testing/python/raises.py b/testing/python/raises.py index cd463d74b..4970f9a56 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- import sys -import six - import pytest from _pytest.outcomes import Failed from _pytest.warning_types import PytestDeprecationWarning @@ -265,16 +263,7 @@ class TestRaises(object): def __class__(self): assert False, "via __class__" - if six.PY2: - with pytest.raises(pytest.fail.Exception) as excinfo: - with pytest.raises(CrappyClass()): - pass - assert "DID NOT RAISE" in excinfo.value.args[0] - - with pytest.raises(CrappyClass) as excinfo: - raise CrappyClass() - else: - with pytest.raises(AssertionError) as excinfo: - with pytest.raises(CrappyClass()): - pass - assert "via __class__" in excinfo.value.args[0] + with pytest.raises(AssertionError) as excinfo: + with pytest.raises(CrappyClass()): + pass + assert "via __class__" in excinfo.value.args[0] diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index da7758e8e..bd681b0e6 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -30,19 +30,7 @@ def equal_with_bash(prefix, ffc, fc, out=None): def _wrapcall(*args, **kargs): try: - if sys.version_info > (2, 7): - return subprocess.check_output(*args, **kargs).decode().splitlines() - if "stdout" in kargs: - raise ValueError("stdout argument not allowed, it will be overridden.") - process = subprocess.Popen(stdout=subprocess.PIPE, *args, **kargs) - output, unused_err = process.communicate() - retcode = process.poll() - if retcode: - cmd = kargs.get("args") - if cmd is None: - cmd = args[0] - raise subprocess.CalledProcessError(retcode, cmd) - return output.decode().splitlines() + return subprocess.check_output(*args, **kargs).decode().splitlines() except subprocess.CalledProcessError: return [] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 2085ffd8b..1fbf7b120 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import collections.abc as collections_abc import sys import textwrap @@ -15,8 +16,6 @@ from _pytest import outcomes from _pytest.assertion import truncate from _pytest.assertion import util -PY3 = sys.version_info >= (3, 0) - def mock_config(): class Config(object): @@ -372,14 +371,6 @@ class TestAssert_reprcompare(object): {0, 2}, """ Full diff: - - set([0, 1]) - ? ^ - + set([0, 2]) - ? ^ - """ - if not PY3 - else """ - Full diff: - {0, 1} ? ^ + {0, 2} @@ -483,10 +474,7 @@ class TestAssert_reprcompare(object): assert len(expl) > 1 def test_Sequence(self): - if sys.version_info >= (3, 3): - import collections.abc as collections_abc - else: - import collections as collections_abc + if not hasattr(collections_abc, "MutableSequence"): pytest.skip("cannot import MutableSequence") MutableSequence = collections_abc.MutableSequence @@ -589,10 +577,7 @@ class TestAssert_reprcompare(object): return "\xff" expl = callequal(A(), "1") - if PY3: - assert expl == ["ÿ == '1'", "+ 1"] - else: - assert expl == [u"\ufffd == '1'", u"+ 1"] + assert expl == ["ÿ == '1'", "+ 1"] def test_format_nonascii_explanation(self): assert util.format_explanation("λ") @@ -1100,10 +1085,6 @@ def test_traceback_failure(testdir): ) -@pytest.mark.skipif( - sys.version_info[:2] <= (3, 3), - reason="Python 3.4+ shows chained exceptions on multiprocess", -) def test_exception_handling_no_traceback(testdir): """ Handle chain exceptions in tasks submitted by the multiprocess module (#1984). @@ -1137,9 +1118,7 @@ def test_exception_handling_no_traceback(testdir): ) -@pytest.mark.skipif( - "'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" -) +@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names") def test_warn_missing(testdir): testdir.makepyfile("") result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h") @@ -1187,45 +1166,6 @@ def test_AssertionError_message(testdir): ) -@pytest.mark.skipif(PY3, reason="This bug does not exist on PY3") -def test_set_with_unsortable_elements(): - # issue #718 - class UnsortableKey(object): - def __init__(self, name): - self.name = name - - def __lt__(self, other): - raise RuntimeError() - - def __repr__(self): - return "repr({})".format(self.name) - - def __eq__(self, other): - return self.name == other.name - - def __hash__(self): - return hash(self.name) - - left_set = {UnsortableKey(str(i)) for i in range(1, 3)} - right_set = {UnsortableKey(str(i)) for i in range(2, 4)} - expl = callequal(left_set, right_set, verbose=True) - # skip first line because it contains the "construction" of the set, which does not have a guaranteed order - expl = expl[1:] - dedent = textwrap.dedent( - """ - Extra items in the left set: - repr(1) - Extra items in the right set: - repr(3) - Full diff (fallback to calling repr on each item): - - repr(1) - repr(2) - + repr(3) - """ - ).strip() - assert "\n".join(expl) == dedent - - def test_diff_newline_at_end(monkeypatch, testdir): testdir.makepyfile( r""" diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 22f53ab95..cc9de3071 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -3,6 +3,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import ast import glob import os import py_compile @@ -22,11 +23,6 @@ from _pytest.assertion.rewrite import PYTEST_TAG from _pytest.assertion.rewrite import rewrite_asserts from _pytest.main import EXIT_NOTESTSCOLLECTED -ast = pytest.importorskip("ast") -if sys.platform.startswith("java"): - # XXX should be xfail - pytest.skip("assert rewrite does currently not work on jython") - def setup_module(mod): mod._old_reprcompare = util._reprcompare @@ -166,18 +162,12 @@ class TestAssertionRewrite(object): msg = getmsg(f, {"cls": X}).splitlines() if verbose > 0: - if six.PY2: - assert msg == [ - "assert == 42", - " -", - " +42", - ] - else: - assert msg == [ - "assert .X'> == 42", - " -.X'>", - " +42", - ] + + assert msg == [ + "assert .X'> == 42", + " -.X'>", + " +42", + ] else: assert msg == ["assert cls == 42"] @@ -277,9 +267,6 @@ class TestAssertionRewrite(object): ["*AssertionError: To be escaped: %", "*assert 1 == 2"] ) - @pytest.mark.skipif( - sys.version_info < (3,), reason="bytes is a string type in python 2" - ) def test_assertion_messages_bytes(self, testdir): testdir.makepyfile("def test_bytes_assertion():\n assert False, b'ohai!'\n") result = testdir.runpytest() @@ -426,7 +413,6 @@ class TestAssertionRewrite(object): assert getmsg(f) == "assert (False or (4 % 2))" - @pytest.mark.skipif("sys.version_info < (3,5)") def test_at_operator_issue1290(self, testdir): testdir.makepyfile( """ @@ -441,7 +427,6 @@ class TestAssertionRewrite(object): ) testdir.runpytest().assert_outcomes(passed=1) - @pytest.mark.skipif("sys.version_info < (3,5)") def test_starred_with_side_effect(self, testdir): """See #4412""" testdir.makepyfile( @@ -822,9 +807,6 @@ def test_rewritten(): assert testdir.runpytest_subprocess().ret == 0 def test_orphaned_pyc_file(self, testdir): - if sys.version_info < (3, 0) and hasattr(sys, "pypy_version_info"): - pytest.skip("pypy2 doesn't run orphaned pyc files") - testdir.makepyfile( """ import orphan @@ -890,10 +872,6 @@ def test_rewritten(): testdir.tmpdir.join("test_newlines.py").write(b, "wb") assert testdir.runpytest().ret == 0 - @pytest.mark.skipif( - sys.version_info < (3, 4), - reason="packages without __init__.py not supported on python 2", - ) def test_package_without__init__py(self, testdir): pkg = testdir.mkdir("a_package_without_init_py") pkg.join("module.py").ensure() @@ -976,26 +954,6 @@ def test_rewritten(): result.stdout.fnmatch_lines(["*= 1 passed in *=*"]) assert "pytest-warning summary" not in result.stdout.str() - @pytest.mark.skipif(sys.version_info[0] > 2, reason="python 2 only") - def test_rewrite_future_imports(self, testdir): - """Test that rewritten modules don't inherit the __future__ flags - from the assertrewrite module. - - assertion.rewrite imports __future__.division (and others), so - ensure rewritten modules don't inherit those flags. - - The test below will fail if __future__.division is enabled - """ - testdir.makepyfile( - """ - def test(): - x = 1 / 2 - assert type(x) is int - """ - ) - result = testdir.runpytest() - assert result.ret == 0 - class TestAssertionRewriteHookDetails(object): def test_loader_is_package_false_for_module(self, testdir): @@ -1025,48 +983,6 @@ class TestAssertionRewriteHookDetails(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 3 passed*"]) - @pytest.mark.skipif("sys.version_info[0] >= 3") - @pytest.mark.xfail("hasattr(sys, 'pypy_translation_info')") - def test_assume_ascii(self, testdir): - content = "u'\xe2\x99\xa5\x01\xfe'" - testdir.tmpdir.join("test_encoding.py").write(content, "wb") - res = testdir.runpytest() - assert res.ret != 0 - assert "SyntaxError: Non-ASCII character" in res.stdout.str() - - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_detect_coding_cookie(self, testdir): - testdir.makepyfile( - test_cookie=""" - # -*- coding: utf-8 -*- - u"St\xc3\xa4d" - def test_rewritten(): - assert "@py_builtins" in globals()""" - ) - assert testdir.runpytest().ret == 0 - - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_detect_coding_cookie_second_line(self, testdir): - testdir.makepyfile( - test_cookie=""" - # -*- coding: utf-8 -*- - u"St\xc3\xa4d" - def test_rewritten(): - assert "@py_builtins" in globals()""" - ) - assert testdir.runpytest().ret == 0 - - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_detect_coding_cookie_crlf(self, testdir): - testdir.makepyfile( - test_cookie=""" - # -*- coding: utf-8 -*- - u"St\xc3\xa4d" - def test_rewritten(): - assert "@py_builtins" in globals()""" - ) - assert testdir.runpytest().ret == 0 - def test_sys_meta_path_munged(self, testdir): testdir.makepyfile( """ diff --git a/testing/test_capture.py b/testing/test_capture.py index 01123bf5b..f101c0ec3 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -13,12 +13,10 @@ import textwrap from io import UnsupportedOperation import py -from six import text_type import pytest from _pytest import capture from _pytest.capture import CaptureManager -from _pytest.compat import _PY3 from _pytest.main import EXIT_NOTESTSCOLLECTED # note: py.io capture tests where copied from @@ -100,10 +98,7 @@ class TestCaptureManager(object): def test_capturing_unicode(testdir, method): if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (2, 2): pytest.xfail("does not work on pypy < 2.2") - if sys.version_info >= (3, 0): - obj = "'b\u00f6y'" - else: - obj = "u'\u00f6y'" + obj = "'b\u00f6y'" testdir.makepyfile( """ # -*- coding: utf-8 -*- @@ -545,9 +540,6 @@ class TestCaptureFixture(object): ) reprec.assertoutcome(passed=1) - @pytest.mark.skipif( - sys.version_info < (3,), reason="only have capsysbinary in python 3" - ) def test_capsysbinary(self, testdir): reprec = testdir.inline_runsource( """\ @@ -562,25 +554,6 @@ class TestCaptureFixture(object): ) reprec.assertoutcome(passed=1) - @pytest.mark.skipif( - sys.version_info >= (3,), reason="only have capsysbinary in python 3" - ) - def test_capsysbinary_forbidden_in_python2(self, testdir): - testdir.makepyfile( - """\ - def test_hello(capsysbinary): - pass - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines( - [ - "*test_hello*", - "*capsysbinary is only supported on Python 3*", - "*1 error in*", - ] - ) - def test_partial_setup_failure(self, testdir): p = testdir.makepyfile( """\ @@ -843,17 +816,9 @@ class TestCaptureIO(object): def test_unicode_and_str_mixture(self): f = capture.CaptureIO() - if sys.version_info >= (3, 0): - f.write("\u00f6") - pytest.raises(TypeError, f.write, b"hello") - else: - f.write(u"\u00f6") - f.write(b"hello") - s = f.getvalue() - f.close() - assert isinstance(s, text_type) + f.write("\u00f6") + pytest.raises(TypeError, f.write, b"hello") - @pytest.mark.skipif(sys.version_info[0] == 2, reason="python 3 only behaviour") def test_write_bytes_to_buffer(self): """In python3, stdout / stderr are text io wrappers (exposing a buffer property of the underlying bytestream). See issue #1407 @@ -876,7 +841,6 @@ def test_dontreadfrominput(): f.close() # just for completeness -@pytest.mark.skipif("sys.version_info < (3,)", reason="python2 has no buffer") def test_dontreadfrominput_buffer_python3(): from _pytest.capture import DontReadFromInput @@ -891,17 +855,7 @@ def test_dontreadfrominput_buffer_python3(): f.close() # just for completeness -@pytest.mark.skipif("sys.version_info >= (3,)", reason="python2 has no buffer") -def test_dontreadfrominput_buffer_python2(): - from _pytest.capture import DontReadFromInput - - f = DontReadFromInput() - with pytest.raises(AttributeError): - f.buffer - f.close() # just for completeness - - -@pytest.yield_fixture +@pytest.fixture def tmpfile(testdir): f = testdir.makepyfile("").open("wb+") yield f @@ -1118,16 +1072,6 @@ class TestStdCapture(object): out, err = cap.readouterr() assert out == u"hxąć\n" - @pytest.mark.skipif( - "sys.version_info >= (3,)", reason="text output different for bytes on python3" - ) - def test_capturing_readouterr_decode_error_handling(self): - with self.getcapture() as cap: - # triggered an internal error in pytest - print("\xa6") - out, err = cap.readouterr() - assert out == u"\ufffd\n" - def test_reset_twice_error(self): with self.getcapture() as cap: print("hello") @@ -1571,9 +1515,6 @@ def test_typeerror_encodedfile_write(testdir): assert result_with_capture.ret == result_without_capture.ret - if _PY3: - result_with_capture.stdout.fnmatch_lines( - ["E TypeError: write() argument must be str, not bytes"] - ) - else: - assert result_with_capture.ret == 0 + result_with_capture.stdout.fnmatch_lines( + ["E TypeError: write() argument must be str, not bytes"] + ) diff --git a/testing/test_collection.py b/testing/test_collection.py index 1cc893866..1a42e425f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -38,8 +38,6 @@ class TestCollector(object): assert fn1 == fn2 assert fn1 != modcol - if sys.version_info < (3, 0): - assert cmp(fn1, fn2) == 0 # NOQA assert hash(fn1) == hash(fn2) fn3 = testdir.collect_by_name(modcol, "test_fail") diff --git a/testing/test_compat.py b/testing/test_compat.py index b4298d153..f0ae07e05 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -6,8 +6,6 @@ from __future__ import print_function import sys from functools import wraps -import six - import pytest from _pytest.compat import _PytestWrapper from _pytest.compat import get_real_func @@ -62,8 +60,6 @@ def test_get_real_func(): def inner(): pass # pragma: no cover - if six.PY2: - inner.__wrapped__ = f return inner def func(): @@ -81,9 +77,6 @@ def test_get_real_func(): assert get_real_func(wrapped_func2) is wrapped_func -@pytest.mark.skipif( - sys.version_info < (3, 4), reason="asyncio available in Python 3.4+" -) def test_is_generator_asyncio(testdir): testdir.makepyfile( """ diff --git a/testing/test_config.py b/testing/test_config.py index e588c262a..2cda7f40b 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -218,12 +218,9 @@ class TestConfigAPI(object): assert config.getoption(x) == "this" pytest.raises(ValueError, config.getoption, "qweqwe") - @pytest.mark.skipif("sys.version_info[0] < 3") def test_config_getoption_unicode(self, testdir): testdir.makeconftest( """ - from __future__ import unicode_literals - def pytest_addoption(parser): parser.addoption('--hello', type=str) """ diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 25a35c3c1..57eda456d 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -3,7 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import sys import textwrap import pytest @@ -830,13 +829,11 @@ class TestLiterals(object): """ ) reprec = testdir.inline_run() - passed = int(sys.version_info[0] >= 3) - reprec.assertoutcome(passed=passed, failed=int(not passed)) + reprec.assertoutcome(passed=1) def test_bytes_literal(self, testdir): """Test that doctests which output bytes fail in Python 3 when - the ALLOW_BYTES option is not used. The same test should pass - in Python 2 (#1287). + the ALLOW_BYTES option is not used. (#1287). """ testdir.maketxtfile( test_doc=""" @@ -845,8 +842,7 @@ class TestLiterals(object): """ ) reprec = testdir.inline_run() - passed = int(sys.version_info[0] == 2) - reprec.assertoutcome(passed=passed, failed=int(not passed)) + reprec.assertoutcome(failed=1) class TestDoctestSkips(object): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 4c21c94d3..b87f1a9ec 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -4,7 +4,6 @@ from __future__ import division from __future__ import print_function import os -import sys from xml.dom import minidom import py @@ -585,8 +584,7 @@ class TestPython(object): assert result.ret == 1 tnode = dom.find_first_by_tag("testcase") fnode = tnode.find_first_by_tag("failure") - if not sys.platform.startswith("java"): - assert "hx" in fnode.toxml() + assert "hx" in fnode.toxml() def test_assertion_binchars(self, testdir): """this test did fail when the escaping wasnt strict""" diff --git a/testing/test_mark.py b/testing/test_mark.py index dd9d35230..dc6e28a7d 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -5,8 +5,7 @@ from __future__ import print_function import os import sys - -import six +from unittest import mock import pytest from _pytest.main import EXIT_INTERRUPTED @@ -17,11 +16,6 @@ from _pytest.nodes import Node from _pytest.warning_types import PytestDeprecationWarning from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG -try: - import mock -except ImportError: - import unittest.mock as mock - ignore_markinfo = pytest.mark.filterwarnings( "ignore:MarkInfo objects:pytest.RemovedInPytest4Warning" ) @@ -1009,10 +1003,7 @@ def test_pytest_param_id_requires_string(): with pytest.raises(TypeError) as excinfo: pytest.param(id=True) msg, = excinfo.value.args - if six.PY2: - assert msg == "Expected id to be a string, got : True" - else: - assert msg == "Expected id to be a string, got : True" + assert msg == "Expected id to be a string, got : True" @pytest.mark.parametrize("s", (None, "hello world")) diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 9b2f45502..279b0c4a4 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -8,8 +8,6 @@ import re import sys import textwrap -import six - import pytest from _pytest.monkeypatch import MonkeyPatch @@ -209,22 +207,6 @@ class TestEnvironWarnings(object): VAR_NAME = u"PYTEST_INTERNAL_MY_VAR" - @pytest.mark.skipif(six.PY3, reason="Python 2 only test") - def test_setenv_unicode_key(self, monkeypatch): - with pytest.warns( - pytest.PytestWarning, - match="Environment variable name {!r} should be str".format(self.VAR_NAME), - ): - monkeypatch.setenv(self.VAR_NAME, "2") - - @pytest.mark.skipif(six.PY3, reason="Python 2 only test") - def test_delenv_unicode_key(self, monkeypatch): - with pytest.warns( - pytest.PytestWarning, - match="Environment variable name {!r} should be str".format(self.VAR_NAME), - ): - monkeypatch.delenv(self.VAR_NAME, raising=False) - def test_setenv_non_str_warning(self, monkeypatch): value = 2 msg = ( @@ -349,10 +331,8 @@ def test_importerror(testdir): result = testdir.runpytest() result.stdout.fnmatch_lines( """ - *import error in package.a: No module named {0}doesnotexist{0}* - """.format( - "'" if sys.version_info > (3, 0) else "" - ) + *import error in package.a: No module named 'doesnotexist'* + """ ) diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 9491f6d90..0d17ad73f 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -3,8 +3,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import sys - import pytest @@ -74,10 +72,7 @@ class TestPasteCapture(object): """ ) result = testdir.runpytest("--pastebin=all") - if sys.version_info[0] == 3: - expected_msg = "*assert '☺' == 1*" - else: - expected_msg = "*assert '\\xe2\\x98\\xba' == 1*" + expected_msg = "*assert '☺' == 1*" result.stdout.fnmatch_lines( [ expected_msg, @@ -110,14 +105,9 @@ class TestPaste(object): return DummyFile() - if sys.version_info < (3, 0): - import urllib + import urllib.request - monkeypatch.setattr(urllib, "urlopen", mocked) - else: - import urllib.request - - monkeypatch.setattr(urllib.request, "urlopen", mocked) + monkeypatch.setattr(urllib.request, "urlopen", mocked) return calls def test_create_new_paste(self, pastebin, mocked_urlopen): @@ -126,7 +116,7 @@ class TestPaste(object): assert len(mocked_urlopen) == 1 url, data = mocked_urlopen[0] assert type(data) is bytes - lexer = "python3" if sys.version_info[0] == 3 else "python" + lexer = "python3" assert url == "https://bpaste.net" assert "lexer=%s" % lexer in data.decode() assert "code=full-paste-contents" in data.decode() diff --git a/testing/test_pdb.py b/testing/test_pdb.py index d31d60469..874654802 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -6,8 +6,6 @@ from __future__ import print_function import os import sys -import six - import _pytest._code import pytest from _pytest.debugging import _validate_usepdb_cls @@ -537,10 +535,7 @@ class TestPDB(object): import sys import types - if sys.version_info < (3, ): - do_debug_func = pdb.Pdb.do_debug.im_func - else: - do_debug_func = pdb.Pdb.do_debug + do_debug_func = pdb.Pdb.do_debug newglobals = do_debug_func.__globals__.copy() newglobals['Pdb'] = self.__class__ @@ -866,8 +861,6 @@ class TestDebuggingBreakpoints(object): assert SUPPORTS_BREAKPOINT_BUILTIN is True if sys.version_info.major == 3 and sys.version_info.minor == 5: assert SUPPORTS_BREAKPOINT_BUILTIN is False - if sys.version_info.major == 2 and sys.version_info.minor == 7: - assert SUPPORTS_BREAKPOINT_BUILTIN is False @pytest.mark.skipif( not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" @@ -1183,24 +1176,17 @@ def test_pdbcls_via_local_module(testdir): def test_raises_bdbquit_with_eoferror(testdir): """It is not guaranteed that DontReadFromInput's read is called.""" - if six.PY2: - builtin_module = "__builtin__" - input_func = "raw_input" - else: - builtin_module = "builtins" - input_func = "input" + p1 = testdir.makepyfile( """ def input_without_read(*args, **kwargs): raise EOFError() def test(monkeypatch): - import {builtin_module} - monkeypatch.setattr({builtin_module}, {input_func!r}, input_without_read) + import builtins + monkeypatch.setattr(builtins, "input", input_without_read) __import__('pdb').set_trace() - """.format( - builtin_module=builtin_module, input_func=input_func - ) + """ ) result = testdir.runpytest(str(p1)) result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"]) diff --git a/testing/test_runner.py b/testing/test_runner.py index 6906efb91..5b37d12db 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -654,10 +654,7 @@ def test_pytest_fail_notrace_non_ascii(testdir, str_prefix): % str_prefix ) result = testdir.runpytest() - if sys.version_info[0] >= 3: - result.stdout.fnmatch_lines(["*test_hello*", "oh oh: ☺"]) - else: - result.stdout.fnmatch_lines(["*test_hello*", "oh oh: *"]) + result.stdout.fnmatch_lines(["*test_hello*", "oh oh: ☺"]) assert "def test_hello" not in result.stdout.str() diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 55119ae12..ce183171f 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -49,22 +49,6 @@ class TestEvaluator(object): expl = ev.getexplanation() assert expl == "condition: hasattr(os, 'sep')" - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_marked_one_arg_unicode(self, testdir): - item = testdir.getitem( - """ - import pytest - @pytest.mark.xyz(u"hasattr(os, 'sep')") - def test_func(): - pass - """ - ) - ev = MarkEvaluator(item, "xyz") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "condition: hasattr(os, 'sep')" - def test_marked_one_arg_with_reason(self, testdir): item = testdir.getitem( """ @@ -893,10 +877,7 @@ def test_errors_in_xfail_skip_expressions(testdir): ) result = testdir.runpytest() markline = " ^" - if sys.platform.startswith("java"): - # XXX report this to java - markline = "*" + markline[8:] - elif hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (6,): + if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (6,): markline = markline[5:] elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): markline = markline[4:] diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 40ffe98af..14e5bcbca 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -6,7 +6,6 @@ from __future__ import print_function import sys import attr -import six import pytest from _pytest import pathlib @@ -348,8 +347,6 @@ class TestNumberedDir(object): def attempt_symlink_to(path, to_path): """Try to make a symlink from "path" to "to_path", skipping in case this platform does not support it or we don't have sufficient privileges (common on Windows).""" - if sys.platform.startswith("win") and six.PY2: - pytest.skip("pathlib for some reason cannot make symlinks on Python 2") try: Path(path).symlink_to(Path(to_path)) except OSError: diff --git a/testing/test_unittest.py b/testing/test_unittest.py index bb41952ab..f310cd8cc 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -459,9 +459,6 @@ class TestTrialUnittest(object): pass """ ) - from _pytest.compat import _is_unittest_unexpected_success_a_failure - - should_fail = _is_unittest_unexpected_success_a_failure() result = testdir.runpytest("-rxs", *self.ignore_unclosed_socket_warning) result.stdout.fnmatch_lines_random( [ @@ -472,12 +469,10 @@ class TestTrialUnittest(object): "*i2wanto*", "*sys.version_info*", "*skip_in_method*", - "*1 failed*4 skipped*3 xfailed*" - if should_fail - else "*4 skipped*3 xfail*1 xpass*", + "*1 failed*4 skipped*3 xfailed*", ] ) - assert result.ret == (1 if should_fail else 0) + assert result.ret == 1 def test_trial_error(self, testdir): testdir.makepyfile( @@ -745,22 +740,17 @@ def test_unittest_expected_failure_for_passing_test_is_fail(testdir, runner): unittest.main() """ ) - from _pytest.compat import _is_unittest_unexpected_success_a_failure - should_fail = _is_unittest_unexpected_success_a_failure() if runner == "pytest": result = testdir.runpytest("-rxX") result.stdout.fnmatch_lines( - [ - "*MyTestCase*test_passing_test_is_fail*", - "*1 failed*" if should_fail else "*1 xpassed*", - ] + ["*MyTestCase*test_passing_test_is_fail*", "*1 failed*"] ) else: result = testdir.runpython(script) result.stderr.fnmatch_lines(["*1 test in*", "*(unexpected successes=1)*"]) - assert result.ret == (1 if should_fail else 0) + assert result.ret == 1 @pytest.mark.parametrize( diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 092604d7d..4a5ebac7d 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import sys import warnings -import six - import pytest WARNINGS_SUMMARY_HEADER = "warnings summary" @@ -126,9 +123,6 @@ def test_ignore(testdir, pyfile_with_warnings, method): assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() -@pytest.mark.skipif( - sys.version_info < (3, 0), reason="warnings message is unicode is ok in python3" -) @pytest.mark.filterwarnings("always") def test_unicode(testdir, pyfile_with_warnings): testdir.makepyfile( @@ -157,68 +151,6 @@ def test_unicode(testdir, pyfile_with_warnings): ) -@pytest.mark.skipif( - sys.version_info >= (3, 0), - reason="warnings message is broken as it is not str instance", -) -def test_py2_unicode(testdir, pyfile_with_warnings): - if getattr(sys, "pypy_version_info", ())[:2] == (5, 9) and sys.platform.startswith( - "win" - ): - pytest.xfail("fails with unicode error on PyPy2 5.9 and Windows (#2905)") - testdir.makepyfile( - """ - # -*- coding: utf-8 -*- - import warnings - import pytest - - - @pytest.fixture - def fix(): - warnings.warn(u"测试") - yield - - @pytest.mark.filterwarnings('always') - def test_func(fix): - pass - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines( - [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, - "*test_py2_unicode.py:8: UserWarning: \\u6d4b\\u8bd5", - '*warnings.warn(u"\u6d4b\u8bd5")', - "*warnings.py:*: UnicodeWarning: Warning is using unicode non*", - "* 1 passed, 2 warnings*", - ] - ) - - -def test_py2_unicode_ascii(testdir): - """Ensure that our warning about 'unicode warnings containing non-ascii messages' - does not trigger with ascii-convertible messages""" - testdir.makeini("[pytest]") - testdir.makepyfile( - """ - import pytest - import warnings - - @pytest.mark.filterwarnings('always') - def test_func(): - warnings.warn(u"hello") - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines( - [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, - '*warnings.warn(u"hello")', - "* 1 passed, 1 warnings in*", - ] - ) - - def test_works_with_filterwarnings(testdir): """Ensure our warnings capture does not mess with pre-installed filters (#2430).""" testdir.makepyfile( @@ -569,33 +501,6 @@ class TestDeprecationWarningsByDefault: assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() -@pytest.mark.skipif(six.PY3, reason="Python 2 only issue") -def test_infinite_loop_warning_against_unicode_usage_py2(testdir): - """ - We need to be careful when raising the warning about unicode usage with "warnings.warn" - because it might be overwritten by users and this itself causes another warning (#3691). - """ - testdir.makepyfile( - """ - # -*- coding: utf-8 -*- - from __future__ import unicode_literals - import warnings - import pytest - - def _custom_showwarning(message, *a, **b): - return "WARNING: {}".format(message) - - warnings.formatwarning = _custom_showwarning - - @pytest.mark.filterwarnings("default") - def test_custom_warning_formatter(): - warnings.warn("Â¥") - """ - ) - result = testdir.runpytest_subprocess() - result.stdout.fnmatch_lines(["*1 passed, * warnings in*"]) - - @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) def test_removed_in_pytest4_warning_as_error(testdir, change_default): testdir.makepyfile( diff --git a/tox.ini b/tox.ini index 0b1be0d33..7e5b182f6 100644 --- a/tox.ini +++ b/tox.ini @@ -5,16 +5,13 @@ distshare = {homedir}/.tox/distshare # make sure to update environment list in travis.yml and appveyor.yml envlist = linting - py27 - py34 py35 py36 py37 py38 pypy pypy3 - {py27,py37}-{pexpect,xdist,twisted,numpy,pluggymaster} - py27-nobyte-xdist + py37-{pexpect,xdist,twisted,numpy,pluggymaster} doctesting py37-freeze docs @@ -56,15 +53,6 @@ deps = {env:_PYTEST_TOX_EXTRA_DEP:} platform = {env:_PYTEST_TOX_PLATFORM:.*} -[testenv:py27-subprocess] -deps = - pytest-xdist>=1.13 - py27: mock - nose -commands = - pytest -n auto --runpytest=subprocess {posargs} - - [testenv:linting] skip_install = True basepython = python3 @@ -109,11 +97,6 @@ commands = rm -rf {envdir}/.pytest_cache make regen -[testenv:jython] -changedir = testing -commands = - {envpython} {envbindir}/py.test-jython {posargs} - [testenv:py37-freeze] changedir = testing/freeze # Disable PEP 517 with pip, which does not work with PyInstaller currently.