From 10bea20e216e1383327cb6c26c96f1bd1b6a84e5 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Sat, 10 Feb 2018 16:02:12 -0500 Subject: Tweaks to the shipping instructions --- howto.txt | 4 ++++ 1 file changed, 4 insertions(+) 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. -- cgit v1.2.1 From 6872784b1e228196992eab018d61e1f76c2815d2 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 19 Feb 2018 08:05:55 -0500 Subject: A little better debug logging --- coverage/debug.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 -- cgit v1.2.1 From 2ea70fbacc628259487e248b2b5a88c5b4eab5ac Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 19 Feb 2018 15:24:55 -0500 Subject: Make a test a little more specific --- tests/test_process.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_process.py b/tests/test_process.py index 18564cb..35dddd0 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -676,9 +676,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 +691,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" ) -- cgit v1.2.1 From 7cc26bfa090f4cf9b6a3799d420539f757ee60d8 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 19 Feb 2018 17:36:29 -0500 Subject: Fix english, and give a test a name that isn't a prefix of other names --- tests/test_concurrency.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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) -- cgit v1.2.1 From 12955ec533a5186702593130c5b194a3a69d1807 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 20 Feb 2018 08:26:35 -0500 Subject: A new warning for files already imported before coverage starts --- CHANGES.rst | 4 ++- coverage/control.py | 77 +++++++++++++++++++++++++++++++++++---------------- coverage/multiproc.py | 1 + doc/cmd.rst | 7 +++++ igor.py | 5 +--- tests/test_api.py | 1 + tests/test_process.py | 24 ++++++++++++++++ 7 files changed, 90 insertions(+), 29 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/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/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/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_process.py b/tests/test_process.py index 35dddd0..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 -- cgit v1.2.1 From 408b58b86498b6b163526640db3f97d3a9e31e26 Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Tue, 20 Feb 2018 08:29:12 -0500 Subject: A new feature means next version will be 4.6 --- coverage/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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): -- cgit v1.2.1