summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml8
-rw-r--r--CONTRIBUTORS.txt1
-rw-r--r--Makefile6
-rw-r--r--README.rst2
-rw-r--r--appveyor.yml16
-rw-r--r--coverage/ctracer/tracer.c2
-rw-r--r--coverage/ctracer/util.h8
-rw-r--r--coverage/env.py33
-rw-r--r--coverage/execfile.py10
-rw-r--r--coverage/files.py5
-rw-r--r--[-rwxr-xr-x]coverage/htmlfiles/keybd_closed.pngbin112 -> 112 bytes
-rw-r--r--[-rwxr-xr-x]coverage/htmlfiles/keybd_open.pngbin112 -> 112 bytes
-rw-r--r--coverage/parser.py2
-rw-r--r--coverage/sqldata.py4
-rw-r--r--coverage/xmlreport.py13
-rw-r--r--doc/cmd.rst5
-rw-r--r--doc/index.rst4
-rw-r--r--doc/requirements.pip4
-rw-r--r--howto.txt7
-rwxr-xr-xlab/set_env.py121
-rw-r--r--requirements/dev.pip4
-rw-r--r--requirements/pytest.pip8
-rw-r--r--requirements/tox.pip2
-rw-r--r--requirements/wheel.pip3
-rw-r--r--setup.py13
-rw-r--r--tests/conftest.py3
-rw-r--r--tests/goldtest.py19
-rw-r--r--tests/test_api.py2
-rw-r--r--tests/test_arcs.py22
-rw-r--r--tests/test_data.py16
-rw-r--r--tests/test_files.py13
-rw-r--r--tests/test_plugins.py28
-rw-r--r--tests/test_process.py19
-rw-r--r--tests/test_templite.py18
-rw-r--r--tests/test_xml.py292
-rw-r--r--tox.ini16
-rw-r--r--tox_wheels.ini2
37 files changed, 380 insertions, 351 deletions
diff --git a/.travis.yml b/.travis.yml
index 79ae11e0..ac189e57 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,17 @@
# Tell Travis what to do
# https://travis-ci.com/nedbat/coveragepy
+dist: xenial
language: python
cache: pip
python:
- '2.7'
- - '3.4'
- '3.5'
- '3.6'
- - 'pypy'
- - 'pypy3.5'
+ - '3.7'
+ - 'pypy2.7-6.0'
+ - 'pypy3.5-6.0'
env:
matrix:
@@ -23,7 +24,6 @@ install:
script:
- tox
- - pip freeze
- if [[ $COVERAGE_COVERAGE == 'yes' ]]; then python igor.py combine_html; fi
- if [[ $COVERAGE_COVERAGE == 'yes' ]]; then pip install codecov; fi
- if [[ $COVERAGE_COVERAGE == 'yes' ]]; then codecov -X gcov --file coverage.xml; fi
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 93f27153..50a8fba8 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -101,6 +101,7 @@ Scott Belden
Sigve Tjora
Stan Hu
Stefan Behnel
+Stephan Richter
Stephen Finucane
Steve Leonard
Steve Peak
diff --git a/Makefile b/Makefile
index a62c9021..e9f790a7 100644
--- a/Makefile
+++ b/Makefile
@@ -51,10 +51,12 @@ test:
PYTEST_SMOKE_ARGS = -n 6 -m "not expensive" --maxfail=3 $(ARGS)
smoke:
- COVERAGE_NO_PYTRACER=1 tox -q -e py27,py34 -- $(PYTEST_SMOKE_ARGS)
+ # Run tests with the C tracer in the lowest supported Python versions.
+ COVERAGE_NO_PYTRACER=1 tox -q -e py27,py35 -- $(PYTEST_SMOKE_ARGS)
pysmoke:
- COVERAGE_NO_CTRACER=1 tox -q -e py27,py34 -- $(PYTEST_SMOKE_ARGS)
+ # Run tests with the Python tracer in the lowest supported Python versions.
+ COVERAGE_NO_CTRACER=1 tox -q -e py27,py35 -- $(PYTEST_SMOKE_ARGS)
metacov:
COVERAGE_COVERAGE=yes tox $(ARGS)
diff --git a/README.rst b/README.rst
index 5d709e1e..e7ce69fa 100644
--- a/README.rst
+++ b/README.rst
@@ -35,7 +35,7 @@ library to determine which lines are executable, and which have been executed.
Coverage.py runs on many versions of Python:
* CPython 2.7.
-* CPython 3.4 through pre-alpha 3.8.
+* CPython 3.5 through pre-alpha 3.8.
* PyPy2 6.0 and PyPy3 6.0.
* Jython 2.7.1, though not for reporting.
* IronPython 2.7.7, though not for reporting.
diff --git a/appveyor.yml b/appveyor.yml
index 1131efe1..f4a41b51 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -34,18 +34,6 @@ environment:
PYTHON_VERSION: "2.7.15"
PYTHON_ARCH: "64"
- - JOB: "3.4 32-bit"
- TOXENV: "py34"
- PYTHON: "C:\\Python34"
- PYTHON_VERSION: "3.4.4"
- PYTHON_ARCH: "32"
-
- - JOB: "3.4 64-bit"
- TOXENV: "py34"
- PYTHON: "C:\\Python34-x64"
- PYTHON_VERSION: "3.4.4"
- PYTHON_ARCH: "64"
-
- JOB: "3.5 32-bit"
TOXENV: "py35"
PYTHON: "C:\\Python35"
@@ -122,8 +110,8 @@ install:
# Install requirements.
- "%CMD_IN_ENV% pip install -r requirements/ci.pip"
- # Make a python3.4.bat file in the current directory so that tox will find it
- # and python3.4 will mean what we want it to.
+ # Make a pythonX.Y.bat file in the current directory so that tox will find it
+ # and pythonX.Y will mean what we want it to.
- "python -c \"import os; open('python{}.{}.bat'.format(*os.environ['TOXENV'][2:]), 'w').write('@{}\\\\python \\x25*\\n'.format(os.environ['PYTHON']))\""
build_script:
diff --git a/coverage/ctracer/tracer.c b/coverage/ctracer/tracer.c
index 7d639112..d497a94d 100644
--- a/coverage/ctracer/tracer.c
+++ b/coverage/ctracer/tracer.c
@@ -541,7 +541,7 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame)
/* Make the frame right in case settrace(gettrace()) happens. */
Py_INCREF(self);
- My_XSETREF(frame->f_trace, (PyObject*)self);
+ Py_XSETREF(frame->f_trace, (PyObject*)self);
/* A call event is really a "start frame" event, and can happen for
* re-entering a generator also. f_lasti is -1 for a true call, and a
diff --git a/coverage/ctracer/util.h b/coverage/ctracer/util.h
index 96d2e51c..cb8aceb9 100644
--- a/coverage/ctracer/util.h
+++ b/coverage/ctracer/util.h
@@ -44,14 +44,6 @@
#endif /* Py3k */
-// Undocumented, and not in 2.6, so our own copy of it.
-#define My_XSETREF(op, op2) \
- do { \
- PyObject *_py_tmp = (PyObject *)(op); \
- (op) = (op2); \
- Py_XDECREF(_py_tmp); \
- } while (0)
-
/* The values returned to indicate ok or error. */
#define RET_OK 0
#define RET_ERROR -1
diff --git a/coverage/env.py b/coverage/env.py
index d97b193c..83b4be65 100644
--- a/coverage/env.py
+++ b/coverage/env.py
@@ -28,6 +28,39 @@ PY3 = PYVERSION >= (3, 0)
class PYBEHAVIOR(object):
"""Flags indicating this Python's behavior."""
+ # Is "if __debug__" optimized away?
+ optimize_if_debug = (not PYPY)
+
+ # If "if not __debug__" optimized away?
+ optimize_if_not_debug = (not PYPY) and (PYVERSION >= (3, 7, 0, 'alpha', 4))
+
+ # 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))
+
+ # Do .pyc files conform to PEP 552? Hash-based pyc's.
+ hashed_pyc_pep552 = (PYVERSION >= (3, 7, 0, 'alpha', 4))
+
+ # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It
+ # used to be an empty string (meaning the current directory). It changed
+ # to be the actual path to the current directory, so that os.chdir wouldn't
+ # affect the outcome.
+ actual_syspath0_dash_m = (PYVERSION >= (3, 7, 0, 'beta', 3))
+
# When a break/continue/return statement in a try block jumps to a finally
# block, does the finally block do the break/continue/return (pre-3.8), or
# does the finally jump back to the break/continue/return (3.8) to do the
diff --git a/coverage/execfile.py b/coverage/execfile.py
index 4fc6a85f..97997b06 100644
--- a/coverage/execfile.py
+++ b/coverage/execfile.py
@@ -124,11 +124,7 @@ class PyRunner(object):
should_update_sys_path = True
if self.as_module:
- # Python 3.7.0b3 changed the behavior of the sys.path[0] entry for -m. It
- # used to be an empty string (meaning the current directory). It changed
- # to be the actual path to the current directory, so that os.chdir wouldn't
- # affect the outcome.
- if env.PYVERSION >= (3, 7, 0, 'beta', 3):
+ if env.PYBEHAVIOR.actual_syspath0_dash_m:
path0 = os.getcwd()
else:
path0 = ""
@@ -290,7 +286,7 @@ def make_code_from_pyc(filename):
raise NoCode("Bad magic number in .pyc file")
date_based = True
- if env.PYVERSION >= (3, 7, 0, 'alpha', 4):
+ if env.PYBEHAVIOR.hashed_pyc_pep552:
flags = struct.unpack('<L', fpyc.read(4))[0]
hash_based = flags & 0x01
if hash_based:
@@ -299,7 +295,7 @@ 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.PYVERSION >= (3, 3):
+ if env.PYBEHAVIOR.size_in_pyc:
# 3.3 added another long to the header (size), skip it.
fpyc.read(4)
diff --git a/coverage/files.py b/coverage/files.py
index b328f653..d9495912 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -59,6 +59,7 @@ def canonical_filename(filename):
"""
if filename not in CANONICAL_FILENAME_CACHE:
+ cf = filename
if not os.path.isabs(filename):
for path in [os.curdir] + sys.path:
if path is None:
@@ -69,9 +70,9 @@ def canonical_filename(filename):
except UnicodeError:
exists = False
if exists:
- filename = f
+ cf = f
break
- cf = abs_file(filename)
+ cf = abs_file(cf)
CANONICAL_FILENAME_CACHE[filename] = cf
return CANONICAL_FILENAME_CACHE[filename]
diff --git a/coverage/htmlfiles/keybd_closed.png b/coverage/htmlfiles/keybd_closed.png
index db114023..db114023 100755..100644
--- a/coverage/htmlfiles/keybd_closed.png
+++ b/coverage/htmlfiles/keybd_closed.png
Binary files differ
diff --git a/coverage/htmlfiles/keybd_open.png b/coverage/htmlfiles/keybd_open.png
index db114023..db114023 100755..100644
--- a/coverage/htmlfiles/keybd_open.png
+++ b/coverage/htmlfiles/keybd_open.png
Binary files differ
diff --git a/coverage/parser.py b/coverage/parser.py
index 1c19f69e..6ae99fe4 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -409,7 +409,7 @@ class ByteParser(object):
yield (byte_num, line_num)
last_line_num = line_num
byte_num += byte_incr
- if env.PYVERSION >= (3, 6) and line_incr >= 0x80:
+ if env.PYBEHAVIOR.negative_lnotab and line_incr >= 0x80:
line_incr -= 0x100
line_num += line_incr
if line_num != last_line_num:
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index 0219a8a2..bf8c1e43 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -545,7 +545,7 @@ class CoverageSqliteData(SimpleReprMixin):
if file_id is None:
return None
else:
- query = "select lineno from line where file_id = ?"
+ query = "select distinct lineno from line where file_id = ?"
data = [file_id]
if context is not None:
query += " and context_id = ?"
@@ -560,7 +560,7 @@ class CoverageSqliteData(SimpleReprMixin):
if file_id is None:
return None
else:
- query = "select fromno, tono from arc where file_id = ?"
+ query = "select distinct fromno, tono from arc where file_id = ?"
data = [file_id]
if context is not None:
query += " and context_id = ?"
diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py
index 6c07337a..8ecdc24a 100644
--- a/coverage/xmlreport.py
+++ b/coverage/xmlreport.py
@@ -6,7 +6,6 @@
import os
import os.path
-import re
import sys
import time
import xml.dom.minidom
@@ -225,16 +224,4 @@ def serialize_xml(dom):
out = dom.toprettyxml()
if env.PY2:
out = out.encode("utf8")
- # In Python 3.8, minidom lost the sorting of attributes: https://bugs.python.org/issue34160
- # For the limited kinds of XML we produce, this re-sorts them.
- if env.PYVERSION >= (3, 8):
- rx_attr = r' [\w-]+="[^"]*"'
- rx_attrs = r'(' + rx_attr + ')+'
- fixed_lines = []
- for line in out.splitlines(True):
- hollow_line = re.sub(rx_attrs, u"☺", line)
- attrs = sorted(re.findall(rx_attr, line))
- new_line = hollow_line.replace(u"☺", "".join(attrs))
- fixed_lines.append(new_line)
- out = "".join(fixed_lines)
return out
diff --git a/doc/cmd.rst b/doc/cmd.rst
index 86a858e4..1344d20e 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -207,7 +207,7 @@ can include a path to another directory.
By default, each run of your program starts with an empty data set. If you need
to run your program multiple times to get complete data (for example, because
you need to supply disjoint options), you can accumulate data across runs with
-the ``-a`` flag on the **run** command.
+the ``--append`` flag on the **run** command.
To erase the collected data, use the **erase** command::
@@ -482,7 +482,8 @@ to log:
* ``multiproc``: log the start and stop of multiprocessing processes.
-* ``pid``: annotate all warnings and debug output with the process id.
+* ``pid``: annotate all warnings and debug output with the process and thread
+ ids.
* ``plugin``: print information about plugin operations.
diff --git a/doc/index.rst b/doc/index.rst
index 7ed08189..b06e805d 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -31,7 +31,7 @@ not.
The latest version is coverage.py 5.0a4, released November 25, 2018.
It is supported on:
- * Python versions 2.7, 3.4, 3.5, 3.6, 3.7, and pre-alpha 3.8.
+ * Python versions 2.7, 3.5, 3.6, 3.7, and pre-alpha 3.8.
* PyPy2 6.0 and PyPy3 6.0.
@@ -40,7 +40,7 @@ not.
* IronPython 2.7.7, though only for running code, not reporting.
**This is a pre-release build. The usual warnings about possible bugs
- apply.** The latest stable version is coverage.py 4.5.2, `described here`_.
+ apply.** The latest stable version is coverage.py 4.5.3, `described here`_.
.. _described here: http://coverage.readthedocs.io/
diff --git a/doc/requirements.pip b/doc/requirements.pip
index 6aa6e6e3..de417d05 100644
--- a/doc/requirements.pip
+++ b/doc/requirements.pip
@@ -4,6 +4,6 @@
doc8==0.8.0
pyenchant==2.0.0
-sphinx==1.8.3
+sphinx==1.8.4
sphinxcontrib-spelling==4.2.0
-sphinx_rtd_theme==0.4.2
+sphinx_rtd_theme==0.4.3
diff --git a/howto.txt b/howto.txt
index e357b9a4..2067f24d 100644
--- a/howto.txt
+++ b/howto.txt
@@ -1,5 +1,6 @@
* Release checklist
+- Check that the current virtualenv matches the current coverage branch.
- Version number in coverage/version.py
version_info = (4, 0, 2, 'alpha', 1)
version_info = (4, 0, 2, 'beta', 1)
@@ -23,10 +24,9 @@
- Generate new sample_html to get the latest, incl footer version number:
make clean
pip install -e .
- pip install nose
cd ~/cog/trunk
rm -rf htmlcov
- coverage run --branch --source=cogapp -m nose cogapp/test_cogapp.py:CogTestsInMemory
+ coverage run --branch --source=cogapp -m pytest -k CogTestsInMemory
coverage combine
coverage html
- IF PRE-RELEASE:
@@ -45,7 +45,7 @@
$ make publish
- Kits:
- Start fresh:
- - $ make clean
+ - $ make sterile
- Source kit and wheels:
- $ make kit wheel
- Linux wheels:
@@ -60,6 +60,7 @@
- $ make kit_upload
- Tag the tree
- git tag coverage-3.0.1
+ - git push
- git push --tags
- Bump version:
- coverage/version.py
diff --git a/lab/set_env.py b/lab/set_env.py
deleted file mode 100755
index fe0a4d49..00000000
--- a/lab/set_env.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/usr/bin/env python3
-#
-# Run this like:
-#
-# $ $(lab/set_env.py)
-#
-
-import functools
-import glob
-import itertools
-import os
-import re
-import sys
-
-# Some other environment variables that could be useful:
-# $set_env.py: PYTEST_ADDOPTS - Extra arguments to pytest.
-
-pstderr = functools.partial(print, file=sys.stderr)
-
-SETTINGS = []
-
-def find_settings():
- line_pattern = r"\$set_env.py: (\w+) - (.*)"
- globs = "*/*.py *.py"
-
- filenames = itertools.chain.from_iterable(glob.glob(g) for g in globs.split())
- files = 0
- for filename in filenames:
- files += 1
- with open(filename) as f:
- for line in f:
- m = re.search(line_pattern, line)
- if m:
- SETTINGS.append(m.groups())
- SETTINGS.sort()
- pstderr("Read {} files".format(files))
-
-def read_them():
- values = {}
- for name, _ in SETTINGS:
- values[name] = os.environ.get(name)
- return values
-
-def show_them(values):
- for i, (name, description) in enumerate(SETTINGS, start=1):
- value = values[name]
- if value is None:
- eq = ' '
- value = ''
- else:
- eq = '='
- value = repr(value)
- pstderr("{:2d}: {:>30s} {} {:12s} {}".format(i, name, eq, value, description))
-
-def set_by_num(values, n, value):
- setting_name = SETTINGS[int(n)-1][0]
- values[setting_name] = value
-
-PROMPT = "(# value | x # | q) ::> "
-
-def get_new_values(values):
- show = True
- while True:
- if show:
- show_them(values)
- show = False
- pstderr("")
- pstderr(PROMPT, end='')
- sys.stderr.flush()
- try:
- cmd = input("").strip().split()
- except EOFError:
- pstderr("\n")
- break
- if not cmd:
- continue
- if cmd[0] == 'q':
- break
- if cmd[0] == 'x':
- if len(cmd) < 2:
- pstderr("Need numbers of entries to delete")
- continue
- try:
- nums = map(int, cmd[1:])
- except ValueError:
- pstderr("Need numbers of entries to delete")
- continue
- else:
- for num in nums:
- set_by_num(values, num, None)
- else:
- try:
- num = int(cmd[0])
- except ValueError:
- pstderr("Don't understand option {!r}".format(cmd[0]))
- continue
- else:
- if len(cmd) >= 2:
- set_by_num(values, num, " ".join(cmd[1:]))
- else:
- pstderr("Need a value to set")
- continue
- show = True
-
- return values
-
-def as_exports(values):
- exports = []
- for name, value in values.items():
- if value is None:
- exports.append("export -n {}".format(name))
- else:
- exports.append("export {}={!r}".format(name, value))
- return "eval " + "; ".join(exports)
-
-def main():
- find_settings()
- print(as_exports(get_new_values(read_them())))
-
-if __name__ == '__main__':
- main()
diff --git a/requirements/dev.pip b/requirements/dev.pip
index 415dcda2..ef467537 100644
--- a/requirements/dev.pip
+++ b/requirements/dev.pip
@@ -4,7 +4,7 @@
# Requirements for doing local development work on coverage.py.
# https://requires.io/github/nedbat/coveragepy/requirements/
-pip==18.1
+pip==19.0.3
virtualenv==16.1.0
# PyPI requirements for running tests.
@@ -19,4 +19,4 @@ readme_renderer==24.0
# for kitting.
requests==2.21.0
-twine==1.12.1
+twine==1.13.0
diff --git a/requirements/pytest.pip b/requirements/pytest.pip
index 6ec37a72..0274ec05 100644
--- a/requirements/pytest.pip
+++ b/requirements/pytest.pip
@@ -3,12 +3,12 @@
# The pytest specifics used by coverage.py
-pytest==4.0.2
+pytest==4.3.0
pluggy>=0.7 # pytest needs this, but pip doesn't understand
-pytest-xdist==1.25.0
-flaky==3.4.0
+pytest-xdist==1.26.1
+flaky==3.5.3
mock==2.0.0
-PyContracts==1.8.7
+PyContracts==1.8.12
# Our testing mixins
unittest-mixins==1.6
diff --git a/requirements/tox.pip b/requirements/tox.pip
index 0ba0e97c..81fa10dd 100644
--- a/requirements/tox.pip
+++ b/requirements/tox.pip
@@ -2,6 +2,6 @@
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
# The version of tox used by coverage.py
-tox==3.6.1
+tox==3.7.0
# Adds env recreation on requirements file changes.
tox-battery==0.5.1
diff --git a/requirements/wheel.pip b/requirements/wheel.pip
index 5c766d59..56ced495 100644
--- a/requirements/wheel.pip
+++ b/requirements/wheel.pip
@@ -2,6 +2,7 @@
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
# Things needed to make wheels for coverage.py
-setuptools==40.6.3
+
+setuptools==40.8.0
# pin so auditwheel will work: https://github.com/pypa/auditwheel/issues/102
wheel==0.31.1
diff --git a/setup.py b/setup.py
index 77dcab3d..a964412c 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,6 @@ Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
-Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
@@ -103,12 +102,20 @@ setup_args = dict(
author_email='ned@nedbatchelder.com',
description=doc,
long_description=long_description,
+ long_description_content_type='text/x-rst',
keywords='code coverage testing',
license='Apache 2.0',
classifiers=classifier_list,
url="https://github.com/nedbat/coveragepy",
-
- python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4",
+ project_urls={
+ 'Documentation': __url__,
+ 'Funding': (
+ 'https://tidelift.com/subscription/pkg/pypi-coverage'
+ '?utm_source=pypi-coverage&utm_medium=referral&utm_campaign=pypi'
+ ),
+ 'Issues': 'https://github.com/nedbat/coveragepy/issues',
+ },
+ python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4",
)
# A replacement for the build_ext command which raises a single exception
diff --git a/tests/conftest.py b/tests/conftest.py
index c883ef7b..d0572dca 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,6 +16,9 @@ import pytest
from coverage import env
+# Pytest can take additional options:
+# $set_env.py: PYTEST_ADDOPTS - Extra arguments to pytest.
+
@pytest.fixture(autouse=True)
def set_warnings():
"""Enable DeprecationWarnings during all tests."""
diff --git a/tests/goldtest.py b/tests/goldtest.py
index 4c6c3c96..b5e32f5f 100644
--- a/tests/goldtest.py
+++ b/tests/goldtest.py
@@ -10,6 +10,7 @@ import os
import os.path
import re
import sys
+import xml.etree.ElementTree
from unittest_mixins import change_dir # pylint: disable=unused-import
@@ -79,12 +80,19 @@ def compare(
# ourselves.
text_diff = []
for f in diff_files:
+
expected_file = os.path.join(expected_dir, f)
- actual_file = os.path.join(actual_dir, f)
with open(expected_file, READ_MODE) 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:
actual = fobj.read()
+ if actual_file.endswith(".xml"):
+ actual = canonicalize_xml(actual)
+
if scrubs:
expected = scrub(expected, scrubs)
actual = scrub(actual, scrubs)
@@ -102,6 +110,15 @@ def compare(
assert not actual_only, "Files in %s only: %s" % (actual_dir, actual_only)
+def canonicalize_xml(xtext):
+ """Canonicalize some XML text."""
+ root = xml.etree.ElementTree.fromstring(xtext)
+ for node in root.iter():
+ node.attrib = dict(sorted(node.items()))
+ xtext = xml.etree.ElementTree.tostring(root)
+ return xtext.decode('utf8')
+
+
def contains(filename, *strlist):
"""Check that the file contains all of a list of strings.
diff --git a/tests/test_api.py b/tests/test_api.py
index 2f6f7a2f..755a89a2 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -495,7 +495,7 @@ class NamespaceModuleTest(UsingModulesMixin, CoverageTest):
def setUp(self):
super(NamespaceModuleTest, self).setUp()
- if env.PYVERSION < (3, 3):
+ if not env.PYBEHAVIOR.namespaces_pep420:
self.skipTest("Python before 3.3 doesn't have namespace packages")
def test_explicit_namespace_module(self):
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index d3430af2..cbbac64a 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -1056,7 +1056,7 @@ class YieldTest(CoverageTest):
self.assertEqual(self.stdout(), "20\n12\n")
def test_yield_from(self):
- if env.PYVERSION < (3, 3):
+ if not env.PYBEHAVIOR.yield_from:
self.skipTest("Python before 3.3 doesn't have 'yield from'")
self.check_coverage("""\
def gen(inp):
@@ -1159,8 +1159,8 @@ class OptimizedIfTest(CoverageTest):
arcz=".1 1C CE EF F.",
)
- def test_constant_if(self):
- if env.PYPY:
+ def test_if_debug(self):
+ if not env.PYBEHAVIOR.optimize_if_debug:
self.skipTest("PyPy doesn't optimize away 'if __debug__:'")
# CPython optimizes away "if __debug__:"
self.check_coverage("""\
@@ -1173,13 +1173,17 @@ class OptimizedIfTest(CoverageTest):
""",
arcz=".1 12 24 41 26 61 1.",
)
+
+ def test_if_not_debug(self):
# Before 3.7, no Python optimized away "if not __debug__:"
- if env.PYVERSION < (3, 7, 0, 'alpha', 4):
- arcz = ".1 12 23 31 34 41 26 61 1."
- arcz_missing = "34 41"
- else:
+ if not env.PYBEHAVIOR.optimize_if_debug:
+ self.skipTest("PyPy doesn't optimize away 'if __debug__:'")
+ elif env.PYBEHAVIOR.optimize_if_not_debug:
arcz = ".1 12 23 31 26 61 1."
arcz_missing = ""
+ else:
+ arcz = ".1 12 23 31 34 41 26 61 1."
+ arcz_missing = "34 41"
self.check_coverage("""\
for value in [True, False]:
if value:
@@ -1230,7 +1234,7 @@ class MiscArcTest(CoverageTest):
)
def test_unpacked_literals(self):
- if env.PYVERSION < (3, 5):
+ if not env.PYBEHAVIOR.unpackings_pep448:
self.skipTest("Don't have unpacked literals until 3.5")
self.check_coverage("""\
d = {
@@ -1492,7 +1496,7 @@ class AsyncTest(CoverageTest):
"""Tests of the new async and await keywords in Python 3.5"""
def setUp(self):
- if env.PYVERSION < (3, 5):
+ if not env.PYBEHAVIOR.async_syntax:
self.skipTest("Async features are new in Python 3.5")
super(AsyncTest, self).setUp()
diff --git a/tests/test_data.py b/tests/test_data.py
index bda73810..3f96288f 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -199,6 +199,22 @@ class CoverageDataTest(DataTestHelpers, CoverageTest):
covdata.add_run_info(count=17)
self.assertEqual(covdata.run_infos(), [{"hello": "there", "count": 17}])
+ def test_no_duplicate_lines(self):
+ covdata = CoverageData()
+ covdata.set_context("context1")
+ covdata.add_lines(LINES_1)
+ covdata.set_context("context2")
+ covdata.add_lines(LINES_1)
+ self.assertEqual(covdata.lines('a.py'), A_PY_LINES_1)
+
+ def test_no_duplicate_arcs(self):
+ covdata = CoverageData()
+ covdata.set_context("context1")
+ covdata.add_arcs(ARCS_3)
+ covdata.set_context("context2")
+ covdata.add_arcs(ARCS_3)
+ self.assertEqual(covdata.arcs('x.py'), X_PY_ARCS_3)
+
def test_no_arcs_vs_unmeasured_file(self):
covdata = CoverageData()
covdata.add_arcs(ARCS_3)
diff --git a/tests/test_files.py b/tests/test_files.py
index b4490ea6..e271da1b 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -57,6 +57,19 @@ class FilesTest(CoverageTest):
rel = os.path.join('sub', trick, 'file1.py')
self.assertEqual(files.relative_filename(abs_file(rel)), rel)
+ def test_canonical_filename_ensure_cache_hit(self):
+ self.make_file("sub/proj1/file1.py")
+ d = actual_path(self.abs_path("sub/proj1"))
+ self.chdir(d)
+ files.set_relative_directory()
+ canonical_path = files.canonical_filename('sub/proj1/file1.py')
+ self.assertEqual(canonical_path, self.abs_path('file1.py'))
+ # After the filename has been converted, it should be in the cache.
+ self.assertIn('sub/proj1/file1.py', files.CANONICAL_FILENAME_CACHE)
+ self.assertEqual(
+ files.canonical_filename('sub/proj1/file1.py'),
+ self.abs_path('file1.py'))
+
@pytest.mark.parametrize("original, flat", [
(u"a/b/c.py", u"a_b_c_py"),
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index d3365a6d..14d07c1a 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -4,6 +4,7 @@
"""Tests for plugins."""
import os.path
+from xml.etree import ElementTree
import coverage
from coverage import env
@@ -455,14 +456,25 @@ class GoodFileTracerTest(FileTracerTest):
total = cov.xml_report(include=["*.html"], omit=["uni*.html"])
self.assertAlmostEqual(total, 36.36, places=2)
- with open("coverage.xml") as fxml:
- xml = fxml.read()
-
- for snip in [
- 'filename="bar_4.html" line-rate="0.5" name="bar_4.html"',
- 'filename="foo_7.html" line-rate="0.2857" name="foo_7.html"',
- ]:
- self.assertIn(snip, xml)
+ dom = ElementTree.parse("coverage.xml")
+ classes = {}
+ for elt in dom.findall(".//class"):
+ classes[elt.get('name')] = elt
+
+ assert classes['bar_4.html'].attrib == {
+ 'branch-rate': '1',
+ 'complexity': '0',
+ 'filename': 'bar_4.html',
+ 'line-rate': '0.5',
+ 'name': 'bar_4.html',
+ }
+ assert classes['foo_7.html'].attrib == {
+ 'branch-rate': '1',
+ 'complexity': '0',
+ 'filename': 'foo_7.html',
+ 'line-rate': '0.2857',
+ 'name': 'foo_7.html',
+ }
def test_defer_to_python(self):
# A plugin that measures, but then wants built-in python reporting.
diff --git a/tests/test_process.py b/tests/test_process.py
index a1e71f1c..f234a9ef 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -11,6 +11,7 @@ import os.path
import re
import sys
import textwrap
+from xml.etree import ElementTree
import pytest
@@ -1266,12 +1267,18 @@ class UnicodeFilePathsTest(CoverageTest):
self.assertEqual(out, "")
with open("coverage.xml", "rb") as xmlf:
xml = xmlf.read()
- self.assertIn(u' filename="\xe2/accented.py"'.encode('utf8'), xml)
- self.assertIn(u' name="accented.py"'.encode('utf8'), xml)
- self.assertIn(
- u'<package branch-rate="0" complexity="0" line-rate="1" name="\xe2">'.encode('utf8'),
- xml
- )
+ self.assertIn(b' filename="\xc3\xa2/accented.py"', xml)
+ self.assertIn(b' name="accented.py"', xml)
+
+ dom = ElementTree.parse("coverage.xml")
+ elts = dom.findall(u".//package[@name='â']")
+ assert len(elts) == 1
+ assert elts[0].attrib == {
+ "branch-rate": u"0",
+ "complexity": u"0",
+ "line-rate": u"1",
+ "name": u"â",
+ }
report_expected = (
u"Name Stmts Miss Cover\n"
diff --git a/tests/test_templite.py b/tests/test_templite.py
index 16942db8..3b1e38af 100644
--- a/tests/test_templite.py
+++ b/tests/test_templite.py
@@ -252,13 +252,23 @@ class TempliteTest(CoverageTest):
"@{% for n in nums -%}\n"
" {% for a in abc -%}\n"
" {# this disappears completely -#}\n"
- " {{a -}}\n"
+ " {{a-}}\n"
" {{n -}}\n"
+ " {{n -}}\n"
" {% endfor %}\n"
"{% endfor %}!\n",
{'nums': [0, 1, 2], 'abc': ['a', 'b', 'c']},
- "@a0b0c0\na1b1c1\na2b2c2\n!\n"
+ "@a00b00c00\na11b11c11\na22b22c22\n!\n"
)
+ self.try_render(
+ "@{% for n in nums -%}\n"
+ " {{n -}}\n"
+ " x\n"
+ "{% endfor %}!\n",
+ {'nums': [0, 1, 2]},
+ "@0x\n1x\n2x\n!\n"
+ )
+ self.try_render(" hello ", {}, " hello ")
def test_non_ascii(self):
self.try_render(
@@ -269,8 +279,8 @@ class TempliteTest(CoverageTest):
def test_exception_during_evaluation(self):
# TypeError: Couldn't evaluate {{ foo.bar.baz }}:
- msg = "Couldn't evaluate None.bar"
- with self.assertRaisesRegex(TempliteValueError, msg):
+ regex = "^Couldn't evaluate None.bar$"
+ with self.assertRaisesRegex(TempliteValueError, regex):
self.try_render(
"Hey {{foo.bar.baz}} there", {'foo': None}, "Hey ??? there"
)
diff --git a/tests/test_xml.py b/tests/test_xml.py
index 185e6ad1..09ab2f85 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -7,6 +7,7 @@
import os
import os.path
import re
+from xml.etree import ElementTree
import coverage
from coverage.backward import import_local_file
@@ -14,7 +15,6 @@ from coverage.files import abs_file
from tests.coveragetest import CoverageTest
from tests.goldtest import change_dir, compare, gold_path
-from tests.helpers import re_line, re_lines
class XmlTestHelpers(CoverageTest):
@@ -30,7 +30,7 @@ class XmlTestHelpers(CoverageTest):
self.make_file("sub/__init__.py")
self.make_file("sub/doit.py", "print('doit!')")
self.make_file("main.py", "import sub.doit")
- cov = coverage.Coverage()
+ cov = coverage.Coverage(source=["."])
self.start_import_stop(cov, "main")
return cov
@@ -59,10 +59,36 @@ class XmlTestHelpers(CoverageTest):
filename = here("f{0}.py".format(i))
self.make_file(filename, "# {0}\n".format(filename))
- def assert_source(self, xml, src):
+ def assert_source(self, xmldom, src):
"""Assert that the XML has a <source> element with `src`."""
src = abs_file(src)
- self.assertRegex(xml, r'<source>\s*{0}\s*</source>'.format(re.escape(src)))
+ elts = xmldom.findall(".//sources/source")
+ assert any(elt.text == src for elt in elts)
+
+
+class XmlTestHelpersTest(XmlTestHelpers, CoverageTest):
+ """Tests of methods in XmlTestHelpers."""
+
+ def test_assert_source(self):
+ dom = ElementTree.fromstring("""\
+ <doc>
+ <src>foo</src>
+ <sources>
+ <source>{cwd}something</source>
+ <source>{cwd}another</source>
+ </sources>
+ </doc>
+ """.format(cwd=abs_file(".")+os.sep))
+
+ self.assert_source(dom, "something")
+ self.assert_source(dom, "another")
+
+ with self.assertRaises(AssertionError):
+ self.assert_source(dom, "hello")
+ with self.assertRaises(AssertionError):
+ self.assert_source(dom, "foo")
+ with self.assertRaises(AssertionError):
+ self.assert_source(dom, "thing")
class XmlReportTest(XmlTestHelpers, CoverageTest):
@@ -110,25 +136,28 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
def test_filename_format_showing_everything(self):
cov = self.run_doit()
- cov.xml_report(outfile="-")
- xml = self.stdout()
- doit_line = re_line(xml, "class.*doit")
- self.assertIn('filename="sub/doit.py"', doit_line)
+ cov.xml_report()
+ dom = ElementTree.parse("coverage.xml")
+ elts = dom.findall(".//class[@name='doit.py']")
+ assert len(elts) == 1
+ assert elts[0].get('filename') == "sub/doit.py"
def test_filename_format_including_filename(self):
cov = self.run_doit()
- cov.xml_report(["sub/doit.py"], outfile="-")
- xml = self.stdout()
- doit_line = re_line(xml, "class.*doit")
- self.assertIn('filename="sub/doit.py"', doit_line)
+ cov.xml_report(["sub/doit.py"])
+ dom = ElementTree.parse("coverage.xml")
+ elts = dom.findall(".//class[@name='doit.py']")
+ assert len(elts) == 1
+ assert elts[0].get('filename') == "sub/doit.py"
def test_filename_format_including_module(self):
cov = self.run_doit()
import sub.doit # pylint: disable=import-error
- cov.xml_report([sub.doit], outfile="-")
- xml = self.stdout()
- doit_line = re_line(xml, "class.*doit")
- self.assertIn('filename="sub/doit.py"', doit_line)
+ cov.xml_report([sub.doit])
+ dom = ElementTree.parse("coverage.xml")
+ elts = dom.findall(".//class[@name='doit.py']")
+ assert len(elts) == 1
+ assert elts[0].get('filename') == "sub/doit.py"
def test_reporting_on_nothing(self):
# Used to raise a zero division error:
@@ -136,28 +165,31 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
self.make_file("empty.py", "")
cov = coverage.Coverage()
empty = self.start_import_stop(cov, "empty")
- cov.xml_report([empty], outfile="-")
- xml = self.stdout()
- empty_line = re_line(xml, "class.*empty")
- self.assertIn('filename="empty.py"', empty_line)
- self.assertIn('line-rate="1"', empty_line)
+ cov.xml_report([empty])
+ dom = ElementTree.parse("coverage.xml")
+ elts = dom.findall(".//class[@name='empty.py']")
+ assert len(elts) == 1
+ assert elts[0].get('filename') == "empty.py"
+ assert elts[0].get('line-rate') == '1'
def test_empty_file_is_100_not_0(self):
# https://bitbucket.org/ned/coveragepy/issue/345
cov = self.run_doit()
- cov.xml_report(outfile="-")
- xml = self.stdout()
- init_line = re_line(xml, 'filename="sub/__init__.py"')
- self.assertIn('line-rate="1"', init_line)
+ cov.xml_report()
+ dom = ElementTree.parse("coverage.xml")
+ elts = dom.findall(".//class[@name='__init__.py']")
+ assert len(elts) == 1
+ assert elts[0].get('line-rate') == '1'
def test_curdir_source(self):
# With no source= option, the XML report should explain that the source
# is in the current directory.
cov = self.run_doit()
- cov.xml_report(outfile="-")
- xml = self.stdout()
- self.assert_source(xml, ".")
- self.assertEqual(xml.count('<source>'), 1)
+ cov.xml_report()
+ dom = ElementTree.parse("coverage.xml")
+ self.assert_source(dom, ".")
+ sources = dom.findall(".//source")
+ assert len(sources) == 1
def test_deep_source(self):
# When using source=, the XML report needs to mention those directories
@@ -170,21 +202,33 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
mod_foo = import_local_file("foo", "src/main/foo.py") # pragma: nested
mod_bar = import_local_file("bar", "also/over/there/bar.py") # pragma: nested
cov.stop() # pragma: nested
- cov.xml_report([mod_foo, mod_bar], outfile="-")
- xml = self.stdout()
-
- self.assert_source(xml, "src/main")
- self.assert_source(xml, "also/over/there")
- self.assertEqual(xml.count('<source>'), 2)
-
- self.assertIn(
- '<class branch-rate="0" complexity="0" filename="foo.py" line-rate="1" name="foo.py">',
- xml
- )
- self.assertIn(
- '<class branch-rate="0" complexity="0" filename="bar.py" line-rate="1" name="bar.py">',
- xml
- )
+ cov.xml_report([mod_foo, mod_bar])
+ dom = ElementTree.parse("coverage.xml")
+
+ self.assert_source(dom, "src/main")
+ self.assert_source(dom, "also/over/there")
+ sources = dom.findall(".//source")
+ assert len(sources) == 2
+
+ foo_class = dom.findall(".//class[@name='foo.py']")
+ assert len(foo_class) == 1
+ assert foo_class[0].attrib == {
+ 'branch-rate': '0',
+ 'complexity': '0',
+ 'filename': 'foo.py',
+ 'line-rate': '1',
+ 'name': 'foo.py',
+ }
+
+ bar_class = dom.findall(".//class[@name='bar.py']")
+ assert len(bar_class) == 1
+ assert bar_class[0].attrib == {
+ 'branch-rate': '0',
+ 'complexity': '0',
+ 'filename': 'bar.py',
+ 'line-rate': '1',
+ 'name': 'bar.py',
+ }
def test_nonascii_directory(self):
# https://bitbucket.org/ned/coveragepy/issues/573/cant-generate-xml-report-if-some-source
@@ -195,22 +239,33 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
cov.xml_report()
+def unbackslash(v):
+ """Find strings in `v`, and replace backslashes with slashes throughout."""
+ if isinstance(v, (tuple, list)):
+ return [unbackslash(vv) for vv in v]
+ elif isinstance(v, dict):
+ return {k: unbackslash(vv) for k, vv in v.items()}
+ else:
+ assert isinstance(v, str)
+ return v.replace("\\", "/")
+
+
class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
"""Tests about the package structure reported in the coverage.xml file."""
def package_and_class_tags(self, cov):
"""Run an XML report on `cov`, and get the package and class tags."""
- self.captured_stdout.truncate(0)
- cov.xml_report(outfile="-")
- packages_and_classes = re_lines(self.stdout(), r"<package |<class ")
- scrubs = r' branch-rate="0"| complexity="0"| line-rate="[\d.]+"'
- return clean(packages_and_classes, scrubs)
+ cov.xml_report()
+ dom = ElementTree.parse("coverage.xml")
+ for node in dom.iter():
+ if node.tag in ('package', 'class'):
+ yield (node.tag, {a:v for a,v in node.items() if a in ('name', 'filename')})
def assert_package_and_class_tags(self, cov, result):
"""Check the XML package and class tags from `cov` match `result`."""
- self.assertMultiLineEqual(
- self.package_and_class_tags(cov),
- clean(result)
+ self.assertEqual(
+ unbackslash(list(self.package_and_class_tags(cov))),
+ unbackslash(result),
)
def test_package_names(self):
@@ -220,18 +275,18 @@ class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
""")
cov = coverage.Coverage(source=".")
self.start_import_stop(cov, "main")
- self.assert_package_and_class_tags(cov, """\
- <package name=".">
- <class filename="main.py" name="main.py">
- <package name="d0">
- <class filename="d0/__init__.py" name="__init__.py">
- <class filename="d0/f0.py" name="f0.py">
- <package name="d0.d0">
- <class filename="d0/d0/__init__.py" name="__init__.py">
- <class filename="d0/d0/f0.py" name="f0.py">
- """)
-
- def test_package_depth(self):
+ self.assert_package_and_class_tags(cov, [
+ ('package', {'name': "."}),
+ ('class', {'filename': "main.py", 'name': "main.py"}),
+ ('package', {'name': "d0"}),
+ ('class', {'filename': "d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/f0.py", 'name': "f0.py"}),
+ ('package', {'name': "d0.d0"}),
+ ('class', {'filename': "d0/d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/d0/f0.py", 'name': "f0.py"}),
+ ])
+
+ def test_package_depth_1(self):
self.make_tree(width=1, depth=4)
self.make_file("main.py", """\
from d0.d0 import f0
@@ -240,46 +295,62 @@ class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
self.start_import_stop(cov, "main")
cov.set_option("xml:package_depth", 1)
- self.assert_package_and_class_tags(cov, """\
- <package name=".">
- <class filename="main.py" name="main.py">
- <package name="d0">
- <class filename="d0/__init__.py" name="__init__.py">
- <class filename="d0/d0/__init__.py" name="d0/__init__.py">
- <class filename="d0/d0/d0/__init__.py" name="d0/d0/__init__.py">
- <class filename="d0/d0/d0/f0.py" name="d0/d0/f0.py">
- <class filename="d0/d0/f0.py" name="d0/f0.py">
- <class filename="d0/f0.py" name="f0.py">
+ self.assert_package_and_class_tags(cov, [
+ ('package', {'name': "."}),
+ ('class', {'filename': "main.py", 'name': "main.py"}),
+ ('package', {'name': "d0"}),
+ ('class', {'filename': "d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/d0/__init__.py", 'name': "d0/__init__.py"}),
+ ('class', {'filename': "d0/d0/d0/__init__.py", 'name': "d0/d0/__init__.py"}),
+ ('class', {'filename': "d0/d0/d0/f0.py", 'name': "d0/d0/f0.py"}),
+ ('class', {'filename': "d0/d0/f0.py", 'name': "d0/f0.py"}),
+ ('class', {'filename': "d0/f0.py", 'name': "f0.py"}),
+ ])
+
+ def test_package_depth_2(self):
+ self.make_tree(width=1, depth=4)
+ self.make_file("main.py", """\
+ from d0.d0 import f0
""")
+ cov = coverage.Coverage(source=".")
+ self.start_import_stop(cov, "main")
cov.set_option("xml:package_depth", 2)
- self.assert_package_and_class_tags(cov, """\
- <package name=".">
- <class filename="main.py" name="main.py">
- <package name="d0">
- <class filename="d0/__init__.py" name="__init__.py">
- <class filename="d0/f0.py" name="f0.py">
- <package name="d0.d0">
- <class filename="d0/d0/__init__.py" name="__init__.py">
- <class filename="d0/d0/d0/__init__.py" name="d0/__init__.py">
- <class filename="d0/d0/d0/f0.py" name="d0/f0.py">
- <class filename="d0/d0/f0.py" name="f0.py">
+ self.assert_package_and_class_tags(cov, [
+ ('package', {'name': "."}),
+ ('class', {'filename': "main.py", 'name': "main.py"}),
+ ('package', {'name': "d0"}),
+ ('class', {'filename': "d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/f0.py", 'name': "f0.py"}),
+ ('package', {'name': "d0.d0"}),
+ ('class', {'filename': "d0/d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/d0/d0/__init__.py", 'name': "d0/__init__.py"}),
+ ('class', {'filename': "d0/d0/d0/f0.py", 'name': "d0/f0.py"}),
+ ('class', {'filename': "d0/d0/f0.py", 'name': "f0.py"}),
+ ])
+
+ def test_package_depth_3(self):
+ self.make_tree(width=1, depth=4)
+ self.make_file("main.py", """\
+ from d0.d0 import f0
""")
+ cov = coverage.Coverage(source=".")
+ self.start_import_stop(cov, "main")
cov.set_option("xml:package_depth", 3)
- self.assert_package_and_class_tags(cov, """\
- <package name=".">
- <class filename="main.py" name="main.py">
- <package name="d0">
- <class filename="d0/__init__.py" name="__init__.py">
- <class filename="d0/f0.py" name="f0.py">
- <package name="d0.d0">
- <class filename="d0/d0/__init__.py" name="__init__.py">
- <class filename="d0/d0/f0.py" name="f0.py">
- <package name="d0.d0.d0">
- <class filename="d0/d0/d0/__init__.py" name="__init__.py">
- <class filename="d0/d0/d0/f0.py" name="f0.py">
- """)
+ self.assert_package_and_class_tags(cov, [
+ ('package', {'name': "."}),
+ ('class', {'filename': "main.py", 'name': "main.py"}),
+ ('package', {'name': "d0"}),
+ ('class', {'filename': "d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/f0.py", 'name': "f0.py"}),
+ ('package', {'name': "d0.d0"}),
+ ('class', {'filename': "d0/d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/d0/f0.py", 'name': "f0.py"}),
+ ('package', {'name': "d0.d0.d0"}),
+ ('class', {'filename': "d0/d0/d0/__init__.py", 'name': "__init__.py"}),
+ ('class', {'filename': "d0/d0/d0/f0.py", 'name': "f0.py"}),
+ ])
def test_source_prefix(self):
# https://bitbucket.org/ned/coveragepy/issues/465
@@ -287,26 +358,13 @@ class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
self.make_file("src/mod.py", "print(17)")
cov = coverage.Coverage(source=["src"])
self.start_import_stop(cov, "mod", modfile="src/mod.py")
- self.assert_package_and_class_tags(cov, """\
- <package name=".">
- <class filename="mod.py" name="mod.py">
- """)
- xml = self.stdout()
- self.assert_source(xml, "src")
-
-
-def clean(text, scrub=None):
- """Clean text to prepare it for comparison.
-
- Remove text matching `scrub`, and leading whitespace. Convert backslashes
- to forward slashes.
- """
- if scrub:
- text = re.sub(scrub, "", text)
- text = re.sub(r"(?m)^\s+", "", text)
- text = re.sub(r"\\", "/", text)
- return text
+ self.assert_package_and_class_tags(cov, [
+ ('package', {'name': "."}),
+ ('class', {'filename': "mod.py", 'name': "mod.py"}),
+ ])
+ dom = ElementTree.parse("coverage.xml")
+ self.assert_source(dom, "src")
def compare_xml(expected, actual, **kwargs):
diff --git a/tox.ini b/tox.ini
index bb237f83..83ab9edb 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,34,35,36,37,38}, pypy{2,3}, doc, lint
+envlist = py{27,35,36,37,38}, pypy{2,3}, doc, lint
skip_missing_interpreters = {env:COVERAGE_SKIP_MISSING_INTERPRETERS:True}
toxworkdir = {env:TOXWORKDIR:.tox}
@@ -13,12 +13,12 @@ deps =
# Check here for what might be out of date:
# https://requires.io/github/nedbat/coveragepy/requirements/
-r requirements/pytest.pip
- pip==18.1
- setuptools==40.6.3
+ pip==19.0.3
+ setuptools==40.8.0
# gevent 1.3 causes a failure: https://github.com/nedbat/coveragepy/issues/663
- py{27,34,35,36}: gevent==1.2.2
- py{27,34,35,36,37,38}: eventlet==0.24.1
- py{27,34,35,36,37,38}: greenlet==0.4.15
+ py{27,35,36}: gevent==1.2.2
+ py{27,35,36,37,38}: eventlet==0.24.1
+ py{27,35,36,37,38}: greenlet==0.4.15
# Windows can't update the pip version with pip running, so use Python
# to install things.
@@ -85,7 +85,7 @@ setenv =
commands =
python -m tabnanny {env:LINTABLE}
python igor.py check_eol
- check-manifest --ignore 'lab*,perf*,doc/sample_html*,.treerc'
+ check-manifest --ignore 'lab*,perf*,doc/sample_html*,.treerc,.github*'
python setup.py -q sdist bdist_wheel
twine check dist/*
python -m pylint --notes= {env:LINTABLE}
@@ -94,8 +94,8 @@ commands =
#2.7: py27, lint
python =
2.7: py27
- 3.4: py34
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 7381cafb..a4854605 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,34,35,36,37,sys}
+envlist = py{27,35,36,37,38,sys}
toxworkdir = {toxinidir}/.tox_kits
[testenv]