summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <nedbat@gmail.com>2018-02-21 00:50:07 +0000
committerNed Batchelder <nedbat@gmail.com>2018-02-21 00:50:07 +0000
commit22045a0580c9a2f901724120957be2b6748db766 (patch)
tree8c0fc39c923a68cee297a7effbf8295fa874a91a
parent5d3e1409670a1f15edae206391eb615fc9ee1ca8 (diff)
parent408b58b86498b6b163526640db3f97d3a9e31e26 (diff)
downloadpython-coveragepy-22045a0580c9a2f901724120957be2b6748db766.tar.gz
Merged in salty_horse/coveragepy/salty_horse/use-https-for-codecovio-image-1519130663808 (pull request #137)
Use https for codecov.io image
-rw-r--r--CHANGES.rst4
-rw-r--r--coverage/control.py77
-rw-r--r--coverage/debug.py5
-rw-r--r--coverage/multiproc.py1
-rw-r--r--coverage/version.py2
-rw-r--r--doc/cmd.rst7
-rw-r--r--howto.txt4
-rw-r--r--igor.py5
-rw-r--r--tests/test_api.py1
-rw-r--r--tests/test_concurrency.py6
-rw-r--r--tests/test_process.py30
11 files changed, 106 insertions, 36 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index f2fcfc6..126ced9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -18,7 +18,9 @@ Change history for Coverage.py
Unreleased
----------
-None yet
+- A new warning (already-imported) is issued if measurable files have already
+ been imported before coverage.py started measurement. See
+ :ref:`cmd_warnings` for more information.
.. _changes_451:
diff --git a/coverage/control.py b/coverage/control.py
index b82c804..daa00bd 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -74,6 +74,10 @@ class Coverage(object):
cov.html_report(directory='covhtml')
"""
+ # A global to know if we have ever checked for files imported before
+ # coverage has been started.
+ _checked_preimported = False
+
def __init__(
self, data_file=None, data_suffix=None, cover_pylib=None,
auto_data=False, timid=None, branch=None, config_file=True,
@@ -164,6 +168,7 @@ class Coverage(object):
# Is it ok for no data to be collected?
self._warn_no_data = True
self._warn_unimported_source = True
+ self._warn_preimported_source = True
# A record of all the warnings that have been issued.
self._warnings = []
@@ -434,7 +439,7 @@ class Coverage(object):
else:
return dunder_name
- def _should_trace_internal(self, filename, frame):
+ def _should_trace_internal(self, filename, frame=None):
"""Decide whether to trace execution in `filename`, with a reason.
This function is called from the trace function. As each new file name
@@ -452,22 +457,23 @@ class Coverage(object):
disp.reason = reason
return disp
- # Compiled Python files have two file names: frame.f_code.co_filename is
- # the file name at the time the .pyc was compiled. The second name is
- # __file__, which is where the .pyc was actually loaded from. Since
- # .pyc files can be moved after compilation (for example, by being
- # installed), we look for __file__ in the frame and prefer it to the
- # co_filename value.
- dunder_file = frame.f_globals and frame.f_globals.get('__file__')
- if dunder_file:
- filename = source_for_file(dunder_file)
- if original_filename and not original_filename.startswith('<'):
- orig = os.path.basename(original_filename)
- if orig != os.path.basename(filename):
- # Files shouldn't be renamed when moved. This happens when
- # exec'ing code. If it seems like something is wrong with
- # the frame's file name, then just use the original.
- filename = original_filename
+ if frame is not None:
+ # Compiled Python files have two file names: frame.f_code.co_filename is
+ # the file name at the time the .pyc was compiled. The second name is
+ # __file__, which is where the .pyc was actually loaded from. Since
+ # .pyc files can be moved after compilation (for example, by being
+ # installed), we look for __file__ in the frame and prefer it to the
+ # co_filename value.
+ dunder_file = frame.f_globals and frame.f_globals.get('__file__')
+ if dunder_file:
+ filename = source_for_file(dunder_file)
+ if original_filename and not original_filename.startswith('<'):
+ orig = os.path.basename(original_filename)
+ if orig != os.path.basename(filename):
+ # Files shouldn't be renamed when moved. This happens when
+ # exec'ing code. If it seems like something is wrong with
+ # the frame's file name, then just use the original.
+ filename = original_filename
if not filename:
# Empty string is pretty useless.
@@ -534,22 +540,21 @@ class Coverage(object):
"Plugin %r didn't set source_filename for %r" %
(plugin, disp.original_filename)
)
- reason = self._check_include_omit_etc_internal(
- disp.source_filename, frame,
- )
+ module_globals = frame.f_globals if frame is not None else {}
+ reason = self._check_include_omit_etc_internal(disp.source_filename, module_globals)
if reason:
nope(disp, reason)
return disp
- def _check_include_omit_etc_internal(self, filename, frame):
+ def _check_include_omit_etc_internal(self, filename, module_globals):
"""Check a file name against the include, omit, etc, rules.
Returns a string or None. String means, don't trace, and is the reason
why. None means no reason found to not trace.
"""
- modulename = self._name_for_module(frame.f_globals, filename)
+ modulename = self._name_for_module(module_globals, filename)
# If the user specified source or include, then that's authoritative
# about the outer bound of what to measure and we don't have to apply
@@ -599,7 +604,8 @@ class Coverage(object):
Returns a boolean: True if the file should be traced, False if not.
"""
- reason = self._check_include_omit_etc_internal(filename, frame)
+ module_globals = frame.f_globals if frame is not None else {}
+ reason = self._check_include_omit_etc_internal(filename, module_globals)
if self.debug.should('trace'):
if not reason:
msg = "Including %r" % (filename,)
@@ -698,9 +704,31 @@ class Coverage(object):
if self._auto_load:
self.load()
+ # See if we think some code that would eventually be measured has already been imported.
+ if not Coverage._checked_preimported and self._warn_preimported_source:
+ if self.include or self.source or self.source_pkgs:
+ self._check_for_already_imported_files()
+ Coverage._checked_preimported = True
+
self.collector.start()
self._started = True
+ def _check_for_already_imported_files(self):
+ """Examine sys.modules looking for files that will be measured."""
+ warned = set()
+ for mod in list(sys.modules.values()):
+ filename = getattr(mod, "__file__", None)
+ if filename is None:
+ continue
+ if filename in warned:
+ continue
+
+ disp = self._should_trace_internal(filename)
+ if disp.trace:
+ msg = "Already imported a file that will be measured: {0}".format(filename)
+ self._warn(msg, slug="already-imported")
+ warned.add(filename)
+
def stop(self):
"""Stop measuring code coverage."""
if self._started:
@@ -1277,10 +1305,11 @@ def process_startup():
cov = Coverage(config_file=cps)
process_startup.coverage = cov
- cov.start()
cov._warn_no_data = False
cov._warn_unimported_source = False
+ cov._warn_preimported_source = False
cov._auto_save = True
+ cov.start()
return cov
diff --git a/coverage/debug.py b/coverage/debug.py
index e68736f..6e6e801 100644
--- a/coverage/debug.py
+++ b/coverage/debug.py
@@ -215,7 +215,7 @@ class DebugOutputFile(object): # pragma: debugging
self.write("New process: executable: %s\n" % (sys.executable,))
self.write("New process: cmd: %s\n" % (cmd,))
if hasattr(os, 'getppid'):
- self.write("New process: parent pid: %s\n" % (os.getppid(),))
+ self.write("New process: pid: %s, parent pid: %s\n" % (os.getpid(), os.getppid()))
SYS_MOD_NAME = '$coverage.debug.DebugOutputFile.the_one'
@@ -234,7 +234,8 @@ class DebugOutputFile(object): # pragma: debugging
# on a class attribute. Yes, this is aggressively gross.
the_one = sys.modules.get(cls.SYS_MOD_NAME)
if the_one is None:
- assert fileobj is not None
+ if fileobj is None:
+ fileobj = open("/tmp/debug_log.txt", "a")
sys.modules[cls.SYS_MOD_NAME] = the_one = cls(fileobj, show_process, filters)
return the_one
diff --git a/coverage/multiproc.py b/coverage/multiproc.py
index fe83731..986ee9d 100644
--- a/coverage/multiproc.py
+++ b/coverage/multiproc.py
@@ -33,6 +33,7 @@ class ProcessWithCoverage(OriginalProcess):
from coverage import Coverage # avoid circular import
rcfile = os.environ[COVERAGE_RCFILE_ENV]
cov = Coverage(data_suffix=True, config_file=rcfile)
+ cov._warn_preimported_source = False
cov.start()
debug = cov.debug
try:
diff --git a/coverage/version.py b/coverage/version.py
index dbeb64f..8a1b39e 100644
--- a/coverage/version.py
+++ b/coverage/version.py
@@ -5,7 +5,7 @@
# This file is exec'ed in setup.py, don't import anything!
# Same semantics as sys.version_info.
-version_info = (4, 5, 2, 'alpha', 0)
+version_info = (4, 6, 0, 'alpha', 0)
def _make_version(major, minor, micro, releaselevel, serial):
diff --git a/doc/cmd.rst b/doc/cmd.rst
index ef4c113..baf1ca0 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -171,6 +171,13 @@ could affect the measurement process. The possible warnings include:
when coverage started. This meant coverage.py couldn't monitor its
execution.
+* "Already imported a file that will be measured: XXX (already-imported)"
+
+ File XXX had already been imported when coverage.py started measurement. Your
+ setting for ``--source`` or ``--include`` indicates that you wanted to
+ measure that file. Lines will be missing from the coverage report since the
+ execution during import hadn't been measured.
+
* "--include is ignored because --source is set (include-ignored)"
Both ``--include`` and ``--source`` were specified while running code. Both
diff --git a/howto.txt b/howto.txt
index 14c5191..2707be5 100644
--- a/howto.txt
+++ b/howto.txt
@@ -74,13 +74,17 @@
- Update readthedocs
- visit https://readthedocs.org/projects/coverage/versions/
- find the latest tag in the inactive list, edit it, make it active.
+ - keep just the latest version of each x.y release, make the rest inactive.
- IF NOT BETA:
+ - visit https://readthedocs.org/projects/coverage/builds/
+ - wait for the new tag build to finish successfully.
- visit https://readthedocs.org/dashboard/coverage/versions/
- change the default version to the new version
- Update bitbucket:
- Issue tracker should get new version number in picker.
# Note: don't delete old version numbers: it marks changes on the tickets
# with that number.
+- Visit the fixed issues on bitbucket and mention the version it was fixed in.
- Announce on coveragepy-announce@googlegroups.com .
- Announce on TIP.
diff --git a/igor.py b/igor.py
index 43ce330..3f5ce12 100644
--- a/igor.py
+++ b/igor.py
@@ -122,11 +122,8 @@ def run_tests_with_coverage(tracer, *runner_args):
import coverage
cov = coverage.Coverage(config_file="metacov.ini", data_suffix=False)
- # Cheap trick: the coverage.py code itself is excluded from measurement,
- # but if we clobber the cover_prefix in the coverage object, we can defeat
- # the self-detection.
- cov.cover_prefix = "Please measure coverage.py!"
cov._warn_unimported_source = False
+ cov._warn_preimported_source = False
cov.start()
try:
diff --git a/tests/test_api.py b/tests/test_api.py
index b461c50..7c2672d 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -5,6 +5,7 @@
import fnmatch
import os
+import os.path
import sys
import textwrap
import warnings
diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py
index 7100604..76e1d9e 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -335,7 +335,7 @@ MULTI_CODE = """
import sys
def process_worker_main(args):
- # Need to pause, or the tasks go too quick, and some processes
+ # Need to pause, or the tasks go too quickly, and some processes
# in the pool don't get any work, and then don't record data.
time.sleep(0.02)
ret = work(*args)
@@ -359,7 +359,7 @@ MULTI_CODE = """
"""
-@flaky(max_runs=10) # Sometimes a test fails due to inherent randomness. Try one more time.
+@flaky(max_runs=10) # Sometimes a test fails due to inherent randomness. Try more times.
class MultiprocessingTest(CoverageTest):
"""Test support of the multiprocessing module."""
@@ -403,7 +403,7 @@ class MultiprocessingTest(CoverageTest):
last_line = self.squeezed_lines(out)[-1]
self.assertRegex(last_line, r"multi.py \d+ 0 100%")
- def test_multiprocessing(self):
+ def test_multiprocessing_simple(self):
nprocs = 3
upto = 30
code = (SQUARE_OR_CUBE_WORK + MULTI_CODE).format(NPROCS=nprocs, UPTO=upto)
diff --git a/tests/test_process.py b/tests/test_process.py
index 18564cb..70329b5 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -585,6 +585,30 @@ class ProcessTest(CoverageTest):
self.assertIn("Trace function changed", out)
+ def test_warn_preimported(self):
+ self.make_file("hello.py", """\
+ import goodbye
+ import coverage
+ cov = coverage.Coverage(include=["good*"])
+ cov.start()
+ print(goodbye.f())
+ cov.stop()
+ """)
+ self.make_file("goodbye.py", """\
+ def f():
+ return "Goodbye!"
+ """)
+ goodbye_path = os.path.abspath("goodbye.py")
+
+ out = self.run_command("python hello.py")
+ self.assertIn("Goodbye!", out)
+
+ msg = (
+ "Coverage.py warning: "
+ "Already imported a file that will be measured: {0} "
+ "(already-imported)").format(goodbye_path)
+ self.assertIn(msg, out)
+
def test_note(self):
if env.PYPY and env.PY3 and env.PYPYVERSION[:3] == (5, 10, 0):
# https://bitbucket.org/pypy/pypy/issues/2729/pypy3-510-incorrectly-decodes-astral-plane
@@ -676,9 +700,11 @@ class ProcessTest(CoverageTest):
pass
""")
self.make_file("run_twice.py", """\
+ import sys
import coverage
- for _ in [1, 2]:
+ for i in [1, 2]:
+ sys.stderr.write("Run %s\\n" % i)
inst = coverage.Coverage(source=['foo'])
inst.load()
inst.start()
@@ -689,6 +715,8 @@ class ProcessTest(CoverageTest):
out = self.run_command("python run_twice.py")
self.assertEqual(
out,
+ "Run 1\n"
+ "Run 2\n"
"Coverage.py warning: Module foo was previously imported, but not measured "
"(module-not-measured)\n"
)