summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/control.py23
-rw-r--r--doc/contributing.rst14
-rw-r--r--howto.txt7
-rw-r--r--igor.py76
-rw-r--r--metacov.ini7
5 files changed, 112 insertions, 15 deletions
diff --git a/coverage/control.py b/coverage/control.py
index 81017b91..1f6dbd7e 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -104,7 +104,6 @@ class coverage(object):
)
self.auto_data = auto_data
- self.atexit_registered = False
# _exclude_re is a dict mapping exclusion list names to compiled
# regexes.
@@ -180,6 +179,12 @@ class coverage(object):
# Set the reporting precision.
Numbers.set_precision(self.config.precision)
+ # Is it ok for no data to be collected?
+ self._warn_no_data = True
+ self._started = False
+
+ atexit.register(self._atexit)
+
def _canonical_dir(self, morf):
"""Return the canonical directory of the module or file `morf`."""
return os.path.split(CodeUnit(morf, self.file_locator).filename)[0]
@@ -338,10 +343,6 @@ class coverage(object):
self.data_suffix = self.run_suffix
if self.auto_data:
self.load()
- # Save coverage data when Python exits.
- if not self.atexit_registered:
- atexit.register(self.save)
- self.atexit_registered = True
# Create the matchers we need for _should_trace
if self.source or self.source_pkgs:
@@ -358,11 +359,20 @@ class coverage(object):
self._harvested = False
self.collector.start()
+ self._started = True
def stop(self):
"""Stop measuring code coverage."""
+ self._started = False
self.collector.stop()
+ def _atexit(self):
+ """Clean up on process shutdown."""
+ if self._started:
+ self.stop()
+ if self.auto_data:
+ self.save()
+
def erase(self):
"""Erase previously-collected coverage data.
@@ -468,7 +478,7 @@ class coverage(object):
# Find out if we got any data.
summary = self.data.summary()
- if not summary:
+ if not summary and self._warn_no_data:
self._warn("No data was collected.")
# Find files that were never executed at all.
@@ -688,3 +698,4 @@ def process_startup():
# Measuring coverage within coverage.py takes yet more trickery.
cov.cover_dir = "Please measure coverage.py!"
cov.start()
+ cov._warn_no_data = False
diff --git a/doc/contributing.rst b/doc/contributing.rst
index 23eb1adb..90b9ba1b 100644
--- a/doc/contributing.rst
+++ b/doc/contributing.rst
@@ -138,6 +138,20 @@ some warnings. Please try to keep it that way, but don't let pylint warnings
keep you from sending patches. I can clean them up.
+Coverage testing coverage.py
+----------------------------
+
+Coverage.py can measure itself, but it's complicated. The process has been
+packaged up to make it easier::
+
+ $ COVERAGE_COVERAGE=yes tox
+ $ python igor.py combine_html
+
+Then look at htmlcov/index.html. Note that due to the recursive nature of
+coverage.py measuring itself, there are some parts of the code that will never
+appear as covered, even though they are executed.
+
+
Contributing
------------
diff --git a/howto.txt b/howto.txt
index 2cde1a17..406b0903 100644
--- a/howto.txt
+++ b/howto.txt
@@ -72,11 +72,10 @@
- activate the virtualenv
- $ tox
+- For complete coverage testing:
-- For complete coverage testing, in each Python installation, create a
- "zzz_coverage_process_start.pth" containing::
-
- import coverage; coverage.process_startup()
+ $ COVERAGE_COVERAGE=yes tox
+ $ python igor.py combine_html
- To run the Javascript tests:
diff --git a/igor.py b/igor.py
index bef1c934..b6c34060 100644
--- a/igor.py
+++ b/igor.py
@@ -30,8 +30,8 @@ def do_remove_extension(args):
except OSError:
pass
-def do_test_with_tracer(args):
- """Run nosetests with a particular tracer."""
+def run_tests(args):
+ """The actual running of tests."""
import nose.core
tracer = args[0]
if tracer == "py":
@@ -46,6 +46,78 @@ def do_test_with_tracer(args):
nose_args = ["nosetests"] + args[1:]
nose.core.main(argv=nose_args)
+def run_tests_with_coverage(args):
+ """Run tests, but with coverage."""
+ import coverage
+
+ os.environ['COVERAGE_COVERAGE'] = 'yes please'
+ os.environ['COVERAGE_PROCESS_START'] = os.path.abspath('metacov.ini')
+
+ # Create the .pth file that will let us measure coverage in sub-processes.
+ import nose
+ pth_path = os.path.join(os.path.dirname(os.path.dirname(nose.__file__)), "covcov.pth")
+ pth_file = open(pth_path, "w")
+ try:
+ pth_file.write("import coverage; coverage.process_startup()\n")
+ finally:
+ pth_file.close()
+
+ tracer = os.environ.get('COVERAGE_TEST_TRACER', 'c')
+ version = "%s%s" % sys.version_info[:2]
+ suffix = "%s_%s" % (version, tracer)
+
+ cov = coverage.coverage(config_file="metacov.ini", data_suffix=suffix)
+ # Cheap trick: the coverage 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.erase()
+ cov.start()
+
+ try:
+ # Re-import coverage to get it coverage tested! I don't understand all the
+ # mechanics here, but if I don't carry over the imported modules (in
+ # covmods), then things go haywire (os == None, eventually).
+ covmods = {}
+ covdir = os.path.split(coverage.__file__)[0]
+ # We have to make a list since we'll be deleting in the loop.
+ modules = list(sys.modules.items())
+ for name, mod in modules:
+ if name.startswith('coverage'):
+ if hasattr(mod, '__file__') and mod.__file__.startswith(covdir):
+ covmods[name] = mod
+ del sys.modules[name]
+ import coverage # don't warn about re-import: pylint: disable=W0404
+ sys.modules.update(covmods)
+
+ # Run nosetests, with the arguments from our command line.
+ print(":: Running nosetests")
+ try:
+ run_tests(args)
+ except SystemExit:
+ # nose3 seems to raise SystemExit, not sure why?
+ pass
+ finally:
+ cov.stop()
+ os.remove(pth_path)
+
+ print(":: Saving data %s" % suffix)
+ cov.save()
+
+def do_combine_html(args):
+ import coverage
+ cov = coverage.coverage(config_file="metacov.ini")
+ cov.combine()
+ cov.save()
+ cov.html_report()
+
+def do_test_with_tracer(args):
+ """Run nosetests with a particular tracer."""
+ if os.environ.get("COVERAGE_COVERAGE", ""):
+ return run_tests_with_coverage(args)
+ else:
+ return run_tests(args)
+
def do_zip_mods(args):
"""Build the zipmods.zip file."""
zf = zipfile.ZipFile("test/zipmods.zip", "w")
diff --git a/metacov.ini b/metacov.ini
index 9d8b4d97..3a7b0238 100644
--- a/metacov.ini
+++ b/metacov.ini
@@ -1,11 +1,11 @@
# Settings to use when using coverage.py to measure itself.
[run]
branch = true
-data_file = c:\ned\coverage\trunk\.coverage
+data_file = /home/ned/coverage/trunk/.coverage.meta
parallel = true
source =
- c:\ned\coverage\trunk\coverage
- c:\ned\coverage\trunk\test
+ /home/ned/coverage/trunk/coverage
+ /home/ned/coverage/trunk/test
[report]
exclude_lines =
@@ -17,3 +17,4 @@ exclude_lines =
omit = mock.py
ignore_errors = true
+precision = 3