diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2016-01-02 16:28:02 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2016-01-02 16:28:02 -0500 |
commit | a0aa685214e9cccc361cddafea937346bd6dfdad (patch) | |
tree | c089983f78e4d1a13282b28407450631c89ed64d | |
parent | 3440e214df5ddd0f507ecd76c2350eb8d9dd6a75 (diff) | |
parent | 8b110d3a3f7fbddfcbdaa1b090776e0f388a312e (diff) | |
download | python-coveragepy-git-a0aa685214e9cccc361cddafea937346bd6dfdad.tar.gz |
Merge in the default branch
--HG--
branch : ast-branch
-rw-r--r-- | AUTHORS.txt | 2 | ||||
-rw-r--r-- | CHANGES.rst | 11 | ||||
-rw-r--r-- | Makefile | 5 | ||||
-rw-r--r-- | coverage/monkey.py | 42 | ||||
-rw-r--r-- | coverage/parser.py | 5 | ||||
-rw-r--r-- | coverage/phystokens.py | 2 | ||||
-rw-r--r-- | igor.py | 7 | ||||
-rw-r--r-- | requirements/dev.pip | 2 | ||||
-rw-r--r-- | tests/test_api.py | 23 | ||||
-rw-r--r-- | tests/test_arcs.py | 18 | ||||
-rw-r--r-- | tests/test_concurrency.py | 7 | ||||
-rw-r--r-- | tests/test_config.py | 7 | ||||
-rw-r--r-- | tests/test_coverage.py | 76 | ||||
-rw-r--r-- | tests/test_parser.py | 2 | ||||
-rw-r--r-- | tests/test_phystokens.py | 36 |
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 @@ -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 @@ -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) |