summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2016-01-02 16:28:02 -0500
committerNed Batchelder <ned@nedbatchelder.com>2016-01-02 16:28:02 -0500
commita0aa685214e9cccc361cddafea937346bd6dfdad (patch)
treec089983f78e4d1a13282b28407450631c89ed64d
parent3440e214df5ddd0f507ecd76c2350eb8d9dd6a75 (diff)
parent8b110d3a3f7fbddfcbdaa1b090776e0f388a312e (diff)
downloadpython-coveragepy-git-a0aa685214e9cccc361cddafea937346bd6dfdad.tar.gz
Merge in the default branch
--HG-- branch : ast-branch
-rw-r--r--AUTHORS.txt2
-rw-r--r--CHANGES.rst11
-rw-r--r--Makefile5
-rw-r--r--coverage/monkey.py42
-rw-r--r--coverage/parser.py5
-rw-r--r--coverage/phystokens.py2
-rw-r--r--igor.py7
-rw-r--r--requirements/dev.pip2
-rw-r--r--tests/test_api.py23
-rw-r--r--tests/test_arcs.py18
-rw-r--r--tests/test_concurrency.py7
-rw-r--r--tests/test_config.py7
-rw-r--r--tests/test_coverage.py76
-rw-r--r--tests/test_parser.py2
-rw-r--r--tests/test_phystokens.py36
15 files changed, 149 insertions, 96 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt
index de3d6502..83ee3710 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -50,11 +50,13 @@ Marcus Cobden
Mark van der Wal
Martin Fuzzey
Matthew Desmarais
+Max Linke
Mickie Betz
Noel O'Boyle
Pablo Carballo
Patrick Mezard
Peter Portante
+Rodrigue Cloutier
Roger Hu
Ross Lawley
Sandra Martocchia
diff --git a/CHANGES.rst b/CHANGES.rst
index cc8b7a03..f100c805 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -23,8 +23,19 @@ Unreleased
and they will apply to the entire function or class being decorated. This
implements the feature requested in `issue 131`_.
+- Multiprocessing support is now available on Windows. Thanks, Rodrigue
+ Cloutier.
+
+- Files with two encoding declarations are properly supported, fixing
+ `issue 453`_. Thanks, Max Linke.
+
+- Non-ascii characters in regexes in the configuration file worked in 3.7, but
+ stopped working in 4.0. Now they work again, closing `issue 455`_.
+
.. _issue 131: https://bitbucket.org/ned/coveragepy/issues/131/pragma-on-a-decorator-line-should-affect
.. _issue 434: https://bitbucket.org/ned/coveragepy/issues/434/indexerror-in-python-35
+.. _issue 453: https://bitbucket.org/ned/coveragepy/issues/453/source-code-encoding-can-only-be-specified
+.. _issue 455: https://bitbucket.org/ned/coveragepy/issues/455/unusual-exclusions-stopped-working-in
Version 4.0.3 --- 2015-11-24
diff --git a/Makefile b/Makefile
index ce265013..e90d55df 100644
--- a/Makefile
+++ b/Makefile
@@ -61,7 +61,10 @@ kit_upload:
twine upload dist/*
kit_local:
- cp -v dist/* `awk -F "=" '/find-links/ {print $$2}' ~/.pip/pip.conf`
+ # pip.conf looks like this:
+ # [global]
+ # find-links = file:///Users/ned/Downloads/local_pypi
+ cp -v dist/* `awk -F "//" '/find-links/ {print $$2}' ~/.pip/pip.conf`
# pip caches wheels of things it has installed. Clean them out so we
# don't go crazy trying to figure out why our new code isn't installing.
find ~/Library/Caches/pip/wheels -name 'coverage-*' -delete
diff --git a/coverage/monkey.py b/coverage/monkey.py
index c4ec68c6..b896dbf5 100644
--- a/coverage/monkey.py
+++ b/coverage/monkey.py
@@ -11,6 +11,28 @@ import sys
# monkey-patched.
PATCHED_MARKER = "_coverage$patched"
+if sys.version_info >= (3, 4):
+
+ klass = multiprocessing.process.BaseProcess
+else:
+ klass = multiprocessing.Process
+
+original_bootstrap = klass._bootstrap
+
+
+class ProcessWithCoverage(klass):
+ """A replacement for multiprocess.Process that starts coverage."""
+ def _bootstrap(self):
+ """Wrapper around _bootstrap to start coverage."""
+ from coverage import Coverage
+ cov = Coverage(data_suffix=True)
+ cov.start()
+ try:
+ return original_bootstrap(self)
+ finally:
+ cov.stop()
+ cov.save()
+
def patch_multiprocessing():
"""Monkey-patch the multiprocessing module.
@@ -23,26 +45,6 @@ def patch_multiprocessing():
return
if sys.version_info >= (3, 4):
- klass = multiprocessing.process.BaseProcess
- else:
- klass = multiprocessing.Process
-
- original_bootstrap = klass._bootstrap
-
- class ProcessWithCoverage(klass):
- """A replacement for multiprocess.Process that starts coverage."""
- def _bootstrap(self):
- """Wrapper around _bootstrap to start coverage."""
- from coverage import Coverage
- cov = Coverage(data_suffix=True)
- cov.start()
- try:
- return original_bootstrap(self)
- finally:
- cov.stop()
- cov.save()
-
- if sys.version_info >= (3, 4):
klass._bootstrap = ProcessWithCoverage._bootstrap
else:
multiprocessing.Process = ProcessWithCoverage
diff --git a/coverage/parser.py b/coverage/parser.py
index d85f0b57..c11bc222 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -98,7 +98,10 @@ class PythonParser(object):
part of it.
"""
- regex_c = re.compile(join_regex(regexes))
+ 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):
if regex_c.search(ltext):
diff --git a/coverage/phystokens.py b/coverage/phystokens.py
index b34b1c3b..5aa3402c 100644
--- a/coverage/phystokens.py
+++ b/coverage/phystokens.py
@@ -291,5 +291,5 @@ def compile_unicode(source, filename, mode):
@contract(source='unicode', returns='unicode')
def neuter_encoding_declaration(source):
"""Return `source`, with any encoding declaration neutered."""
- source = COOKIE_RE.sub("# (deleted declaration)", source, count=1)
+ source = COOKIE_RE.sub("# (deleted declaration)", source, count=2)
return source
diff --git a/igor.py b/igor.py
index 409fdc91..b857fcc3 100644
--- a/igor.py
+++ b/igor.py
@@ -328,7 +328,12 @@ def print_banner(label):
if '__pypy__' in sys.builtin_module_names:
version += " (pypy %s)" % ".".join(str(v) for v in sys.pypy_version_info)
- which_python = os.path.relpath(sys.executable)
+ try:
+ which_python = os.path.relpath(sys.executable)
+ except ValueError:
+ # On Windows having a python executable on a different drives
+ # than the sources cannot be relative
+ which_python = sys.executable
print('=== %s %s %s (%s) ===' % (impl, version, label, which_python))
sys.stdout.flush()
diff --git a/requirements/dev.pip b/requirements/dev.pip
index 703bfc7e..035a6f74 100644
--- a/requirements/dev.pip
+++ b/requirements/dev.pip
@@ -13,7 +13,7 @@ greenlet==0.4.9
mock==1.3.0
PyContracts==1.7.9
pyenchant==1.6.6
-pylint==1.4.4
+pylint==1.4.5
# for kitting.
requests==2.8.1
diff --git a/tests/test_api.py b/tests/test_api.py
index a7cadab3..3ea5b3ef 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -278,18 +278,17 @@ class ApiTest(CoverageTest):
self.start_import_stop(cov, "code2")
self.check_code1_code2(cov)
- if 0: # expected failure
- # for https://bitbucket.org/ned/coveragepy/issue/79
- def test_start_save_stop(self):
- self.make_code1_code2()
- cov = coverage.Coverage()
- cov.start()
- self.import_local_file("code1")
- cov.save()
- self.import_local_file("code2")
- cov.stop()
-
- self.check_code1_code2(cov)
+ def test_start_save_stop(self):
+ self.skip("Expected failure: https://bitbucket.org/ned/coveragepy/issue/79")
+ self.make_code1_code2()
+ cov = coverage.Coverage()
+ cov.start()
+ self.import_local_file("code1")
+ cov.save()
+ self.import_local_file("code2")
+ cov.stop()
+
+ self.check_code1_code2(cov)
def make_corrupt_data_files(self):
"""Make some good and some bad data files."""
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index 88442049..37e8b9b7 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -146,15 +146,15 @@ class SimpleArcTest(CoverageTest):
arcz=".1 16 6. .2 23 3. 25 5.", arcz_missing="25 5."
)
- if 0: # expected failure
- def test_unused_lambdas_are_confusing_bug_90(self):
- self.check_coverage("""\
- a = 1
- fn = lambda x: x
- b = 3
- """,
- arcz=".1 12 .2 2-2 23 3."
- )
+ def test_unused_lambdas_are_confusing_bug_90(self):
+ self.skip("Expected failure: bug 90")
+ self.check_coverage("""\
+ a = 1
+ fn = lambda x: x
+ b = 3
+ """,
+ arcz=".1 12 .2 2-2 23 3."
+ )
class WithTest(CoverageTest):
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index c6d750d0..0f5ffe95 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -222,13 +222,6 @@ class ConcurrencyTest(CoverageTest):
class MultiprocessingTest(CoverageTest):
"""Test support of the multiprocessing module."""
- def setUp(self):
- super(MultiprocessingTest, self).setUp()
- # Currently, this doesn't work on Windows, something about pickling
- # the monkey-patched Process class?
- if env.WINDOWS:
- self.skip("Multiprocessing support doesn't work on Windows")
-
def test_multiprocessing(self):
self.make_file("multi.py", """\
import multiprocessing
diff --git a/tests/test_config.py b/tests/test_config.py
index 93a7bbf6..5667e930 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -379,11 +379,18 @@ class ConfigFileTest(CoverageTest):
def test_non_ascii(self):
self.make_file(".coveragerc", """\
+ [report]
+ exclude_lines =
+ first
+ ✘${TOX_ENVNAME}
+ third
[html]
title = tabblo & «ταБЬℓσ» # numbers
""")
+ self.set_environ("TOX_ENVNAME", "weirdo")
cov = coverage.Coverage()
+ self.assertEqual(cov.config.exclude_list, ["first", "✘weirdo", "third"])
self.assertEqual(cov.config.html_title, "tabblo & «ταБЬℓσ» # numbers")
def test_unreadable_config(self):
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
index 9bb0f488..ea7604b1 100644
--- a/tests/test_coverage.py
+++ b/tests/test_coverage.py
@@ -1,3 +1,4 @@
+# coding: utf-8
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
@@ -402,35 +403,35 @@ class SimpleStatementTest(CoverageTest):
""",
[1,2,3,4,5], "4")
- if 0: # expected failure
+ def test_strange_unexecuted_continue(self):
# Peephole optimization of jumps to jumps can mean that some statements
# never hit the line tracer. The behavior is different in different
# versions of Python, so don't run this test:
- def test_strange_unexecuted_continue(self):
- self.check_coverage("""\
- a = b = c = 0
- for n in range(100):
- if n % 2:
- if n % 4:
- a += 1
- continue # <-- This line may not be hit.
- else:
- b += 1
- c += 1
- assert a == 50 and b == 50 and c == 50
-
- a = b = c = 0
- for n in range(100):
- if n % 2:
- if n % 3:
- a += 1
- continue # <-- This line is always hit.
- else:
- b += 1
- c += 1
- assert a == 33 and b == 50 and c == 50
- """,
- [1,2,3,4,5,6,8,9,10, 12,13,14,15,16,17,19,20,21], "")
+ self.skip("Expected failure: peephole optimization of jumps to jumps")
+ self.check_coverage("""\
+ a = b = c = 0
+ for n in range(100):
+ if n % 2:
+ if n % 4:
+ a += 1
+ continue # <-- This line may not be hit.
+ else:
+ b += 1
+ c += 1
+ assert a == 50 and b == 50 and c == 50
+
+ a = b = c = 0
+ for n in range(100):
+ if n % 2:
+ if n % 3:
+ a += 1
+ continue # <-- This line is always hit.
+ else:
+ b += 1
+ c += 1
+ assert a == 33 and b == 50 and c == 50
+ """,
+ [1,2,3,4,5,6,8,9,10, 12,13,14,15,16,17,19,20,21], "")
def test_import(self):
self.check_coverage("""\
@@ -1148,7 +1149,7 @@ class ExcludeTest(CoverageTest):
self.check_coverage("""\
a = 1; b = 2
- if 0:
+ if len([]):
a = 4 # -cc
""",
[1,3], "", excludes=['-cc'])
@@ -1169,19 +1170,19 @@ class ExcludeTest(CoverageTest):
self.check_coverage("""\
a = 1; b = 2
- if 0:
+ if len([]): # not-here
a = 4
b = 5
c = 6
assert a == 1 and b == 2
""",
- [1,7], "", excludes=['if 0:'])
+ [1,7], "", excludes=['not-here'])
def test_excluding_if_but_not_else_suite(self):
self.check_coverage("""\
a = 1; b = 2
- if 0:
+ if len([]): # not-here
a = 4
b = 5
c = 6
@@ -1190,7 +1191,7 @@ class ExcludeTest(CoverageTest):
b = 9
assert a == 8 and b == 9
""",
- [1,8,9,10], "", excludes=['if 0:'])
+ [1,8,9,10], "", excludes=['not-here'])
def test_excluding_else_suite(self):
self.check_coverage("""\
@@ -1249,7 +1250,7 @@ class ExcludeTest(CoverageTest):
self.check_coverage("""\
def foo():
a = 2
- if 0: x = 3 # no cover
+ if len([]): x = 3 # no cover
b = 4
foo()
@@ -1510,6 +1511,17 @@ class ExcludeTest(CoverageTest):
""",
[8,9], "", excludes=['#pragma: NO COVER'])
+ def test_excludes_non_ascii(self):
+ self.check_coverage("""\
+ # coding: utf-8
+ a = 1; b = 2
+
+ if len([]):
+ a = 5 # ✘cover
+ """,
+ [2, 4], "", excludes=['✘cover']
+ )
+
class Py24Test(CoverageTest):
"""Tests of new syntax in Python 2.4."""
diff --git a/tests/test_parser.py b/tests/test_parser.py
index e6f28737..c32fdc4d 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -81,7 +81,7 @@ class PythonParserTest(CoverageTest):
def __init__(self):
pass
- if 0: # nocover
+ if len([]): # nocover
class Bar:
pass
""")
diff --git a/tests/test_phystokens.py b/tests/test_phystokens.py
index e28fb176..380f36ff 100644
--- a/tests/test_phystokens.py
+++ b/tests/test_phystokens.py
@@ -97,12 +97,13 @@ else:
ENCODING_DECLARATION_SOURCES = [
# Various forms from http://www.python.org/dev/peps/pep-0263/
- b"# coding=cp850\n\n",
- b"#!/usr/bin/python\n# -*- coding: cp850 -*-\n",
- b"#!/usr/bin/python\n# vim: set fileencoding=cp850:\n",
- b"# This Python file uses this encoding: cp850\n",
- b"# This file uses a different encoding:\n# coding: cp850\n",
- b"\n# coding=cp850\n\n",
+ (1, b"# coding=cp850\n\n"),
+ (1, b"#!/usr/bin/python\n# -*- coding: cp850 -*-\n"),
+ (1, b"#!/usr/bin/python\n# vim: set fileencoding=cp850:\n"),
+ (1, b"# This Python file uses this encoding: cp850\n"),
+ (1, b"# This file uses a different encoding:\n# coding: cp850\n"),
+ (1, b"\n# coding=cp850\n\n"),
+ (2, b"# -*- coding:cp850 -*-\n# vim: fileencoding=cp850\n"),
]
class SourceEncodingTest(CoverageTest):
@@ -111,7 +112,7 @@ class SourceEncodingTest(CoverageTest):
run_in_temp_dir = False
def test_detect_source_encoding(self):
- for source in ENCODING_DECLARATION_SOURCES:
+ for _, source in ENCODING_DECLARATION_SOURCES:
self.assertEqual(
source_encoding(source),
'cp850',
@@ -153,7 +154,7 @@ class NeuterEncodingDeclarationTest(CoverageTest):
run_in_temp_dir = False
def test_neuter_encoding_declaration(self):
- for source in ENCODING_DECLARATION_SOURCES:
+ for lines_diff_expected, source in ENCODING_DECLARATION_SOURCES:
neutered = neuter_encoding_declaration(source.decode("ascii"))
neutered = neutered.encode("ascii")
@@ -166,8 +167,10 @@ class NeuterEncodingDeclarationTest(CoverageTest):
lines_different = sum(
int(nline != sline) for nline, sline in zip(neutered_lines, source_lines)
)
- self.assertEqual(lines_different, 1)
+ self.assertEqual(lines_diff_expected, lines_different)
+ # The neutered source will be detected as having no encoding
+ # declaration.
self.assertEqual(
source_encoding(neutered),
DEF_ENCODING,
@@ -180,6 +183,19 @@ class CompileUnicodeTest(CoverageTest):
run_in_temp_dir = False
+ def assert_compile_unicode(self, source):
+ """Assert that `source` will compile properly with `compile_unicode`."""
+ source += u"a = 42\n"
+ # This doesn't raise an exception:
+ code = compile_unicode(source, "<string>", "exec")
+ globs = {}
+ exec(code, globs)
+ self.assertEqual(globs['a'], 42)
+
def test_cp1252(self):
uni = u"""# coding: cp1252\n# \u201C curly \u201D\n"""
- compile_unicode(uni, "<string>", "exec")
+ self.assert_compile_unicode(uni)
+
+ def test_double_coding_declaration(self):
+ uni = u"""# -*- coding:utf-8 -*-\n# vim: fileencoding=utf-8\n"""
+ self.assert_compile_unicode(uni)