summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml4
-rw-r--r--Makefile6
-rw-r--r--appveyor.yml20
-rw-r--r--coverage/backunittest.py33
-rw-r--r--coverage/backward.py178
-rw-r--r--coverage/cmdline.py4
-rw-r--r--coverage/collector.py3
-rw-r--r--coverage/config.py14
-rw-r--r--coverage/control.py7
-rw-r--r--coverage/debug.py5
-rw-r--r--coverage/env.py20
-rw-r--r--coverage/execfile.py14
-rw-r--r--coverage/files.py20
-rw-r--r--coverage/html.py9
-rw-r--r--coverage/inorout.py3
-rw-r--r--coverage/misc.py10
-rw-r--r--coverage/multiproc.py15
-rw-r--r--coverage/numbits.py32
-rw-r--r--coverage/parser.py17
-rw-r--r--coverage/phystokens.py113
-rw-r--r--coverage/pytracer.py2
-rw-r--r--coverage/report.py6
-rw-r--r--coverage/results.py9
-rw-r--r--coverage/sqldata.py29
-rw-r--r--coverage/summary.py5
-rw-r--r--coverage/templite.py8
-rw-r--r--coverage/tomlconfig.py3
-rw-r--r--coverage/xmlreport.py16
-rw-r--r--setup.py4
-rw-r--r--tests/conftest.py4
-rw-r--r--tests/coveragetest.py13
-rw-r--r--tests/gold/html/bom/2/bom_py.html75
-rw-r--r--tests/gold/html/bom/2/index.html84
-rw-r--r--tests/gold/html/bom/bom_py.html22
-rw-r--r--tests/gold/html/bom/index.html18
-rw-r--r--tests/goldtest.py10
-rw-r--r--tests/helpers.py7
-rw-r--r--tests/test_api.py14
-rw-r--r--tests/test_arcs.py28
-rw-r--r--tests/test_backward.py22
-rw-r--r--tests/test_concurrency.py22
-rw-r--r--tests/test_context.py7
-rw-r--r--tests/test_coverage.py16
-rw-r--r--tests/test_debug.py4
-rw-r--r--tests/test_execfile.py3
-rw-r--r--tests/test_html.py18
-rw-r--r--tests/test_numbits.py3
-rw-r--r--tests/test_oddball.py2
-rw-r--r--tests/test_parser.py4
-rw-r--r--tests/test_phystokens.py7
-rw-r--r--tests/test_plugins.py11
-rw-r--r--tests/test_process.py19
-rw-r--r--tests/test_summary.py18
-rw-r--r--tests/test_testing.py14
-rw-r--r--tox.ini13
-rw-r--r--tox_wheels.ini5
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
diff --git a/Makefile b/Makefile
index e1675d9b..d76d5e57 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/setup.py b/setup.py
index 8c837d72..91eadf98 100644
--- a/setup.py
+++ b/setup.py
@@ -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 &nbsp;
- <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> &nbsp; toggle line displays
- </p>
- <p class="keyhelp">
- <span class="key">j</span>
- <span class="key">k</span> &nbsp; next/prev highlighted chunk
- </p>
- <p class="keyhelp">
- <span class="key">0</span> &nbsp; (zero) top of page
- </p>
- <p class="keyhelp">
- <span class="key">1</span> &nbsp; (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>&nbsp;</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&#215;4 = 12, &#247;2 = 6&#177;0"</span>&nbsp;</span><span class="r"></span></p>
- <p id="t3" class="pln"><span class="n"><a href="#t3">3</a></span><span class="t">&nbsp;</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>&nbsp;</span><span class="r"></span></p>
- <p id="t5" class="pln"><span class="n"><a href="#t5">5</a></span><span class="t">&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</span><span class="r"></span></p>
-</div>
-<div id="footer">
- <div class="content">
- <p>
- <a class="nav" href="index.html">&#xab; index</a> &nbsp; &nbsp; <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> &nbsp; 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 &nbsp;
- <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 &nbsp;
+ <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">&nbsp;</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>&nbsp;</span><span class="r"></span></p>
<p id="t5" class="pln"><span class="n"><a href="#t5">5</a></span><span class="t">&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</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>&nbsp;</span><span class="r"></span></p>
</div>
<div id="footer">
<div class="content">
<p>
- <a class="nav" href="index.html">&#xab; index</a> &nbsp; &nbsp; <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">&#xab; index</a> &nbsp; &nbsp; <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`."""
diff --git a/tox.ini b/tox.ini
index 57c4d4bc..aa6d37db 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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