diff options
-rw-r--r-- | coverage/cmdline.py | 12 | ||||
-rw-r--r-- | coverage/control.py | 32 | ||||
-rw-r--r-- | coverage/misc.py | 4 | ||||
-rw-r--r-- | coverage/parser.py | 12 | ||||
-rw-r--r-- | coverage/report.py | 5 | ||||
-rw-r--r-- | test/test_coverage.py | 22 |
6 files changed, 74 insertions, 13 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 0915b10..1029ad6 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -3,6 +3,7 @@ import optparse, sys from coverage.execfile import run_python_file +from coverage.misc import CoverageException class Opts(object): @@ -283,6 +284,8 @@ CMDS = { } +OK, ERR = 0, 1 + class CoverageScript(object): """The command-line interface to Coverage.""" @@ -330,7 +333,6 @@ class CoverageScript(object): """ # Collect the command-line options. - OK, ERR = 0, 1 if not argv: self.help_fn(topic='minimum_help') @@ -566,4 +568,10 @@ def main(): This is installed as the script entrypoint. """ - return CoverageScript().command_line(sys.argv[1:]) + try: + status = CoverageScript().command_line(sys.argv[1:]) + except CoverageException: + _, err, _ = sys.exc_info() + print(err) + status = ERR + return status diff --git a/coverage/control.py b/coverage/control.py index 82b9e98..0510e77 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -10,7 +10,7 @@ from coverage.collector import Collector from coverage.data import CoverageData from coverage.files import FileLocator from coverage.html import HtmlReporter -from coverage.misc import format_lines, CoverageException +from coverage.misc import format_lines, CoverageException, NoSource from coverage.summary import SummaryReporter from coverage.xmlreport import XmlReporter @@ -350,9 +350,7 @@ class Analysis(object): if not os.path.exists(self.filename): source = self.coverage.file_locator.get_zip_data(self.filename) if not source: - raise CoverageException( - "No source for code '%s'." % self.filename - ) + raise NoSource("No source for code: %r" % self.filename) self.parser = CodeParser( text=source, filename=self.filename, @@ -415,3 +413,29 @@ class Analysis(object): if e not in possible and e[0] != e[1] ] return sorted(unpredicted) + + def branch_lines(self): + """Returns lines that have more than one exit.""" + exit_counts = {} + for l1,l2 in self.arc_possibilities(): + if l1 not in exit_counts: + exit_counts[l1] = 0 + exit_counts[l1] += 1 + + return [l1 for l1,count in exit_counts.items() if count > 1] + + def missing_branch_arcs(self): + """Return arcs that weren't executed from branch lines. + + Returns {l1:[l2a,l2b,...], ...} + + """ + missing = self.arcs_missing() + branch_lines = set(self.branch_lines()) + mba = {} + for l1, l2 in missing: + if l1 in branch_lines: + if l1 not in mba: + mba[l1] = [] + mba[l1].append(l2) + return mba diff --git a/coverage/misc.py b/coverage/misc.py index 8a8b511..5ef982c 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -49,3 +49,7 @@ def format_lines(statements, lines): class CoverageException(Exception): """An exception specific to Coverage.""" pass + +class NoSource(CoverageException): + """Used to indicate we couldn't find the source for a module.""" + pass diff --git a/coverage/parser.py b/coverage/parser.py index 8957123..433cb66 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -4,7 +4,7 @@ import glob, opcode, os, re, sys, token, tokenize from coverage.backward import set, sorted, StringIO # pylint: disable-msg=W0622 from coverage.bytecode import ByteCodes, CodeObjects -from coverage.misc import nice_pair, CoverageException +from coverage.misc import nice_pair, CoverageException, NoSource class CodeParser(object): @@ -20,9 +20,13 @@ class CodeParser(object): assert text or filename, "CodeParser needs either text or filename" self.filename = filename or "<code>" if not text: - sourcef = open(self.filename, 'rU') - self.text = sourcef.read() - sourcef.close() + try: + sourcef = open(self.filename, 'rU') + self.text = sourcef.read() + sourcef.close() + except IOError: + _, err, _ = sys.exc_info() + raise NoSource("No source for code: %r: %s" % (self.filename, err)) self.text = self.text.replace('\r\n', '\n') self.exclude = exclude diff --git a/coverage/report.py b/coverage/report.py index 8b069d7..7f3e3e0 100644 --- a/coverage/report.py +++ b/coverage/report.py @@ -2,6 +2,7 @@ import os from coverage.codeunit import code_unit_factory +from coverage.misc import NoSource class Reporter(object): """A base class for all reporters.""" @@ -51,8 +52,6 @@ class Reporter(object): for cu in self.code_units: try: report_fn(cu, self.coverage._analyze(cu)) - except KeyboardInterrupt: - raise - except: + except NoSource: if not self.ignore_errors: raise diff --git a/test/test_coverage.py b/test/test_coverage.py index b4d5752..92aef08 100644 --- a/test/test_coverage.py +++ b/test/test_coverage.py @@ -1725,6 +1725,28 @@ class ProcessTest(CoverageTest): data.read_file(".coverage") self.assertEqual(data.summary()['b_or_c.py'], 7) + def test_missing_source_file(self): + # Check what happens if the source is missing when reporting happens. + self.make_file("fleeting.py", """\ + s = 'goodbye, cruel world!' + """) + + self.run_command("coverage run fleeting.py") + os.remove("fleeting.py") + out = self.run_command("coverage html -d htmlcov") + self.assert_matches(out, "No source for code: '.*fleeting.py'") + self.assert_("Traceback" not in out) + + # It happens that the code paths are different for *.py and other files. + self.make_file("fleeting", """\ + s = 'goodbye, cruel world!' + """) + + self.run_command("coverage run fleeting") + os.remove("fleeting") + out = self.run_command("coverage html -d htmlcov") + self.assert_matches(out, "No source for code: '.*fleeting'") + self.assert_("Traceback" not in out) class RecursionTest(CoverageTest): """Check what happens when recursive code gets near limits.""" |