diff options
56 files changed, 153 insertions, 919 deletions
diff --git a/.travis.yml b/.travis.yml index 2135dae0..2494c8b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,13 @@ language: python cache: pip python: - - '2.7' - '3.5' - '3.6' - '3.7' - '3.8' - - 'pypy2.7-6.0' - 'pypy3.5-6.0' -# Only testing it for python3.8 on aarch64 platform, since it already has a lot of jobs to test and takes long time. +# Only testing it for python3.8 on aarch64 platform, since it already has a lot of jobs to test and takes long time. matrix: include: - python: 3.8 @@ -57,15 +57,15 @@ pep8: pycodestyle --filename=*.py --repeat $(LINTABLE) test: - tox -e py27,py35 $(ARGS) + tox -e py35 $(ARGS) PYTEST_SMOKE_ARGS = -n 6 -m "not expensive" --maxfail=3 $(ARGS) smoke: ## Run tests quickly with the C tracer in the lowest supported Python versions. - COVERAGE_NO_PYTRACER=1 tox -q -e py27,py35 -- $(PYTEST_SMOKE_ARGS) + COVERAGE_NO_PYTRACER=1 tox -q -e py35 -- $(PYTEST_SMOKE_ARGS) pysmoke: ## Run tests quickly with the Python tracer in the lowest supported Python versions. - COVERAGE_NO_CTRACER=1 tox -q -e py27,py35 -- $(PYTEST_SMOKE_ARGS) + COVERAGE_NO_CTRACER=1 tox -q -e py35 -- $(PYTEST_SMOKE_ARGS) DOCKER_RUN = docker run -it --init --rm -v `pwd`:/io RUN_MANYLINUX_X86 = $(DOCKER_RUN) quay.io/pypa/manylinux1_x86_64 /io/ci/manylinux.sh diff --git a/appveyor.yml b/appveyor.yml index 76e21dad..ee143fc6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -20,12 +20,6 @@ environment: # version pre-installed on AppVeyor. # matrix: - - JOB: "2.7 64-bit" - TOXENV: "py27" - PYTHON: "C:\\Python27-x64" - PYTHON_VERSION: "2.7.17" - PYTHON_ARCH: "64" - - JOB: "3.5 64-bit" TOXENV: "py35" PYTHON: "C:\\Python35-x64" @@ -58,13 +52,6 @@ environment: # 32-bit jobs don't run the tests under the Python tracer, since that should # be exactly the same as 64-bit. - - JOB: "2.7 32-bit" - TOXENV: "py27" - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.17" - PYTHON_ARCH: "32" - COVERAGE_NO_PYTRACER: "1" - - JOB: "3.5 32-bit" TOXENV: "py35" PYTHON: "C:\\Python35" @@ -101,13 +88,6 @@ environment: COVERAGE_NO_PYTRACER: "1" # Meta coverage - - JOB: "Meta 2.7" - TOXENV: "py27" - PYTHON: "C:\\Python27" - PYTHON_VERSION: "2.7.17" - PYTHON_ARCH: "32" - COVERAGE_COVERAGE: "yes" - - JOB: "Meta 3.6" TOXENV: "py36" PYTHON: "C:\\Python36" diff --git a/coverage/backunittest.py b/coverage/backunittest.py deleted file mode 100644 index 078f48cc..00000000 --- a/coverage/backunittest.py +++ /dev/null @@ -1,33 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -"""Implementations of unittest features from the future.""" - -import unittest - - -def unittest_has(method): - """Does `unittest.TestCase` have `method` defined?""" - return hasattr(unittest.TestCase, method) - - -class TestCase(unittest.TestCase): - """Just like unittest.TestCase, but with assert methods added. - - Designed to be compatible with 3.1 unittest. Methods are only defined if - `unittest` doesn't have them. - - """ - # pylint: disable=arguments-differ, deprecated-method - - if not unittest_has('assertCountEqual'): - def assertCountEqual(self, *args, **kwargs): - return self.assertItemsEqual(*args, **kwargs) - - if not unittest_has('assertRaisesRegex'): - def assertRaisesRegex(self, *args, **kwargs): - return self.assertRaisesRegexp(*args, **kwargs) - - if not unittest_has('assertRegex'): - def assertRegex(self, *args, **kwargs): - return self.assertRegexpMatches(*args, **kwargs) diff --git a/coverage/backward.py b/coverage/backward.py index 37b49167..227de5fa 100644 --- a/coverage/backward.py +++ b/coverage/backward.py @@ -9,165 +9,15 @@ import os import sys -from coverage import env - - -# Pythons 2 and 3 differ on where to get StringIO. -try: - from cStringIO import StringIO -except ImportError: - from io import StringIO - -# In py3, ConfigParser was renamed to the more-standard configparser. -# But there's a py3 backport that installs "configparser" in py2, and I don't -# want it because it has annoying deprecation warnings. So try the real py2 -# import first. -try: - import ConfigParser as configparser -except ImportError: - import configparser - -# What's a string called? -try: - string_class = basestring -except NameError: - string_class = str - -# What's a Unicode string called? -try: - unicode_class = unicode -except NameError: - unicode_class = str - -# range or xrange? -try: - range = xrange # pylint: disable=redefined-builtin -except NameError: - range = range - -try: - from itertools import zip_longest -except ImportError: - from itertools import izip_longest as zip_longest - -# Where do we get the thread id from? -try: - from thread import get_ident as get_thread_id -except ImportError: - from threading import get_ident as get_thread_id - try: os.PathLike except AttributeError: # This is Python 2 and 3 - path_types = (bytes, string_class, unicode_class) + path_types = (bytes, str) else: # 3.6+ path_types = (bytes, str, os.PathLike) -# shlex.quote is new, but there's an undocumented implementation in "pipes", -# who knew!? -try: - from shlex import quote as shlex_quote -except ImportError: - # Useful function, available under a different (undocumented) name - # in Python versions earlier than 3.3. - from pipes import quote as shlex_quote - -try: - import reprlib -except ImportError: - import repr as reprlib - -# A function to iterate listlessly over a dict's items, and one to get the -# items as a list. -try: - {}.iteritems -except AttributeError: - # Python 3 - def iitems(d): - """Produce the items from dict `d`.""" - return d.items() - - def litems(d): - """Return a list of items from dict `d`.""" - return list(d.items()) -else: - # Python 2 - def iitems(d): - """Produce the items from dict `d`.""" - return d.iteritems() - - def litems(d): - """Return a list of items from dict `d`.""" - return d.items() - -# Getting the `next` function from an iterator is different in 2 and 3. -try: - iter([]).next -except AttributeError: - def iternext(seq): - """Get the `next` function for iterating over `seq`.""" - return iter(seq).__next__ -else: - def iternext(seq): - """Get the `next` function for iterating over `seq`.""" - return iter(seq).next - -# Python 3.x is picky about bytes and strings, so provide methods to -# get them right, and make them no-ops in 2.x -if env.PY3: - def to_bytes(s): - """Convert string `s` to bytes.""" - return s.encode('utf8') - - def to_string(b): - """Convert bytes `b` to string.""" - return b.decode('utf8') - - def binary_bytes(byte_values): - """Produce a byte string with the ints from `byte_values`.""" - return bytes(byte_values) - - def byte_to_int(byte): - """Turn a byte indexed from a bytes object into an int.""" - return byte - - def bytes_to_ints(bytes_value): - """Turn a bytes object into a sequence of ints.""" - # In Python 3, iterating bytes gives ints. - return bytes_value - -else: - def to_bytes(s): - """Convert string `s` to bytes (no-op in 2.x).""" - return s - - def to_string(b): - """Convert bytes `b` to string.""" - return b - - def binary_bytes(byte_values): - """Produce a byte string with the ints from `byte_values`.""" - return "".join(chr(b) for b in byte_values) - - def byte_to_int(byte): - """Turn a byte indexed from a bytes object into an int.""" - return ord(byte) - - def bytes_to_ints(bytes_value): - """Turn a bytes object into a sequence of ints.""" - for byte in bytes_value: - yield ord(byte) - - -try: - # In Python 2.x, the builtins were in __builtin__ - BUILTINS = sys.modules['__builtin__'] -except KeyError: - # In Python 3.x, they're in builtins - BUILTINS = sys.modules['builtins'] - # imp was deprecated in Python 3.3 try: @@ -191,32 +41,6 @@ except AttributeError: PYC_MAGIC_NUMBER = imp.get_magic() -def code_object(fn): - """Get the code object from a function.""" - try: - return fn.func_code - except AttributeError: - return fn.__code__ - - -try: - from types import SimpleNamespace -except ImportError: - # The code from https://docs.python.org/3/library/types.html#types.SimpleNamespace - class SimpleNamespace: - """Python implementation of SimpleNamespace, for Python 2.""" - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - keys = sorted(self.__dict__) - items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) - return "{}({})".format(type(self).__name__, ", ".join(items)) - - def __eq__(self, other): - return self.__dict__ == other.__dict__ - - def invalidate_import_caches(): """Invalidate any import caches that may or may not exist.""" if importlib and hasattr(importlib, "invalidate_caches"): diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 9fddb6bb..f4db1ce4 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -20,7 +20,7 @@ from coverage.collector import CTracer from coverage.data import line_counts from coverage.debug import info_formatter, info_header, short_stack from coverage.execfile import PyRunner -from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource, output_encoding +from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource from coverage.results import should_fail_under @@ -834,8 +834,6 @@ def main(argv=None): except BaseCoverageException as err: # A controlled error inside coverage.py: print the message to the user. msg = err.args[0] - if env.PY2: - msg = msg.encode(output_encoding()) print(msg) status = ERR except SystemExit as err: diff --git a/coverage/collector.py b/coverage/collector.py index a042357f..921e1d12 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -7,7 +7,6 @@ import os import sys from coverage import env -from coverage.backward import litems, range # pylint: disable=redefined-builtin from coverage.debug import short_stack from coverage.disposition import FileDisposition from coverage.misc import CoverageException, isolate_module @@ -398,7 +397,7 @@ class Collector(object): runtime_err = None for _ in range(3): try: - items = litems(d) + items = list(d.items()) except RuntimeError as ex: runtime_err = ex else: diff --git a/coverage/config.py b/coverage/config.py index 78a3e86a..66b4a081 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -4,13 +4,12 @@ """Config file for coverage.py""" import collections +import configparser import copy import os import os.path import re -from coverage import env -from coverage.backward import configparser, iitems, string_class from coverage.misc import contract, CoverageException, isolate_module from coverage.misc import substitute_variables @@ -37,10 +36,7 @@ class HandyConfigParser(configparser.RawConfigParser): def read(self, filenames, encoding=None): """Read a file name as UTF-8 configuration data.""" - kwargs = {} - if env.PYVERSION >= (3, 2): - kwargs['encoding'] = encoding or "utf-8" - return configparser.RawConfigParser.read(self, filenames, **kwargs) + return configparser.RawConfigParser.read(self, filenames, encoding=encoding or "utf-8") def has_option(self, section, option): for section_prefix in self.section_prefixes: @@ -241,9 +237,9 @@ class CoverageConfig(object): def from_args(self, **kwargs): """Read config values from `kwargs`.""" - for k, v in iitems(kwargs): + for k, v in kwargs.items(): if v is not None: - if k in self.MUST_BE_LIST and isinstance(v, string_class): + if k in self.MUST_BE_LIST and isinstance(v, str): v = [v] setattr(self, k, v) @@ -293,7 +289,7 @@ class CoverageConfig(object): section, option = option_spec[1].split(":") all_options[section].add(option) - for section, options in iitems(all_options): + for section, options in all_options.items(): real_section = cp.has_section(section) if real_section: for unknown in set(cp.options(section)) - options: diff --git a/coverage/control.py b/coverage/control.py index f7db26e9..6144e2bc 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -13,7 +13,6 @@ import time from coverage import env from coverage.annotate import AnnotateReporter -from coverage.backward import string_class, iitems from coverage.collector import Collector, CTracer from coverage.config import read_coverage_config from coverage.context import should_start_context_test_function, combine_context_switchers @@ -451,7 +450,7 @@ class Coverage(object): suffix = self._data_suffix_specified if suffix or self.config.parallel: - if not isinstance(suffix, string_class): + if not isinstance(suffix, str): # if data_suffix=True, use .machinename.pid.random suffix = True else: @@ -778,7 +777,7 @@ class Coverage(object): plugin = None file_reporter = "python" - if isinstance(morf, string_class): + if isinstance(morf, str): mapped_morf = self._file_mapper(morf) plugin_name = self._data.file_tracer(mapped_morf) if plugin_name: @@ -1020,7 +1019,7 @@ class Coverage(object): ('path', sys.path), ('environment', sorted( ("%s = %s" % (k, v)) - for k, v in iitems(os.environ) + for k, v in os.environ.items() if any(slug in k for slug in ("COV", "PY")) )), ('command_line', " ".join(getattr(sys, 'argv', ['-none-']))), diff --git a/coverage/debug.py b/coverage/debug.py index 194f16f5..efcaca2a 100644 --- a/coverage/debug.py +++ b/coverage/debug.py @@ -6,16 +6,17 @@ import contextlib import functools import inspect +import io import itertools import os import pprint +import reprlib import sys try: import _thread except ImportError: import thread as _thread -from coverage.backward import reprlib, StringIO from coverage.misc import isolate_module os = isolate_module(os) @@ -86,7 +87,7 @@ class DebugControl(object): class DebugControlString(DebugControl): """A `DebugControl` that writes to a StringIO, for testing.""" def __init__(self, options): - super(DebugControlString, self).__init__(options, StringIO()) + super(DebugControlString, self).__init__(options, io.StringIO()) def get_output(self): """Get the output text from the `DebugControl`.""" diff --git a/coverage/env.py b/coverage/env.py index b5da3b47..52ada906 100644 --- a/coverage/env.py +++ b/coverage/env.py @@ -14,17 +14,12 @@ LINUX = sys.platform.startswith("linux") # Python versions. We amend version_info with one more value, a zero if an # official version, or 1 if built from source beyond an official version. PYVERSION = sys.version_info + (int(platform.python_version()[-1] == "+"),) -PY2 = PYVERSION < (3, 0) -PY3 = PYVERSION >= (3, 0) # Python implementations. PYPY = (platform.python_implementation() == 'PyPy') if PYPY: PYPYVERSION = sys.pypy_version_info -PYPY2 = PYPY and PY2 -PYPY3 = PYPY and PY3 - JYTHON = (platform.python_implementation() == 'Jython') IRONPYTHON = (platform.python_implementation() == 'IronPython') @@ -41,21 +36,6 @@ class PYBEHAVIOR(object): # Is "if not __debug__" optimized away even better? optimize_if_not_debug2 = (not PYPY) and (PYVERSION >= (3, 8, 0, 'beta', 1)) - # Do we have yield-from? - yield_from = (PYVERSION >= (3, 3)) - - # Do we have PEP 420 namespace packages? - namespaces_pep420 = (PYVERSION >= (3, 3)) - - # Do .pyc files have the source file size recorded in them? - size_in_pyc = (PYVERSION >= (3, 3)) - - # Do we have async and await syntax? - async_syntax = (PYVERSION >= (3, 5)) - - # PEP 448 defined additional unpacking generalizations - unpackings_pep448 = (PYVERSION >= (3, 5)) - # Can co_lnotab have negative deltas? negative_lnotab = (PYVERSION >= (3, 6)) and not (PYPY and PYPYVERSION < (7, 2)) diff --git a/coverage/execfile.py b/coverage/execfile.py index 29409d51..4a2da442 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -11,7 +11,6 @@ import sys import types from coverage import env -from coverage.backward import BUILTINS from coverage.backward import PYC_MAGIC_NUMBER, imp, importlib_util_find_spec from coverage.files import canonical_filename, python_reported_file from coverage.misc import CoverageException, ExceptionDuringRun, NoCode, NoSource, isolate_module @@ -182,9 +181,6 @@ class PyRunner(object): else: raise NoSource("Can't find '__main__' module in '%s'" % self.arg0) - if env.PY2: - self.arg0 = os.path.abspath(self.arg0) - # Make a spec. I don't know if this is the right way to do it. try: import importlib.machinery @@ -197,8 +193,7 @@ class PyRunner(object): self.package = "" self.loader = DummyLoader("__main__") else: - if env.PY3: - self.loader = DummyLoader("__main__") + self.loader = DummyLoader("__main__") self.arg0 = python_reported_file(self.arg0) @@ -220,7 +215,7 @@ class PyRunner(object): if self.spec is not None: main_mod.__spec__ = self.spec - main_mod.__builtins__ = BUILTINS + main_mod.__builtins__ = sys.modules['builtins'] sys.modules['__main__'] = main_mod @@ -352,9 +347,8 @@ def make_code_from_pyc(filename): if date_based: # Skip the junk in the header that we don't need. fpyc.read(4) # Skip the moddate. - if env.PYBEHAVIOR.size_in_pyc: - # 3.3 added another long to the header (size), skip it. - fpyc.read(4) + # 3.3 added another long to the header (size), skip it. + fpyc.read(4) # The rest of the file is the code object we want. code = marshal.load(fpyc) diff --git a/coverage/files.py b/coverage/files.py index 5c2ff1ac..64a30763 100644 --- a/coverage/files.py +++ b/coverage/files.py @@ -13,7 +13,6 @@ import re import sys from coverage import env -from coverage.backward import unicode_class from coverage.misc import contract, CoverageException, join_regex, isolate_module @@ -105,8 +104,6 @@ if env.WINDOWS: def actual_path(path): """Get the actual path of `path`, including the correct case.""" - if env.PY2 and isinstance(path, unicode_class): - path = path.encode(sys.getfilesystemencoding()) if path in _ACTUAL_PATH_CACHE: return _ACTUAL_PATH_CACHE[path] @@ -143,19 +140,10 @@ else: return filename -if env.PY2: - @contract(returns='unicode') - def unicode_filename(filename): - """Return a Unicode version of `filename`.""" - if isinstance(filename, str): - encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() - filename = filename.decode(encoding, "replace") - return filename -else: - @contract(filename='unicode', returns='unicode') - def unicode_filename(filename): - """Return a Unicode version of `filename`.""" - return filename +@contract(filename='unicode', returns='unicode') +def unicode_filename(filename): + """Return a Unicode version of `filename`.""" + return filename @contract(returns='unicode') diff --git a/coverage/html.py b/coverage/html.py index 596e1143..cb641b5a 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -8,10 +8,9 @@ import json import os import re import shutil +from types import SimpleNamespace import coverage -from coverage import env -from coverage.backward import iitems, SimpleNamespace from coverage.data import add_data_to_hash from coverage.files import flat_rootname from coverage.misc import CoverageException, ensure_dir, file_be_gone, Hasher, isolate_module @@ -173,8 +172,6 @@ class HtmlReporter(object): self.config = self.coverage.config self.directory = self.config.html_dir title = self.config.html_title - if env.PY2: - title = title.decode("utf8") if self.config.extra_css: self.extra_css = os.path.basename(self.config.extra_css) @@ -423,7 +420,7 @@ class IncrementalChecker(object): if usable: self.files = {} - for filename, fileinfo in iitems(status['files']): + for filename, fileinfo in status['files'].items(): fileinfo['index']['nums'] = Numbers(*fileinfo['index']['nums']) self.files[filename] = fileinfo self.globals = status['globals'] @@ -434,7 +431,7 @@ class IncrementalChecker(object): """Write the current status.""" status_file = os.path.join(self.directory, self.STATUS_FILE) files = {} - for filename, fileinfo in iitems(self.files): + for filename, fileinfo in self.files.items(): fileinfo['index']['nums'] = fileinfo['index']['nums'].init_args() files[filename] = fileinfo diff --git a/coverage/inorout.py b/coverage/inorout.py index d5e8b226..3354c4a1 100644 --- a/coverage/inorout.py +++ b/coverage/inorout.py @@ -14,7 +14,6 @@ import sys import traceback from coverage import env -from coverage.backward import code_object from coverage.disposition import FileDisposition, disposition_init from coverage.files import TreeMatcher, FnmatchMatcher, ModuleMatcher from coverage.files import prep_patterns, find_python_files, canonical_filename @@ -158,7 +157,7 @@ class InOrOut(object): # objects still have the file names. So dig into one to find # the path to exclude. The "filename" might be synthetic, # don't be fooled by those. - structseq_file = code_object(_structseq.structseq_new).co_filename + structseq_file = _structseq.structseq_new.__code__.co_filename if not structseq_file.startswith("<"): self.pylib_paths.add(canonical_path(structseq_file)) diff --git a/coverage/misc.py b/coverage/misc.py index 5c4381ab..9fc08efd 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -16,7 +16,6 @@ import sys import types from coverage import env -from coverage.backward import to_bytes, unicode_class ISOLATED_MODULES = {} @@ -71,8 +70,7 @@ if USE_CONTRACTS: # Define contract words that PyContract doesn't have. new_contract('bytes', lambda v: isinstance(v, bytes)) - if env.PY3: - new_contract('unicode', lambda v: isinstance(v, unicode_class)) + new_contract('unicode', lambda v: isinstance(v, str)) def one_of(argnames): """Ensure that only one of the argnames is non-None.""" @@ -204,15 +202,15 @@ class Hasher(object): def update(self, v): """Add `v` to the hash, recursively if needed.""" - self.md5.update(to_bytes(str(type(v)))) - if isinstance(v, unicode_class): + self.md5.update(str(type(v)).encode("utf-8")) + if isinstance(v, str): self.md5.update(v.encode('utf8')) elif isinstance(v, bytes): self.md5.update(v) elif v is None: pass elif isinstance(v, (int, float)): - self.md5.update(to_bytes(str(v))) + self.md5.update(str(v).encode("utf-8")) elif isinstance(v, (tuple, list)): for e in v: self.update(e) diff --git a/coverage/multiproc.py b/coverage/multiproc.py index 2931b3be..d2ef692d 100644 --- a/coverage/multiproc.py +++ b/coverage/multiproc.py @@ -10,7 +10,6 @@ import os.path import sys import traceback -from coverage import env from coverage.misc import contract # An attribute that will be set on the module to indicate that it has been @@ -18,14 +17,9 @@ from coverage.misc import contract PATCHED_MARKER = "_coverage$patched" -if env.PYVERSION >= (3, 4): - OriginalProcess = multiprocessing.process.BaseProcess -else: - OriginalProcess = multiprocessing.Process +original_bootstrap = multiprocessing.process.BaseProcess._bootstrap -original_bootstrap = OriginalProcess._bootstrap - -class ProcessWithCoverage(OriginalProcess): # pylint: disable=abstract-method +class ProcessWithCoverage(multiprocessing.process.BaseProcess): # pylint: disable=abstract-method """A replacement for multiprocess.Process that starts coverage.""" def _bootstrap(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -79,10 +73,7 @@ def patch_multiprocessing(rcfile): if hasattr(multiprocessing, PATCHED_MARKER): return - if env.PYVERSION >= (3, 4): - OriginalProcess._bootstrap = ProcessWithCoverage._bootstrap - else: - multiprocessing.Process = ProcessWithCoverage + multiprocessing.process.BaseProcess._bootstrap = ProcessWithCoverage._bootstrap # Set the value in ProcessWithCoverage that will be pickled into the child # process. diff --git a/coverage/numbits.py b/coverage/numbits.py index 6ca96fbc..b4c5e869 100644 --- a/coverage/numbits.py +++ b/coverage/numbits.py @@ -13,24 +13,16 @@ in the blobs should be considered an implementation detail that might change in the future. Use these functions to work with those binary blobs of data. """ +from itertools import zip_longest import json -from coverage import env -from coverage.backward import byte_to_int, bytes_to_ints, binary_bytes, zip_longest from coverage.misc import contract, new_contract -if env.PY3: - def _to_blob(b): - """Convert a bytestring into a type SQLite will accept for a blob.""" - return b +def _to_blob(b): + """Convert a bytestring into a type SQLite will accept for a blob.""" + return b - new_contract('blob', lambda v: isinstance(v, bytes)) -else: - def _to_blob(b): - """Convert a bytestring into a type SQLite will accept for a blob.""" - return buffer(b) # pylint: disable=undefined-variable - - new_contract('blob', lambda v: isinstance(v, buffer)) # pylint: disable=undefined-variable +new_contract('blob', lambda v: isinstance(v, bytes)) @contract(nums='Iterable', returns='blob') @@ -69,7 +61,7 @@ def numbits_to_nums(numbits): """ nums = [] - for byte_i, byte in enumerate(bytes_to_ints(numbits)): + for byte_i, byte in enumerate(numbits): for bit_i in range(8): if (byte & (1 << bit_i)): nums.append(byte_i * 8 + bit_i) @@ -83,8 +75,8 @@ def numbits_union(numbits1, numbits2): Returns: A new numbits, the union of `numbits1` and `numbits2`. """ - byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0) - return _to_blob(binary_bytes(b1 | b2 for b1, b2 in byte_pairs)) + byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0) + return _to_blob(bytes(b1 | b2 for b1, b2 in byte_pairs)) @contract(numbits1='blob', numbits2='blob', returns='blob') @@ -94,8 +86,8 @@ def numbits_intersection(numbits1, numbits2): Returns: A new numbits, the intersection `numbits1` and `numbits2`. """ - byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0) - intersection_bytes = binary_bytes(b1 & b2 for b1, b2 in byte_pairs) + byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0) + intersection_bytes = bytes(b1 & b2 for b1, b2 in byte_pairs) return _to_blob(intersection_bytes.rstrip(b'\0')) @@ -109,7 +101,7 @@ def numbits_any_intersection(numbits1, numbits2): Returns: A bool, True if there is any number in both `numbits1` and `numbits2`. """ - byte_pairs = zip_longest(bytes_to_ints(numbits1), bytes_to_ints(numbits2), fillvalue=0) + byte_pairs = zip_longest(numbits1, numbits2, fillvalue=0) return any(b1 & b2 for b1, b2 in byte_pairs) @@ -123,7 +115,7 @@ def num_in_numbits(num, numbits): nbyte, nbit = divmod(num, 8) if nbyte >= len(numbits): return False - return bool(byte_to_int(numbits[nbyte]) & (1 << nbit)) + return bool(numbits[nbyte] & (1 << nbit)) def register_sqlite_functions(connection): diff --git a/coverage/parser.py b/coverage/parser.py index f0d378c6..ae86059e 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -11,8 +11,6 @@ import token import tokenize from coverage import env -from coverage.backward import range # pylint: disable=redefined-builtin -from coverage.backward import bytes_to_ints, string_class from coverage.bytecode import code_objects from coverage.debug import short_stack from coverage.misc import contract, join_regex, new_contract, nice_pair, one_of @@ -105,8 +103,6 @@ class PythonParser(object): """ combined = join_regex(regexes) - if env.PY2: - combined = combined.decode("utf8") regex_c = re.compile(combined) matches = set() for i, ltext in enumerate(self.lines, start=1): @@ -400,8 +396,8 @@ class ByteParser(object): """ # Adapted from dis.py in the standard library. - byte_increments = bytes_to_ints(self.code.co_lnotab[0::2]) - line_increments = bytes_to_ints(self.code.co_lnotab[1::2]) + byte_increments = self.code.co_lnotab[0::2] + line_increments = self.code.co_lnotab[1::2] last_line_num = None line_num = self.code.co_firstlineno @@ -599,7 +595,7 @@ class AstArcAnalyzer(object): def _line__Dict(self, node): # Python 3.5 changed how dict literals are made. - if env.PYVERSION >= (3, 5) and node.keys: + if node.keys: if node.keys[0] is not None: return node.keys[0].lineno else: @@ -1110,7 +1106,7 @@ class AstArcAnalyzer(object): def _handle__While(self, node): constant_test = self.is_constant_expr(node.test) start = to_top = self.line_for_node(node.test) - if constant_test and (env.PY3 or constant_test == "Num"): + if constant_test: to_top = self.line_for_node(node.body[0]) self.block_stack.append(LoopBlock(start=to_top)) from_start = ArcStart(start, cause="the condition on line {lineno} was never true") @@ -1183,8 +1179,7 @@ class AstArcAnalyzer(object): _code_object__GeneratorExp = _make_oneline_code_method("generator expression") _code_object__DictComp = _make_oneline_code_method("dictionary comprehension") _code_object__SetComp = _make_oneline_code_method("set comprehension") - if env.PY3: - _code_object__ListComp = _make_oneline_code_method("list comprehension") + _code_object__ListComp = _make_oneline_code_method("list comprehension") if AST_DUMP: # pragma: debugging @@ -1196,7 +1191,7 @@ if AST_DUMP: # pragma: debugging """Is `value` simple enough to be displayed on a single line?""" return ( value in [None, [], (), {}, set()] or - isinstance(value, (string_class, int, float)) + isinstance(value, (str, int, float)) ) def ast_dump(node, depth=0): diff --git a/coverage/phystokens.py b/coverage/phystokens.py index b6866e7d..7b65786c 100644 --- a/coverage/phystokens.py +++ b/coverage/phystokens.py @@ -3,15 +3,11 @@ """Better tokenizing for coverage.py.""" -import codecs import keyword import re -import sys import token import tokenize -from coverage import env -from coverage.backward import iternext, unicode_class from coverage.misc import contract @@ -143,7 +139,7 @@ class CachedTokenizer(object): """A stand-in for `tokenize.generate_tokens`.""" if text != self.last_text: self.last_text = text - readline = iternext(text.splitlines(True)) + readline = iter(text.splitlines(True)).__next__ self.last_tokens = list(tokenize.generate_tokens(readline)) return self.last_tokens @@ -154,102 +150,7 @@ generate_tokens = CachedTokenizer().generate_tokens COOKIE_RE = re.compile(r"^[ \t]*#.*coding[:=][ \t]*([-\w.]+)", flags=re.MULTILINE) @contract(source='bytes') -def _source_encoding_py2(source): - """Determine the encoding for `source`, according to PEP 263. - - `source` is a byte string, the text of the program. - - Returns a string, the name of the encoding. - - """ - assert isinstance(source, bytes) - - # Do this so the detect_encode code we copied will work. - readline = iternext(source.splitlines(True)) - - # This is mostly code adapted from Py3.2's tokenize module. - - def _get_normal_name(orig_enc): - """Imitates get_normal_name in tokenizer.c.""" - # Only care about the first 12 characters. - enc = orig_enc[:12].lower().replace("_", "-") - if re.match(r"^utf-8($|-)", enc): - return "utf-8" - if re.match(r"^(latin-1|iso-8859-1|iso-latin-1)($|-)", enc): - return "iso-8859-1" - return orig_enc - - # From detect_encode(): - # It detects the encoding from the presence of a UTF-8 BOM or an encoding - # cookie as specified in PEP-0263. If both a BOM and a cookie are present, - # but disagree, a SyntaxError will be raised. If the encoding cookie is an - # invalid charset, raise a SyntaxError. Note that if a UTF-8 BOM is found, - # 'utf-8-sig' is returned. - - # If no encoding is specified, then the default will be returned. - default = 'ascii' - - bom_found = False - encoding = None - - def read_or_stop(): - """Get the next source line, or ''.""" - try: - return readline() - except StopIteration: - return '' - - def find_cookie(line): - """Find an encoding cookie in `line`.""" - try: - line_string = line.decode('ascii') - except UnicodeDecodeError: - return None - - matches = COOKIE_RE.findall(line_string) - if not matches: - return None - encoding = _get_normal_name(matches[0]) - try: - codec = codecs.lookup(encoding) - except LookupError: - # This behavior mimics the Python interpreter - raise SyntaxError("unknown encoding: " + encoding) - - if bom_found: - # codecs in 2.3 were raw tuples of functions, assume the best. - codec_name = getattr(codec, 'name', encoding) - if codec_name != 'utf-8': - # This behavior mimics the Python interpreter - raise SyntaxError('encoding problem: utf-8') - encoding += '-sig' - return encoding - - first = read_or_stop() - if first.startswith(codecs.BOM_UTF8): - bom_found = True - first = first[3:] - default = 'utf-8-sig' - if not first: - return default - - encoding = find_cookie(first) - if encoding: - return encoding - - second = read_or_stop() - if not second: - return default - - encoding = find_cookie(second) - if encoding: - return encoding - - return default - - -@contract(source='bytes') -def _source_encoding_py3(source): +def source_encoding(source): """Determine the encoding for `source`, according to PEP 263. `source` is a byte string: the text of the program. @@ -257,16 +158,10 @@ def _source_encoding_py3(source): Returns a string, the name of the encoding. """ - readline = iternext(source.splitlines(True)) + readline = iter(source.splitlines(True)).__next__ return tokenize.detect_encoding(readline)[0] -if env.PY3: - source_encoding = _source_encoding_py3 -else: - source_encoding = _source_encoding_py2 - - @contract(source='unicode') def compile_unicode(source, filename, mode): """Just like the `compile` builtin, but works on any Unicode string. @@ -280,8 +175,6 @@ def compile_unicode(source, filename, mode): """ source = neuter_encoding_declaration(source) - if env.PY2 and isinstance(filename, unicode_class): - filename = filename.encode(sys.getfilesystemencoding(), "replace") code = compile(source, filename, mode) return code diff --git a/coverage/pytracer.py b/coverage/pytracer.py index 44bfc8d6..84b7d1d9 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -11,8 +11,6 @@ from coverage import env # We need the YIELD_VALUE opcode below, in a comparison-friendly form. YIELD_VALUE = dis.opmap['YIELD_VALUE'] -if env.PY2: - YIELD_VALUE = chr(YIELD_VALUE) class PyTracer(object): diff --git a/coverage/report.py b/coverage/report.py index 64678ff9..0398d170 100644 --- a/coverage/report.py +++ b/coverage/report.py @@ -4,7 +4,6 @@ """Reporter foundation for coverage.py.""" import sys -from coverage import env from coverage.files import prep_patterns, FnmatchMatcher from coverage.misc import CoverageException, NoSource, NotPython, ensure_dir_for_file, file_be_gone @@ -26,10 +25,7 @@ def render_report(output_path, reporter, morfs): # HTMLReport does this using the Report plumbing because # its task is more complex, being multiple files. ensure_dir_for_file(output_path) - open_kwargs = {} - if env.PY3: - open_kwargs['encoding'] = 'utf8' - outfile = open(output_path, "w", **open_kwargs) + outfile = open(output_path, "w", encoding="utf8") file_to_close = outfile try: return reporter.report(morfs, outfile=outfile) diff --git a/coverage/results.py b/coverage/results.py index ae8366bf..e4725d85 100644 --- a/coverage/results.py +++ b/coverage/results.py @@ -5,7 +5,6 @@ import collections -from coverage.backward import iitems from coverage.debug import SimpleReprMixin from coverage.misc import contract, CoverageException, nice_pair @@ -32,8 +31,8 @@ class Analysis(object): self.no_branch = self.file_reporter.no_branch_lines() n_branches = self._total_branches() mba = self.missing_branch_arcs() - n_partial_branches = sum(len(v) for k,v in iitems(mba) if k not in self.missing) - n_missing_branches = sum(len(v) for k,v in iitems(mba)) + n_partial_branches = sum(len(v) for k,v in mba.items() if k not in self.missing) + n_missing_branches = sum(len(v) for k,v in mba.items()) else: self._arc_possibilities = [] self.exit_counts = {} @@ -59,7 +58,7 @@ class Analysis(object): """ if branches and self.has_arcs(): - arcs = iitems(self.missing_branch_arcs()) + arcs = self.missing_branch_arcs().items() else: arcs = None @@ -113,7 +112,7 @@ class Analysis(object): def _branch_lines(self): """Returns a list of line numbers that have more than one exit.""" - return [l1 for l1,count in iitems(self.exit_counts) if count > 1] + return [l1 for l1,count in self.exit_counts.items() if count > 1] def _total_branches(self): """How many total branches are there?""" diff --git a/coverage/sqldata.py b/coverage/sqldata.py index b8ee8853..1d62e09f 100644 --- a/coverage/sqldata.py +++ b/coverage/sqldata.py @@ -15,9 +15,8 @@ import re import sqlite3 import sys import zlib +from threading import get_ident as get_thread_id -from coverage import env -from coverage.backward import get_thread_id, iitems, to_bytes, to_string from coverage.debug import NoDebugging, SimpleReprMixin, clipped_repr from coverage.files import PathAliases from coverage.misc import CoverageException, contract, file_be_gone, filename_suffix, isolate_module @@ -328,7 +327,7 @@ class CoverageData(SimpleReprMixin): if self._debug.should('dataio'): self._debug.write("Dumping data from data file {!r}".format(self._filename)) with self._connect() as con: - return b'z' + zlib.compress(to_bytes(con.dump())) + return b'z' + zlib.compress(con.dump().encode("utf-8")) @contract(data='bytes') def loads(self, data): @@ -349,7 +348,7 @@ class CoverageData(SimpleReprMixin): raise CoverageException( "Unrecognized serialization: {!r} (head of {} bytes)".format(data[:40], len(data)) ) - script = to_string(zlib.decompress(data[1:])) + script = zlib.decompress(data[1:]).decode('utf-8') self._dbs[get_thread_id()] = db = SqliteDb(self._filename, self._debug) with db: db.executescript(script) @@ -439,7 +438,7 @@ class CoverageData(SimpleReprMixin): return with self._connect() as con: self._set_context_id() - for filename, linenos in iitems(line_data): + for filename, linenos in line_data.items(): linemap = nums_to_numbits(linenos) file_id = self._file_id(filename, add=True) query = "select numbits from line_bits where file_id = ? and context_id = ?" @@ -471,7 +470,7 @@ class CoverageData(SimpleReprMixin): return with self._connect() as con: self._set_context_id() - for filename, arcs in iitems(arc_data): + for filename, arcs in arc_data.items(): file_id = self._file_id(filename, add=True) data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs] con.executemany( @@ -509,7 +508,7 @@ class CoverageData(SimpleReprMixin): return self._start_using() with self._connect() as con: - for filename, plugin_name in iitems(file_tracers): + for filename, plugin_name in file_tracers.items(): file_id = self._file_id(filename) if file_id is None: raise CoverageException( @@ -984,20 +983,6 @@ class SqliteDb(SimpleReprMixin): if self.con is not None: return - # SQLite on Windows on py2 won't open a file if the filename argument - # has non-ascii characters in it. Opening a relative file name avoids - # a problem if the current directory has non-ascii. - filename = self.filename - if env.WINDOWS and env.PY2: - try: - filename = os.path.relpath(self.filename) - except ValueError: - # ValueError can be raised under Windows when os.getcwd() returns a - # folder from a different drive than the drive of self.filename in - # which case we keep the original value of self.filename unchanged, - # hoping that we won't face the non-ascii directory problem. - pass - # It can happen that Python switches threads while the tracer writes # data. The second thread will also try to write to the data, # effectively causing a nested context. However, given the idempotent @@ -1005,7 +990,7 @@ class SqliteDb(SimpleReprMixin): # is not a problem. if self.debug: self.debug.write("Connecting to {!r}".format(self.filename)) - self.con = sqlite3.connect(filename, check_same_thread=False) + self.con = sqlite3.connect(self.filename, check_same_thread=False) self.con.create_function('REGEXP', 2, _regexp) # This pragma makes writing faster. It disables rollbacks, but we never need them. diff --git a/coverage/summary.py b/coverage/summary.py index 97d9fff0..baca8b3d 100644 --- a/coverage/summary.py +++ b/coverage/summary.py @@ -5,10 +5,9 @@ import sys -from coverage import env from coverage.report import get_analysis_to_report from coverage.results import Numbers -from coverage.misc import NotPython, CoverageException, output_encoding +from coverage.misc import NotPython, CoverageException class SummaryReporter(object): @@ -27,8 +26,6 @@ class SummaryReporter(object): def writeout(self, line): """Write a line to the output, adding a newline.""" - if env.PY2: - line = line.encode(output_encoding()) self.outfile.write(line.rstrip()) self.outfile.write("\n") diff --git a/coverage/templite.py b/coverage/templite.py index 7d4024e0..91748013 100644 --- a/coverage/templite.py +++ b/coverage/templite.py @@ -12,8 +12,6 @@ http://aosabook.org/en/500L/a-template-engine.html import re -from coverage import env - class TempliteSyntaxError(ValueError): """Raised when a template has a syntax error.""" @@ -137,10 +135,6 @@ class Templite(object): code.add_line("result = []") code.add_line("append_result = result.append") code.add_line("extend_result = result.extend") - if env.PY2: - code.add_line("to_str = unicode") - else: - code.add_line("to_str = str") buffered = [] @@ -172,7 +166,7 @@ class Templite(object): elif token.startswith('{{'): # An expression to evaluate. expr = self._expr_code(token[start:end].strip()) - buffered.append("to_str(%s)" % expr) + buffered.append("str(%s)" % expr) else: # token.startswith('{%') # Action tag: split into words and parse further. diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py index 25542f99..ad35d21f 100644 --- a/coverage/tomlconfig.py +++ b/coverage/tomlconfig.py @@ -3,12 +3,13 @@ """TOML configuration support for coverage.py""" +import configparser import io import os import re from coverage import env -from coverage.backward import configparser, path_types +from coverage.backward import path_types from coverage.misc import CoverageException, substitute_variables diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 265bf02c..b7b35ffe 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -10,9 +10,7 @@ import sys import time import xml.dom.minidom -from coverage import env from coverage import __url__, __version__, files -from coverage.backward import iitems from coverage.misc import isolate_module from coverage.report import get_analysis_to_report @@ -91,13 +89,13 @@ class XmlReporter(object): xcoverage.appendChild(xpackages) # Populate the XML DOM with the package info. - for pkg_name, pkg_data in sorted(iitems(self.packages)): + for pkg_name, pkg_data in sorted(self.packages.items()): class_elts, lhits, lnum, bhits, bnum = pkg_data xpackage = self.xml_out.createElement("package") xpackages.appendChild(xpackage) xclasses = self.xml_out.createElement("classes") xpackage.appendChild(xclasses) - for _, class_elt in sorted(iitems(class_elts)): + for _, class_elt in sorted(class_elts.items()): xclasses.appendChild(class_elt) xpackage.setAttribute("name", pkg_name.replace(os.sep, '.')) xpackage.setAttribute("line-rate", rate(lhits, lnum)) @@ -127,7 +125,7 @@ class XmlReporter(object): xcoverage.setAttribute("complexity", "0") # Write the output file. - outfile.write(serialize_xml(self.xml_out)) + outfile.write(self.xml_out.toprettyxml()) # Return the total percentage. denom = lnum_tot + bnum_tot @@ -218,11 +216,3 @@ class XmlReporter(object): package[2] += class_lines package[3] += class_br_hits package[4] += class_branches - - -def serialize_xml(dom): - """Serialize a minidom node to XML.""" - out = dom.toprettyxml() - if env.PY2: - out = out.encode("utf8") - return out @@ -4,7 +4,7 @@ """Code coverage measurement for Python""" # Distutils setup for coverage.py -# This file is used unchanged under all versions of Python, 2.x and 3.x. +# This file is used unchanged under all versions of Python. import os import sys @@ -25,8 +25,6 @@ Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python -Programming Language :: Python :: 2 -Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 diff --git a/tests/conftest.py b/tests/conftest.py index 82a6b0f2..210c54c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,8 +40,8 @@ def set_warnings(): category=DeprecationWarning, message=".* instead of inspect.getfullargspec", ) - if env.PYPY3: - # pypy3 warns about unclosed files a lot. + if env.PYPY: + # pypy warns about unclosed files a lot. warnings.filterwarnings("ignore", r".*unclosed file", category=ResourceWarning) diff --git a/tests/coveragetest.py b/tests/coveragetest.py index 58cfb3dc..1cf6e217 100644 --- a/tests/coveragetest.py +++ b/tests/coveragetest.py @@ -7,6 +7,7 @@ import contextlib import datetime import functools import glob +import io import os import os.path import random @@ -14,6 +15,7 @@ import re import shlex import sys import types +import unittest import pytest from unittest_mixins import ( @@ -23,8 +25,7 @@ from unittest_mixins import ( import coverage from coverage import env -from coverage.backunittest import TestCase, unittest -from coverage.backward import StringIO, import_local_file, string_class, shlex_quote +from coverage.backward import import_local_file from coverage.cmdline import CoverageScript from coverage.misc import StopEverything @@ -69,7 +70,7 @@ class CoverageTest( TempDirMixin, DelayedAssertionMixin, CoverageTestMethodsMixin, - TestCase, + unittest.TestCase, ): """A base class for coverage.py test cases.""" @@ -209,7 +210,7 @@ class CoverageTest( self.fail("None of the lines choices matched %r" % statements) missing_formatted = analysis.missing_formatted() - if isinstance(missing, string_class): + if isinstance(missing, str): self.assertEqual(missing_formatted, missing) else: for missing_list in missing: @@ -236,7 +237,7 @@ class CoverageTest( ) if report: - frep = StringIO() + frep = io.StringIO() cov.report(mod, file=frep, show_missing=True) rep = " ".join(frep.getvalue().split("\n")[2].split()[1:]) self.assertEqual(report, rep) @@ -418,7 +419,7 @@ class CoverageTest( else: command_words = [command_name] - cmd = " ".join([shlex_quote(w) for w in command_words] + command_args) + cmd = " ".join([shlex.quote(w) for w in command_words] + command_args) # Add our test modules directory to PYTHONPATH. I'm sure there's too # much path munging here, but... diff --git a/tests/gold/html/bom/2/bom_py.html b/tests/gold/html/bom/2/bom_py.html deleted file mode 100644 index 3d1c98fb..00000000 --- a/tests/gold/html/bom/2/bom_py.html +++ /dev/null @@ -1,75 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=emulateIE7" /> - <title>Coverage for bom.py: 71%</title> - <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> - <script type="text/javascript" src="jquery.hotkeys.js"></script> - <script type="text/javascript" src="jquery.isonscreen.js"></script> - <script type="text/javascript" src="coverage_html.js"></script> - <script type="text/javascript"> - jQuery(document).ready(coverage.pyfile_ready); - </script> -</head> -<body class="pyfile"> -<div id="header"> - <div class="content"> - <h1>Coverage for <b>bom.py</b> : - <span class="pc_cov">71%</span> - </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <h2 class="stats"> - 7 statements - <span class="run shortkey_r button_toggle_run">5 run</span> - <span class="mis show_mis shortkey_m button_toggle_mis">2 missing</span> - <span class="exc show_exc shortkey_x button_toggle_exc">0 excluded</span> - </h2> - </div> -</div> -<div class="help_panel"> - <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> - <p class="legend">Hot-keys on this page</p> - <div> - <p class="keyhelp"> - <span class="key">r</span> - <span class="key">m</span> - <span class="key">x</span> - <span class="key">p</span> toggle line displays - </p> - <p class="keyhelp"> - <span class="key">j</span> - <span class="key">k</span> next/prev highlighted chunk - </p> - <p class="keyhelp"> - <span class="key">0</span> (zero) top of page - </p> - <p class="keyhelp"> - <span class="key">1</span> (one) first highlighted chunk - </p> - </div> -</div> -<div id="source"> - <p id="t1" class="pln"><span class="n"><a href="#t1">1</a></span><span class="t"><span class="com"># A Python source file in utf-8, with BOM.</span> </span><span class="r"></span></p> - <p id="t2" class="run"><span class="n"><a href="#t2">2</a></span><span class="t"><span class="nam">math</span> <span class="op">=</span> <span class="str">"3×4 = 12, ÷2 = 6±0"</span> </span><span class="r"></span></p> - <p id="t3" class="pln"><span class="n"><a href="#t3">3</a></span><span class="t"> </span><span class="r"></span></p> - <p id="t4" class="run"><span class="n"><a href="#t4">4</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p> - <p id="t5" class="pln"><span class="n"><a href="#t5">5</a></span><span class="t"> </span><span class="r"></span></p> - <p id="t6" class="run"><span class="n"><a href="#t6">6</a></span><span class="t"><span class="key">if</span> <span class="nam">sys</span><span class="op">.</span><span class="nam">version_info</span> <span class="op">>=</span> <span class="op">(</span><span class="num">3</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p> - <p id="t7" class="mis show_mis"><span class="n"><a href="#t7">7</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span> </span><span class="r"></span></p> - <p id="t8" class="mis show_mis"><span class="n"><a href="#t8">8</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">.</span><span class="nam">encode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span> </span><span class="r"></span></p> - <p id="t9" class="pln"><span class="n"><a href="#t9">9</a></span><span class="t"><span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p> - <p id="t10" class="run"><span class="n"><a href="#t10">10</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span> </span><span class="r"></span></p> - <p id="t11" class="run"><span class="n"><a href="#t11">11</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">.</span><span class="nam">decode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span> </span><span class="r"></span></p> -</div> -<div id="footer"> - <div class="content"> - <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a9">coverage.py v5.0a9</a>, - created at 2019-10-14 09:32 - </p> - </div> -</div> -</body> -</html> diff --git a/tests/gold/html/bom/2/index.html b/tests/gold/html/bom/2/index.html deleted file mode 100644 index 3fa67f0f..00000000 --- a/tests/gold/html/bom/2/index.html +++ /dev/null @@ -1,84 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> - <title>Coverage report</title> - <link rel="stylesheet" href="style.css" type="text/css"> - <script type="text/javascript" src="jquery.min.js"></script> - <script type="text/javascript" src="jquery.ba-throttle-debounce.min.js"></script> - <script type="text/javascript" src="jquery.tablesorter.min.js"></script> - <script type="text/javascript" src="jquery.hotkeys.js"></script> - <script type="text/javascript" src="coverage_html.js"></script> - <script type="text/javascript"> - jQuery(document).ready(coverage.index_ready); - </script> -</head> -<body class="indexfile"> -<div id="header"> - <div class="content"> - <h1>Coverage report: - <span class="pc_cov">71%</span> - </h1> - <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> - <form id="filter_container"> - <input id="filter" type="text" value="" placeholder="filter..." /> - </form> - </div> -</div> -<div class="help_panel"> - <img id="panel_icon" src="keybd_open.png" alt="Hide keyboard shortcuts" /> - <p class="legend">Hot-keys on this page</p> - <div> - <p class="keyhelp"> - <span class="key">n</span> - <span class="key">s</span> - <span class="key">m</span> - <span class="key">x</span> - <span class="key">c</span> change column sorting - </p> - </div> -</div> -<div id="index"> - <table class="index"> - <thead> - <tr class="tablehead" title="Click to sort"> - <th class="name left headerSortDown shortkey_n">Module</th> - <th class="shortkey_s">statements</th> - <th class="shortkey_m">missing</th> - <th class="shortkey_x">excluded</th> - <th class="right shortkey_c">coverage</th> - </tr> - </thead> - <tfoot> - <tr class="total"> - <td class="name left">Total</td> - <td>7</td> - <td>2</td> - <td>0</td> - <td class="right" data-ratio="5 7">71%</td> - </tr> - </tfoot> - <tbody> - <tr class="file"> - <td class="name left"><a href="bom_py.html">bom.py</a></td> - <td>7</td> - <td>2</td> - <td>0</td> - <td class="right" data-ratio="5 7">71%</td> - </tr> - </tbody> - </table> - <p id="no_rows"> - No items found using the specified filter. - </p> -</div> -<div id="footer"> - <div class="content"> - <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a9">coverage.py v5.0a9</a>, - created at 2019-10-13 11:41 - </p> - </div> -</div> -</body> -</html> diff --git a/tests/gold/html/bom/bom_py.html b/tests/gold/html/bom/bom_py.html index 635f8b51..e1decaa9 100644 --- a/tests/gold/html/bom/bom_py.html +++ b/tests/gold/html/bom/bom_py.html @@ -3,7 +3,7 @@ <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=emulateIE7" /> - <title>Coverage for bom.py: 71%</title> + <title>Coverage for bom.py: 100%</title> <link rel="stylesheet" href="style.css" type="text/css"> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript" src="jquery.hotkeys.js"></script> @@ -17,13 +17,13 @@ <div id="header"> <div class="content"> <h1>Coverage for <b>bom.py</b> : - <span class="pc_cov">71%</span> + <span class="pc_cov">100%</span> </h1> <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> <h2 class="stats"> - 7 statements - <span class="run shortkey_r button_toggle_run">5 run</span> - <span class="mis show_mis shortkey_m button_toggle_mis">2 missing</span> + 4 statements + <span class="run shortkey_r button_toggle_run">4 run</span> + <span class="mis show_mis shortkey_m button_toggle_mis">0 missing</span> <span class="exc show_exc shortkey_x button_toggle_exc">0 excluded</span> </h2> </div> @@ -56,18 +56,14 @@ <p id="t3" class="pln"><span class="n"><a href="#t3">3</a></span><span class="t"> </span><span class="r"></span></p> <p id="t4" class="run"><span class="n"><a href="#t4">4</a></span><span class="t"><span class="key">import</span> <span class="nam">sys</span> </span><span class="r"></span></p> <p id="t5" class="pln"><span class="n"><a href="#t5">5</a></span><span class="t"> </span><span class="r"></span></p> - <p id="t6" class="run"><span class="n"><a href="#t6">6</a></span><span class="t"><span class="key">if</span> <span class="nam">sys</span><span class="op">.</span><span class="nam">version_info</span> <span class="op">>=</span> <span class="op">(</span><span class="num">3</span><span class="op">,</span> <span class="num">0</span><span class="op">)</span><span class="op">:</span> </span><span class="r"></span></p> - <p id="t7" class="run"><span class="n"><a href="#t7">7</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span> </span><span class="r"></span></p> - <p id="t8" class="run"><span class="n"><a href="#t8">8</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">.</span><span class="nam">encode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span> </span><span class="r"></span></p> - <p id="t9" class="pln"><span class="n"><a href="#t9">9</a></span><span class="t"><span class="key">else</span><span class="op">:</span> </span><span class="r"></span></p> - <p id="t10" class="mis show_mis"><span class="n"><a href="#t10">10</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span> </span><span class="r"></span></p> - <p id="t11" class="mis show_mis"><span class="n"><a href="#t11">11</a></span><span class="t"> <span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">.</span><span class="nam">decode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span> </span><span class="r"></span></p> + <p id="t6" class="run"><span class="n"><a href="#t6">6</a></span><span class="t"><span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">)</span> <span class="op">==</span> <span class="num">18</span> </span><span class="r"></span></p> + <p id="t7" class="run"><span class="n"><a href="#t7">7</a></span><span class="t"><span class="key">assert</span> <span class="nam">len</span><span class="op">(</span><span class="nam">math</span><span class="op">.</span><span class="nam">encode</span><span class="op">(</span><span class="str">'utf-8'</span><span class="op">)</span><span class="op">)</span> <span class="op">==</span> <span class="num">21</span> </span><span class="r"></span></p> </div> <div id="footer"> <div class="content"> <p> - <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a9">coverage.py v5.0a9</a>, - created at 2019-10-14 09:27 + <a class="nav" href="index.html">« index</a> <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0.4a0">coverage.py v5.0.4a0</a>, + created at 2020-03-03 06:44 </p> </div> </div> diff --git a/tests/gold/html/bom/index.html b/tests/gold/html/bom/index.html index b08e9c1c..bc8fd96e 100644 --- a/tests/gold/html/bom/index.html +++ b/tests/gold/html/bom/index.html @@ -17,7 +17,7 @@ <div id="header"> <div class="content"> <h1>Coverage report: - <span class="pc_cov">71%</span> + <span class="pc_cov">100%</span> </h1> <img id="keyboard_icon" src="keybd_closed.png" alt="Show keyboard shortcuts" /> <form id="filter_container"> @@ -52,19 +52,19 @@ <tfoot> <tr class="total"> <td class="name left">Total</td> - <td>7</td> - <td>2</td> + <td>4</td> <td>0</td> - <td class="right" data-ratio="5 7">71%</td> + <td>0</td> + <td class="right" data-ratio="4 4">100%</td> </tr> </tfoot> <tbody> <tr class="file"> <td class="name left"><a href="bom_py.html">bom.py</a></td> - <td>7</td> - <td>2</td> + <td>4</td> + <td>0</td> <td>0</td> - <td class="right" data-ratio="5 7">71%</td> + <td class="right" data-ratio="4 4">100%</td> </tr> </tbody> </table> @@ -75,8 +75,8 @@ <div id="footer"> <div class="content"> <p> - <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0a9">coverage.py v5.0a9</a>, - created at 2019-10-14 09:27 + <a class="nav" href="https://coverage.readthedocs.io/en/coverage-5.0.4a0">coverage.py v5.0.4a0</a>, + created at 2020-03-03 06:44 </p> </div> </div> diff --git a/tests/goldtest.py b/tests/goldtest.py index 16301417..a3972f1e 100644 --- a/tests/goldtest.py +++ b/tests/goldtest.py @@ -12,8 +12,6 @@ import re import sys import xml.etree.ElementTree -from coverage import env - from tests.coveragetest import TESTS_DIR @@ -22,10 +20,6 @@ def gold_path(path): return os.path.join(TESTS_DIR, "gold", path) -# "rU" was deprecated in 3.4 -READ_MODE = "rU" if env.PYVERSION < (3, 4) else "r" - - def versioned_directory(d): """Find a subdirectory of d specific to the Python version. For example, on Python 3.6.4 rc 1, it returns the first of these @@ -80,13 +74,13 @@ def compare( for f in diff_files: expected_file = os.path.join(expected_dir, f) - with open(expected_file, READ_MODE) as fobj: + with open(expected_file) as fobj: expected = fobj.read() if expected_file.endswith(".xml"): expected = canonicalize_xml(expected) actual_file = os.path.join(actual_dir, f) - with open(actual_file, READ_MODE) as fobj: + with open(actual_file) as fobj: actual = fobj.read() if actual_file.endswith(".xml"): actual = canonicalize_xml(actual) diff --git a/tests/helpers.py b/tests/helpers.py index 9c6a0ad8..dc9ffddf 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -8,12 +8,10 @@ import os import re import shutil import subprocess -import sys from unittest_mixins import ModuleCleaner -from coverage import env -from coverage.backward import invalidate_import_caches, unicode_class +from coverage.backward import invalidate_import_caches from coverage.misc import output_encoding @@ -23,9 +21,6 @@ def run_command(cmd): Returns the exit status code and the combined stdout and stderr. """ - if env.PY2 and isinstance(cmd, unicode_class): - cmd = cmd.encode(sys.getfilesystemencoding()) - # In some strange cases (PyPy3 in a virtualenv!?) the stdout encoding of # the subprocess is set incorrectly to ascii. Use an environment variable # to force the encoding to be the same as ours. diff --git a/tests/test_api.py b/tests/test_api.py index 8ab57955..af4d413e 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -5,6 +5,7 @@ import fnmatch import glob +import io import os import os.path import re @@ -16,7 +17,7 @@ from unittest_mixins import change_dir import coverage from coverage import env -from coverage.backward import code_object, import_local_file, StringIO +from coverage.backward import import_local_file from coverage.data import line_counts from coverage.files import abs_file from coverage.misc import CoverageException @@ -269,7 +270,7 @@ class ApiTest(CoverageTest): def f1(): a = 1 # pylint: disable=unused-variable - one_line_number = code_object(f1).co_firstlineno + 1 + one_line_number = f1.__code__.co_firstlineno + 1 lines = [] def run_one_function(f): @@ -776,11 +777,6 @@ class CurrentInstanceTest(CoverageTest): class NamespaceModuleTest(UsingModulesMixin, CoverageTest): """Test PEP-420 namespace modules.""" - def setUp(self): - if not env.PYBEHAVIOR.namespaces_pep420: - self.skipTest("Python before 3.3 doesn't have namespace packages") - super(NamespaceModuleTest, self).setUp() - def test_explicit_namespace_module(self): self.make_file("main.py", "import namespace_420\n") @@ -936,7 +932,7 @@ class ReportIncludeOmitTest(IncludeOmitTestsMixin, CoverageTest): cov.start() import usepkgs # pragma: nested # pylint: disable=import-error, unused-import cov.stop() # pragma: nested - report = StringIO() + report = io.StringIO() cov.report(file=report, **kwargs) return report.getvalue() @@ -1059,7 +1055,7 @@ class TestRunnerPluginTest(CoverageTest): self.start_import_stop(cov, "prog") cov.combine() cov.save() - report = StringIO() + report = io.StringIO() cov.report(show_missing=None, ignore_errors=True, file=report, skip_covered=None, skip_empty=None) self.assertEqual(report.getvalue(), textwrap.dedent("""\ diff --git a/tests/test_arcs.py b/tests/test_arcs.py index bf17f712..1c22c351 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -267,10 +267,8 @@ class LoopArcTest(CoverageTest): # 3.x thinks it's constant. if env.PYBEHAVIOR.nix_while_true: arcz = ".1 13 34 45 36 63 57 7." - elif env.PY3: - arcz = ".1 12 23 34 45 36 63 57 7." else: - arcz = ".1 12 23 34 45 36 62 57 7." + arcz = ".1 12 23 34 45 36 63 57 7." self.check_coverage("""\ a, i = 1, 0 while True: @@ -307,10 +305,8 @@ class LoopArcTest(CoverageTest): # A continue in a while-true needs to jump to the right place. if env.PYBEHAVIOR.nix_while_true: arcz = ".1 13 34 45 53 46 67 7." - elif env.PY3: - arcz = ".1 12 23 34 45 53 46 67 7." else: - arcz = ".1 12 23 34 45 52 46 67 7." + arcz = ".1 12 23 34 45 53 46 67 7." self.check_coverage("""\ up = iter('ta') while True: @@ -382,11 +378,7 @@ class LoopArcTest(CoverageTest): ) def test_confusing_for_loop_bug_175(self): - if env.PY3: - # Py3 counts the list comp as a separate code object. - arcz = ".1 -22 2-2 12 23 34 45 53 3." - else: - arcz = ".1 12 23 34 45 53 3." + arcz = ".1 -22 2-2 12 23 34 45 53 3." self.check_coverage("""\ o = [(1,2), (3,4)] o = [a for a in o] @@ -396,10 +388,7 @@ class LoopArcTest(CoverageTest): """, arcz=arcz, ) - if env.PY3: - arcz = ".1 12 -22 2-2 23 34 42 2." - else: - arcz = ".1 12 23 34 42 2." + arcz = ".1 12 -22 2-2 23 34 42 2." self.check_coverage("""\ o = [(1,2), (3,4)] for tup in [a for a in o]: @@ -1048,8 +1037,6 @@ class YieldTest(CoverageTest): self.assertEqual(self.stdout(), "20\n12\n") def test_yield_from(self): - if not env.PYBEHAVIOR.yield_from: - self.skipTest("Python before 3.3 doesn't have 'yield from'") self.check_coverage("""\ def gen(inp): i = 2 @@ -1224,8 +1211,6 @@ class MiscArcTest(CoverageTest): ) def test_unpacked_literals(self): - if not env.PYBEHAVIOR.unpackings_pep448: - self.skipTest("Don't have unpacked literals until 3.5") self.check_coverage("""\ d = { 'a': 2, @@ -1478,11 +1463,6 @@ class LambdaArcTest(CoverageTest): class AsyncTest(CoverageTest): """Tests of the new async and await keywords in Python 3.5""" - def setUp(self): - if not env.PYBEHAVIOR.async_syntax: - self.skipTest("Async features are new in Python 3.5") - super(AsyncTest, self).setUp() - def test_async(self): self.check_coverage("""\ import asyncio diff --git a/tests/test_backward.py b/tests/test_backward.py deleted file mode 100644 index 8acb8707..00000000 --- a/tests/test_backward.py +++ /dev/null @@ -1,22 +0,0 @@ -# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 -# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt - -"""Tests that our version shims in backward.py are working.""" - -from coverage.backunittest import TestCase -from coverage.backward import iitems, binary_bytes, bytes_to_ints - - -class BackwardTest(TestCase): - """Tests of things from backward.py.""" - - def test_iitems(self): - d = {'a': 1, 'b': 2, 'c': 3} - items = [('a', 1), ('b', 2), ('c', 3)] - self.assertCountEqual(list(iitems(d)), items) - - def test_binary_bytes(self): - byte_values = [0, 255, 17, 23, 42, 57] - bb = binary_bytes(byte_values) - self.assertEqual(len(bb), len(byte_values)) - self.assertEqual(byte_values, list(bytes_to_ints(bb))) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 9bfd88ca..bf5583fe 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -144,13 +144,7 @@ PRINT_SUM_RANGE = """ """ # Import the things to use threads. -if env.PY2: - THREAD = """ - import threading - import Queue as queue - """ -else: - THREAD = """ +THREAD = """ import threading import queue """ @@ -382,12 +376,7 @@ class MultiprocessingTest(CoverageTest): source = . """ % concurrency) - if env.PYVERSION >= (3, 4): - start_methods = ['fork', 'spawn'] - else: - start_methods = [''] - - for start_method in start_methods: + for start_method in ['fork', 'spawn']: if start_method and start_method not in multiprocessing.get_all_start_methods(): continue @@ -449,12 +438,7 @@ class MultiprocessingTest(CoverageTest): omit = */site-packages/* """) - if env.PYVERSION >= (3, 4): - start_methods = ['fork', 'spawn'] - else: - start_methods = [''] - - for start_method in start_methods: + for start_method in ['fork', 'spawn']: if start_method and start_method not in multiprocessing.get_all_start_methods(): continue diff --git a/tests/test_context.py b/tests/test_context.py index 137300a5..968ffed0 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -7,7 +7,6 @@ import inspect import os.path import coverage -from coverage import env from coverage.context import qualname_from_frame from coverage.data import CoverageData @@ -278,12 +277,6 @@ class QualnameTest(CoverageTest): c.meth = patch_meth self.assertEqual(c.meth(c), "tests.test_context.patch_meth") - def test_oldstyle(self): - if not env.PY2: - self.skipTest("Old-style classes are only in Python 2") - self.assertEqual(OldStyle().meth(), "tests.test_context.OldStyle.meth") - self.assertEqual(OldChild().meth(), "tests.test_context.OldStyle.meth") - def test_bug_829(self): # A class with a name like a function shouldn't confuse qualname_from_frame. class test_something(object): # pylint: disable=unused-variable diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 1b23f408..4bf8ba6e 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -341,22 +341,6 @@ class SimpleStatementTest(CoverageTest): """, [1,2,3,6,9], "") - def test_print(self): - if env.PY3: # Print statement is gone in Py3k. - self.skipTest("No more print statement in Python 3.") - - self.check_coverage("""\ - print "hello, world!" - print ("hey: %d" % - 17) - print "goodbye" - print "hello, world!", - print ("hey: %d" % - 17), - print "goodbye", - """, - [1,2,4,5,6,8], "") - def test_raise(self): self.check_coverage("""\ try: diff --git a/tests/test_debug.py b/tests/test_debug.py index 228e33b0..2bac3dcf 100644 --- a/tests/test_debug.py +++ b/tests/test_debug.py @@ -3,12 +3,12 @@ """Tests of coverage/debug.py""" +import io import os import pytest import coverage -from coverage.backward import StringIO from coverage.debug import filter_text, info_formatter, info_header, short_id, short_stack from coverage.debug import clipped_repr from coverage.env import C_TRACER @@ -105,7 +105,7 @@ class DebugTraceTest(CoverageTest): f1(i) """) - debug_out = StringIO() + debug_out = io.StringIO() cov = coverage.Coverage(debug=debug) cov._debug_file = debug_out self.start_import_stop(cov, "f1") diff --git a/tests/test_execfile.py b/tests/test_execfile.py index 5a718aae..d53fc53e 100644 --- a/tests/test_execfile.py +++ b/tests/test_execfile.py @@ -12,7 +12,6 @@ import re import sys from coverage import env -from coverage.backward import binary_bytes from coverage.execfile import run_python_file, run_python_module from coverage.files import python_reported_file from coverage.misc import NoCode, NoSource @@ -150,7 +149,7 @@ class RunPycFileTest(CoverageTest): # Jam Python 2.1 magic number into the .pyc file. with open(pycfile, "r+b") as fpyc: fpyc.seek(0) - fpyc.write(binary_bytes([0x2a, 0xeb, 0x0d, 0x0a])) + fpyc.write(bytes([0x2a, 0xeb, 0x0d, 0x0a])) with self.assertRaisesRegex(NoCode, "Bad magic number in .pyc file"): run_python_file([pycfile]) diff --git a/tests/test_html.py b/tests/test_html.py index b543fa08..6d41bf53 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -16,7 +16,6 @@ import mock from unittest_mixins import change_dir import coverage -from coverage.backward import unicode_class from coverage import env from coverage.files import flat_rootname import coverage.html @@ -625,10 +624,10 @@ def compare_html(expected, actual): (r'<span class="(nam|key)">(print|True|False)</span>', r'<span class="nam">\2</span>'), # Occasionally an absolute path is in the HTML report. (filepath_to_regex(TESTS_DIR), 'TESTS_DIR'), - (filepath_to_regex(flat_rootname(unicode_class(TESTS_DIR))), '_TESTS_DIR'), + (filepath_to_regex(flat_rootname(str(TESTS_DIR))), '_TESTS_DIR'), # The temp dir the tests make. (filepath_to_regex(os.getcwd()), 'TEST_TMPDIR'), - (filepath_to_regex(flat_rootname(unicode_class(os.getcwd()))), '_TEST_TMPDIR'), + (filepath_to_regex(flat_rootname(str(os.getcwd()))), '_TEST_TMPDIR'), (r'/private/var/folders/[\w/]{35}/coverage_test/tests_test_html_\w+_\d{8}', 'TEST_TMPDIR'), (r'_private_var_folders_\w{35}_coverage_test_tests_test_html_\w+_\d{8}', '_TEST_TMPDIR'), ] @@ -742,12 +741,8 @@ math = "3\xc3\x974 = 12, \xc3\xb72 = 6\xc2\xb10" import sys -if sys.version_info >= (3, 0): - assert len(math) == 18 - assert len(math.encode('utf-8')) == 21 -else: - assert len(math) == 21 - assert len(math.decode('utf-8')) == 18 +assert len(math) == 18 +assert len(math.encode('utf-8')) == 21 """.replace(b"\n", b"\r\n")) # It's important that the source file really have a BOM, which can @@ -756,7 +751,7 @@ else: with open("bom.py", "rb") as f: data = f.read() assert data[:3] == b"\xef\xbb\xbf" - assert data.count(b"\r\n") == 11 + assert data.count(b"\r\n") == 7 cov = coverage.Coverage() bom = self.start_import_stop(cov, "bom") @@ -1017,9 +1012,6 @@ assert len(math) == 18 def test_unicode(self): surrogate = u"\U000e0100" - if env.PY2: - surrogate = surrogate.encode('utf-8') - self.make_file("unicode.py", """\ # -*- coding: utf-8 -*- # A Python source file with exotic characters. diff --git a/tests/test_numbits.py b/tests/test_numbits.py index 232d48d3..c520341d 100644 --- a/tests/test_numbits.py +++ b/tests/test_numbits.py @@ -10,7 +10,6 @@ from hypothesis import example, given, settings from hypothesis.strategies import sets, integers from coverage import env -from coverage.backward import byte_to_int from coverage.numbits import ( nums_to_numbits, numbits_to_nums, numbits_union, numbits_intersection, numbits_any_intersection, num_in_numbits, register_sqlite_functions, @@ -33,7 +32,7 @@ if env.METACOV: def good_numbits(numbits): """Assert that numbits is good.""" # It shouldn't end with a zero byte, that should have been trimmed off. - assert (not numbits) or (byte_to_int(numbits[-1]) != 0) + assert (not numbits) or (numbits[-1] != 0) class NumbitsOpTest(CoverageTest): diff --git a/tests/test_oddball.py b/tests/test_oddball.py index 90b92249..f8635821 100644 --- a/tests/test_oddball.py +++ b/tests/test_oddball.py @@ -565,8 +565,6 @@ class ExecTest(CoverageTest): def test_unencodable_filename(self): # https://github.com/nedbat/coveragepy/issues/891 - if env.PYVERSION < (3, 0): - self.skipTest("Python 2 can't seem to compile the file.") self.make_file("bug891.py", r"""exec(compile("pass", "\udcff.py", "exec"))""") cov = coverage.Coverage() self.start_import_stop(cov, "bug891") diff --git a/tests/test_parser.py b/tests/test_parser.py index 19264043..1369f11a 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -20,8 +20,6 @@ class PythonParserTest(CoverageTest): def parse_source(self, text): """Parse `text` as source, and return the `PythonParser` used.""" - if env.PY2: - text = text.decode("ascii") text = textwrap.dedent(text) parser = PythonParser(text=text, exclude="nocover") parser.parse_source() @@ -139,7 +137,7 @@ class PythonParserTest(CoverageTest): @xfail( - env.PYPY3 and env.PYPYVERSION >= (7, 3, 0), + env.PYPY and env.PYPYVERSION >= (7, 3, 0), "https://bitbucket.org/pypy/pypy/issues/3139", ) def test_decorator_pragmas(self): diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py index 6f38fc94..57b049e9 100644 --- a/tests/test_phystokens.py +++ b/tests/test_phystokens.py @@ -102,10 +102,7 @@ class PhysTokensTest(CoverageTest): # The default encoding is different in Python 2 and Python 3. -if env.PY3: - DEF_ENCODING = "utf-8" -else: - DEF_ENCODING = "ascii" +DEF_ENCODING = "utf-8" ENCODING_DECLARATION_SOURCES = [ @@ -135,7 +132,7 @@ class SourceEncodingTest(CoverageTest): ) def test_detect_source_encoding_not_in_comment(self): - if env.PYPY3: # pragma: no metacov + if env.PYPY: # pragma: no metacov # PyPy3 gets this case wrong. Not sure what I can do about it, # so skip the test. self.skipTest("PyPy3 is wrong about non-comment encoding. Skip it.") diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 2477f5ce..b90f94ca 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -4,12 +4,13 @@ """Tests for plugins.""" import inspect +import io import os.path from xml.etree import ElementTree import coverage from coverage import env -from coverage.backward import StringIO, import_local_file +from coverage.backward import import_local_file from coverage.data import line_counts from coverage.control import Plugins from coverage.misc import CoverageException @@ -186,7 +187,7 @@ class PluginTest(CoverageTest): def coverage_init(reg, options): reg.add_file_tracer(Plugin()) """) - debug_out = StringIO() + debug_out = io.StringIO() cov = coverage.Coverage(debug=["sys"]) cov._debug_file = debug_out cov.set_option("run:plugins", ["plugin_sys_info"]) @@ -216,7 +217,7 @@ class PluginTest(CoverageTest): def coverage_init(reg, options): reg.add_configurer(Plugin()) """) - debug_out = StringIO() + debug_out = io.StringIO() cov = coverage.Coverage(debug=["sys"]) cov._debug_file = debug_out cov.set_option("run:plugins", ["plugin_no_sys_info"]) @@ -415,7 +416,7 @@ class GoodFileTracerTest(FileTracerTest): self.start_import_stop(cov, "caller") - repout = StringIO() + repout = io.StringIO() total = cov.report(file=repout, include=["*.html"], omit=["uni*.html"], show_missing=True) report = repout.getvalue().splitlines() expected = [ @@ -515,7 +516,7 @@ class GoodFileTracerTest(FileTracerTest): cov.set_option("run:plugins", ["fairly_odd_plugin"]) self.start_import_stop(cov, "unsuspecting") - repout = StringIO() + repout = io.StringIO() total = cov.report(file=repout, show_missing=True) report = repout.getvalue().splitlines() expected = [ diff --git a/tests/test_process.py b/tests/test_process.py index a9b8e00a..efb8c41f 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -21,7 +21,6 @@ import coverage from coverage import env from coverage.data import line_counts from coverage.files import python_reported_file -from coverage.misc import output_encoding from tests.coveragetest import CoverageTest, TESTS_DIR, xfail from tests.helpers import re_lines @@ -714,8 +713,6 @@ class ProcessTest(CoverageTest): @pytest.mark.expensive def test_fullcoverage(self): # pragma: no metacov - if env.PY2: # This doesn't work on Python 2. - self.skipTest("fullcoverage doesn't work on Python 2.") # It only works with the C tracer, and if we aren't measuring ourselves. if not env.C_TRACER or env.METACOV: self.skipTest("fullcoverage only works with the C tracer.") @@ -743,7 +740,7 @@ class ProcessTest(CoverageTest): self.assertGreater(line_counts(data)['os.py'], 50) @xfail( - env.PYPY3 and env.PYPYVERSION >= (7, 1, 1), + env.PYPY and env.PYPYVERSION >= (7, 1, 1), "https://bitbucket.org/pypy/pypy/issues/3074" ) def test_lang_c(self): @@ -883,15 +880,9 @@ class EnvironmentTest(CoverageTest): expected = self.run_command("python -m with_main") actual = self.run_command("coverage run -m with_main") - if env.PY2: - assert expected.endswith("No module named with_main\n") - assert actual.endswith("No module named with_main\n") - else: - self.assert_tryexecfile_output(expected, actual) + self.assert_tryexecfile_output(expected, actual) def test_coverage_run_dashm_dir_with_init_is_like_python(self): - if env.PY2: - self.skipTest("Python 2 runs __main__ twice, I can't be bothered to make it work.") with open(TRY_EXECFILE) as f: self.make_file("with_main/__main__.py", f.read()) self.make_file("with_main/__init__.py", "") @@ -1301,9 +1292,6 @@ class UnicodeFilePathsTest(CoverageTest): u"h\xe2t.py 1 0 100%\n" ) - if env.PY2: - report_expected = report_expected.encode(output_encoding()) - out = self.run_command("coverage report") self.assertEqual(out, report_expected) @@ -1345,9 +1333,6 @@ class UnicodeFilePathsTest(CoverageTest): u"\xe2%saccented.py 1 0 100%%\n" % os.sep ) - if env.PY2: - report_expected = report_expected.encode(output_encoding()) - out = self.run_command("coverage report") self.assertEqual(out, report_expected) diff --git a/tests/test_summary.py b/tests/test_summary.py index ab6414af..2e41b034 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -5,6 +5,7 @@ """Test text-based summary reporting for coverage.py""" import glob +import io import os import os.path import py_compile @@ -12,10 +13,9 @@ import re import coverage from coverage import env -from coverage.backward import StringIO from coverage.control import Coverage from coverage.data import CoverageData -from coverage.misc import CoverageException, output_encoding +from coverage.misc import CoverageException from coverage.summary import SummaryReporter from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin @@ -567,8 +567,6 @@ class SummaryTest(UsingModulesMixin, CoverageTest): # The actual error message varies version to version errmsg = re.sub(r": '.*' at", ": 'error' at", errmsg) expected = u"Couldn't parse 'accented\xe2.py' as Python source: 'error' at line 1" - if env.PY2: - expected = expected.encode(output_encoding()) self.assertEqual(expected, errmsg) def test_dotpy_not_python_ignored(self): @@ -639,7 +637,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): def get_report(self, cov): """Get the report from `cov`, and canonicalize it.""" - repout = StringIO() + repout = io.StringIO() cov.report(file=repout, show_missing=False) report = repout.getvalue().replace('\\', '/') report = re.sub(r" +", " ", report) @@ -730,10 +728,6 @@ class SummaryTest(UsingModulesMixin, CoverageTest): self.assertIn("mod.py 1 0 100%", report) def test_missing_py_file_during_run(self): - # PyPy2 doesn't run bare .pyc files. - if env.PYPY2: - self.skipTest("PyPy2 doesn't run bare .pyc files") - # Create two Python files. self.make_file("mod.py", "a = 1\n") self.make_file("main.py", "import mod\n") @@ -745,7 +739,7 @@ class SummaryTest(UsingModulesMixin, CoverageTest): # Python 3 puts the .pyc files in a __pycache__ directory, and will # not import from there without source. It will import a .pyc from # the source location though. - if env.PY3 and not env.JYTHON: + if not env.JYTHON: pycs = glob.glob("__pycache__/mod.*.pyc") self.assertEqual(len(pycs), 1) os.rename(pycs[0], "mod.pyc") @@ -778,7 +772,7 @@ class SummaryTest2(UsingModulesMixin, CoverageTest): import usepkgs # pragma: nested # pylint: disable=import-error, unused-import cov.stop() # pragma: nested - repout = StringIO() + repout = io.StringIO() cov.report(file=repout, show_missing=False) report = repout.getvalue().replace('\\', '/') @@ -856,7 +850,7 @@ class TestSummaryReporterConfiguration(CoverageTest): for name, value in options: cov.set_option(name, value) printer = SummaryReporter(cov) - destination = StringIO() + destination = io.StringIO() printer.report([], destination) return destination.getvalue() diff --git a/tests/test_testing.py b/tests/test_testing.py index 2fda956b..688657a8 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -8,11 +8,11 @@ import datetime import os import re import sys +import unittest import pytest import coverage -from coverage.backunittest import TestCase, unittest from coverage.files import actual_path from coverage.misc import StopEverything import coverage.optional @@ -28,18 +28,6 @@ def test_xdist_sys_path_nuttiness_is_fixed(): assert os.environ.get('PYTHONPATH') is None -class TestingTest(TestCase): - """Tests of helper methods on `backunittest.TestCase`.""" - - def test_assert_count_equal(self): - self.assertCountEqual(set(), set()) - self.assertCountEqual(set([1,2,3]), set([3,1,2])) - with self.assertRaises(AssertionError): - self.assertCountEqual(set([1,2,3]), set()) - with self.assertRaises(AssertionError): - self.assertCountEqual(set([1,2,3]), set([4,5,6])) - - class CoverageTestTest(CoverageTest): """Test the methods in `CoverageTest`.""" @@ -2,7 +2,7 @@ # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt [tox] -envlist = py{27,35,36,37,38,39}, pypy{2,3}, doc, lint +envlist = py{35,36,37,38,39}, pypy3, doc, lint skip_missing_interpreters = {env:COVERAGE_SKIP_MISSING_INTERPRETERS:True} toxworkdir = {env:TOXWORKDIR:.tox} @@ -18,9 +18,9 @@ deps = pip==20.0.2 setuptools==41.4.0 # gevent 1.3 causes a failure: https://github.com/nedbat/coveragepy/issues/663 - py{27,35,36}: gevent==1.2.2 - py{27,35,36,37,38}: eventlet==0.25.1 - py{27,35,36,37,38}: greenlet==0.4.15 + py{35,36}: gevent==1.2.2 + py{35,36,37,38}: eventlet==0.25.1 + py{35,36,37,38}: greenlet==0.4.15 # Windows can't update the pip version with pip running, so use Python # to install things. @@ -28,7 +28,7 @@ install_command = python -m pip install -U {opts} {packages} passenv = * setenv = - pypy,pypy{2,3}: COVERAGE_NO_CTRACER=no C extension under PyPy + pypy,pypy3: COVERAGE_NO_CTRACER=no C extension under PyPy jython: COVERAGE_NO_CTRACER=no C extension under Jython jython: PYTEST_ADDOPTS=-n 0 @@ -85,11 +85,8 @@ commands = python -m pylint --notes= -j 4 {env:LINTABLE} [travis] -#2.7: py27, lint python = - 2.7: py27 3.5: py35 3.6: py36 3.7: py37 - pypy: pypy pypy3.5: pypy3 diff --git a/tox_wheels.ini b/tox_wheels.ini index 92a1ddf4..9960528c 100644 --- a/tox_wheels.ini +++ b/tox_wheels.ini @@ -2,7 +2,7 @@ # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt [tox] -envlist = py{27,35,36,37,38,sys} +envlist = py{35,36,37,38,sys} toxworkdir = {toxinidir}/.tox/wheels [testenv] @@ -13,9 +13,6 @@ commands = python -c "import sys; print(sys.real_prefix)" python setup.py bdist_wheel {posargs} -[testenv:py27] -basepython = python2.7 - [testenv:pysys] # For building with the Mac Framework Python. basepython = /usr/bin/python |
